Compare commits

...
19 Commits
Author SHA1 Message Date
platform-mcpandClaude Opus 4.8 fccc0115c4 feat: render WRNexus bot widget globally via shared layout
Deploy to Production / Build & Verify (push) Failing after 16m44s
E2E Test Suite / Security Header Tests (push) Failing after 10m27s
E2E Test Suite / Mobile Device Tests (push) Failing after 10m28s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 11m21s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 12m13s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 13m6s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 13m58s
E2E Test Suite / Form Interaction Tests (push) Failing after 14m51s
E2E Test Suite / Smoke Tests (P0) (push) Failing after 21m43s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
Deploy to Production / Pre-Deploy Tests (push) Failing after 14m59s
E2E Test Suite / Test Report Summary (push) Failing after 14m58s
Deploy to Production / Deploy to Fly.io (push) Failing after 13m21s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 13m48s
Deploy to Production / Deploy to Render (push) Failing after 14m14s
Deploy to Production / Deploy to Railway (push) Failing after 14m40s
Deploy to Production / Post-Deploy Verification (push) Failing after 14m10s
Deploy to Production / Notify on Failure (push) Successful in 1s
Inject the WRNexus bot widget loader (bots/widget.js) once into
BaseLayout.astro's <head> so the chat widget appears on every route
(pages + blog posts) without per-page duplication. Uses is:inline so
Astro does not bundle/transform the external script, preserving the
data-bot-id and async attributes.

Update CSP in middleware.ts to permit the widget: script-src and
connect-src already allowed https://api.wrnexus.com (web-push SDK);
added it to frame-src for the widget's iframe. No other directives
weakened.

Verified: build is clean, widget tag served on /, /about, /contact,
and a blog post; widget.js loads 200; zero console CSP violations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 00:29:32 +05:30
platform-mcp 189260bbb3 pre-agent: Add WRNexus chat widget script to layout 2026-06-26 00:27:14 +05:30
platform-mcpandClaude Opus 4.8 8e7070cbaf feat(footer): make push notification button reflect subscription state
E2E Test Suite / Smoke Tests (P0) (push) Failing after 9m36s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 10m42s
E2E Test Suite / Form Interaction Tests (push) Failing after 11m35s
E2E Test Suite / Security Header Tests (push) Failing after 11m20s
E2E Test Suite / Mobile Device Tests (push) Failing after 12m13s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 13m5s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 13m57s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 14m50s
E2E Test Suite / Test Report Summary (push) Failing after 12m50s
Deploy to Production / Build & Verify (push) Failing after 19m19s
Deploy to Production / Pre-Deploy Tests (push) Failing after 14m59s
Deploy to Production / Deploy to Fly.io (push) Failing after 12m23s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 12m26s
Deploy to Production / Deploy to Render (push) Failing after 13m16s
Deploy to Production / Deploy to Railway (push) Failing after 14m8s
Deploy to Production / Post-Deploy Verification (push) Failing after 14m57s
Deploy to Production / Notify on Failure (push) Failing after 14m58s
The WRNexus web-push button always showed "Enable notifications". It now
reads WRNexusPush.isSubscribed() on load and renders the correct state:
- hides entirely when SDK/browser push support is missing
- "Enable notifications" -> calls prompt() (user gesture)
- "Unsubscribe" when already subscribed -> calls unsubscribe()
- disabled "Notifications blocked" when permission is denied
- in-progress disabled state to prevent double-clicks; re-reads state after
  each action and re-renders the label

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 02:39:37 +05:30
platform-mcp a8d7db562d pre-agent: Make push notification button reflect subscription 2026-06-24 02:37:20 +05:30
platform-mcp e220f46d35 agent: Host WRNexus push service worker at site root 2026-06-24 01:45:37 +05:30
platform-mcp 93460ece19 pre-agent: Host WRNexus push service worker at site root 2026-06-24 01:42:48 +05:30
platform-mcpandClaude Opus 4.8 7b67897612 fix(csp): allow WRNexus push SDK origin in script-src and connect-src
E2E Test Suite / Form Interaction Tests (push) Failing after 10m29s
E2E Test Suite / Security Header Tests (push) Failing after 10m15s
E2E Test Suite / Mobile Device Tests (push) Failing after 11m8s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 12m0s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 12m52s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 13m44s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 14m37s
E2E Test Suite / Smoke Tests (P0) (push) Failing after 21m17s
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 12m14s
Deploy to Production / Notify on Failure (push) Successful in 1s
Deploy to Production / Build & Verify (push) Failing after 5m47s
Deploy to Production / Pre-Deploy Tests (push) Failing after 14m54s
Deploy to Production / Deploy to Railway (push) Failing after 14m58s
Deploy to Production / Deploy to Render (push) Failing after 14m35s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 14m6s
Deploy to Production / Deploy to Fly.io (push) Failing after 13m43s
Deploy to Production / Post-Deploy Verification (push) Failing after 14m11s
The web-push SDK (https://api.wrnexus.com/push/sdk.js) and its runtime
config fetch were blocked by the Content-Security-Policy set in
src/middleware.ts. Added https://api.wrnexus.com to script-src (prod and
dev) and connect-src. No wildcards introduced; existing analytics
origins (GTM, GA, Plausible) unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 01:38:51 +05:30
platform-mcp c5f79f1fd2 pre-agent: Allow WRNexus push SDK in CSP script-src 2026-06-24 01:35:55 +05:30
platform-mcpandClaude Opus 4.8 41abcaa7a4 feat: integrate WRNexus web-push SDK and subscribe control
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
Deploy to Production / Pre-Deploy Tests (push) Failing after 12m56s
Deploy to Production / Build & Verify (push) Failing after 17m52s
Deploy to Production / Deploy to Fly.io (push) Failing after 12m36s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 13m3s
Deploy to Production / Deploy to Render (push) Failing after 13m30s
Deploy to Production / Deploy to Railway (push) Failing after 13m55s
Deploy to Production / Post-Deploy Verification (push) Failing after 14m57s
Deploy to Production / Notify on Failure (push) Successful in 1s
- Add WRNexus push SDK (is:inline) to BaseLayout <head> so it loads on
  every page that should collect subscribers (api.wrnexus.com/push/sdk.js)
- Add "Enable notifications" subscribe button in the footer wired via
  addEventListener to window.WRNexusPush.subscribe()

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 01:33:20 +05:30
platform-mcp 966f3b23fa pre-agent: Add WRNexus push notification SDK and subscribe bu 2026-06-24 01:30:45 +05:30
platform-mcp 45b30918a0 pre-agent: Add WRNexus push notification SDK and subscribe bu 2026-06-24 01:30:12 +05:30
platform-mcp 7eeec7e781 pre-agent: Draft PR and earned-mentions plan 2026-06-19 09:38:43 +05:30
platform-mcp 527131a462 pre-agent: Create directory listing and Google Business Profi 2026-06-19 09:36:05 +05:30
platform-mcpandClaude Opus 4.8 6b07b83583 chore(seo): open robots.txt to all major AI crawlers for GEO
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 10m48s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 11m43s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 12m35s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 13m27s
E2E Test Suite / Form Interaction Tests (push) Failing after 14m20s
E2E Test Suite / Security Header Tests (push) Failing after 14m1s
E2E Test Suite / Mobile Device Tests (push) Failing after 14m56s
E2E Test Suite / Smoke Tests (P0) (push) Failing after 21m57s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
Deploy to Production / Build & Verify (push) Failing after 17m16s
E2E Test Suite / Test Report Summary (push) Failing after 13m13s
Deploy to Production / Pre-Deploy Tests (push) Failing after 12m22s
Deploy to Production / Deploy to Fly.io (push) Failing after 13m13s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 14m5s
Deploy to Production / Deploy to Render (push) Failing after 14m5s
Deploy to Production / Deploy to Railway (push) Failing after 14m57s
Deploy to Production / Post-Deploy Verification (push) Failing after 13h5m7s
Deploy to Production / Notify on Failure (push) Failing after 15m4s
Repo robots.txt already matched the deployed file (verified via diff
against https://workroot.in/robots.txt). Added commonly-cited AI agents
that were still missing, each with Allow: /:
- OpenAI: OAI-SearchBot
- Anthropic: ClaudeBot, Claude-SearchBot, Claude-User
- Perplexity: Perplexity-User
- Meta: Meta-ExternalAgent / meta-externalagent
- Apple: Applebot, Applebot-Extended
- Amazon: Amazonbot
- ByteDance: Bytespider
- Cohere: cohere-ai
- DuckDuckGo: DuckAssistBot
- You.com: YouBot
- Diffbot

Disallow list unchanged (/admin/, /private/, /_astro/) so no content
directory needed for citation is blocked. Sitemap line preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 09:35:47 +05:30
platform-mcp c7e130ce4d pre-agent: Verify robots.txt keeps AI crawlers open 2026-06-19 09:34:50 +05:30
platform-mcpandClaude Opus 4.8 5a2b68763f chore(content): unify company facts for NAP consistency
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 / Smoke Tests (P0) (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
Ping Search Engines / Notify Search Engines (push) Failing after 14m45s
Reconcile inconsistent company facts to the canonical set (single
source of truth):

- Structured-data Organization/LocalBusiness/publisher/provider names
  now use the legal entity name "WorkRoot IT Solutions LLP"
  (SEO.astro, utils/seo.ts) for NAP consistency. Brand short forms
  ("WorkRoot", "WorkRoot IT Solutions") retained only as display brand
  and alternateName.
- Headline government-reach proof figure unified to "11+ districts"
  across all company-level proof bands and marketing/SEO copy
  (ProofNumbers, /about, /portfolio, homepage, /services, SEO desc).
  Specific Bihar Police Management System case studies / service-page
  stat tiles keep the factual "11 districts" — documented rationale.
- Verified phone (+91 9561417403), email (admin@workroot.in),
  founded (2022), 20+ projects, 50,000+ records, 99.9% uptime and
  IIT Patna partnership are consistent site-wide.

Added .agent/scratch/FACTS-CANONICAL.md as the single source of truth
plus an off-site checklist (LinkedIn, Clutch, GoodFirms, Crunchbase,
GeM) to keep external listings identical. Build passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 09:34:30 +05:30
platform-mcp 84624976a8 pre-agent: Audit and unify company facts (NAP consistency) 2026-06-19 09:23:55 +05:30
platform-mcpandClaude Opus 4.8 a3dd4b9ffa content: add quotable, extractable company facts + comparison table to homepage
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 11m34s
Deploy to Production / Pre-Deploy Tests (push) Failing after 13m8s
Deploy to Production / Build & Verify (push) Failing after 16m0s
Deploy to Production / Deploy to Fly.io (push) Failing after 14m5s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 14m6s
Deploy to Production / Deploy to Render (push) Failing after 14m57s
Deploy to Production / Deploy to Railway (push) Failing after 14m58s
Deploy to Production / Post-Deploy Verification (push) Failing after 14m57s
Deploy to Production / Notify on Failure (push) Failing after 13h10m32s
Add an LLM-citable "WorkRoot at a glance" section to the homepage with 7
standalone factual sentences that each bundle the company name + a concrete
claim + a number (founded 2022, 20+ projects, 11 districts, 50,000+ records,
99.9% uptime, IIT Patna partnership, GeM empanelment, 15,000+ officers, 8+
years), so each line stays accurate when quoted out of context.

- New companyFacts list + "Custom software vs off-the-shelf ERP" comparison
  table in src/pages/index.astro (reuses the table pattern from the custom-ERP
  service page; final row carries the canonical 11-districts / 99.9% proof).
- Rewrite vague HomepageFaq answers into definitive, self-contained one-
  paragraph answers; visible text still drives the single FAQPage JSON-LD.
- Normalize stray "11+ districts" in the services array to the canonical
  "11 districts" for exact sitewide numerical consistency.

Verified: build passes; rendered homepage shows all 7 facts, the comparison
table, definitive FAQ answers, exactly one FAQPage JSON-LD, and no "11+".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 09:23:30 +05:30
platform-mcp 0121641163 pre-agent: Add quotable, extractable facts for LLM citation 2026-06-19 09:17:58 +05:30
25 changed files with 1253 additions and 368 deletions
+48 -38
View File
@@ -1,11 +1,11 @@
# Graph Report - 2e709500-79ab-403f-9ca0-3f027ac9f36e (2026-06-19) # Graph Report - 2e709500-79ab-403f-9ca0-3f027ac9f36e (2026-06-24)
## Corpus Check ## Corpus Check
- 61 files · ~611,807 words - 64 files · ~615,668 words
- Verdict: corpus is large enough that graph structure adds value. - Verdict: corpus is large enough that graph structure adds value.
## Summary ## Summary
- 596 nodes · 620 edges · 114 communities (87 shown, 27 thin omitted) - 619 nodes · 640 edges · 117 communities (90 shown, 27 thin omitted)
- Extraction: 99% EXTRACTED · 1% INFERRED · 0% AMBIGUOUS · INFERRED: 4 edges (avg confidence: 0.8) - Extraction: 99% EXTRACTED · 1% INFERRED · 0% AMBIGUOUS · INFERRED: 4 edges (avg confidence: 0.8)
- Token cost: 0 input · 0 output - Token cost: 0 input · 0 output
@@ -50,9 +50,8 @@
- [[_COMMUNITY_Community 34|Community 34]] - [[_COMMUNITY_Community 34|Community 34]]
- [[_COMMUNITY_Community 35|Community 35]] - [[_COMMUNITY_Community 35|Community 35]]
- [[_COMMUNITY_Community 36|Community 36]] - [[_COMMUNITY_Community 36|Community 36]]
- [[_COMMUNITY_Community 41|Community 41]] - [[_COMMUNITY_Community 37|Community 37]]
- [[_COMMUNITY_Community 42|Community 42]] - [[_COMMUNITY_Community 38|Community 38]]
- [[_COMMUNITY_Community 43|Community 43]]
- [[_COMMUNITY_Community 44|Community 44]] - [[_COMMUNITY_Community 44|Community 44]]
- [[_COMMUNITY_Community 45|Community 45]] - [[_COMMUNITY_Community 45|Community 45]]
- [[_COMMUNITY_Community 46|Community 46]] - [[_COMMUNITY_Community 46|Community 46]]
@@ -71,6 +70,9 @@
- [[_COMMUNITY_Community 59|Community 59]] - [[_COMMUNITY_Community 59|Community 59]]
- [[_COMMUNITY_Community 60|Community 60]] - [[_COMMUNITY_Community 60|Community 60]]
- [[_COMMUNITY_Community 61|Community 61]] - [[_COMMUNITY_Community 61|Community 61]]
- [[_COMMUNITY_Community 62|Community 62]]
- [[_COMMUNITY_Community 63|Community 63]]
- [[_COMMUNITY_Community 64|Community 64]]
## God Nodes (most connected - your core abstractions) ## God Nodes (most connected - your core abstractions)
1. `Performance Testing Scripts` - 12 edges 1. `Performance Testing Scripts` - 12 edges
@@ -94,7 +96,7 @@
- `POST()` --calls--> `logApiRequest()` [INFERRED] - `POST()` --calls--> `logApiRequest()` [INFERRED]
src/pages/api/newsletter.ts → src/utils/logger.ts src/pages/api/newsletter.ts → src/utils/logger.ts
## Communities (114 total, 27 thin omitted) ## Communities (117 total, 27 thin omitted)
### Community 0 - "Community 0" ### Community 0 - "Community 0"
Cohesion: 0.05 Cohesion: 0.05
@@ -144,49 +146,49 @@ Nodes (11): Code Example, code:astro (---), code:javascript (// Traditional SPA:
Cohesion: 0.18 Cohesion: 0.18
Nodes (10): code:hcl (# Define AWS EC2 instance), code:dockerfile (# Multi-stage build for a Go application), code:yaml (# AWS Cost allocation tags), Common Pitfalls, Conclusion, Containerization as a Migration Strategy, Cost Optimization Strategies, Infrastructure as Code (+2 more) Nodes (10): code:hcl (# Define AWS EC2 instance), code:dockerfile (# Multi-stage build for a Go application), code:yaml (# AWS Cost allocation tags), Common Pitfalls, Conclusion, Containerization as a Migration Strategy, Cost Optimization Strategies, Infrastructure as Code (+2 more)
### Community 18 - "Community 18" ### Community 16 - "Community 16"
Cohesion: 0.43 Cohesion: 0.2
Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter() Nodes (9): Catalogue Listing vs Bidding: How You Actually Win, Common Reasons Software Vendors Get Rejected or Delayed, Document Checklist, Fees, Caution Money, and Timelines, Frequently Asked Questions, MSME and Startup Benefits Worth Claiming, Step-by-Step Registration Process, Turning GeM Access Into Government Contracts (+1 more)
### Community 19 - "Community 19" ### Community 18 - "Community 18"
Cohesion: 0.36 Cohesion: 0.22
Nodes (7): extract_scores(), get_issues(), main(), Run Lighthouse audit on a URL, Extract key scores from Lighthouse data, Extract issues from Lighthouse audit, run_lighthouse() Nodes (8): 2026 Cost Ranges by Project Tier, A Simple Way to Estimate Your Own Cost, Build vs Buy: When Custom Actually Pays Off, Frequently Asked Questions, Getting an Accurate Number for Your Project, Hidden Costs Buyers Forget to Budget, The Biggest Cost Driver: The Team, What "Custom ERP" Actually Means for Pricing
### Community 20 - "Community 20" ### Community 20 - "Community 20"
Cohesion: 0.46 Cohesion: 0.43
Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady() Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter()
### Community 21 - "Community 21" ### Community 21 - "Community 21"
Cohesion: 0.36 Cohesion: 0.36
Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue()
### Community 22 - "Community 22"
Cohesion: 0.43
Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter()
### Community 23 - "Community 23"
Cohesion: 0.46
Nodes (7): checkRateLimit(), corsHeaders(), escapeHtml(), OPTIONS(), POST(), sendContactEmail(), validateContactForm()
### Community 24 - "Community 24"
Cohesion: 0.36
Nodes (7): extract_scores(), get_issues(), main(), Run Lighthouse audit on a URL, Extract key scores from Lighthouse data, Extract issues from Lighthouse audit, run_lighthouse() Nodes (7): extract_scores(), get_issues(), main(), Run Lighthouse audit on a URL, Extract key scores from Lighthouse data, Extract issues from Lighthouse audit, run_lighthouse()
### Community 25 - "Community 25" ### Community 22 - "Community 22"
Cohesion: 0.46 Cohesion: 0.46
Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady() Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady()
### Community 26 - "Community 26" ### Community 23 - "Community 23"
Cohesion: 0.36 Cohesion: 0.36
Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue() Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue()
### Community 24 - "Community 24"
Cohesion: 0.46
Nodes (7): checkRateLimit(), corsHeaders(), escapeHtml(), OPTIONS(), POST(), sendContactEmail(), validateContactForm()
### Community 25 - "Community 25"
Cohesion: 0.43
Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter()
### Community 26 - "Community 26"
Cohesion: 0.36
Nodes (7): extract_scores(), get_issues(), main(), Run Lighthouse audit on a URL, Extract key scores from Lighthouse data, Extract issues from Lighthouse audit, run_lighthouse()
### Community 27 - "Community 27" ### Community 27 - "Community 27"
Cohesion: 0.48 Cohesion: 0.46
Nodes (5): initAllAnimations(), initButtonRipple(), initCounters(), initProgressBars(), initScrollReveal() Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady()
### Community 28 - "Community 28" ### Community 28 - "Community 28"
Cohesion: 0.48 Cohesion: 0.36
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard() Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue()
### Community 29 - "Community 29" ### Community 29 - "Community 29"
Cohesion: 0.48 Cohesion: 0.48
@@ -197,23 +199,31 @@ Cohesion: 0.48
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard() Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard()
### Community 31 - "Community 31" ### Community 31 - "Community 31"
Cohesion: 0.47 Cohesion: 0.48
Nodes (5): main(), measure_response_time(), print_stats(), Measure response time for a URL over multiple iterations, Print statistics for response times Nodes (5): initAllAnimations(), initButtonRipple(), initCounters(), initProgressBars(), initScrollReveal()
### Community 32 - "Community 32"
Cohesion: 0.48
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard()
### Community 33 - "Community 33" ### Community 33 - "Community 33"
Cohesion: 0.47 Cohesion: 0.47
Nodes (5): main(), measure_response_time(), print_stats(), Measure response time for a URL over multiple iterations, Print statistics for response times Nodes (5): main(), measure_response_time(), print_stats(), Measure response time for a URL over multiple iterations, Print statistics for response times
### Community 35 - "Community 35" ### Community 35 - "Community 35"
Cohesion: 0.47
Nodes (5): main(), measure_response_time(), print_stats(), Measure response time for a URL over multiple iterations, Print statistics for response times
### Community 37 - "Community 37"
Cohesion: 0.6 Cohesion: 0.6
Nodes (3): formatUptime(), GET(), percentile() Nodes (3): formatUptime(), GET(), percentile()
### Community 36 - "Community 36" ### Community 38 - "Community 38"
Cohesion: 0.6 Cohesion: 0.6
Nodes (3): formatUptime(), GET(), percentile() Nodes (3): formatUptime(), GET(), percentile()
## Knowledge Gaps ## Knowledge Gaps
- **140 isolated node(s):** `Run Lighthouse audit on a URL`, `Extract key scores from Lighthouse data`, `Extract issues from Lighthouse audit`, `Measure response time for a URL over multiple iterations`, `Print statistics for response times` (+135 more) - **156 isolated node(s):** `Run Lighthouse audit on a URL`, `Extract key scores from Lighthouse data`, `Extract issues from Lighthouse audit`, `Measure response time for a URL over multiple iterations`, `Print statistics for response times` (+151 more)
These have ≤1 connection - possible missing edges or undocumented components. These have ≤1 connection - possible missing edges or undocumented components.
- **27 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes. - **27 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
@@ -223,7 +233,7 @@ _Questions this graph is uniquely positioned to answer:_
- **Are the 2 inferred relationships involving `captureException()` (e.g. with `sendContactEmail()` and `subscribeToNewsletter()`) actually correct?** - **Are the 2 inferred relationships involving `captureException()` (e.g. with `sendContactEmail()` and `subscribeToNewsletter()`) actually correct?**
_`captureException()` has 2 INFERRED edges - model-reasoned connections that need verification._ _`captureException()` has 2 INFERRED edges - model-reasoned connections that need verification._
- **What connects `Run Lighthouse audit on a URL`, `Extract key scores from Lighthouse data`, `Extract issues from Lighthouse audit` to the rest of the system?** - **What connects `Run Lighthouse audit on a URL`, `Extract key scores from Lighthouse data`, `Extract issues from Lighthouse audit` to the rest of the system?**
_140 weakly-connected nodes found - possible documentation gaps or missing edges._ _156 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `Community 0` be split into smaller, more focused modules?** - **Should `Community 0` be split into smaller, more focused modules?**
_Cohesion score 0.05 - nodes in this community are weakly interconnected._ _Cohesion score 0.05 - nodes in this community are weakly interconnected._
- **Should `Community 1` be split into smaller, more focused modules?** - **Should `Community 1` be split into smaller, more focused modules?**
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_middleware_ts", "label": "middleware.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L1"}, {"id": "src_middleware_validatecsrforigin", "label": "validateCsrfOrigin()", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L19"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_middleware_ts", "target": "astro_middleware", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L1", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_middleware_ts", "target": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_utils_logger_ts", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L2", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_middleware_ts", "target": "utils_logger_logger", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L2", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_middleware_ts", "target": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_utils_sentry_ts", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L3", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_middleware_ts", "target": "utils_sentry_captureexception", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L3", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_middleware_ts", "target": "src_middleware_validatecsrforigin", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L19", "weight": 1.0}], "raw_calls": [{"caller_nid": "src_middleware_validatecsrforigin", "callee": "includes", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L24"}, {"caller_nid": "src_middleware_validatecsrforigin", "callee": "startsWith", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L25"}, {"caller_nid": "src_middleware_validatecsrforigin", "callee": "get", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L36"}, {"caller_nid": "src_middleware_validatecsrforigin", "callee": "get", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L37"}, {"caller_nid": "src_middleware_validatecsrforigin", "callee": "startsWith", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L52"}, {"caller_nid": "src_middleware_validatecsrforigin", "callee": "startsWith", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L52"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_sitemap_xml_ts", "label": "sitemap.xml.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L1"}, {"id": "pages_sitemap_xml_get", "label": "GET()", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L13"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_sitemap_xml_ts", "target": "astro", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L1", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_sitemap_xml_ts", "target": "astro_content", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L2", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_sitemap_xml_ts", "target": "pages_sitemap_xml_get", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L13", "weight": 1.0}], "raw_calls": [{"caller_nid": "pages_sitemap_xml_get", "callee": "getCollection", "is_member_call": false, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L15"}, {"caller_nid": "pages_sitemap_xml_get", "callee": "getCollection", "is_member_call": false, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L20"}, {"caller_nid": "pages_sitemap_xml_get", "callee": "map", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L43"}, {"caller_nid": "pages_sitemap_xml_get", "callee": "map", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L52"}, {"caller_nid": "pages_sitemap_xml_get", "callee": "join", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L69"}, {"caller_nid": "pages_sitemap_xml_get", "callee": "map", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L69"}]}
File diff suppressed because one or more lines are too long
+697 -290
View File
File diff suppressed because it is too large Load Diff
+52
View File
@@ -58,6 +58,9 @@ Allow: /
User-agent: GPTBot User-agent: GPTBot
Allow: / Allow: /
User-agent: OAI-SearchBot
Allow: /
# Anthropic/Claude # Anthropic/Claude
User-agent: anthropic-ai User-agent: anthropic-ai
Allow: / Allow: /
@@ -65,10 +68,22 @@ Allow: /
User-agent: Claude-Web User-agent: Claude-Web
Allow: / Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: Claude-SearchBot
Allow: /
User-agent: Claude-User
Allow: /
# Perplexity AI # Perplexity AI
User-agent: PerplexityBot User-agent: PerplexityBot
Allow: / Allow: /
User-agent: Perplexity-User
Allow: /
# Google Gemini/Bard # Google Gemini/Bard
User-agent: Google-Extended User-agent: Google-Extended
Allow: / Allow: /
@@ -80,3 +95,40 @@ Allow: /
# Meta AI # Meta AI
User-agent: FacebookBot User-agent: FacebookBot
Allow: / Allow: /
User-agent: Meta-ExternalAgent
Allow: /
User-agent: meta-externalagent
Allow: /
# Apple Intelligence
User-agent: Applebot
Allow: /
User-agent: Applebot-Extended
Allow: /
# Amazon AI
User-agent: Amazonbot
Allow: /
# ByteDance / TikTok AI
User-agent: Bytespider
Allow: /
# Cohere AI
User-agent: cohere-ai
Allow: /
# DuckDuckGo AI (DuckAssist)
User-agent: DuckAssistBot
Allow: /
# You.com AI
User-agent: YouBot
Allow: /
# Diffbot (knowledge graph / AI training)
User-agent: Diffbot
Allow: /
+166
View File
@@ -0,0 +1,166 @@
/*!
* WRNexus Web Push — service worker.
*
* ┌─────────────────────────────────────────────────────────────────────────┐
* │ HOST THIS FILE ON YOUR OWN ORIGIN. │
* │ │
* │ A service worker can only control the origin it is served from, so the │
* │ browser will NOT let wrnexus.com register a worker for your site. Copy │
* │ this file to the ROOT of your site and serve it at: │
* │ │
* │ https://your-domain.example/wrnexus-push-sw.js │
* │ │
* │ It must be served same-origin, over HTTPS, with │
* │ `Content-Type: text/javascript`. To control the whole site from a │
* │ non-root path, also send `Service-Worker-Allowed: /`. │
* └─────────────────────────────────────────────────────────────────────────┘
*
* Core behaviour needs NO configuration — the click/delivery tracking URLs are
* carried inside each push payload built by the WRNexus API. The optional
* CONFIG block below is only used to silently re-subscribe after the browser
* rotates a subscription (`pushsubscriptionchange`); leave it blank to skip
* that.
*/
/* eslint-disable no-restricted-globals */
// ── OPTIONAL: only needed for automatic re-subscription on key rotation ──────
var WRNEXUS_PUSH_CONFIG = {
// Your site's publicKey (the same value as data-site-id in the snippet).
siteKey: '',
// The WRNexus API origin (e.g. 'https://api.wrnexus.com').
apiBase: '',
// Your site's VAPID public key (shown in Push → Settings).
vapidPublicKey: '',
};
self.addEventListener('install', function () {
self.skipWaiting();
});
self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim());
});
// CORS-simple POST (text/plain, no custom headers, no credentials) so the
// receipt reaches the WRNexus API cross-origin without a preflight.
function postReceipt(url) {
if (!url) return Promise.resolve();
return fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
credentials: 'omit',
keepalive: true,
mode: 'cors',
}).catch(function () {});
}
function urlBase64ToUint8Array(base64String) {
var padding = '='.repeat((4 - (base64String.length % 4)) % 4);
var base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
var raw = self.atob(base64);
var out = new Uint8Array(raw.length);
for (var i = 0; i < raw.length; ++i) out[i] = raw.charCodeAt(i);
return out;
}
function keyToB64(buf) {
if (!buf) return '';
var bytes = new Uint8Array(buf);
var binary = '';
for (var i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
return self
.btoa(binary)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
// ── Incoming notification ─────────────────────────────────────────────────
self.addEventListener('push', function (event) {
var data = {};
try {
data = event.data ? event.data.json() : {};
} catch (_e) {
try {
data = { title: event.data && event.data.text() };
} catch (_e2) {
data = {};
}
}
var title = data.title || 'Notification';
var options = {
body: data.body || '',
icon: data.icon || undefined,
image: data.image || undefined,
badge: data.badge || undefined,
tag: data.tag || data.campaignId || undefined,
requireInteraction: !!data.requireInteraction,
data: {
clickUrl: data.clickUrl || data.url || '/',
url: data.url || '/',
openUrl: data.openUrl || '',
campaignId: data.campaignId || '',
subscriptionId: data.subscriptionId || '',
},
};
event.waitUntil(
Promise.all([
self.registration.showNotification(title, options),
// Delivery receipt — records a `delivered` event for analytics.
postReceipt(data.deliveredUrl),
])
);
});
// ── Click → record + open the destination ─────────────────────────────────
self.addEventListener('notificationclick', function (event) {
var d = (event.notification && event.notification.data) || {};
event.notification.close();
// `clickUrl` is a signed WRNexus redirect: hitting it records the `clicked`
// event and 302s to the real destination, so tracking fires from the open.
var target = d.clickUrl || d.url || '/';
event.waitUntil(
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function (list) {
// Reuse an existing tab on the destination when possible.
for (var i = 0; i < list.length; i++) {
var client = list[i];
if (d.url && client.url === d.url && 'focus' in client) return client.focus();
}
if (self.clients.openWindow) return self.clients.openWindow(target);
return undefined;
})
);
});
// ── Optional: re-subscribe after the browser rotates the subscription ──────
self.addEventListener('pushsubscriptionchange', function (event) {
var cfg = WRNEXUS_PUSH_CONFIG || {};
if (!cfg.siteKey || !cfg.apiBase || !cfg.vapidPublicKey) return;
event.waitUntil(
self.registration.pushManager
.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(cfg.vapidPublicKey) })
.then(function (sub) {
return fetch(cfg.apiBase.replace(/\/+$/, '') + '/api/push/subscriptions', {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
credentials: 'omit',
mode: 'cors',
body: JSON.stringify({
key: cfg.siteKey,
endpoint: sub.endpoint,
keys: {
p256dh: keyToB64(sub.getKey('p256dh')),
auth: keyToB64(sub.getKey('auth')),
},
metadata: { source: 'pushsubscriptionchange' },
}),
}).catch(function () {});
})
.catch(function () {})
);
});
+117
View File
@@ -166,6 +166,23 @@ const currentYear = new Date().getFullYear();
By subscribing, you agree to our By subscribing, you agree to our
<a href="/privacy" class="text-primary hover:underline">Privacy Policy</a>. <a href="/privacy" class="text-primary hover:underline">Privacy Policy</a>.
</p> </p>
<!-- Web push opt-in (WRNexus). User-gesture subscribe control.
Hidden by default; the client script below reveals it only when the
WRNexus SDK + browser push support are available, and sets the
correct label based on the visitor's current subscription state. -->
<button
id="wrnexus-subscribe"
type="button"
hidden
aria-label="Enable notifications"
class="mt-6 w-full px-4 py-3 bg-secondary-800 border border-secondary-700 text-white font-medium rounded-lg hover:bg-secondary-700 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:ring-offset-secondary-900 transition-all duration-200 text-sm flex items-center justify-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed"
>
<svg class="w-4 h-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
<span id="wrnexus-subscribe-label">Enable notifications</span>
</button>
</div> </div>
</div> </div>
</div> </div>
@@ -193,6 +210,106 @@ const currentYear = new Date().getFullYear();
</div> </div>
</footer> </footer>
<!-- WRNexus push subscribe: state-aware control wired via addEventListener
(Astro strips inline on* handlers). The button reflects whether the
visitor is already subscribed and lets them toggle the subscription. -->
<script is:inline>
(function () {
function init() {
var btn = document.getElementById('wrnexus-subscribe');
var label = document.getElementById('wrnexus-subscribe-label');
if (!btn || !label) return;
var push = window.WRNexusPush;
var supported =
push &&
'Notification' in window &&
'serviceWorker' in navigator &&
'PushManager' in window;
// No SDK or no browser push support: keep the button hidden entirely.
if (!supported) {
btn.hidden = true;
return;
}
// Permission permanently blocked by the user: the SDK can't re-prompt,
// so surface a clear, non-actionable disabled state instead of a button
// that silently does nothing.
if (window.Notification && Notification.permission === 'denied') {
label.textContent = 'Notifications blocked';
btn.setAttribute('aria-label', 'Notifications are blocked in your browser settings');
btn.disabled = true;
btn.hidden = false;
return;
}
// Render the button for a given subscription state.
// Default UX: subscribed users get an actionable "Unsubscribe" toggle
// rather than a dead "Already subscribed" label.
// (Alternative: render a disabled "Already subscribed" indicator instead
// of the Unsubscribe toggle if product prefers a non-actionable state.)
function render(subscribed) {
if (subscribed) {
label.textContent = 'Unsubscribe';
btn.setAttribute('aria-label', 'Unsubscribe from notifications');
} else {
label.textContent = 'Enable notifications';
btn.setAttribute('aria-label', 'Enable notifications');
}
btn.dataset.subscribed = subscribed ? 'true' : 'false';
btn.disabled = false;
btn.hidden = false;
}
async function refresh() {
try {
var subscribed = await push.isSubscribed();
render(!!subscribed);
} catch (err) {
// On error, fall back to the unsubscribed label so the control stays usable.
console.error('[wrnexus-push] isSubscribed() failed:', err);
render(false);
}
}
btn.addEventListener('click', async function () {
if (btn.disabled) return;
var wasSubscribed = btn.dataset.subscribed === 'true';
var busyLabel = wasSubscribed ? 'Unsubscribing…' : 'Enabling…';
// In-progress state to prevent double-clicks while the promise is pending.
btn.disabled = true;
label.textContent = busyLabel;
try {
if (wasSubscribed) {
await push.unsubscribe();
} else {
// prompt() must run from this real user gesture.
await push.prompt();
}
} catch (err) {
console.error('[wrnexus-push] toggle failed:', err);
} finally {
// Re-read the authoritative state and re-render the label.
await refresh();
}
});
// Determine and render the initial state.
refresh();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>
<script> <script>
import { trackNewsletterSignup } from '../utils/analytics'; import { trackNewsletterSignup } from '../utils/analytics';
+18 -11
View File
@@ -18,61 +18,68 @@
// - Keyboard: native <button> handles Enter & Space; Tab moves between // - Keyboard: native <button> handles Enter & Space; Tab moves between
// items. No custom key handling needed. // items. No custom key handling needed.
// Each answer is written as a definitive, self-contained paragraph: it can be
// read (or quoted) in isolation without the question for context, and the
// buyer-stage answers bundle the WorkRoot name with the canonical numbers
// (founded 2022 · 20+ projects · 11+ districts · 50,000+ records · 99.9%
// uptime · GeM · IIT Patna). Keep every figure identical to <ProofNumbers />
// and the homepage `companyFacts` set — the visible text below is also the
// source for the FAQPage JSON-LD emitted at the bottom of this component.
const moneyFaqs = [ const moneyFaqs = [
{ {
question: 'How much does a project cost? Do you work with fixed budgets?', question: 'How much does a project cost? Do you work with fixed budgets?',
answer: answer:
'Projects start from ₹1,00,000, and most web, mobile, and ERP builds land between ₹2L and ₹15L depending on scope. And yes — we work with fixed budgets: tell us the number you have in mind and we will scope what we can deliver within it. Every quote is free, itemised, and no-obligation.', 'WorkRoot IT Solutions projects start from ₹1,00,000, and most web, mobile, and ERP builds land between ₹2,00,000 and ₹15,00,000 depending on scope. Yes — we work with fixed budgets: tell us the number you have in mind and we will scope exactly what we can deliver within it. Every quote is free, itemised, and carries no obligation.',
}, },
{ {
question: 'How much does a custom ERP cost?', question: 'How much does a custom ERP cost?',
answer: answer:
'Typical engagements run ₹2L–₹15L depending on scope. We offer fixed-price options for well-defined projects and time-and-materials for evolving ones.', 'A custom ERP from WorkRoot IT Solutions typically runs ₹2,00,000–₹15,00,000, with a genuinely production-grade single-department system starting around ₹6,00,000. We offer fixed-price quotes for well-defined scopes and time-and-materials pricing for evolving ones, and every estimate is free and itemised.',
}, },
{ {
question: 'What is your typical timeline?', question: 'What is your typical timeline?',
answer: answer:
'416 weeks from kickoff to launch. MVPs ship in ~6 weeks on average.', 'Most WorkRoot IT Solutions engagements run 416 weeks from kickoff to launch, and a typical MVP ships in about 6 weeks. We work in 2-week sprints with a live staging environment from day one, so you see and steer working software early rather than waiting for a single big-bang release.',
}, },
{ {
question: 'Do you sign NDAs?', question: 'Do you sign NDAs?',
answer: answer:
'Yes — happily. We sign mutual NDAs before any detailed discussion.', 'Yes. WorkRoot IT Solutions signs a mutual NDA before any detailed discussion of your idea, data, or codebase — at no cost and with no conditions attached. Your concept and commercial details stay confidential from the very first call.',
}, },
{ {
question: 'Who owns the source code?', question: 'Who owns the source code?',
answer: answer:
'You do. Full IP transfer is included; we hand over repositories, deployment access, and documentation.', 'You do. Every WorkRoot IT Solutions engagement includes full intellectual-property transfer on delivery: you receive the complete source repositories, deployment access, and documentation, with no per-seat licence fees and no vendor lock-in. You are free to host, extend, or hand the system to another team at any time.',
}, },
{ {
question: 'Do you work with government tenders / GeM?', question: 'Do you work with government tenders / GeM?',
answer: answer:
"Yes. We're empanelled for relevant categories and have delivered platforms used across 11 districts in Bihar.", 'Yes. WorkRoot IT Solutions is empanelled on the Government e-Marketplace (GeM) for relevant IT service categories and can respond to government tenders. We have delivered public-service platforms live across 11+ districts of Bihar at 99.9% uptime, including a Police Management System and a Smart Crematorium Management System for the Bihar Government.',
}, },
{ {
question: 'What technologies do you work with?', question: 'What technologies do you work with?',
answer: answer:
'Astro, Next.js, Angular, React Native, Spring Boot, Go, Node.js, PostgreSQL, and the full AWS/GCP stack.', 'WorkRoot IT Solutions builds on Astro, Next.js, Angular, and React Native on the front end; Spring Boot, Go, and Node.js on the back end; and PostgreSQL or MySQL for data — the same stack behind the IIT Patna University Management System and our multi-district government deployments, all deployed and operated on the AWS and GCP cloud.',
}, },
{ {
question: 'Do you offer ongoing support after launch?', question: 'Do you offer ongoing support after launch?',
answer: answer:
'Yes. We offer maintenance packages covering bug fixes, security patches, performance monitoring, and feature enhancements with response SLAs to match your uptime needs.', 'Yes. WorkRoot IT Solutions offers maintenance packages covering bug fixes, security patches, performance monitoring, and feature enhancements, each with a response SLA matched to your uptime needs — the same operational discipline that keeps our government systems running at 99.9% uptime.',
}, },
{ {
question: 'What is your development process?', question: 'What is your development process?',
answer: 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.', 'WorkRoot IT Solutions works in Agile 2-week sprints with weekly check-ins and a live staging environment from day one. You see working software early and steer the roadmap as it ships, rather than waiting for a single final handover at the end of the project.',
}, },
{ {
question: 'Can you work with our existing team?', question: 'Can you work with our existing team?',
answer: 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.', 'Absolutely. WorkRoot IT Solutions augments in-house teams as readily as it delivers fully managed builds, adapting to your repository, branch strategy, ticketing system, and code-review process so we slot into the way your team already works.',
}, },
{ {
question: 'How do you ensure project security?', question: 'How do you ensure project security?',
answer: 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.', 'Security is built into every phase at WorkRoot IT Solutions: OWASP-aligned code reviews, encrypted data at rest and in transit, role-based access control, regular dependency audits, and GDPR- and SOC 2-style controls where a project demands them. The same architecture secures a Bihar government police platform deployed across 11 districts.',
}, },
]; ];
+2 -2
View File
@@ -40,12 +40,12 @@ interface Props {
// credibility bug this set was created to fix. // credibility bug this set was created to fix.
// //
// - "20+ Projects" — total delivered engagements // - "20+ Projects" — total delivered engagements
// - "11 Districts Deployed" matches hero trust badge copy // - "11+ Districts Deployed" — cumulative Bihar government district reach
// - "50,000+ Records Managed" — records under management across deployments // - "50,000+ Records Managed" — records under management across deployments
// - "8+ Years Avg Experience" — average team experience // - "8+ Years Avg Experience" — average team experience
const defaultItems: ProofItem[] = [ const defaultItems: ProofItem[] = [
{ value: '20+', label: 'Projects' }, { value: '20+', label: 'Projects' },
{ value: '11', label: 'Districts Deployed' }, { value: '11+', label: 'Districts Deployed' },
{ value: '50,000+', label: 'Records Managed' }, { value: '50,000+', label: 'Records Managed' },
{ value: '8+', label: 'Years Avg Experience' }, { value: '8+', label: 'Years Avg Experience' },
]; ];
+7 -7
View File
@@ -54,7 +54,7 @@ const orgId = `${baseUrl}/#organization`;
// src/pages/index.astro). All three are 5★, so the aggregate rating is 5.0 // src/pages/index.astro). All three are 5★, so the aggregate rating is 5.0
// over reviewCount = 3. // over reviewCount = 3.
// //
// Headline proof numbers in the description (20+ projects · 11 districts · // Headline proof numbers in the description (20+ projects · 11+ districts ·
// 50,000+ records · 99.9% uptime) mirror the canonical set in // 50,000+ records · 99.9% uptime) mirror the canonical set in
// ProofNumbers.astro and the /services stats — keep them identical site-wide. // ProofNumbers.astro and the /services stats — keep them identical site-wide.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -102,7 +102,7 @@ const organizationSchema = {
"height": "512" "height": "512"
}, },
"image": `${baseUrl}/og-image.jpg`, "image": `${baseUrl}/og-image.jpg`,
"description": "WorkRoot IT Solutions LLP builds production-grade web, mobile, ERP, and government IT systems — 20+ projects delivered across 11 districts, 50,000+ records managed, and 99.9% uptime on mission-critical government deployments.", "description": "WorkRoot IT Solutions LLP builds production-grade web, mobile, ERP, and government IT systems — 20+ projects delivered across 11+ districts, 50,000+ records managed, and 99.9% uptime on mission-critical government deployments.",
"foundingDate": "2022", "foundingDate": "2022",
"founder": { "founder": {
"@type": "Person", "@type": "Person",
@@ -238,7 +238,7 @@ const blogPostingSchema = article && type === 'BlogPosting' ? {
"dateModified": article.dateModified || article.datePublished, "dateModified": article.dateModified || article.datePublished,
"publisher": { "publisher": {
"@type": "Organization", "@type": "Organization",
"name": "WorkRoot IT Solutions", "name": "WorkRoot IT Solutions LLP",
"url": baseUrl, "url": baseUrl,
"logo": { "logo": {
"@type": "ImageObject", "@type": "ImageObject",
@@ -298,7 +298,7 @@ const webPageSchema = type === 'WebPage' && Astro.url.pathname !== '/' ? {
}, },
"about": { "about": {
"@type": "Organization", "@type": "Organization",
"name": "WorkRoot IT Solutions" "name": "WorkRoot IT Solutions LLP"
}, },
"primaryImageOfPage": { "primaryImageOfPage": {
"@type": "ImageObject", "@type": "ImageObject",
@@ -330,7 +330,7 @@ const aboutPageSchema = type === 'AboutPage' ? {
"inLanguage": "en-US", "inLanguage": "en-US",
"mainEntity": { "mainEntity": {
"@type": "Organization", "@type": "Organization",
"name": "WorkRoot IT Solutions", "name": "WorkRoot IT Solutions LLP",
"url": baseUrl "url": baseUrl
} }
} : null; } : null;
@@ -345,8 +345,8 @@ const contactPageSchema = type === 'ContactPage' ? {
"inLanguage": "en-US", "inLanguage": "en-US",
"mainEntity": { "mainEntity": {
"@type": "LocalBusiness", "@type": "LocalBusiness",
"name": "WorkRoot IT Solutions", "name": "WorkRoot IT Solutions LLP",
"alternateName": "WorkRoot IT Solutions LLP", "alternateName": "WorkRoot IT Solutions",
"url": baseUrl, "url": baseUrl,
"telephone": "+91-9561417403", "telephone": "+91-9561417403",
"email": "admin@workroot.in", "email": "admin@workroot.in",
+11
View File
@@ -181,6 +181,17 @@ const fullTitle = title === 'Home' ? homeTitle : `${title} | ${brand}`;
<!-- Analytics (GA4 or Plausible) --> <!-- Analytics (GA4 or Plausible) -->
<Analytics /> <Analytics />
<!-- WRNexus Web Push SDK — loaded once globally so it runs on every page
that should collect push subscribers. is:inline keeps Astro's bundler
from rewriting the external src and preserves data-site-id / defer. -->
<script is:inline src="https://api.wrnexus.com/push/sdk.js" data-site-id="pk_rP51XN8tZnud5HdX9UqidjHk" defer></script>
<!-- WRNexus Bot Widget — loaded once globally here so the chat widget
renders on every route (pages + blog posts) via this shared layout.
is:inline keeps Astro's bundler from rewriting the external src and
preserves data-bot-id / async. -->
<script is:inline src="https://api.wrnexus.com/bots/widget.js" data-bot-id="d3998f1b-51d7-4f7c-a57e-3fa1af4394a0" async></script>
<!-- Additional head content slot --> <!-- Additional head content slot -->
<slot name="head" /> <slot name="head" />
</head> </head>
+6 -4
View File
@@ -155,11 +155,13 @@ export const onRequest = defineMiddleware(async (context, next) => {
// - inline scripts/styles: Required for Astro framework // - inline scripts/styles: Required for Astro framework
// - https://www.googletagmanager.com & https://www.google-analytics.com: GA4 // - https://www.googletagmanager.com & https://www.google-analytics.com: GA4
// - https://plausible.io: Plausible Analytics // - https://plausible.io: Plausible Analytics
// - https://api.wrnexus.com: WRNexus web-push SDK (sdk.js), bot widget
// (bots/widget.js) + runtime config/iframe served from the same origin
// Note: unsafe-eval is removed in production (only needed during Astro HMR dev mode) // Note: unsafe-eval is removed in production (only needed during Astro HMR dev mode)
const isProduction = import.meta.env.PROD; const isProduction = import.meta.env.PROD;
const scriptSrc = isProduction const scriptSrc = isProduction
? "script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com https://plausible.io" ? "script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com https://plausible.io https://api.wrnexus.com"
: "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com https://plausible.io"; : "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com https://plausible.io https://api.wrnexus.com";
const csp = [ const csp = [
"default-src 'self'", "default-src 'self'",
@@ -167,11 +169,11 @@ export const onRequest = defineMiddleware(async (context, next) => {
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"img-src 'self' data: https://images.unsplash.com https://www.google-analytics.com https://www.googletagmanager.com https://maps.googleapis.com https://maps.gstatic.com", "img-src 'self' data: https://images.unsplash.com https://www.google-analytics.com https://www.googletagmanager.com https://maps.googleapis.com https://maps.gstatic.com",
"font-src 'self' data: https://fonts.gstatic.com", "font-src 'self' data: https://fonts.gstatic.com",
"connect-src 'self' https://www.google-analytics.com https://analytics.google.com https://stats.g.doubleclick.net https://plausible.io", "connect-src 'self' https://www.google-analytics.com https://analytics.google.com https://stats.g.doubleclick.net https://plausible.io https://api.wrnexus.com",
"worker-src 'self'", "worker-src 'self'",
"manifest-src 'self'", "manifest-src 'self'",
"object-src 'none'", // Block Flash/plugins (defense-in-depth) "object-src 'none'", // Block Flash/plugins (defense-in-depth)
"frame-src https://www.google.com", // Allow Google Maps iframe embed "frame-src https://www.google.com https://api.wrnexus.com", // Google Maps iframe + WRNexus bot widget iframe
"frame-ancestors 'none'", "frame-ancestors 'none'",
"base-uri 'self'", "base-uri 'self'",
"form-action 'self'", "form-action 'self'",
+1 -1
View File
@@ -49,7 +49,7 @@ const teamMembers = [
// stats counters) so the numbers never contradict across pages. // stats counters) so the numbers never contradict across pages.
const stats = [ const stats = [
{ value: '20+', label: 'Projects' }, { value: '20+', label: 'Projects' },
{ value: '11', label: 'Districts Deployed' }, { value: '11+', label: 'Districts Deployed' },
{ value: '50,000+', label: 'Records Managed' }, { value: '50,000+', label: 'Records Managed' },
{ value: '8+', label: 'Years Avg Experience' }, { value: '8+', label: 'Years Avg Experience' },
]; ];
+108 -3
View File
@@ -124,7 +124,7 @@ const testimonials = [
]; ];
// NOTE: The homepage stat band is rendered once by <ProofNumbers />, which // NOTE: The homepage stat band is rendered once by <ProofNumbers />, which
// holds the canonical site-wide proof set (20+ Projects · 11 Districts · // holds the canonical site-wide proof set (20+ Projects · 11+ Districts ·
// 50,000+ Records · 8+ Years). The previously-duplicated "Numbers That // 50,000+ Records · 8+ Years). The previously-duplicated "Numbers That
// Matter" stats section (and its local `stats` array + count-up script) was // 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. // removed so a visitor never sees the same four numbers twice on one page.
@@ -171,11 +171,42 @@ const techStack = [
// "Common Questions" block that used to live above the tech-stack section // "Common Questions" block that used to live above the tech-stack section
// was removed; its unique questions were merged into HomepageFaq's // was removed; its unique questions were merged into HomepageFaq's
// `moneyFaqs` array so no buyer-facing content was dropped. // `moneyFaqs` array so no buyer-facing content was dropped.
// Quotable, self-contained company facts. Each sentence deliberately bundles
// the full company name + a concrete claim + a number so the line stays
// accurate and citable when lifted out of context (search snippets, press
// quotes, AI assistants). Every figure here is the canonical site-wide value
// and MUST stay identical to <ProofNumbers />, the hero trust badge, /about,
// and the /services pages. Do not change a number here without updating it
// everywhere it appears — mismatched stats reintroduce the credibility bug
// this canonical set was created to fix.
const companyFacts = [
'WorkRoot IT Solutions was founded in 2022 and has delivered 20+ production software systems for startups, enterprises, and government bodies.',
'WorkRoot IT Solutions runs government platforms across 11+ districts of Bihar, India, sustaining 99.9% uptime on mission-critical public services.',
'WorkRoot IT Solutions manages 50,000+ live records across its government and enterprise deployments.',
'WorkRoot IT Solutions built the University Management System for IIT Patna on a Next.js and Spring Boot stack.',
'WorkRoot IT Solutions is empanelled on the Government e-Marketplace (GeM), making it eligible to bid on Indian government IT tenders.',
'WorkRoot IT Solutions delivered a Police Management System that digitised FIRs and case tracking for 15,000+ officers across 11 districts of Bihar.',
'WorkRoot IT Solutions is a senior in-house engineering team averaging 8+ years of experience per engineer.',
];
// Comparison table — custom software (WorkRoot) vs off-the-shelf ERP / SaaS.
// Mirrors the deeper table on /services/custom-erp-development, surfaced on the
// homepage so the core buying trade-off is framed before a late-stage visitor
// reaches the inline lead form. The final row carries the canonical
// "11+ districts · 99.9% uptime" proof point.
const comparison = [
{ dimension: 'Fit to your workflow', offShelf: 'You reshape your process to fit the software', custom: 'The software is built around your exact process' },
{ dimension: 'Source-code ownership', offShelf: 'Vendor-locked, closed source', custom: 'Full IP transfer on delivery — you own the code' },
{ dimension: 'Licensing cost', offShelf: 'Recurring per-seat fees that grow with headcount', custom: 'One-time build — no per-seat licence tax' },
{ dimension: 'Data residency', offShelf: 'The vendors cloud and region', custom: 'Your servers, your region, your control' },
{ dimension: 'Proven at scale', offShelf: 'Generic, one-size-fits-all', custom: 'Live across 11+ districts at 99.9% uptime' },
];
--- ---
<BaseLayout <BaseLayout
title="Home" title="Home"
description="WorkRoot IT Solutions builds production-grade web, mobile, ERP, and government IT systems. 20+ projects delivered across 11 districts with 99.9% uptime." description="WorkRoot IT Solutions builds production-grade web, mobile, ERP, and government IT systems. 20+ projects delivered across 11+ districts with 99.9% uptime."
> >
<SEO <SEO
slot="head" slot="head"
@@ -208,7 +239,7 @@ const techStack = [
<svg class="w-5 h-5 text-accent-400" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true"> <svg class="w-5 h-5 text-accent-400" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
<path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/> <path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg> </svg>
<span class="text-sm font-medium text-white">Trusted by 20+ teams · Deployed across 11 districts</span> <span class="text-sm font-medium text-white">Trusted by 20+ teams · Deployed across 11+ districts</span>
</div> </div>
<!-- Main heading — benefit-led: outcome + audience --> <!-- Main heading — benefit-led: outcome + audience -->
@@ -681,6 +712,80 @@ const techStack = [
</div> </div>
</section> </section>
<!-- Company Facts + Comparison — extractable, self-contained statements
that bundle the WorkRoot name with a concrete claim and a number so each
line stays accurate when quoted out of context (search snippets, press,
AI assistants). Followed by a custom-vs-off-the-shelf comparison table so
the buying trade-off is framed in one glance. Every figure matches the
canonical set in <ProofNumbers />. -->
<section
id="company-facts"
class="py-24 bg-white dark:bg-secondary-900 border-t border-secondary-100 dark:border-secondary-800"
aria-labelledby="company-facts-heading"
>
<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-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 text-sm font-bold uppercase tracking-wider mb-4">The facts</span>
<h2 id="company-facts-heading" class="text-4xl sm:text-5xl font-bold text-secondary-900 dark:text-secondary-100 mb-6">WorkRoot at a glance</h2>
<p class="text-xl text-secondary-600 dark:text-secondary-400">
The record behind the work, stated plainly — so you know exactly who you're hiring.
</p>
</div>
<ul class="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-5 max-w-5xl mx-auto mb-20" role="list">
{companyFacts.map((fact, index) => (
<li
class="flex items-start gap-4 bg-secondary-50 dark:bg-secondary-800/60 rounded-2xl border border-secondary-100 dark:border-secondary-700 p-5 sm:p-6"
data-animate="fade-up"
data-delay={String((index % 2) * 100)}
>
<span class="flex-shrink-0 w-10 h-10 rounded-xl bg-primary-100 dark:bg-primary-900/40 flex items-center justify-center" aria-hidden="true">
<svg class="w-6 h-6 text-primary-600 dark:text-primary-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</span>
<p class="text-base sm:text-lg text-secondary-800 dark:text-secondary-200 leading-relaxed">{fact}</p>
</li>
))}
</ul>
<!-- Comparison table: custom build vs off-the-shelf. Reuses the markup
pattern from /services/custom-erp-development for visual consistency. -->
<div class="max-w-4xl mx-auto" data-animate="fade-up">
<div class="text-center mb-8">
<h3 class="text-2xl sm:text-3xl font-bold text-secondary-900 dark:text-secondary-100 mb-3">Custom software vs off-the-shelf ERP</h3>
<p class="text-secondary-600 dark:text-secondary-400 text-base sm:text-lg">Where a bespoke WorkRoot build pays you back over a packaged SaaS tool.</p>
</div>
<div class="overflow-x-auto rounded-2xl border border-secondary-200 dark:border-secondary-700 shadow-sm">
<table class="w-full text-left border-collapse min-w-[640px]">
<caption class="sr-only">Comparison of off-the-shelf ERP software versus custom software built by WorkRoot IT Solutions</caption>
<thead>
<tr class="bg-secondary-50 dark:bg-secondary-800">
<th scope="col" class="px-5 py-4 text-sm font-bold text-secondary-700 dark:text-secondary-200">Dimension</th>
<th scope="col" class="px-5 py-4 text-sm font-bold text-secondary-500 dark:text-secondary-400">Off-the-shelf ERP</th>
<th scope="col" class="px-5 py-4 text-sm font-bold text-primary-700 dark:text-primary-300">Custom software (WorkRoot)</th>
</tr>
</thead>
<tbody>
{comparison.map((row, i) => (
<tr class={i % 2 === 0 ? 'bg-white dark:bg-secondary-900' : 'bg-secondary-50/50 dark:bg-secondary-800/40'}>
<th scope="row" class="px-5 py-4 text-sm font-semibold text-secondary-900 dark:text-secondary-100 align-top">{row.dimension}</th>
<td class="px-5 py-4 text-sm text-secondary-600 dark:text-secondary-400 align-top">{row.offShelf}</td>
<td class="px-5 py-4 text-sm text-secondary-700 dark:text-secondary-200 align-top font-medium">
<span class="inline-flex items-start gap-2">
<svg class="w-4 h-4 mt-0.5 flex-shrink-0 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /></svg>
{row.custom}
</span>
</td>
</tr>
))}
</tbody>
</table>
</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
+1 -1
View File
@@ -230,7 +230,7 @@ const featuredProjects = projects.filter(p => p.featured);
<div class="mt-14 grid grid-cols-2 sm:grid-cols-4 gap-4 lg:gap-6 reveal-on-scroll reveal-delay-2"> <div class="mt-14 grid grid-cols-2 sm:grid-cols-4 gap-4 lg:gap-6 reveal-on-scroll reveal-delay-2">
{[ {[
{ value: '20+', label: 'Projects' }, { value: '20+', label: 'Projects' },
{ value: '11', label: 'Districts Deployed' }, { value: '11+', label: 'Districts Deployed' },
{ value: '50,000+', label: 'Records Managed' }, { value: '50,000+', label: 'Records Managed' },
{ value: '8+', label: 'Years Avg Experience' }, { value: '8+', label: 'Years Avg Experience' },
].map((stat) => ( ].map((stat) => (
+1 -1
View File
@@ -294,7 +294,7 @@ const processSteps = [
<BaseLayout <BaseLayout
title="ERP, Web & Mobile App Development Services" title="ERP, Web & Mobile App Development Services"
description="Custom web development, mobile apps, ERP platforms, and government IT solutions from WorkRoot. 20+ projects delivered across 11 districts — get a free quote." description="Custom web development, mobile apps, ERP platforms, and government IT solutions from WorkRoot. 20+ projects delivered across 11+ districts — get a free quote."
> >
<SEO <SEO
slot="head" slot="head"
@@ -4,7 +4,7 @@
// ServiceFaq) and feeds Service + FAQ schema into SEO.astro. // ServiceFaq) and feeds Service + FAQ schema into SEO.astro.
// //
// All company stats mirror the canonical site-wide set used in // All company stats mirror the canonical site-wide set used in
// ProofNumbers.astro, /portfolio, and SEO.astro — 20+ projects · 11 districts · // ProofNumbers.astro, /portfolio, and SEO.astro — 20+ projects · 11+ districts ·
// 50,000+ records · 99.9% uptime · 8+ years. Case-study numbers are taken // 50,000+ records · 99.9% uptime · 8+ years. Case-study numbers are taken
// verbatim from the IIT Patna and Bihar government case studies on /portfolio. // verbatim from the IIT Patna and Bihar government case studies on /portfolio.
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
@@ -44,7 +44,7 @@ const comparison = [
const stats = [ const stats = [
{ value: '5+', label: 'ERP & enterprise deployments' }, { value: '5+', label: 'ERP & enterprise deployments' },
{ value: '20,000+', label: 'Users across systems' }, { value: '20,000+', label: 'Users across systems' },
{ value: '11', label: 'Districts running our platforms' }, { value: '11+', label: 'Districts running our platforms' },
{ value: '99.9%', label: 'Uptime on critical systems' }, { value: '99.9%', label: 'Uptime on critical systems' },
]; ];
@@ -4,7 +4,9 @@
// schema into SEO.astro. // schema into SEO.astro.
// //
// Canonical stats mirror ProofNumbers.astro / /portfolio / SEO.astro: // Canonical stats mirror ProofNumbers.astro / /portfolio / SEO.astro:
// 20+ projects · 11 districts · 50,000+ records · 99.9% uptime · 8+ years. // 20+ projects · 11+ districts · 50,000+ records · 99.9% uptime · 8+ years.
// (This page's own hero stat shows "11" — the specific Bihar deployment
// underpinning the company-wide "11+" figure; do not inflate it.)
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
import SEO from '../../components/SEO.astro'; import SEO from '../../components/SEO.astro';
import GuaranteeBand from '../../components/GuaranteeBand.astro'; import GuaranteeBand from '../../components/GuaranteeBand.astro';
+1 -1
View File
@@ -35,7 +35,7 @@ const sitemapSections = [
links: [ links: [
{ name: 'Custom ERP Development', url: '/services/custom-erp-development', description: 'Bespoke enterprise systems on Spring Boot & Angular' }, { name: 'Custom ERP Development', url: '/services/custom-erp-development', description: 'Bespoke enterprise systems on Spring Boot & Angular' },
{ name: 'University ERP', url: '/services/university-erp', description: 'Admissions, exams, grading — built for IIT Patna' }, { name: 'University ERP', url: '/services/university-erp', description: 'Admissions, exams, grading — built for IIT Patna' },
{ name: 'Government IT & GeM Vendor', url: '/services/government-it-solutions-gem-vendor', description: 'GeM-empanelled, live across 11 districts' }, { name: 'Government IT & GeM Vendor', url: '/services/government-it-solutions-gem-vendor', description: 'GeM-empanelled, live across 11+ districts' },
{ name: 'Police Management System', url: '/services/police-management-system', description: 'FIR, case tracking & personnel for 15,000+ officers' }, { name: 'Police Management System', url: '/services/police-management-system', description: 'FIR, case tracking & personnel for 15,000+ officers' },
{ name: 'React Native App Development', url: '/services/react-native-app-development', description: 'One codebase for iOS and Android' }, { name: 'React Native App Development', url: '/services/react-native-app-development', description: 'One codebase for iOS and Android' },
], ],
+2 -2
View File
@@ -84,7 +84,7 @@ export function generateArticleSchema(
}, },
publisher: { publisher: {
'@type': 'Organization', '@type': 'Organization',
name: 'WorkRoot IT Solutions', name: 'WorkRoot IT Solutions LLP',
logo: { logo: {
'@type': 'ImageObject', '@type': 'ImageObject',
url: 'https://workroot.in/logo.png', url: 'https://workroot.in/logo.png',
@@ -132,7 +132,7 @@ export function generateServiceSchema(
description: description, description: description,
provider: { provider: {
'@type': 'Organization', '@type': 'Organization',
name: 'WorkRoot IT Solutions', name: 'WorkRoot IT Solutions LLP',
url: siteUrl, url: siteUrl,
}, },
areaServed: { areaServed: {