Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fccc0115c4 | ||
|
|
189260bbb3 | ||
|
|
8e7070cbaf | ||
|
|
a8d7db562d | ||
|
|
e220f46d35 | ||
|
|
93460ece19 | ||
|
|
7b67897612 | ||
|
|
c5f79f1fd2 | ||
|
|
41abcaa7a4 | ||
|
|
966f3b23fa | ||
|
|
45b30918a0 | ||
|
|
7eeec7e781 | ||
|
|
527131a462 | ||
|
|
6b07b83583 | ||
|
|
c7e130ce4d | ||
|
|
5a2b68763f | ||
|
|
84624976a8 | ||
|
|
a3dd4b9ffa | ||
|
|
0121641163 | ||
|
|
8d35785a4c | ||
|
|
c1585570c6 | ||
|
|
22251ea82e | ||
|
|
a21aa99e90 | ||
|
|
e697750e22 | ||
|
|
23acac5c4d | ||
|
|
dccc5d3b7f | ||
|
|
be80d63f04 | ||
|
|
95a5af1d9a | ||
|
|
5a6db9c20f | ||
|
|
870075b33f | ||
|
|
8b16729ab4 | ||
|
|
1ab4dc1033 | ||
|
|
a064260c55 | ||
|
|
a7b1ae4800 | ||
|
|
4b7cfc4ab5 | ||
|
|
a79c734fe9 | ||
|
|
8ac0b8a760 | ||
|
|
52b6392f74 | ||
|
|
14aefa4a71 | ||
|
|
d64a191c12 | ||
|
|
4b2e96c681 | ||
|
|
1b922ddda3 | ||
|
|
bc10d5fe4b | ||
|
|
a5c6e32734 | ||
|
|
35feade8e7 | ||
|
|
c86fce6125 | ||
|
|
7f3d94178d | ||
|
|
9a523c038c | ||
|
|
8e60268caf | ||
|
|
2291336300 | ||
|
|
ff2870ba2a | ||
|
|
ad90b92dcd | ||
|
|
5a6847c774 | ||
|
|
e6feb36523 | ||
|
|
01cafba6c1 | ||
|
|
19a0720fe8 | ||
|
|
5c05779541 | ||
|
|
ce67f90f06 | ||
|
|
fb5e1ea281 | ||
|
|
03c37a623f |
@@ -1,11 +1,11 @@
|
||||
# Graph Report - 2e709500-79ab-403f-9ca0-3f027ac9f36e (2026-06-17)
|
||||
# Graph Report - 2e709500-79ab-403f-9ca0-3f027ac9f36e (2026-06-24)
|
||||
|
||||
## Corpus Check
|
||||
- 57 files · ~323,606 words
|
||||
- 64 files · ~615,668 words
|
||||
- Verdict: corpus is large enough that graph structure adds value.
|
||||
|
||||
## Summary
|
||||
- 522 nodes · 567 edges · 93 communities (86 shown, 7 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)
|
||||
- Token cost: 0 input · 0 output
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
- [[_COMMUNITY_Community 5|Community 5]]
|
||||
- [[_COMMUNITY_Community 6|Community 6]]
|
||||
- [[_COMMUNITY_Community 7|Community 7]]
|
||||
- [[_COMMUNITY_Community 9|Community 9]]
|
||||
- [[_COMMUNITY_Community 8|Community 8]]
|
||||
- [[_COMMUNITY_Community 10|Community 10]]
|
||||
- [[_COMMUNITY_Community 12|Community 12]]
|
||||
- [[_COMMUNITY_Community 11|Community 11]]
|
||||
- [[_COMMUNITY_Community 13|Community 13]]
|
||||
- [[_COMMUNITY_Community 14|Community 14]]
|
||||
- [[_COMMUNITY_Community 15|Community 15]]
|
||||
@@ -49,7 +49,30 @@
|
||||
- [[_COMMUNITY_Community 33|Community 33]]
|
||||
- [[_COMMUNITY_Community 34|Community 34]]
|
||||
- [[_COMMUNITY_Community 35|Community 35]]
|
||||
- [[_COMMUNITY_Community 40|Community 40]]
|
||||
- [[_COMMUNITY_Community 36|Community 36]]
|
||||
- [[_COMMUNITY_Community 37|Community 37]]
|
||||
- [[_COMMUNITY_Community 38|Community 38]]
|
||||
- [[_COMMUNITY_Community 44|Community 44]]
|
||||
- [[_COMMUNITY_Community 45|Community 45]]
|
||||
- [[_COMMUNITY_Community 46|Community 46]]
|
||||
- [[_COMMUNITY_Community 47|Community 47]]
|
||||
- [[_COMMUNITY_Community 48|Community 48]]
|
||||
- [[_COMMUNITY_Community 49|Community 49]]
|
||||
- [[_COMMUNITY_Community 50|Community 50]]
|
||||
- [[_COMMUNITY_Community 51|Community 51]]
|
||||
- [[_COMMUNITY_Community 52|Community 52]]
|
||||
- [[_COMMUNITY_Community 53|Community 53]]
|
||||
- [[_COMMUNITY_Community 54|Community 54]]
|
||||
- [[_COMMUNITY_Community 55|Community 55]]
|
||||
- [[_COMMUNITY_Community 56|Community 56]]
|
||||
- [[_COMMUNITY_Community 57|Community 57]]
|
||||
- [[_COMMUNITY_Community 58|Community 58]]
|
||||
- [[_COMMUNITY_Community 59|Community 59]]
|
||||
- [[_COMMUNITY_Community 60|Community 60]]
|
||||
- [[_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)
|
||||
1. `Performance Testing Scripts` - 12 edges
|
||||
@@ -73,7 +96,7 @@
|
||||
- `POST()` --calls--> `logApiRequest()` [INFERRED]
|
||||
src/pages/api/newsletter.ts → src/utils/logger.ts
|
||||
|
||||
## Communities (93 total, 7 thin omitted)
|
||||
## Communities (117 total, 27 thin omitted)
|
||||
|
||||
### Community 0 - "Community 0"
|
||||
Cohesion: 0.05
|
||||
@@ -105,92 +128,104 @@ Nodes (15): 1. Modular Monolith Over Premature Microservices, 2. Role-Based Acce
|
||||
|
||||
### Community 7 - "Community 7"
|
||||
Cohesion: 0.14
|
||||
Nodes (13): 1. Multi-District Deployment Complexity, 2. Security and Compliance, 3. Reliability Under Constraint, Architecture Decisions That Paid Off, code:java (@Configuration), code:java (@Service), code:java (@Component), code:yaml (# District-specific feature configuration) (+5 more)
|
||||
Nodes (13): 1. Database-per-district with central aggregation, 2. Offline-first for field-critical modules, 3. Audit logging baked into the data layer, Building something similar?, code:yaml (# Per-district deployment descriptor), code:java (@Scheduled(fixedDelay = 300_000) // every 5 minutes), code:yaml (features:), The Architecture We Committed To (+5 more)
|
||||
|
||||
### Community 9 - "Community 9"
|
||||
Cohesion: 0.17
|
||||
Nodes (11): 1. Customer Service Automation, 2. Predictive Analytics, 3. Process Automation, 4. Intelligent Document Processing, code:python (from transformers import pipeline), code:python (import pandas as pd), code:python (# Example: Extracting structured data from forms), Implementation Considerations (+3 more)
|
||||
### Community 8 - "Community 8"
|
||||
Cohesion: 0.14
|
||||
Nodes (13): 1. Multi-District Deployment Complexity, 2. Security and Compliance, 3. Reliability Under Constraint, Architecture Decisions That Paid Off, code:java (@Configuration), code:java (@Service), code:java (@Component), code:yaml (# District-specific feature configuration) (+5 more)
|
||||
|
||||
### Community 10 - "Community 10"
|
||||
Cohesion: 0.17
|
||||
Nodes (11): 1. Customer Service Automation, 2. Predictive Analytics, 3. Process Automation, 4. Intelligent Document Processing, code:python (from transformers import pipeline), code:python (import pandas as pd), code:python (# Example: Extracting structured data from forms), Implementation Considerations (+3 more)
|
||||
|
||||
### Community 11 - "Community 11"
|
||||
Cohesion: 0.17
|
||||
Nodes (11): Code Example, code:astro (---), code:javascript (// Traditional SPA: Everything loads upfront), code:typescript (// src/content/config.ts), code:bash (npm create astro@latest), Content Collections: Type-Safe Content, Getting Started, How Astro Differs from Traditional Frameworks (+3 more)
|
||||
|
||||
### Community 13 - "Community 13"
|
||||
### Community 14 - "Community 14"
|
||||
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)
|
||||
|
||||
### Community 17 - "Community 17"
|
||||
### Community 16 - "Community 16"
|
||||
Cohesion: 0.2
|
||||
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 18 - "Community 18"
|
||||
Cohesion: 0.22
|
||||
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"
|
||||
Cohesion: 0.43
|
||||
Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter()
|
||||
|
||||
### Community 18 - "Community 18"
|
||||
### Community 21 - "Community 21"
|
||||
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 19 - "Community 19"
|
||||
### Community 22 - "Community 22"
|
||||
Cohesion: 0.46
|
||||
Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady()
|
||||
|
||||
### Community 20 - "Community 20"
|
||||
Cohesion: 0.36
|
||||
Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue()
|
||||
|
||||
### Community 21 - "Community 21"
|
||||
Cohesion: 0.46
|
||||
Nodes (7): checkRateLimit(), corsHeaders(), escapeHtml(), OPTIONS(), POST(), sendContactEmail(), validateContactForm()
|
||||
|
||||
### Community 22 - "Community 22"
|
||||
Cohesion: 0.43
|
||||
Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter()
|
||||
|
||||
### Community 23 - "Community 23"
|
||||
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 (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue()
|
||||
|
||||
### Community 24 - "Community 24"
|
||||
Cohesion: 0.46
|
||||
Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady()
|
||||
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"
|
||||
Cohesion: 0.46
|
||||
Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady()
|
||||
|
||||
### Community 28 - "Community 28"
|
||||
Cohesion: 0.36
|
||||
Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue()
|
||||
|
||||
### Community 26 - "Community 26"
|
||||
Cohesion: 0.48
|
||||
Nodes (5): initAllAnimations(), initButtonRipple(), initCounters(), initProgressBars(), initScrollReveal()
|
||||
|
||||
### Community 27 - "Community 27"
|
||||
Cohesion: 0.48
|
||||
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard()
|
||||
|
||||
### Community 28 - "Community 28"
|
||||
Cohesion: 0.48
|
||||
Nodes (5): initAllAnimations(), initButtonRipple(), initCounters(), initProgressBars(), initScrollReveal()
|
||||
|
||||
### Community 29 - "Community 29"
|
||||
Cohesion: 0.48
|
||||
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard()
|
||||
Nodes (5): initAllAnimations(), initButtonRipple(), initCounters(), initProgressBars(), initScrollReveal()
|
||||
|
||||
### Community 30 - "Community 30"
|
||||
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
|
||||
Cohesion: 0.48
|
||||
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard()
|
||||
|
||||
### Community 31 - "Community 31"
|
||||
Cohesion: 0.48
|
||||
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"
|
||||
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 34 - "Community 34"
|
||||
### 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
|
||||
Nodes (3): formatUptime(), GET(), percentile()
|
||||
|
||||
### Community 35 - "Community 35"
|
||||
### Community 38 - "Community 38"
|
||||
Cohesion: 0.6
|
||||
Nodes (3): formatUptime(), GET(), percentile()
|
||||
|
||||
## Knowledge Gaps
|
||||
- **111 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` (+106 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.
|
||||
- **7 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.
|
||||
|
||||
## Suggested Questions
|
||||
_Questions this graph is uniquely positioned to answer:_
|
||||
@@ -198,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?**
|
||||
_`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?**
|
||||
_111 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?**
|
||||
_Cohesion score 0.05 - nodes in this community are weakly interconnected._
|
||||
- **Should `Community 1` be split into smaller, more focused modules?**
|
||||
|
||||
@@ -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_playwright_config_ts", "label": "playwright.config.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright.config.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_config_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright.config.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_e2e_smoke_suite_smoke_suit_6ed4a_has_proper_structured_data_chromium_error_context_md", "label": "error-context.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-6ed4a--has-proper-structured-data-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_6ed4a_has_proper_structured_data_chromium_error_context_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-6ed4a--has-proper-structured-data-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_6ed4a_has_proper_structured_data_chromium_error_context_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-6ed4a--has-proper-structured-data-chromium/error-context.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_e2e_smoke_suite_smoke_suit_6ed4a_has_proper_structured_data_chromium_error_context_md", "target": "e2e_smoke_suite_smoke_suit_6ed4a_has_proper_structured_data_chromium_error_context_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-6ed4a--has-proper-structured-data-chromium/error-context.md", "source_location": "L1", "weight": 1.0}, {"source": "e2e_smoke_suite_smoke_suit_6ed4a_has_proper_structured_data_chromium_error_context_page_snapshot", "target": "e2e_smoke_suite_smoke_suit_6ed4a_has_proper_structured_data_chromium_error_context_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-6ed4a--has-proper-structured-data-chromium/error-context.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -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_playwright_report_data_1639575cca1fe4171917a09f8f7cc59c33654911_md", "label": "1639575cca1fe4171917a09f8f7cc59c33654911.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/1639575cca1fe4171917a09f8f7cc59c33654911.md", "source_location": "L1"}, {"id": "data_1639575cca1fe4171917a09f8f7cc59c33654911_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/1639575cca1fe4171917a09f8f7cc59c33654911.md", "source_location": "L1"}, {"id": "data_1639575cca1fe4171917a09f8f7cc59c33654911_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/1639575cca1fe4171917a09f8f7cc59c33654911.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_1639575cca1fe4171917a09f8f7cc59c33654911_md", "target": "data_1639575cca1fe4171917a09f8f7cc59c33654911_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/1639575cca1fe4171917a09f8f7cc59c33654911.md", "source_location": "L1", "weight": 1.0}, {"source": "data_1639575cca1fe4171917a09f8f7cc59c33654911_page_snapshot", "target": "data_1639575cca1fe4171917a09f8f7cc59c33654911_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/1639575cca1fe4171917a09f8f7cc59c33654911.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_e2e_smoke_suite_smoke_suit_8282a_mepage_has_proper_meta_tags_chromium_error_context_md", "label": "error-context.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-8282a-mepage-has-proper-meta-tags-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_8282a_mepage_has_proper_meta_tags_chromium_error_context_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-8282a-mepage-has-proper-meta-tags-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_8282a_mepage_has_proper_meta_tags_chromium_error_context_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-8282a-mepage-has-proper-meta-tags-chromium/error-context.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_e2e_smoke_suite_smoke_suit_8282a_mepage_has_proper_meta_tags_chromium_error_context_md", "target": "e2e_smoke_suite_smoke_suit_8282a_mepage_has_proper_meta_tags_chromium_error_context_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-8282a-mepage-has-proper-meta-tags-chromium/error-context.md", "source_location": "L1", "weight": 1.0}, {"source": "e2e_smoke_suite_smoke_suit_8282a_mepage_has_proper_meta_tags_chromium_error_context_page_snapshot", "target": "e2e_smoke_suite_smoke_suit_8282a_mepage_has_proper_meta_tags_chromium_error_context_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-8282a-mepage-has-proper-meta-tags-chromium/error-context.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_a1c157260391dca389fbe38c6259c4efd286b319_md", "label": "a1c157260391dca389fbe38c6259c4efd286b319.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/a1c157260391dca389fbe38c6259c4efd286b319.md", "source_location": "L1"}, {"id": "data_a1c157260391dca389fbe38c6259c4efd286b319_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/a1c157260391dca389fbe38c6259c4efd286b319.md", "source_location": "L1"}, {"id": "data_a1c157260391dca389fbe38c6259c4efd286b319_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/a1c157260391dca389fbe38c6259c4efd286b319.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_a1c157260391dca389fbe38c6259c4efd286b319_md", "target": "data_a1c157260391dca389fbe38c6259c4efd286b319_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/a1c157260391dca389fbe38c6259c4efd286b319.md", "source_location": "L1", "weight": 1.0}, {"source": "data_a1c157260391dca389fbe38c6259c4efd286b319_page_snapshot", "target": "data_a1c157260391dca389fbe38c6259c4efd286b319_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/a1c157260391dca389fbe38c6259c4efd286b319.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_448763de83b442e1e9a6c6e4e70870f8a7246e0a_md", "label": "448763de83b442e1e9a6c6e4e70870f8a7246e0a.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/448763de83b442e1e9a6c6e4e70870f8a7246e0a.md", "source_location": "L1"}, {"id": "data_448763de83b442e1e9a6c6e4e70870f8a7246e0a_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/448763de83b442e1e9a6c6e4e70870f8a7246e0a.md", "source_location": "L1"}, {"id": "data_448763de83b442e1e9a6c6e4e70870f8a7246e0a_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/448763de83b442e1e9a6c6e4e70870f8a7246e0a.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_448763de83b442e1e9a6c6e4e70870f8a7246e0a_md", "target": "data_448763de83b442e1e9a6c6e4e70870f8a7246e0a_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/448763de83b442e1e9a6c6e4e70870f8a7246e0a.md", "source_location": "L1", "weight": 1.0}, {"source": "data_448763de83b442e1e9a6c6e4e70870f8a7246e0a_page_snapshot", "target": "data_448763de83b442e1e9a6c6e4e70870f8a7246e0a_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/448763de83b442e1e9a6c6e4e70870f8a7246e0a.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_theme_theme_aria_accessibi_8c42a_ria_checked_desktop_mobile_chromium_error_context_md", "label": "error-context.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/theme-Theme-ARIA-Accessibi-8c42a-ria-checked-desktop-mobile--chromium/error-context.md", "source_location": "L1"}, {"id": "theme_theme_aria_accessibi_8c42a_ria_checked_desktop_mobile_chromium_error_context_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/theme-Theme-ARIA-Accessibi-8c42a-ria-checked-desktop-mobile--chromium/error-context.md", "source_location": "L1"}, {"id": "theme_theme_aria_accessibi_8c42a_ria_checked_desktop_mobile_chromium_error_context_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/theme-Theme-ARIA-Accessibi-8c42a-ria-checked-desktop-mobile--chromium/error-context.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_theme_theme_aria_accessibi_8c42a_ria_checked_desktop_mobile_chromium_error_context_md", "target": "theme_theme_aria_accessibi_8c42a_ria_checked_desktop_mobile_chromium_error_context_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/theme-Theme-ARIA-Accessibi-8c42a-ria-checked-desktop-mobile--chromium/error-context.md", "source_location": "L1", "weight": 1.0}, {"source": "theme_theme_aria_accessibi_8c42a_ria_checked_desktop_mobile_chromium_error_context_page_snapshot", "target": "theme_theme_aria_accessibi_8c42a_ria_checked_desktop_mobile_chromium_error_context_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/theme-Theme-ARIA-Accessibi-8c42a-ria-checked-desktop-mobile--chromium/error-context.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_content_config_ts", "label": "config.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/config.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_content_config_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/content/config.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff_md", "label": "3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff.md", "source_location": "L1"}, {"id": "data_3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff.md", "source_location": "L1"}, {"id": "data_3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff_codeblock_1", "label": "code:yaml (- generic [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff_md", "target": "data_3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff.md", "source_location": "L1", "weight": 1.0}, {"source": "data_3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff_page_snapshot", "target": "data_3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_6a86fc05ec2c2ce701392e4df2db4d19695f2488_md", "label": "6a86fc05ec2c2ce701392e4df2db4d19695f2488.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6a86fc05ec2c2ce701392e4df2db4d19695f2488.md", "source_location": "L1"}, {"id": "data_6a86fc05ec2c2ce701392e4df2db4d19695f2488_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6a86fc05ec2c2ce701392e4df2db4d19695f2488.md", "source_location": "L1"}, {"id": "data_6a86fc05ec2c2ce701392e4df2db4d19695f2488_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6a86fc05ec2c2ce701392e4df2db4d19695f2488.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_6a86fc05ec2c2ce701392e4df2db4d19695f2488_md", "target": "data_6a86fc05ec2c2ce701392e4df2db4d19695f2488_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6a86fc05ec2c2ce701392e4df2db4d19695f2488.md", "source_location": "L1", "weight": 1.0}, {"source": "data_6a86fc05ec2c2ce701392e4df2db4d19695f2488_page_snapshot", "target": "data_6a86fc05ec2c2ce701392e4df2db4d19695f2488_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6a86fc05ec2c2ce701392e4df2db4d19695f2488.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_e2e_smoke_suite_smoke_suit_7fcfd_ntact_form_submission_works_chromium_error_context_md", "label": "error-context.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-7fcfd-ntact-form-submission-works-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_7fcfd_ntact_form_submission_works_chromium_error_context_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-7fcfd-ntact-form-submission-works-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_7fcfd_ntact_form_submission_works_chromium_error_context_codeblock_1", "label": "code:yaml (- generic [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-7fcfd-ntact-form-submission-works-chromium/error-context.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_e2e_smoke_suite_smoke_suit_7fcfd_ntact_form_submission_works_chromium_error_context_md", "target": "e2e_smoke_suite_smoke_suit_7fcfd_ntact_form_submission_works_chromium_error_context_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-7fcfd-ntact-form-submission-works-chromium/error-context.md", "source_location": "L1", "weight": 1.0}, {"source": "e2e_smoke_suite_smoke_suit_7fcfd_ntact_form_submission_works_chromium_error_context_page_snapshot", "target": "e2e_smoke_suite_smoke_suit_7fcfd_ntact_form_submission_works_chromium_error_context_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-7fcfd-ntact-form-submission-works-chromium/error-context.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_e913a96525d005a5baae8ddb2fbea15bbc53abb1_md", "label": "e913a96525d005a5baae8ddb2fbea15bbc53abb1.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/e913a96525d005a5baae8ddb2fbea15bbc53abb1.md", "source_location": "L1"}, {"id": "data_e913a96525d005a5baae8ddb2fbea15bbc53abb1_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/e913a96525d005a5baae8ddb2fbea15bbc53abb1.md", "source_location": "L1"}, {"id": "data_e913a96525d005a5baae8ddb2fbea15bbc53abb1_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/e913a96525d005a5baae8ddb2fbea15bbc53abb1.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_e913a96525d005a5baae8ddb2fbea15bbc53abb1_md", "target": "data_e913a96525d005a5baae8ddb2fbea15bbc53abb1_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/e913a96525d005a5baae8ddb2fbea15bbc53abb1.md", "source_location": "L1", "weight": 1.0}, {"source": "data_e913a96525d005a5baae8ddb2fbea15bbc53abb1_page_snapshot", "target": "data_e913a96525d005a5baae8ddb2fbea15bbc53abb1_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/e913a96525d005a5baae8ddb2fbea15bbc53abb1.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_cc0ed5549c339ad261436d0f63d13eb3259a9dde_md", "label": "cc0ed5549c339ad261436d0f63d13eb3259a9dde.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/cc0ed5549c339ad261436d0f63d13eb3259a9dde.md", "source_location": "L1"}, {"id": "data_cc0ed5549c339ad261436d0f63d13eb3259a9dde_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/cc0ed5549c339ad261436d0f63d13eb3259a9dde.md", "source_location": "L1"}, {"id": "data_cc0ed5549c339ad261436d0f63d13eb3259a9dde_codeblock_1", "label": "code:yaml (- generic [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/cc0ed5549c339ad261436d0f63d13eb3259a9dde.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_cc0ed5549c339ad261436d0f63d13eb3259a9dde_md", "target": "data_cc0ed5549c339ad261436d0f63d13eb3259a9dde_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/cc0ed5549c339ad261436d0f63d13eb3259a9dde.md", "source_location": "L1", "weight": 1.0}, {"source": "data_cc0ed5549c339ad261436d0f63d13eb3259a9dde_page_snapshot", "target": "data_cc0ed5549c339ad261436d0f63d13eb3259a9dde_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/cc0ed5549c339ad261436d0f63d13eb3259a9dde.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_690bd8f0a073eceeb87b8e46e681123950f08f93_md", "label": "690bd8f0a073eceeb87b8e46e681123950f08f93.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/690bd8f0a073eceeb87b8e46e681123950f08f93.md", "source_location": "L1"}, {"id": "data_690bd8f0a073eceeb87b8e46e681123950f08f93_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/690bd8f0a073eceeb87b8e46e681123950f08f93.md", "source_location": "L1"}, {"id": "data_690bd8f0a073eceeb87b8e46e681123950f08f93_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/690bd8f0a073eceeb87b8e46e681123950f08f93.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_690bd8f0a073eceeb87b8e46e681123950f08f93_md", "target": "data_690bd8f0a073eceeb87b8e46e681123950f08f93_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/690bd8f0a073eceeb87b8e46e681123950f08f93.md", "source_location": "L1", "weight": 1.0}, {"source": "data_690bd8f0a073eceeb87b8e46e681123950f08f93_page_snapshot", "target": "data_690bd8f0a073eceeb87b8e46e681123950f08f93_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/690bd8f0a073eceeb87b8e46e681123950f08f93.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_806b690efd1ee745a1991725b2d2aea1b5c435f2_md", "label": "806b690efd1ee745a1991725b2d2aea1b5c435f2.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/806b690efd1ee745a1991725b2d2aea1b5c435f2.md", "source_location": "L1"}, {"id": "data_806b690efd1ee745a1991725b2d2aea1b5c435f2_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/806b690efd1ee745a1991725b2d2aea1b5c435f2.md", "source_location": "L1"}, {"id": "data_806b690efd1ee745a1991725b2d2aea1b5c435f2_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/806b690efd1ee745a1991725b2d2aea1b5c435f2.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_806b690efd1ee745a1991725b2d2aea1b5c435f2_md", "target": "data_806b690efd1ee745a1991725b2d2aea1b5c435f2_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/806b690efd1ee745a1991725b2d2aea1b5c435f2.md", "source_location": "L1", "weight": 1.0}, {"source": "data_806b690efd1ee745a1991725b2d2aea1b5c435f2_page_snapshot", "target": "data_806b690efd1ee745a1991725b2d2aea1b5c435f2_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/806b690efd1ee745a1991725b2d2aea1b5c435f2.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_32e7238c826ab5be125e270cc69efc4a37a8ffb3_md", "label": "32e7238c826ab5be125e270cc69efc4a37a8ffb3.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/32e7238c826ab5be125e270cc69efc4a37a8ffb3.md", "source_location": "L1"}, {"id": "data_32e7238c826ab5be125e270cc69efc4a37a8ffb3_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/32e7238c826ab5be125e270cc69efc4a37a8ffb3.md", "source_location": "L1"}, {"id": "data_32e7238c826ab5be125e270cc69efc4a37a8ffb3_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/32e7238c826ab5be125e270cc69efc4a37a8ffb3.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_32e7238c826ab5be125e270cc69efc4a37a8ffb3_md", "target": "data_32e7238c826ab5be125e270cc69efc4a37a8ffb3_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/32e7238c826ab5be125e270cc69efc4a37a8ffb3.md", "source_location": "L1", "weight": 1.0}, {"source": "data_32e7238c826ab5be125e270cc69efc4a37a8ffb3_page_snapshot", "target": "data_32e7238c826ab5be125e270cc69efc4a37a8ffb3_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/32e7238c826ab5be125e270cc69efc4a37a8ffb3.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_inline_lead_form_spec_ts", "label": "inline-lead-form.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/inline-lead-form.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_inline_lead_form_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/inline-lead-form.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_c0712711f6dffbdf3e61da69d7bf618fa7d12cce_md", "label": "c0712711f6dffbdf3e61da69d7bf618fa7d12cce.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/c0712711f6dffbdf3e61da69d7bf618fa7d12cce.md", "source_location": "L1"}, {"id": "data_c0712711f6dffbdf3e61da69d7bf618fa7d12cce_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/c0712711f6dffbdf3e61da69d7bf618fa7d12cce.md", "source_location": "L1"}, {"id": "data_c0712711f6dffbdf3e61da69d7bf618fa7d12cce_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/c0712711f6dffbdf3e61da69d7bf618fa7d12cce.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_c0712711f6dffbdf3e61da69d7bf618fa7d12cce_md", "target": "data_c0712711f6dffbdf3e61da69d7bf618fa7d12cce_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/c0712711f6dffbdf3e61da69d7bf618fa7d12cce.md", "source_location": "L1", "weight": 1.0}, {"source": "data_c0712711f6dffbdf3e61da69d7bf618fa7d12cce_page_snapshot", "target": "data_c0712711f6dffbdf3e61da69d7bf618fa7d12cce_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/c0712711f6dffbdf3e61da69d7bf618fa7d12cce.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -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_playwright_report_data_0f6bc059f79105264bd8619a97b50f0c07ebb8d8_md", "label": "0f6bc059f79105264bd8619a97b50f0c07ebb8d8.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/0f6bc059f79105264bd8619a97b50f0c07ebb8d8.md", "source_location": "L1"}, {"id": "data_0f6bc059f79105264bd8619a97b50f0c07ebb8d8_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/0f6bc059f79105264bd8619a97b50f0c07ebb8d8.md", "source_location": "L1"}, {"id": "data_0f6bc059f79105264bd8619a97b50f0c07ebb8d8_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/0f6bc059f79105264bd8619a97b50f0c07ebb8d8.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_0f6bc059f79105264bd8619a97b50f0c07ebb8d8_md", "target": "data_0f6bc059f79105264bd8619a97b50f0c07ebb8d8_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/0f6bc059f79105264bd8619a97b50f0c07ebb8d8.md", "source_location": "L1", "weight": 1.0}, {"source": "data_0f6bc059f79105264bd8619a97b50f0c07ebb8d8_page_snapshot", "target": "data_0f6bc059f79105264bd8619a97b50f0c07ebb8d8_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/0f6bc059f79105264bd8619a97b50f0c07ebb8d8.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -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"}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_e2e_smoke_suite_smoke_suit_742f0_enders_correctly_on_desktop_chromium_error_context_md", "label": "error-context.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-742f0-enders-correctly-on-Desktop-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_742f0_enders_correctly_on_desktop_chromium_error_context_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-742f0-enders-correctly-on-Desktop-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_742f0_enders_correctly_on_desktop_chromium_error_context_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-742f0-enders-correctly-on-Desktop-chromium/error-context.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_e2e_smoke_suite_smoke_suit_742f0_enders_correctly_on_desktop_chromium_error_context_md", "target": "e2e_smoke_suite_smoke_suit_742f0_enders_correctly_on_desktop_chromium_error_context_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-742f0-enders-correctly-on-Desktop-chromium/error-context.md", "source_location": "L1", "weight": 1.0}, {"source": "e2e_smoke_suite_smoke_suit_742f0_enders_correctly_on_desktop_chromium_error_context_page_snapshot", "target": "e2e_smoke_suite_smoke_suit_742f0_enders_correctly_on_desktop_chromium_error_context_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-742f0-enders-correctly-on-Desktop-chromium/error-context.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_bbfcf94d298e196a738e1c4aa9fcbebf09850749_md", "label": "bbfcf94d298e196a738e1c4aa9fcbebf09850749.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/bbfcf94d298e196a738e1c4aa9fcbebf09850749.md", "source_location": "L1"}, {"id": "data_bbfcf94d298e196a738e1c4aa9fcbebf09850749_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/bbfcf94d298e196a738e1c4aa9fcbebf09850749.md", "source_location": "L1"}, {"id": "data_bbfcf94d298e196a738e1c4aa9fcbebf09850749_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/bbfcf94d298e196a738e1c4aa9fcbebf09850749.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_bbfcf94d298e196a738e1c4aa9fcbebf09850749_md", "target": "data_bbfcf94d298e196a738e1c4aa9fcbebf09850749_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/bbfcf94d298e196a738e1c4aa9fcbebf09850749.md", "source_location": "L1", "weight": 1.0}, {"source": "data_bbfcf94d298e196a738e1c4aa9fcbebf09850749_page_snapshot", "target": "data_bbfcf94d298e196a738e1c4aa9fcbebf09850749_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/bbfcf94d298e196a738e1c4aa9fcbebf09850749.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_6604b3783e14b0fe2e620b288e8828b6f55be330_md", "label": "6604b3783e14b0fe2e620b288e8828b6f55be330.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6604b3783e14b0fe2e620b288e8828b6f55be330.md", "source_location": "L1"}, {"id": "data_6604b3783e14b0fe2e620b288e8828b6f55be330_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6604b3783e14b0fe2e620b288e8828b6f55be330.md", "source_location": "L1"}, {"id": "data_6604b3783e14b0fe2e620b288e8828b6f55be330_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6604b3783e14b0fe2e620b288e8828b6f55be330.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_6604b3783e14b0fe2e620b288e8828b6f55be330_md", "target": "data_6604b3783e14b0fe2e620b288e8828b6f55be330_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6604b3783e14b0fe2e620b288e8828b6f55be330.md", "source_location": "L1", "weight": 1.0}, {"source": "data_6604b3783e14b0fe2e620b288e8828b6f55be330_page_snapshot", "target": "data_6604b3783e14b0fe2e620b288e8828b6f55be330_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6604b3783e14b0fe2e620b288e8828b6f55be330.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_9b3c49237ac0c6da58c2e4aee5df00459559a271_md", "label": "9b3c49237ac0c6da58c2e4aee5df00459559a271.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/9b3c49237ac0c6da58c2e4aee5df00459559a271.md", "source_location": "L1"}, {"id": "data_9b3c49237ac0c6da58c2e4aee5df00459559a271_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/9b3c49237ac0c6da58c2e4aee5df00459559a271.md", "source_location": "L1"}, {"id": "data_9b3c49237ac0c6da58c2e4aee5df00459559a271_codeblock_1", "label": "code:yaml (- generic [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/9b3c49237ac0c6da58c2e4aee5df00459559a271.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_9b3c49237ac0c6da58c2e4aee5df00459559a271_md", "target": "data_9b3c49237ac0c6da58c2e4aee5df00459559a271_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/9b3c49237ac0c6da58c2e4aee5df00459559a271.md", "source_location": "L1", "weight": 1.0}, {"source": "data_9b3c49237ac0c6da58c2e4aee5df00459559a271_page_snapshot", "target": "data_9b3c49237ac0c6da58c2e4aee5df00459559a271_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/9b3c49237ac0c6da58c2e4aee5df00459559a271.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
@@ -22,6 +22,7 @@
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@types/nodemailer": "^7.0.11",
|
||||
"jsdom": "^29.1.1",
|
||||
"playwright": "^1.58.2",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.9.3"
|
||||
@@ -39,6 +40,57 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/css-color": {
|
||||
"version": "5.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz",
|
||||
"integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/generational-cache": "^1.0.1",
|
||||
"@csstools/css-calc": "^3.2.0",
|
||||
"@csstools/css-color-parser": "^4.1.0",
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/dom-selector": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz",
|
||||
"integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/generational-cache": "^1.0.1",
|
||||
"@asamuzakjp/nwsapi": "^2.3.9",
|
||||
"bidi-js": "^1.0.3",
|
||||
"css-tree": "^3.2.1",
|
||||
"is-potential-custom-element-name": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/generational-cache": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz",
|
||||
"integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/nwsapi": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
|
||||
"integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@astrojs/check": {
|
||||
"version": "0.9.9",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.9.tgz",
|
||||
@@ -554,6 +606,159 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bramus/specificity": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
|
||||
"integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-tree": "^3.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"specificity": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/color-helpers": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
|
||||
"integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-calc": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz",
|
||||
"integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-color-parser": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.7.tgz",
|
||||
"integrity": "sha512-CmjJFQTFQx/U/xNJhSjCQ0ilpesPmNQ8+eOUeM/+kDOVW33qsIjeOXc27vrQDdWVkf83ZSWwtg7kXSUvKDJ8cQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@csstools/color-helpers": "^6.0.2",
|
||||
"@csstools/css-calc": "^3.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-parser-algorithms": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
|
||||
"integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-syntax-patches-for-csstree": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.5.tgz",
|
||||
"integrity": "sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT-0",
|
||||
"peerDependencies": {
|
||||
"css-tree": "^3.2.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"css-tree": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-tokenizer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
|
||||
"integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emmetio/abbreviation": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz",
|
||||
@@ -994,6 +1199,24 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@exodus/bytes": {
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz",
|
||||
"integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@noble/hashes": "^1.8.0 || ^2.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@noble/hashes": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
||||
@@ -2485,6 +2708,16 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bidi-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
||||
"integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"require-from-string": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
@@ -3070,6 +3303,20 @@
|
||||
"node": ">=6.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
|
||||
"integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mdn-data": "2.27.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
@@ -3082,6 +3329,20 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/data-urls": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
|
||||
"integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-mimetype": "^5.0.0",
|
||||
"whatwg-url": "^16.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
@@ -3099,6 +3360,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decimal.js": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
|
||||
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/decode-named-character-reference": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz",
|
||||
@@ -4073,6 +4341,19 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
|
||||
"integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@exodus/bytes": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
|
||||
@@ -4295,6 +4576,13 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-potential-custom-element-name": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
||||
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-promise": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
||||
@@ -4355,6 +4643,83 @@
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom": {
|
||||
"version": "29.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz",
|
||||
"integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/css-color": "^5.1.11",
|
||||
"@asamuzakjp/dom-selector": "^7.1.1",
|
||||
"@bramus/specificity": "^2.4.2",
|
||||
"@csstools/css-syntax-patches-for-csstree": "^1.1.3",
|
||||
"@exodus/bytes": "^1.15.0",
|
||||
"css-tree": "^3.2.1",
|
||||
"data-urls": "^7.0.0",
|
||||
"decimal.js": "^10.6.0",
|
||||
"html-encoding-sniffer": "^6.0.0",
|
||||
"is-potential-custom-element-name": "^1.0.1",
|
||||
"lru-cache": "^11.3.5",
|
||||
"parse5": "^8.0.1",
|
||||
"saxes": "^6.0.0",
|
||||
"symbol-tree": "^3.2.4",
|
||||
"tough-cookie": "^6.0.1",
|
||||
"undici": "^7.25.0",
|
||||
"w3c-xmlserializer": "^5.0.0",
|
||||
"webidl-conversions": "^8.0.1",
|
||||
"whatwg-mimetype": "^5.0.0",
|
||||
"whatwg-url": "^16.0.1",
|
||||
"xml-name-validator": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"canvas": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"canvas": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/entities": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
|
||||
"integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/lru-cache": {
|
||||
"version": "11.5.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
|
||||
"integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/parse5": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
|
||||
"integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
@@ -4798,6 +5163,13 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.27.1",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
|
||||
"integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
|
||||
"dev": true,
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
||||
@@ -6158,6 +6530,16 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
|
||||
@@ -6669,6 +7051,19 @@
|
||||
"node": ">=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/saxes": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"xmlchars": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v12.22.7"
|
||||
}
|
||||
},
|
||||
"node_modules/section-matter": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
|
||||
@@ -7124,6 +7519,13 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.19",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
|
||||
@@ -7216,6 +7618,26 @@
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
"version": "7.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.3.tgz",
|
||||
"integrity": "sha512-A3BDQBeeukYPzB4QdQ1DtdlUmp4x2OCH8n5UVhEWbyANxNep8GavottKzd1xYKFJKjUgMyPT7EzOfnBO55s8Sg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tldts-core": "^7.4.3"
|
||||
},
|
||||
"bin": {
|
||||
"tldts": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts-core": {
|
||||
"version": "7.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.3.tgz",
|
||||
"integrity": "sha512-27ep5H9PzdBrNd5OFM/j3WCU8F3kPwM9D0BOaOf7uYfxMJfyr0K5Tjj69Gri+sZlh2WXd5buIm47NuPF29CDiw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -7237,6 +7659,32 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz",
|
||||
"integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tldts": "^7.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
|
||||
"integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/trim-lines": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
|
||||
@@ -7347,6 +7795,16 @@
|
||||
"semver": "^7.3.8"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
|
||||
"integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
@@ -7959,6 +8417,19 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/w3c-xmlserializer": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
||||
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xml-name-validator": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/web-namespaces": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
|
||||
@@ -7969,6 +8440,41 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
|
||||
"integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-mimetype": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
|
||||
"integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
|
||||
"integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@exodus/bytes": "^1.11.0",
|
||||
"tr46": "^6.0.0",
|
||||
"webidl-conversions": "^8.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which-pm": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/which-pm/-/which-pm-3.0.1.tgz",
|
||||
@@ -8028,6 +8534,23 @@
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/xml-name-validator": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
||||
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlchars": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/xxhash-wasm": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz",
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@types/nodemailer": "^7.0.11",
|
||||
"jsdom": "^29.1.1",
|
||||
"playwright": "^1.58.2",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.9.3"
|
||||
|
||||
@@ -12,7 +12,7 @@ export default defineConfig({
|
||||
['list']
|
||||
],
|
||||
use: {
|
||||
baseURL: 'http://localhost:10000',
|
||||
baseURL: process.env.BASE_URL || 'http://localhost:10000',
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
},
|
||||
|
||||
|
Before Width: | Height: | Size: 495 B After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 410 B After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 28 KiB |
@@ -11,6 +11,18 @@
|
||||
"lang": "en-US",
|
||||
"categories": ["business", "productivity"],
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/apple-touch-icon.png",
|
||||
"sizes": "180x180",
|
||||
|
||||
@@ -58,6 +58,9 @@ Allow: /
|
||||
User-agent: GPTBot
|
||||
Allow: /
|
||||
|
||||
User-agent: OAI-SearchBot
|
||||
Allow: /
|
||||
|
||||
# Anthropic/Claude
|
||||
User-agent: anthropic-ai
|
||||
Allow: /
|
||||
@@ -65,10 +68,22 @@ Allow: /
|
||||
User-agent: Claude-Web
|
||||
Allow: /
|
||||
|
||||
User-agent: ClaudeBot
|
||||
Allow: /
|
||||
|
||||
User-agent: Claude-SearchBot
|
||||
Allow: /
|
||||
|
||||
User-agent: Claude-User
|
||||
Allow: /
|
||||
|
||||
# Perplexity AI
|
||||
User-agent: PerplexityBot
|
||||
Allow: /
|
||||
|
||||
User-agent: Perplexity-User
|
||||
Allow: /
|
||||
|
||||
# Google Gemini/Bard
|
||||
User-agent: Google-Extended
|
||||
Allow: /
|
||||
@@ -80,3 +95,40 @@ Allow: /
|
||||
# Meta AI
|
||||
User-agent: FacebookBot
|
||||
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: /
|
||||
|
||||
@@ -15,6 +15,31 @@
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://workroot.in/services/custom-erp-development/</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://workroot.in/services/university-erp/</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://workroot.in/services/government-it-solutions-gem-vendor/</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://workroot.in/services/police-management-system/</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://workroot.in/services/react-native-app-development/</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://workroot.in/portfolio/</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
|
||||
@@ -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 () {})
|
||||
);
|
||||
});
|
||||
@@ -44,9 +44,7 @@ const currentYear = new Date().getFullYear();
|
||||
<!-- Company Info -->
|
||||
<div class="lg:col-span-1">
|
||||
<a href="/" class="inline-flex items-center gap-2 mb-4 group" aria-label="WorkRoot IT Solutions LLP - Home">
|
||||
<div class="w-10 h-10 bg-primary rounded-lg flex items-center justify-center transition-transform group-hover:scale-105" aria-hidden="true">
|
||||
<span class="text-white font-bold text-lg">W</span>
|
||||
</div>
|
||||
<img src="/favicon.svg" alt="WorkRoot" width="40" height="40" class="w-10 h-10 transition-transform group-hover:scale-105" />
|
||||
<span class="text-xl font-bold text-white">WorkRoot</span>
|
||||
</a>
|
||||
<p class="text-secondary-400 text-sm leading-relaxed mb-6">
|
||||
@@ -168,6 +166,23 @@ const currentYear = new Date().getFullYear();
|
||||
By subscribing, you agree to our
|
||||
<a href="/privacy" class="text-primary hover:underline">Privacy Policy</a>.
|
||||
</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>
|
||||
@@ -195,6 +210,106 @@ const currentYear = new Date().getFullYear();
|
||||
</div>
|
||||
</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>
|
||||
import { trackNewsletterSignup } from '../utils/analytics';
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ const { class: className } = Astro.props;
|
||||
|
||||
const navLinks = [
|
||||
{ name: 'Home', href: '/' },
|
||||
{ name: 'WRNexus', href: 'https://wrnexus.com/', external: true },
|
||||
{ name: 'About', href: '/about' },
|
||||
{ name: 'Services', href: '/services' },
|
||||
{ name: 'Portfolio', href: '/portfolio' },
|
||||
@@ -29,13 +30,21 @@ const currentPath = Astro.url.pathname;
|
||||
]}
|
||||
id="main-header"
|
||||
>
|
||||
<div class="container-wrapper">
|
||||
<!-- Full-bleed inner wrapper: the header background already spans the full
|
||||
viewport (the <header> is fixed left-0 right-0); this inner row stretches
|
||||
edge-to-edge too, keeping only responsive horizontal padding so the logo
|
||||
and nav never touch the screen edges and stay legible down to 360px. -->
|
||||
<div class="w-full px-4 sm:px-6 lg:px-8 xl:px-12">
|
||||
<nav class="flex items-center justify-between h-16 lg:h-20" aria-label="Main navigation">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex items-center gap-2 group" aria-label="WorkRoot IT Solutions LLP - Home">
|
||||
<div class="w-10 h-10 bg-primary rounded-lg flex items-center justify-center transition-transform group-hover:scale-105">
|
||||
<span class="text-white font-bold text-xl">W</span>
|
||||
</div>
|
||||
<img
|
||||
src="/favicon.svg"
|
||||
alt="WorkRoot"
|
||||
width="40"
|
||||
height="40"
|
||||
class="w-10 h-10 transition-transform group-hover:scale-105"
|
||||
/>
|
||||
<div class="hidden sm:block">
|
||||
<span class="font-bold text-xl text-secondary dark:text-white">WorkRoot</span>
|
||||
<span class="text-primary font-semibold text-sm block -mt-1">IT Solutions</span>
|
||||
@@ -45,14 +54,16 @@ const currentPath = Astro.url.pathname;
|
||||
<!-- Desktop Navigation -->
|
||||
<ul class="hidden lg:flex items-center gap-1" role="menubar">
|
||||
{navLinks.map((link) => {
|
||||
const isActive = currentPath === link.href ||
|
||||
(link.href !== '/' && currentPath.startsWith(link.href));
|
||||
const isActive = !link.external && (currentPath === link.href ||
|
||||
(link.href !== '/' && currentPath.startsWith(link.href)));
|
||||
return (
|
||||
<li role="none">
|
||||
<a
|
||||
href={link.href}
|
||||
role="menuitem"
|
||||
data-astro-prefetch="hover"
|
||||
data-astro-prefetch={link.external ? undefined : 'hover'}
|
||||
target={link.external ? '_blank' : undefined}
|
||||
rel={link.external ? 'noopener noreferrer' : undefined}
|
||||
class:list={[
|
||||
'relative px-4 py-2 text-sm font-medium transition-colors rounded-lg',
|
||||
isActive
|
||||
@@ -85,37 +96,61 @@ const currentPath = Astro.url.pathname;
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Button -->
|
||||
<button
|
||||
type="button"
|
||||
class="lg:hidden relative w-10 h-10 flex items-center justify-center text-secondary-600 dark:text-secondary-400 hover:text-primary dark:hover:text-primary-400 transition-colors"
|
||||
id="mobile-menu-toggle"
|
||||
aria-label="Toggle mobile menu"
|
||||
aria-expanded="false"
|
||||
aria-controls="mobile-menu"
|
||||
>
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<!-- Hamburger Icon -->
|
||||
<div class="hamburger-icon" aria-hidden="true">
|
||||
<span class="hamburger-line hamburger-line-1"></span>
|
||||
<span class="hamburger-line hamburger-line-2"></span>
|
||||
<span class="hamburger-line hamburger-line-3"></span>
|
||||
</div>
|
||||
</button>
|
||||
<!-- Mobile actions: prominent click-to-call + hamburger (shown <lg on every page) -->
|
||||
<div class="flex items-center gap-1 lg:hidden">
|
||||
<!-- Click-to-call — always visible in the mobile header so visitors on
|
||||
any page can reach us in one tap without opening the menu. -->
|
||||
<a
|
||||
href="tel:+919561417403"
|
||||
data-cta="header-mobile-call"
|
||||
aria-label="Call WorkRoot at +91 95614 17403"
|
||||
class="inline-flex items-center gap-1.5 h-10 px-3 rounded-lg bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 font-semibold text-sm hover:bg-primary-100 dark:hover:bg-primary-900/50 active:scale-95 transition-all focus:outline-none focus:ring-2 focus:ring-primary-400"
|
||||
>
|
||||
<svg class="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
|
||||
</svg>
|
||||
<span class="hidden xs:inline">Call</span>
|
||||
</a>
|
||||
|
||||
<!-- Mobile Menu Button -->
|
||||
<button
|
||||
type="button"
|
||||
class="relative w-10 h-10 flex items-center justify-center text-secondary-600 dark:text-secondary-400 hover:text-primary dark:hover:text-primary-400 transition-colors"
|
||||
id="mobile-menu-toggle"
|
||||
aria-label="Toggle mobile menu"
|
||||
aria-expanded="false"
|
||||
aria-controls="mobile-menu"
|
||||
>
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<!-- Hamburger Icon -->
|
||||
<div class="hamburger-icon" aria-hidden="true">
|
||||
<span class="hamburger-line hamburger-line-1"></span>
|
||||
<span class="hamburger-line hamburger-line-2"></span>
|
||||
<span class="hamburger-line hamburger-line-3"></span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Overlay -->
|
||||
<!-- Explicit viewport sizing: the header's `backdrop-blur` makes it the
|
||||
containing block for fixed descendants, so `inset-0` alone would only
|
||||
cover the header. w-screen + h-[100dvh] force true full-viewport cover. -->
|
||||
<div
|
||||
id="mobile-menu-overlay"
|
||||
class="fixed inset-0 bg-secondary-900/50 backdrop-blur-sm opacity-0 pointer-events-none transition-opacity duration-300 lg:hidden"
|
||||
class="fixed top-0 left-0 w-screen h-[100dvh] bg-secondary-900/50 backdrop-blur-sm opacity-0 pointer-events-none transition-opacity duration-300 lg:hidden"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
|
||||
<!-- Mobile Menu Panel -->
|
||||
<!-- Full-screen overlay panel. Uses explicit viewport units (w-screen /
|
||||
h-[100dvh]) rather than h-full because the header's backdrop-filter is
|
||||
the containing block for this fixed element, so `h-full` would collapse
|
||||
to the header's ~64px height (the original "small box" bug). -->
|
||||
<div
|
||||
id="mobile-menu"
|
||||
class="fixed top-0 right-0 h-full w-80 max-w-[85vw] bg-white dark:bg-secondary-900 shadow-2xl transform translate-x-full transition-transform duration-300 ease-out lg:hidden"
|
||||
class="fixed top-0 left-0 w-screen h-[100dvh] min-h-screen bg-white dark:bg-secondary-900 shadow-2xl transform translate-x-full transition-transform duration-300 ease-out lg:hidden"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Mobile navigation menu"
|
||||
@@ -124,9 +159,7 @@ const currentPath = Astro.url.pathname;
|
||||
<!-- Mobile Menu Header -->
|
||||
<div class="flex items-center justify-between p-4 border-b border-secondary-100 dark:border-secondary-800">
|
||||
<a href="/" class="flex items-center gap-2" aria-label="WorkRoot IT Solutions LLP - Home">
|
||||
<div class="w-8 h-8 bg-primary rounded-lg flex items-center justify-center" aria-hidden="true">
|
||||
<span class="text-white font-bold text-lg">W</span>
|
||||
</div>
|
||||
<img src="/favicon.svg" alt="WorkRoot" width="32" height="32" class="w-8 h-8" />
|
||||
<span class="font-bold text-lg text-secondary dark:text-white">WorkRoot</span>
|
||||
</a>
|
||||
<button
|
||||
@@ -145,14 +178,16 @@ const currentPath = Astro.url.pathname;
|
||||
<nav class="flex-1 overflow-y-auto py-4">
|
||||
<ul class="space-y-1 px-3" role="menu">
|
||||
{navLinks.map((link, index) => {
|
||||
const isActive = currentPath === link.href ||
|
||||
(link.href !== '/' && currentPath.startsWith(link.href));
|
||||
const isActive = !link.external && (currentPath === link.href ||
|
||||
(link.href !== '/' && currentPath.startsWith(link.href)));
|
||||
return (
|
||||
<li role="none" style={`--delay: ${index * 50}ms`}>
|
||||
<a
|
||||
href={link.href}
|
||||
role="menuitem"
|
||||
data-astro-prefetch="hover"
|
||||
data-astro-prefetch={link.external ? undefined : 'hover'}
|
||||
target={link.external ? '_blank' : undefined}
|
||||
rel={link.external ? 'noopener noreferrer' : undefined}
|
||||
class:list={[
|
||||
'mobile-nav-link flex items-center gap-3 px-4 py-3 rounded-lg text-base font-medium transition-all duration-200',
|
||||
isActive
|
||||
|
||||
@@ -18,56 +18,68 @@
|
||||
// - Keyboard: native <button> handles Enter & Space; Tab moves between
|
||||
// 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 = [
|
||||
{
|
||||
question: 'How much does a project cost? Do you work with fixed budgets?',
|
||||
answer:
|
||||
'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?',
|
||||
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?',
|
||||
answer:
|
||||
'4–16 weeks from kickoff to launch. MVPs ship in ~6 weeks on average.',
|
||||
'Most WorkRoot IT Solutions engagements run 4–16 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?',
|
||||
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?',
|
||||
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?',
|
||||
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?',
|
||||
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?',
|
||||
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?',
|
||||
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?',
|
||||
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?',
|
||||
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.',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
---
|
||||
// InlineLeadForm — Three-field lead capture rendered inline on the homepage.
|
||||
// InlineLeadForm — Compact lead capture rendered inline on the homepage.
|
||||
//
|
||||
// The goal is to cut the friction step of routing every CTA through /contact.
|
||||
// Visitors who reach the bottom of the homepage can submit name + contact
|
||||
// (email OR phone) + project type without a navigation. The submission reuses
|
||||
// /api/contact under the hood and is tagged `source: 'homepage-inline'` so the
|
||||
// backend / analytics layer can segment these leads cleanly.
|
||||
// Goal: let visitors who reach the bottom of the homepage submit a project
|
||||
// enquiry without the friction of navigating to /contact. The submission
|
||||
// reuses the SAME /api/contact endpoint as the full contact page and is tagged
|
||||
// `source: 'homepage-inline'` so the backend / analytics layer can segment
|
||||
// these leads cleanly (it also routes them to the `[Project Inquiry]` admin
|
||||
// subject prefix).
|
||||
//
|
||||
// Why a single "contact" field instead of separate email + phone:
|
||||
// - 3 fields keeps the form psychologically tiny and dramatically lifts
|
||||
// completion. Real-world conversion testing repeatedly shows 3 → 5+ fields
|
||||
// is the biggest single-step drop-off on B2B lead forms.
|
||||
// - We sniff the value on submit. If it has an "@" we treat it as email.
|
||||
// Otherwise we treat it as a phone and synthesize a placeholder email to
|
||||
// satisfy the backend's strict email validation. The phone is also sent
|
||||
// through, so the admin email body shows the real reachable value.
|
||||
// Fields (kept to 5, mobile-first):
|
||||
// 1. Full name — required
|
||||
// 2. Email — required, type="email" (email keyboard on mobile)
|
||||
// 3. Phone — optional, type="tel" (numeric keypad on mobile)
|
||||
// 4. Project type — required, <select> mapped to the backend `subject`
|
||||
// 5. Message — optional <textarea>; synthesized when left blank so
|
||||
// the backend's message>=10 char rule always passes
|
||||
//
|
||||
// Only name + email + project type are *required* — phone and message stay
|
||||
// optional to keep completion friction low on mobile, which is where most of
|
||||
// these submissions originate. The backend already treats `phone` as optional
|
||||
// and only validates its format when present.
|
||||
---
|
||||
|
||||
<section
|
||||
@@ -108,40 +113,68 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Contact (email or phone) -->
|
||||
<div>
|
||||
<label
|
||||
for="inline-lead-contact"
|
||||
class="block text-sm font-semibold text-secondary-700 dark:text-secondary-200 mb-1.5"
|
||||
>
|
||||
Email or phone <span class="text-red-500" aria-hidden="true">*</span>
|
||||
<span class="sr-only">(required)</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="inline-lead-contact"
|
||||
name="contact"
|
||||
required
|
||||
autocomplete="email tel"
|
||||
inputmode="email"
|
||||
aria-required="true"
|
||||
aria-describedby="inline-lead-contact-hint inline-lead-contact-error"
|
||||
placeholder="jane@company.com or +91 95614 17403"
|
||||
class="block w-full min-h-[48px] px-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 bg-secondary-50 dark:bg-secondary-700 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/30 transition-colors"
|
||||
/>
|
||||
<p
|
||||
id="inline-lead-contact-hint"
|
||||
class="mt-1.5 text-xs text-secondary-500 dark:text-secondary-400"
|
||||
>
|
||||
Drop your best email or phone number — whichever is easiest.
|
||||
</p>
|
||||
<p
|
||||
id="inline-lead-contact-error"
|
||||
class="mt-1.5 text-xs text-red-600 dark:text-red-400 hidden"
|
||||
role="alert"
|
||||
>
|
||||
Please enter a valid email address or phone number.
|
||||
</p>
|
||||
<!-- Email + Phone row -->
|
||||
<div class="grid sm:grid-cols-2 gap-5">
|
||||
<!-- Email (required) -->
|
||||
<div>
|
||||
<label
|
||||
for="inline-lead-email"
|
||||
class="block text-sm font-semibold text-secondary-700 dark:text-secondary-200 mb-1.5"
|
||||
>
|
||||
Email <span class="text-red-500" aria-hidden="true">*</span>
|
||||
<span class="sr-only">(required)</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="inline-lead-email"
|
||||
name="email"
|
||||
required
|
||||
maxlength="254"
|
||||
autocomplete="email"
|
||||
inputmode="email"
|
||||
aria-required="true"
|
||||
aria-describedby="inline-lead-email-error"
|
||||
placeholder="jane@company.com"
|
||||
class="block w-full min-h-[48px] px-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 bg-secondary-50 dark:bg-secondary-700 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/30 transition-colors"
|
||||
/>
|
||||
<p
|
||||
id="inline-lead-email-error"
|
||||
class="mt-1.5 text-xs text-red-600 dark:text-red-400 hidden"
|
||||
role="alert"
|
||||
>
|
||||
Please enter a valid email address.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Phone (optional) -->
|
||||
<div>
|
||||
<label
|
||||
for="inline-lead-phone"
|
||||
class="block text-sm font-semibold text-secondary-700 dark:text-secondary-200 mb-1.5"
|
||||
>
|
||||
Phone
|
||||
<span class="font-normal text-secondary-400 dark:text-secondary-500">(optional)</span>
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="inline-lead-phone"
|
||||
name="phone"
|
||||
autocomplete="tel"
|
||||
inputmode="tel"
|
||||
pattern="[+()0-9.\s\-]{7,}"
|
||||
maxlength="32"
|
||||
aria-describedby="inline-lead-phone-error"
|
||||
placeholder="+91 95614 17403"
|
||||
class="block w-full min-h-[48px] px-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 bg-secondary-50 dark:bg-secondary-700 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/30 transition-colors"
|
||||
/>
|
||||
<p
|
||||
id="inline-lead-phone-error"
|
||||
class="mt-1.5 text-xs text-red-600 dark:text-red-400 hidden"
|
||||
role="alert"
|
||||
>
|
||||
Please enter a valid phone number, or leave it blank.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Project type -->
|
||||
@@ -184,6 +217,33 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Message (optional) -->
|
||||
<div>
|
||||
<label
|
||||
for="inline-lead-message"
|
||||
class="block text-sm font-semibold text-secondary-700 dark:text-secondary-200 mb-1.5"
|
||||
>
|
||||
Project details
|
||||
<span class="font-normal text-secondary-400 dark:text-secondary-500">(optional)</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="inline-lead-message"
|
||||
name="message"
|
||||
rows="3"
|
||||
maxlength="5000"
|
||||
aria-describedby="inline-lead-message-error"
|
||||
placeholder="A sentence or two about what you want to build, your timeline, or budget…"
|
||||
class="block w-full min-h-[96px] px-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 bg-secondary-50 dark:bg-secondary-700 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/30 transition-colors resize-y"
|
||||
></textarea>
|
||||
<p
|
||||
id="inline-lead-message-error"
|
||||
class="mt-1.5 text-xs text-red-600 dark:text-red-400 hidden"
|
||||
role="alert"
|
||||
>
|
||||
Please add a little more detail (at least 10 characters), or leave it blank.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- General form error (server-side / network) -->
|
||||
<div
|
||||
id="inline-lead-form-error"
|
||||
@@ -267,13 +327,17 @@
|
||||
if (!form) return;
|
||||
|
||||
var nameInput = document.getElementById('inline-lead-name');
|
||||
var contactInput = document.getElementById('inline-lead-contact');
|
||||
var emailInput = document.getElementById('inline-lead-email');
|
||||
var phoneInput = document.getElementById('inline-lead-phone');
|
||||
var projectTypeInput = document.getElementById('inline-lead-project-type');
|
||||
var messageInput = document.getElementById('inline-lead-message');
|
||||
var websiteInput = document.getElementById('inline-lead-website');
|
||||
|
||||
var nameError = document.getElementById('inline-lead-name-error');
|
||||
var contactError = document.getElementById('inline-lead-contact-error');
|
||||
var emailError = document.getElementById('inline-lead-email-error');
|
||||
var phoneError = document.getElementById('inline-lead-phone-error');
|
||||
var projectTypeError = document.getElementById('inline-lead-project-type-error');
|
||||
var messageError = document.getElementById('inline-lead-message-error');
|
||||
var formError = document.getElementById('inline-lead-form-error');
|
||||
|
||||
var submitBtn = document.getElementById('inline-lead-submit');
|
||||
@@ -283,7 +347,7 @@
|
||||
var successPanel = document.getElementById('inline-lead-success');
|
||||
|
||||
// Tolerant phone test: allow +, digits, spaces, dashes, parens, dots.
|
||||
// We require at least 7 digits so single-digit / accidental presses fail.
|
||||
// Require at least 7 digits so single-digit / accidental presses fail.
|
||||
var PHONE_REGEX = /^[+]?[()\d\s.\-]{7,}$/;
|
||||
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
@@ -311,6 +375,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function phoneIsValid(val) {
|
||||
return PHONE_REGEX.test(val) && digitsOnly(val).length >= 7;
|
||||
}
|
||||
|
||||
function validate() {
|
||||
var ok = true;
|
||||
|
||||
@@ -322,14 +390,21 @@
|
||||
showError(nameError, false);
|
||||
}
|
||||
|
||||
var contactVal = (contactInput.value || '').trim();
|
||||
var isEmail = EMAIL_REGEX.test(contactVal);
|
||||
var isPhone = PHONE_REGEX.test(contactVal) && digitsOnly(contactVal).length >= 7;
|
||||
if (!contactVal || (!isEmail && !isPhone)) {
|
||||
showError(contactError, true);
|
||||
var emailVal = (emailInput.value || '').trim();
|
||||
if (!EMAIL_REGEX.test(emailVal)) {
|
||||
showError(emailError, true);
|
||||
ok = false;
|
||||
} else {
|
||||
showError(contactError, false);
|
||||
showError(emailError, false);
|
||||
}
|
||||
|
||||
// Phone is optional — only validate format when something was typed.
|
||||
var phoneVal = (phoneInput.value || '').trim();
|
||||
if (phoneVal && !phoneIsValid(phoneVal)) {
|
||||
showError(phoneError, true);
|
||||
ok = false;
|
||||
} else {
|
||||
showError(phoneError, false);
|
||||
}
|
||||
|
||||
var projectVal = projectTypeInput.value || '';
|
||||
@@ -340,13 +415,24 @@
|
||||
showError(projectTypeError, false);
|
||||
}
|
||||
|
||||
// Message is optional — only enforce the min length when something was typed.
|
||||
var messageVal = (messageInput.value || '').trim();
|
||||
if (messageVal && messageVal.length < 10) {
|
||||
showError(messageError, true);
|
||||
ok = false;
|
||||
} else {
|
||||
showError(messageError, false);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Live-validate on input/change so the error UI doesn't linger after fix.
|
||||
// Live-validate on blur/change so the error UI clears once a field is fixed.
|
||||
nameInput.addEventListener('blur', validate);
|
||||
contactInput.addEventListener('blur', validate);
|
||||
emailInput.addEventListener('blur', validate);
|
||||
phoneInput.addEventListener('blur', validate);
|
||||
projectTypeInput.addEventListener('change', validate);
|
||||
messageInput.addEventListener('blur', validate);
|
||||
|
||||
// Map the user-friendly project type label to the backend subject code so
|
||||
// the existing /api/contact validator passes. Anything unmapped → 'other'.
|
||||
@@ -359,41 +445,39 @@
|
||||
};
|
||||
|
||||
function buildPayload() {
|
||||
var contactVal = (contactInput.value || '').trim();
|
||||
var isEmail = EMAIL_REGEX.test(contactVal);
|
||||
var nameVal = (nameInput.value || '').trim();
|
||||
var emailVal = (emailInput.value || '').trim();
|
||||
var phoneVal = (phoneInput.value || '').trim();
|
||||
var projectType = projectTypeInput.value || 'Other';
|
||||
var subject = SUBJECT_BY_PROJECT_TYPE[projectType] || 'other';
|
||||
var messageVal = (messageInput.value || '').trim();
|
||||
|
||||
var email, phone;
|
||||
if (isEmail) {
|
||||
email = contactVal;
|
||||
phone = undefined;
|
||||
// The backend requires a message of at least 10 characters. When the
|
||||
// visitor leaves the (optional) details blank, synthesize a concise
|
||||
// summary so the submission still goes through and the admin email
|
||||
// carries the useful context.
|
||||
var message;
|
||||
if (messageVal.length >= 10) {
|
||||
message = messageVal;
|
||||
} else {
|
||||
// Treat as phone. Synthesize a stable placeholder email so the
|
||||
// backend's strict email validator passes — the admin notification
|
||||
// surfaces the real phone in both Phone: and Message: fields so the
|
||||
// team always has a real way to reach the lead.
|
||||
var digits = digitsOnly(contactVal) || 'unknown';
|
||||
email = 'lead-' + digits + '@homepage.workroot.in';
|
||||
phone = contactVal;
|
||||
message =
|
||||
'New project enquiry from the homepage form.\n\n' +
|
||||
'Project type: ' + projectType + '\n' +
|
||||
'Name: ' + nameVal +
|
||||
(phoneVal ? '\nPhone: ' + phoneVal : '') +
|
||||
(messageVal ? '\n\nNote: ' + messageVal : '');
|
||||
}
|
||||
|
||||
var message =
|
||||
'New inline lead from the homepage.\n\n' +
|
||||
'Project type: ' + projectType + '\n' +
|
||||
'Preferred contact: ' + contactVal;
|
||||
|
||||
var payload = {
|
||||
name: nameVal,
|
||||
email: email,
|
||||
email: emailVal,
|
||||
subject: subject,
|
||||
message: message,
|
||||
source: 'homepage-inline',
|
||||
projectType: projectType,
|
||||
website: (websiteInput && websiteInput.value) || '',
|
||||
};
|
||||
if (phone) payload.phone = phone;
|
||||
if (phoneVal) payload.phone = phoneVal;
|
||||
return payload;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,12 +40,12 @@ interface Props {
|
||||
// credibility bug this set was created to fix.
|
||||
//
|
||||
// - "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
|
||||
// - "8+ Years Avg Experience" — average team experience
|
||||
const defaultItems: ProofItem[] = [
|
||||
{ value: '20+', label: 'Projects' },
|
||||
{ value: '11', label: 'Districts Deployed' },
|
||||
{ value: '11+', label: 'Districts Deployed' },
|
||||
{ value: '50,000+', label: 'Records Managed' },
|
||||
{ value: '8+', label: 'Years Avg Experience' },
|
||||
];
|
||||
|
||||
@@ -9,6 +9,11 @@ interface BreadcrumbItem {
|
||||
|
||||
interface Props {
|
||||
type?: 'WebPage' | 'BlogPosting' | 'Article' | 'Service' | 'AboutPage' | 'ContactPage' | 'FAQPage';
|
||||
// When true this instance emits ONLY the site-wide base schema
|
||||
// (Organization + LocalBusiness and WebSite). Rendered once from
|
||||
// BaseLayout's <head> so every route carries the canonical company data,
|
||||
// independently of whether the page adds its own page-specific <SEO>.
|
||||
base?: boolean;
|
||||
breadcrumbs?: BreadcrumbItem[];
|
||||
article?: {
|
||||
author: string;
|
||||
@@ -29,11 +34,173 @@ interface Props {
|
||||
}>;
|
||||
}
|
||||
|
||||
const { type = 'WebPage', breadcrumbs, article, service, faq } = Astro.props;
|
||||
const { type = 'WebPage', base = false, breadcrumbs, article, service, faq } = Astro.props;
|
||||
|
||||
const baseUrl = 'https://workroot.in';
|
||||
const currentUrl = Astro.url.href;
|
||||
|
||||
const orgName = 'WorkRoot IT Solutions LLP';
|
||||
const orgId = `${baseUrl}/#organization`;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Site-wide base schema — Organization + LocalBusiness and WebSite.
|
||||
//
|
||||
// Emitted once per page via `<SEO base />` rendered from BaseLayout's <head>,
|
||||
// so every route (including pages without their own page-specific <SEO>)
|
||||
// carries WorkRoot's canonical structured data.
|
||||
//
|
||||
// `review` / `aggregateRating` are built from the three real, attributable
|
||||
// client testimonials shown on the homepage (see the `testimonials` array in
|
||||
// src/pages/index.astro). All three are 5★, so the aggregate rating is 5.0
|
||||
// over reviewCount = 3.
|
||||
//
|
||||
// Headline proof numbers in the description (20+ projects · 11+ districts ·
|
||||
// 50,000+ records · 99.9% uptime) mirror the canonical set in
|
||||
// ProofNumbers.astro and the /services stats — keep them identical site-wide.
|
||||
// ---------------------------------------------------------------------------
|
||||
const testimonialReviews = [
|
||||
{
|
||||
author: 'Ajay Pruthi',
|
||||
body: 'WorkRoot IT Solutions is very professional and responsive. They built PLNR Financing platform with great attention to detail. The Angular + Spring Boot stack works flawlessly and their ongoing support is excellent.',
|
||||
},
|
||||
{
|
||||
author: 'Dhirendra Singh',
|
||||
body: 'The FPC Cart e-commerce platform delivered by WorkRoot exceeded our expectations. Built with Astro and Golang, it loads incredibly fast and handles our product catalog seamlessly. Their team understood our business needs perfectly.',
|
||||
},
|
||||
{
|
||||
author: 'Dilip Raj',
|
||||
body: 'WorkRoot delivered our University Management System for IIT Patna on time and within budget. The Next.js + Spring Boot architecture is robust and scalable. They are a reliable technology partner for complex enterprise projects.',
|
||||
},
|
||||
];
|
||||
|
||||
const reviewNodes = testimonialReviews.map((review) => ({
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": review.author
|
||||
},
|
||||
"reviewRating": {
|
||||
"@type": "Rating",
|
||||
"ratingValue": "5",
|
||||
"bestRating": "5",
|
||||
"worstRating": "1"
|
||||
},
|
||||
"reviewBody": review.body
|
||||
}));
|
||||
|
||||
const organizationSchema = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": ["Organization", "LocalBusiness"],
|
||||
"@id": orgId,
|
||||
"name": orgName,
|
||||
"alternateName": ["WorkRoot", "WorkRoot IT Solutions"],
|
||||
"url": baseUrl,
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": `${baseUrl}/logo.png`,
|
||||
"width": "512",
|
||||
"height": "512"
|
||||
},
|
||||
"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.",
|
||||
"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": "IN"
|
||||
}, {
|
||||
"@type": "ContactPoint",
|
||||
"telephone": "+91-9561417403",
|
||||
"contactType": "sales",
|
||||
"email": "admin@workroot.in",
|
||||
"availableLanguage": ["English", "Hindi", "Marathi"],
|
||||
"areaServed": "IN"
|
||||
}],
|
||||
"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": "AdministrativeArea",
|
||||
"name": "Bihar"
|
||||
},
|
||||
{
|
||||
"@type": "Country",
|
||||
"name": "India"
|
||||
}
|
||||
],
|
||||
"serviceType": ["Web Development", "Mobile App Development", "ERP & Enterprise Systems", "Government IT Solutions"],
|
||||
"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": String(reviewNodes.length),
|
||||
"bestRating": "5",
|
||||
"worstRating": "1"
|
||||
},
|
||||
"review": reviewNodes
|
||||
};
|
||||
|
||||
const websiteSchema = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": orgName,
|
||||
"url": baseUrl,
|
||||
"description": "Professional IT services including web development, cloud solutions, and digital transformation for businesses.",
|
||||
"publisher": {
|
||||
"@type": "Organization",
|
||||
"@id": orgId,
|
||||
"name": orgName
|
||||
},
|
||||
"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"
|
||||
};
|
||||
|
||||
// Get page title and description from parent layout props
|
||||
const pageTitle = (Astro.props as any).title || 'WorkRoot IT Solutions';
|
||||
const pageDescription = (Astro.props as any).description || 'Professional IT services including web development, cloud solutions, and digital transformation for businesses.';
|
||||
@@ -71,7 +238,7 @@ const blogPostingSchema = article && type === 'BlogPosting' ? {
|
||||
"dateModified": article.dateModified || article.datePublished,
|
||||
"publisher": {
|
||||
"@type": "Organization",
|
||||
"name": "WorkRoot IT Solutions",
|
||||
"name": "WorkRoot IT Solutions LLP",
|
||||
"url": baseUrl,
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
@@ -90,7 +257,9 @@ const blogPostingSchema = article && type === 'BlogPosting' ? {
|
||||
"inLanguage": "en-US"
|
||||
} : null;
|
||||
|
||||
// Generate Service schema
|
||||
// Generate Service schema — reusable node; each service page passes its own
|
||||
// name/description via the `service` prop. Provider is linked by @id to the
|
||||
// site-wide Organization node so the structured-data graph stays connected.
|
||||
const serviceSchema = service ? {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Service",
|
||||
@@ -98,10 +267,20 @@ const serviceSchema = service ? {
|
||||
"description": service.description,
|
||||
"provider": {
|
||||
"@type": "Organization",
|
||||
"name": "WorkRoot IT Solutions",
|
||||
"@id": orgId,
|
||||
"name": orgName,
|
||||
"url": baseUrl
|
||||
},
|
||||
"areaServed": "Worldwide",
|
||||
"areaServed": [
|
||||
{
|
||||
"@type": "AdministrativeArea",
|
||||
"name": "Bihar"
|
||||
},
|
||||
{
|
||||
"@type": "Country",
|
||||
"name": "India"
|
||||
}
|
||||
],
|
||||
"serviceType": service.name
|
||||
} : null;
|
||||
|
||||
@@ -119,7 +298,7 @@ const webPageSchema = type === 'WebPage' && Astro.url.pathname !== '/' ? {
|
||||
},
|
||||
"about": {
|
||||
"@type": "Organization",
|
||||
"name": "WorkRoot IT Solutions"
|
||||
"name": "WorkRoot IT Solutions LLP"
|
||||
},
|
||||
"primaryImageOfPage": {
|
||||
"@type": "ImageObject",
|
||||
@@ -151,7 +330,7 @@ const aboutPageSchema = type === 'AboutPage' ? {
|
||||
"inLanguage": "en-US",
|
||||
"mainEntity": {
|
||||
"@type": "Organization",
|
||||
"name": "WorkRoot IT Solutions",
|
||||
"name": "WorkRoot IT Solutions LLP",
|
||||
"url": baseUrl
|
||||
}
|
||||
} : null;
|
||||
@@ -166,8 +345,8 @@ const contactPageSchema = type === 'ContactPage' ? {
|
||||
"inLanguage": "en-US",
|
||||
"mainEntity": {
|
||||
"@type": "LocalBusiness",
|
||||
"name": "WorkRoot IT Solutions",
|
||||
"alternateName": "WorkRoot IT Solutions LLP",
|
||||
"name": "WorkRoot IT Solutions LLP",
|
||||
"alternateName": "WorkRoot IT Solutions",
|
||||
"url": baseUrl,
|
||||
"telephone": "+91-9561417403",
|
||||
"email": "admin@workroot.in",
|
||||
@@ -214,30 +393,42 @@ const contactPageSchema = type === 'ContactPage' ? {
|
||||
} : null;
|
||||
---
|
||||
|
||||
{breadcrumbSchema && (
|
||||
{/* Site-wide base schema — emitted only by the single `<SEO base />` rendered
|
||||
from BaseLayout's <head>, so it appears exactly once on every page. */}
|
||||
{base && (
|
||||
<script type="application/ld+json" set:html={JSON.stringify(organizationSchema)} />
|
||||
)}
|
||||
|
||||
{base && (
|
||||
<script type="application/ld+json" set:html={JSON.stringify(websiteSchema)} />
|
||||
)}
|
||||
|
||||
{/* Page-specific schema — never emitted by the base instance, so there is no
|
||||
duplication with the site-wide Organization/WebSite nodes above. */}
|
||||
{!base && breadcrumbSchema && (
|
||||
<script type="application/ld+json" set:html={JSON.stringify(breadcrumbSchema)} />
|
||||
)}
|
||||
|
||||
{blogPostingSchema && (
|
||||
{!base && blogPostingSchema && (
|
||||
<script type="application/ld+json" set:html={JSON.stringify(blogPostingSchema)} />
|
||||
)}
|
||||
|
||||
{serviceSchema && (
|
||||
{!base && serviceSchema && (
|
||||
<script type="application/ld+json" set:html={JSON.stringify(serviceSchema)} />
|
||||
)}
|
||||
|
||||
{webPageSchema && (
|
||||
{!base && webPageSchema && (
|
||||
<script type="application/ld+json" set:html={JSON.stringify(webPageSchema)} />
|
||||
)}
|
||||
|
||||
{faqPageSchema && (
|
||||
{!base && faqPageSchema && (
|
||||
<script type="application/ld+json" set:html={JSON.stringify(faqPageSchema)} />
|
||||
)}
|
||||
|
||||
{aboutPageSchema && (
|
||||
{!base && aboutPageSchema && (
|
||||
<script type="application/ld+json" set:html={JSON.stringify(aboutPageSchema)} />
|
||||
)}
|
||||
|
||||
{contactPageSchema && (
|
||||
{!base && contactPageSchema && (
|
||||
<script type="application/ld+json" set:html={JSON.stringify(contactPageSchema)} />
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
---
|
||||
// ServiceFaq — reusable, prop-driven FAQ accordion for the keyword-focused
|
||||
// service landing pages (e.g. /services/custom-erp-development).
|
||||
//
|
||||
// IMPORTANT: this component renders only the VISIBLE accordion. It does NOT
|
||||
// emit FAQPage JSON-LD. The owning page is expected to pass the same `faqs`
|
||||
// array into <SEO faq={faqs} />, which emits exactly one FAQPage node. Keeping
|
||||
// schema emission in SEO.astro (and out of here) guarantees a single FAQPage
|
||||
// per page even when several of these accordions could theoretically render.
|
||||
//
|
||||
// Visual + a11y behaviour mirrors HomepageFaq.astro (single-open accordion,
|
||||
// real <button> questions, aria-expanded, grid-rows expand with no layout
|
||||
// shift) but uses a `svc-faq-*` class namespace so it never collides with the
|
||||
// homepage FAQ script if both ever appear together.
|
||||
|
||||
interface FaqItem {
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
faqs: FaqItem[];
|
||||
eyebrow?: string;
|
||||
heading?: string;
|
||||
subheading?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
faqs,
|
||||
eyebrow = 'Before you reach out',
|
||||
heading = 'Frequently asked questions',
|
||||
subheading = 'Straight answers on scope, cost, timelines, ownership, and how we work.',
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<section
|
||||
class="py-20 sm:py-24 bg-secondary-50 dark:bg-secondary-950 border-t border-secondary-100 dark:border-secondary-800"
|
||||
aria-labelledby="svc-faq-heading"
|
||||
>
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<div class="text-center mb-12 sm:mb-16" data-animate="fade-up">
|
||||
<span
|
||||
class="inline-block px-4 py-2 rounded-full bg-accent-50 dark:bg-accent-900/30 text-accent-700 dark:text-accent-300 text-xs sm:text-sm font-bold uppercase tracking-wider mb-4"
|
||||
>
|
||||
{eyebrow}
|
||||
</span>
|
||||
<h2
|
||||
id="svc-faq-heading"
|
||||
class="text-3xl sm:text-4xl lg:text-5xl font-bold text-secondary-900 dark:text-secondary-100 mb-4 leading-tight"
|
||||
>
|
||||
{heading}
|
||||
</h2>
|
||||
<p class="text-base sm:text-lg text-secondary-600 dark:text-secondary-300">
|
||||
{subheading}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul class="space-y-3 sm:space-y-4" role="list">
|
||||
{
|
||||
faqs.map((item, index) => (
|
||||
<li
|
||||
class="bg-white dark:bg-secondary-800 rounded-2xl border border-secondary-200 dark:border-secondary-700 hover:border-primary-300 dark:hover:border-primary-500/60 shadow-sm transition-colors duration-200 overflow-hidden"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="svc-faq-question w-full text-left px-5 sm:px-7 py-5 sm:py-6 flex items-start justify-between gap-4 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-400 focus-visible:ring-offset-2 focus-visible:ring-offset-secondary-50 dark:focus-visible:ring-offset-secondary-950 rounded-2xl"
|
||||
aria-expanded="false"
|
||||
aria-controls={`svc-faq-answer-${index}`}
|
||||
id={`svc-faq-question-${index}`}
|
||||
>
|
||||
<span class="font-semibold text-base sm:text-lg text-secondary-900 dark:text-secondary-100 leading-snug">
|
||||
{item.question}
|
||||
</span>
|
||||
<svg
|
||||
class="svc-faq-icon w-5 h-5 sm:w-6 sm:h-6 mt-0.5 sm:mt-1 text-primary-500 dark:text-primary-400 flex-shrink-0 transition-transform duration-300"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
class="svc-faq-answer grid grid-rows-[0fr] transition-[grid-template-rows] duration-300 ease-out"
|
||||
id={`svc-faq-answer-${index}`}
|
||||
role="region"
|
||||
aria-labelledby={`svc-faq-question-${index}`}
|
||||
>
|
||||
<div class="overflow-hidden">
|
||||
<p class="px-5 sm:px-7 pb-5 sm:pb-6 -mt-1 text-sm sm:text-base text-secondary-600 dark:text-secondary-300 leading-relaxed">
|
||||
{item.answer}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
|
||||
<p class="text-center text-sm sm:text-base text-secondary-600 dark:text-secondary-400 mt-10 sm:mt-12">
|
||||
Have a different question?{' '}
|
||||
<a
|
||||
href="#inline-lead-form"
|
||||
class="font-semibold text-primary-600 dark:text-primary-300 hover:underline"
|
||||
>
|
||||
Ask us directly →
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.svc-faq-answer[data-expanded='true'] {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
.svc-faq-question[aria-expanded='true'] .svc-faq-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.svc-faq-answer,
|
||||
.svc-faq-icon {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script is:inline>
|
||||
(function () {
|
||||
var buttons = document.querySelectorAll('.svc-faq-question');
|
||||
if (!buttons.length) return;
|
||||
|
||||
function setExpanded(button, expanded) {
|
||||
button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
||||
var answerId = button.getAttribute('aria-controls');
|
||||
if (!answerId) return;
|
||||
var answer = document.getElementById(answerId);
|
||||
if (!answer) return;
|
||||
if (expanded) {
|
||||
answer.setAttribute('data-expanded', 'true');
|
||||
} else {
|
||||
answer.removeAttribute('data-expanded');
|
||||
}
|
||||
}
|
||||
|
||||
buttons.forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
var isOpen = button.getAttribute('aria-expanded') === 'true';
|
||||
buttons.forEach(function (other) {
|
||||
if (other !== button) setExpanded(other, false);
|
||||
});
|
||||
setExpanded(button, !isOpen);
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: "How AI is Transforming Modern Business Operations"
|
||||
description: "Explore the practical applications of artificial intelligence in streamlining business processes and driving innovation."
|
||||
seoTitle: "How AI Is Transforming Business Operations"
|
||||
seoDescription: "How AI is reshaping business operations — practical use cases in customer service, predictive analytics, process automation, and intelligent document handling."
|
||||
pubDate: 2024-01-10
|
||||
heroImage: "/images/blog/ai-business.jpg"
|
||||
category: "AI/ML"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: "Building Scalable Government IT Systems: Lessons from 11-District Deployments"
|
||||
description: "Practical insights from building and deploying large-scale government systems across multiple districts, including Police Management Systems and SCMS projects."
|
||||
seoTitle: "Scaling Government IT Across 11 Districts"
|
||||
seoDescription: "Lessons from deploying government IT systems across 11 districts — database-per-district architecture, offline-first modules, and audit logging baked in."
|
||||
pubDate: 2025-12-15
|
||||
heroImage: "/images/blog/govt-systems.jpg"
|
||||
category: "Cloud"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: "Building Scalable ERP Systems: Lessons from Government & Enterprise Projects"
|
||||
description: "Practical insights on designing and deploying large-scale ERP and management systems for government bodies and enterprises, drawn from real-world project experience."
|
||||
seoTitle: "Building Scalable ERP Systems That Last"
|
||||
seoDescription: "How we design and deploy scalable ERP systems for government and enterprise — modular architecture, role-based access control, and offline-resilient patterns."
|
||||
pubDate: 2024-02-20
|
||||
heroImage: "/images/blog/erp-systems.jpg"
|
||||
category: "Web Development"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: "The Complete Guide to Cloud Migration in 2024"
|
||||
description: "A step-by-step approach to migrating your infrastructure to the cloud while minimizing downtime and maximizing ROI."
|
||||
seoTitle: "A Complete Guide to Cloud Migration in 2024"
|
||||
seoDescription: "A step-by-step cloud migration guide for 2024 — assess workloads, containerize apps, adopt infrastructure as code, and cut downtime while maximizing your ROI."
|
||||
pubDate: 2024-01-05
|
||||
heroImage: "/images/blog/cloud-migration.jpg"
|
||||
category: "Cloud"
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
---
|
||||
title: "How We Deployed a Police Management System Across 11 Bihar Districts"
|
||||
description: "A field-tested account of rolling out a Police Management System to 15,000+ officers across 11 districts in Bihar — the architecture, the rollout plan, the failures, and the numbers that came out the other side."
|
||||
seoTitle: "Deploying a Police System Across 11 Districts"
|
||||
seoDescription: "A field account of rolling out a Police Management System to 15,000+ officers across 11 Bihar districts — the architecture, rollout plan, failures, and results."
|
||||
pubDate: 2026-06-10
|
||||
heroImage: "/images/blog/govt-systems.jpg"
|
||||
category: "DevOps"
|
||||
tags: ["Government IT", "Case Study", "Deployment", "Spring Boot", "Police Management System", "Bihar"]
|
||||
author:
|
||||
name: "Ajay"
|
||||
avatar: "/images/team/ajay.jpg"
|
||||
draft: false
|
||||
---
|
||||
|
||||
When a state police department asks you to digitise case management, personnel tracking, and resource allocation across **11 districts** and put it in front of **15,000+ officers**, the hard part is not the code. It is the rollout. This is the story of how we took a Police Management System (PMS) from a single pilot district in Bihar to a statewide deployment without taking public-safety operations offline — what worked, what broke, and the numbers we measured along the way.
|
||||
|
||||
> **The headline:** 11 districts live in 14 weeks, zero unplanned downtime during cutover, and FIR registration time cut from an average of 38 minutes (paper) to under 6 minutes (digital).
|
||||
|
||||
## The Starting Point
|
||||
|
||||
Before the project, every district ran on paper and a patchwork of disconnected spreadsheets. A case opened in one district was invisible to the next. Personnel rosters lived in filing cabinets. Resource allocation — vehicles, equipment, duty assignments — was reconciled by hand at the end of each shift.
|
||||
|
||||
The brief was deceptively simple: one system, every district, no disruption to live policing. The constraints were not:
|
||||
|
||||
- **Heterogeneous infrastructure.** Some district HQs had fibre; rural outposts shared a single intermittent mobile connection.
|
||||
- **Zero tolerance for data leakage between jurisdictions.** District A must never see District B's open cases.
|
||||
- **Officers with mixed digital literacy.** The system had to be usable by someone who had never filed anything on a computer.
|
||||
- **A non-negotiable audit trail.** Every read and write on sensitive records had to be traceable.
|
||||
|
||||
## The Architecture We Committed To
|
||||
|
||||
We built the backend on **Spring Boot** as a modular monolith — clean module boundaries (cases, personnel, resources, reporting, auth) without the operational tax of running dozens of microservices across 11 sites. The frontend was an **Angular** SPA with an offline-resilient shell so officers could keep working through connectivity drops.
|
||||
|
||||
Three decisions did most of the heavy lifting:
|
||||
|
||||
### 1. Database-per-district with central aggregation
|
||||
|
||||
Each district ran its own operational database, with a central read-only warehouse for state-level analytics. This gave us hard data isolation *for free* — a query in one district physically cannot reach another district's records — and meant one district's database incident never cascaded.
|
||||
|
||||
```yaml
|
||||
# Per-district deployment descriptor
|
||||
district:
|
||||
code: "D04"
|
||||
deployment-mode: "standard" # high-load | standard | minimal
|
||||
database:
|
||||
isolation: dedicated # no cross-district connection strings exist
|
||||
pool-size: 20
|
||||
sync:
|
||||
central-warehouse: nightly
|
||||
```
|
||||
|
||||
### 2. Offline-first for field-critical modules
|
||||
|
||||
FIR registration and duty logging had to work when the network did not. We queued writes locally and synced on a fixed cadence, with conflict resolution on the server. An officer in a rural outpost never sees a spinner waiting on a flaky uplink.
|
||||
|
||||
```java
|
||||
@Scheduled(fixedDelay = 300_000) // every 5 minutes
|
||||
public void syncPendingRecords() {
|
||||
for (PendingRecord record : localStore.getPending()) {
|
||||
try {
|
||||
centralServer.submit(record);
|
||||
localStore.markSynced(record.getId());
|
||||
} catch (ConnectException e) {
|
||||
log.warn("Uplink down — {} records still queued", localStore.pendingCount());
|
||||
break; // stop and retry next cycle; never block the officer
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Audit logging baked into the data layer
|
||||
|
||||
Every access to a sensitive record writes an immutable audit entry with user, action, district, timestamp, and source IP. Critical actions trigger real-time alerts. This was not a reporting feature bolted on at the end — it was a cross-cutting concern in the shared library every module depended on.
|
||||
|
||||
## The Rollout Plan That Actually Held Up
|
||||
|
||||
We did **not** big-bang 11 districts. We sequenced them.
|
||||
|
||||
1. **Pilot (Weeks 1–4): one district.** We picked a mid-sized district with reasonable connectivity and a station house officer who was enthusiastic about the project. Everything we learned here rewrote the playbook for the rest.
|
||||
2. **Hardening (Weeks 5–7): two adjacent districts.** This proved the multi-district data isolation under real concurrent load and exposed our first nasty bug (see below).
|
||||
3. **Wave rollout (Weeks 8–14): the remaining eight, two at a time.** Each wave used **feature flags** so a district could go live with the core modules first and switch on biometric auth or mobile reporting only once its officers were trained.
|
||||
|
||||
```yaml
|
||||
features:
|
||||
biometric-auth:
|
||||
enabled: true
|
||||
districts: ["D01", "D02", "D05"]
|
||||
mobile-reporting:
|
||||
enabled: true
|
||||
districts: ["D01", "D02", "D03", "D04", "D05",
|
||||
"D06", "D07", "D08", "D09", "D10", "D11"]
|
||||
```
|
||||
|
||||
Cutover for each district used **blue-green deployment**: stand up the new version alongside the old, run smoke tests, switch the load balancer, and keep the previous environment warm for 24 hours as an instant rollback path. No district ever experienced a maintenance window during working hours.
|
||||
|
||||
## What Broke (Because Something Always Does)
|
||||
|
||||
**The sync storm.** When we brought districts three and four online, a power restoration after an outage caused hundreds of field devices to flush their queues simultaneously. The central server buckled under the thundering herd. The fix: jittered, exponentially backed-off sync windows so devices never resync in lockstep. It is now standard in every offline module we build.
|
||||
|
||||
**The "helpful" copy-paste.** During training we discovered officers pasting entire case narratives that included pasted formatting payloads. Our early sanitisation was too permissive. We tightened input handling and added server-side normalisation before this ever reached production data.
|
||||
|
||||
**Training was half the project.** We budgeted for software and under-budgeted for people. The districts that went smoothly were the ones where we ran hands-on sessions with real (anonymised) cases, not slide decks. We rebuilt our rollout checklist to make on-site training a gating requirement, not an afterthought.
|
||||
|
||||
## The Numbers
|
||||
|
||||
After all 11 districts were live for one quarter:
|
||||
|
||||
- **38 min → under 6 min** average FIR registration time.
|
||||
- **11/11 districts** on a single, consistent platform with strict jurisdiction isolation.
|
||||
- **15,000+ officers** onboarded.
|
||||
- **Zero** unplanned downtime during any district cutover.
|
||||
- **100%** of sensitive-record access captured in a tamper-evident audit trail.
|
||||
|
||||
## What We'd Tell Anyone Attempting This
|
||||
|
||||
1. **Sequence, never big-bang.** A pilot that teaches you something is worth more than a launch that impresses someone.
|
||||
2. **Design for the worst network in the state, not the best.** If it breaks at 500ms latency, it will break in the field.
|
||||
3. **Make isolation structural, not procedural.** If a query *can* cross jurisdictions, eventually it will. Remove the possibility at the schema and connection layer.
|
||||
4. **Automate every deployment.** With 11+ environments, manual steps are how outages happen. We used Ansible so every cutover was reproducible.
|
||||
5. **Treat training as engineering work.** Budget for it, measure it, and gate go-live on it.
|
||||
|
||||
## Building something similar?
|
||||
|
||||
We have shipped systems like this across multiple Indian states and institutions — police, universities, and government departments — under exactly these constraints. If you are scoping a multi-district government platform, a custom ERP, or any system where downtime is not an option, [get a free proposal](/contact) and we will share what we learned the expensive way so you do not have to.
|
||||
@@ -0,0 +1,125 @@
|
||||
---
|
||||
title: "GeM Empanelment Process for Software Vendors: A Step-by-Step 2026 Guide"
|
||||
description: "A complete, India-specific walkthrough of how software and IT-services vendors get listed and sell on the Government e-Marketplace (GeM) — registration steps, document checklist, fees, timelines, and how the bidding process actually works."
|
||||
seoTitle: "GeM Empanelment for Software Vendors (2026)"
|
||||
seoDescription: "Step-by-step guide to GeM registration and empanelment for software vendors in India — documents, fees, timelines, OEM panel, bids vs catalogue, and common rejection reasons."
|
||||
pubDate: 2026-06-19
|
||||
heroImage: "/images/blog/govt-systems.jpg"
|
||||
category: "Web Development"
|
||||
tags: ["GeM", "Government IT", "Vendor Registration", "Public Procurement", "India", "Software Services"]
|
||||
author:
|
||||
name: "Ajay"
|
||||
avatar: "/images/team/ajay.jpg"
|
||||
draft: false
|
||||
---
|
||||
|
||||
The Government e-Marketplace (GeM) is the single largest channel for selling software and IT services to Indian government bodies — central ministries, state departments, PSUs, and autonomous institutions all procure through it. If you build custom software, mobile apps, or IT services and you are not on GeM, you are invisible to a buyer base that spends tens of thousands of crores a year. This guide explains exactly how a software vendor gets onto GeM, what it costs, how long it takes, and how to actually win business once you are listed.
|
||||
|
||||
One clarification up front: GeM does not use the word "empanelment" the way traditional government tenders do. On GeM you **register as a Seller** and then **publish offerings** (products or, for software companies, *services*). When people say "GeM empanelment for software vendors," they mean getting your company registered, verified, and listed so government buyers can find and buy from you. We use both terms interchangeably below.
|
||||
|
||||
## Who Should Register: Product Vendor vs Service Provider
|
||||
|
||||
Software companies usually fit one or both of these seller types, and the distinction matters because the catalogue, documents, and bidding flow differ.
|
||||
|
||||
| Seller Type | What You Sell on GeM | Typical Software Company Fit |
|
||||
|-------------|----------------------|------------------------------|
|
||||
| **Product Seller** | Listed goods with a catalogue (e.g., software licences, devices) | Off-the-shelf software, SaaS licences, hardware bundles |
|
||||
| **Service Provider** | Service categories with rate-card / bid-based pricing | Custom development, IT manpower, AMC, cloud, web design |
|
||||
| **OEM** | Original manufacturer with an OEM panel | Companies whose own software product is resold by others |
|
||||
|
||||
Most custom-software firms register primarily as **Service Providers** under categories like "Software Development Services," "Web Site Design & Development," "IT Professional Outsourcing," and "Custom Bid for Services."
|
||||
|
||||
## Step-by-Step Registration Process
|
||||
|
||||
The end-to-end flow from sign-up to your first live listing typically looks like this:
|
||||
|
||||
1. **Create a Primary User account** on gem.gov.in using your business email and the Aadhaar or PAN of the authorised signatory. The primary user is legally the organisation's representative.
|
||||
2. **Select your organisation type** — Proprietorship, Partnership, LLP, Private Limited, etc. This determines which constitution documents you upload.
|
||||
3. **Complete organisation verification** by linking your **Udyam (MSME) registration** or **CIN**, and validating your **PAN** and **GSTIN**.
|
||||
4. **Add your business profile** — office address, bank account (for payments), and authorised signatory details.
|
||||
5. **Complete the GeM seller assessment** where applicable (especially for OEMs and certain service categories).
|
||||
6. **List your offerings** — choose the right service category, fill the technical parameters, upload your rate card / documents, and publish.
|
||||
7. **Set up the Caution Money / e-PBG** mechanism if you intend to bid on higher-value tenders (more below).
|
||||
|
||||
### Document Checklist
|
||||
|
||||
Have these ready before you start — missing or mismatched documents are the number-one cause of delay:
|
||||
|
||||
- PAN of the organisation and the authorised signatory
|
||||
- GSTIN certificate
|
||||
- Udyam (MSME) registration certificate (strongly recommended — unlocks exemptions)
|
||||
- Constitution proof (Certificate of Incorporation / Partnership Deed / GST for proprietorship)
|
||||
- Cancelled cheque or bank account proof matching the registered name
|
||||
- Authorised signatory's Aadhaar (for e-sign / OTP verification)
|
||||
- Income Tax Return acknowledgements (often needed to clear turnover-based bid criteria)
|
||||
- Relevant certifications (ISO, CMMI, CERT-In) if your target tenders demand them
|
||||
|
||||
## Fees, Caution Money, and Timelines
|
||||
|
||||
A frequent question: *is GeM registration free?* Basic seller registration on GeM is **free**. The costs come later, when you transact.
|
||||
|
||||
| Item | 2026 Cost (Indicative) | Notes |
|
||||
|------|------------------------|-------|
|
||||
| Seller registration | ₹0 | No registration fee |
|
||||
| Caution Money Deposit (CMD) | ₹5,000 – ₹25,000 | Slab-based on annual GeM turnover; refundable |
|
||||
| Transaction charge | ~0.30% – 0.45% of order value | Levied on the seller above a threshold per order |
|
||||
| e-PBG / EMD for bids | 2%–3% of bid value (often exempt for MSME) | Tender-specific; bid security |
|
||||
|
||||
**Timeline:** with documents in order, organisation verification typically completes in **3–7 working days**, and you can publish your first listing the same week. In practice, factoring in document corrections and category approvals, most software vendors are live and bid-ready in **2–4 weeks**.
|
||||
|
||||
## Catalogue Listing vs Bidding: How You Actually Win
|
||||
|
||||
Getting listed is necessary but not sufficient. There are three ways government buyers actually buy from you on GeM, and your strategy should target the right one.
|
||||
|
||||
| Mode | How It Works | Best For |
|
||||
|------|-------------|----------|
|
||||
| **Direct Purchase** | Buyer purchases directly up to ₹25,000 (or category limit) | Small, fast, low-value services |
|
||||
| **L1 / Comparison** | Buyer compares listed sellers and picks lowest qualifying price | Standard, well-defined service offerings |
|
||||
| **Bid / Reverse Auction** | Formal tender with eligibility criteria; sellers bid | High-value custom projects and AMCs |
|
||||
|
||||
For custom software and ERP work, the **Bid / Reverse Auction** route is where the meaningful contracts live. A buyer publishes a bid with eligibility criteria (turnover, past performance, certifications), and registered sellers submit technical and financial bids. Reverse auctions then drive the price down to the lowest qualifying bidder, so your win depends on meeting eligibility *and* pricing intelligently — not just being cheapest.
|
||||
|
||||
## MSME and Startup Benefits Worth Claiming
|
||||
|
||||
If you are an MSME or a DPIIT-recognised startup, GeM gives you real, money-saving advantages that you should activate during registration:
|
||||
|
||||
- **Exemption from prior turnover and prior experience** criteria in many bids (under the Public Procurement Policy for MSEs).
|
||||
- **EMD / bid-security exemption** in a large share of tenders.
|
||||
- **Reserved procurement** — a mandated share of government purchases is earmarked for MSEs.
|
||||
- **No registration fee** and reduced caution-money slabs.
|
||||
|
||||
Linking your Udyam registration during onboarding is what unlocks these — skipping it leaves money and eligibility on the table.
|
||||
|
||||
## Common Reasons Software Vendors Get Rejected or Delayed
|
||||
|
||||
Avoid these and you will clear verification on the first attempt:
|
||||
|
||||
- **Name mismatches** across PAN, GST, bank account, and Udyam — every record must match exactly.
|
||||
- **Wrong service category** — listing under a category that does not match your offering causes buyer-side rejection.
|
||||
- **Incomplete technical parameters** in the service listing, so buyers cannot find or compare you.
|
||||
- **Not meeting turnover/experience eligibility** in a bid (and not claiming MSME exemption when eligible).
|
||||
- **Outdated documents** — an expired GST or unrenewed Udyam certificate stalls everything.
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
**Is GeM registration mandatory to sell to the government?**
|
||||
For most central and state government procurement of available goods and services, yes — buyers are directed to procure through GeM. If your offering exists on GeM, being registered is effectively required to compete.
|
||||
|
||||
**How long does GeM registration take for a software company?**
|
||||
Organisation verification typically completes in 3–7 working days. Allowing for document corrections and category approvals, most software vendors are live and bid-ready within 2–4 weeks.
|
||||
|
||||
**Does it cost money to register on GeM?**
|
||||
Basic seller registration is free. Costs arise later: a refundable caution-money deposit (₹5,000–₹25,000 by turnover slab), a small per-order transaction charge, and bid-specific securities (often waived for MSMEs).
|
||||
|
||||
**Can a startup or MSME sell on GeM without prior government experience?**
|
||||
Yes. Under the MSE procurement policy and startup provisions, many bids exempt MSMEs and DPIIT-recognised startups from prior turnover and experience requirements — but you must link your Udyam/DPIIT registration to claim it.
|
||||
|
||||
**What's the difference between being listed and winning a contract?**
|
||||
Listing makes you visible. Winning happens through Direct Purchase (small value), L1 comparison, or Bid/Reverse Auction (high value). For custom software and ERP contracts, you win by qualifying on eligibility criteria and competing in the bid — listing alone generates very few orders.
|
||||
|
||||
**Which categories should a custom software firm register under?**
|
||||
Common fits include Software Development Services, Web Site Design & Development, IT Professional Outsourcing / Manpower, Cloud Services, and "Custom Bid for Services" for non-standard scopes. Register under every category that genuinely matches your delivery capability.
|
||||
|
||||
## Turning GeM Access Into Government Contracts
|
||||
|
||||
Registration is the entry ticket; winning is a discipline. The vendors who succeed on GeM keep their documents current, list under precise categories, claim every MSME benefit they qualify for, and price their bids with the reverse-auction dynamic in mind. WorkRoot has delivered software for Indian government bodies — including police and university systems across multiple states — and we understand both the procurement process and the engineering bar it demands. If you want help positioning for government IT work, [get in touch](/contact).
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: "Getting Started with Astro: The Modern Static Site Generator"
|
||||
description: "Learn how Astro combines the best of static site generation with modern component frameworks for blazing-fast websites."
|
||||
seoTitle: "Getting Started with Astro for Fast Sites"
|
||||
seoDescription: "Get started with Astro, the modern site generator — learn islands architecture, type-safe content collections, and how it ships fast, low-JavaScript sites."
|
||||
pubDate: 2024-01-15
|
||||
heroImage: "/images/blog/astro-intro.jpg"
|
||||
category: "Web Development"
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
---
|
||||
title: "How Much Does Custom ERP Cost in India? (2026 Pricing Guide)"
|
||||
description: "A transparent, India-specific breakdown of what a custom ERP actually costs in 2026 — by scope, team, and deployment model — with real rupee ranges, a cost calculator framework, and hidden costs to budget for."
|
||||
seoTitle: "Custom ERP Cost in India (2026 Guide)"
|
||||
seoDescription: "What does a custom ERP cost in India in 2026? Real rupee ranges by scope and team, a cost-driver breakdown, build-vs-buy maths, and hidden costs to plan for."
|
||||
pubDate: 2026-06-19
|
||||
heroImage: "/images/blog/erp-systems.jpg"
|
||||
category: "Web Development"
|
||||
tags: ["ERP", "Custom Software", "Pricing", "India", "Enterprise", "Cost Estimation"]
|
||||
author:
|
||||
name: "Ajay"
|
||||
avatar: "/images/team/ajay.jpg"
|
||||
draft: false
|
||||
---
|
||||
|
||||
"How much will it cost?" is the first question every buyer asks about custom ERP — and the honest answer is "it depends." But "it depends" is not a number you can put in a budget. So in this guide we turn the question into something usable: what a custom ERP actually costs in India in 2026, what drives those numbers up or down, and how to estimate your own project before you talk to a single vendor.
|
||||
|
||||
The short version: a focused single-department custom ERP in India typically lands between **₹6,00,000 and ₹18,00,000**, a multi-module mid-market system between **₹18,00,000 and ₹60,00,000**, and a large multi-tenant or government-grade platform between **₹60,00,000 and ₹2,50,00,000+**. The rest of this article explains how to find *your* number inside those ranges.
|
||||
|
||||
## What "Custom ERP" Actually Means for Pricing
|
||||
|
||||
Before any number makes sense, you have to define scope. "ERP" is an umbrella — your price depends entirely on which modules you actually build. A finance-and-inventory system for a trading firm is a completely different project from a 14-module university ERP with admissions, examinations, hostel, and payroll.
|
||||
|
||||
Custom ERP cost in India is driven by five things, in roughly this order of impact:
|
||||
|
||||
1. **Number and complexity of modules** — each module is a mini-application with its own data model, screens, and rules.
|
||||
2. **Number of distinct user roles and permission rules** — RBAC complexity scales non-linearly.
|
||||
3. **Integrations** — payment gateways, GST/e-invoicing, SMS/WhatsApp, biometric, Tally, government APIs.
|
||||
4. **Deployment model** — single-tenant vs multi-tenant, cloud vs on-premise, offline resilience.
|
||||
5. **Non-functional requirements** — audit logging, data residency, uptime SLA, concurrent-user load.
|
||||
|
||||
## 2026 Cost Ranges by Project Tier
|
||||
|
||||
These are realistic, all-in development ranges for an India-based engagement (Indian team rates, INR), covering design, development, testing, and first deployment. They exclude ongoing maintenance, which we cover separately below.
|
||||
|
||||
| Tier | Typical Use Case | Modules | Timeline | Cost Range (INR) |
|
||||
|------|------------------|---------|----------|------------------|
|
||||
| **Starter** | Single-department tool (inventory, billing, or HR) | 1–2 | 6–10 weeks | ₹6L – ₹18L |
|
||||
| **Growth** | Mid-market multi-department ERP | 4–7 | 4–7 months | ₹18L – ₹60L |
|
||||
| **Enterprise** | Multi-entity / multi-location ERP, complex RBAC | 8–14 | 8–14 months | ₹60L – ₹1.5Cr |
|
||||
| **Government / Platform** | Multi-tenant, offline-resilient, audit-heavy | 10+ | 12–24 months | ₹1.5Cr – ₹2.5Cr+ |
|
||||
|
||||
A useful rule of thumb in 2026: a well-defined, production-grade ERP module costs roughly **₹4,00,000 to ₹9,00,000** to build, depending on how many screens, workflows, and integrations it carries.
|
||||
|
||||
## The Biggest Cost Driver: The Team
|
||||
|
||||
Custom software is labour. The single largest line item in any ERP quote is engineering time, so blended day-rates dominate the total. Here is what Indian ERP development talent typically costs in 2026.
|
||||
|
||||
| Role | Junior (₹/month) | Mid (₹/month) | Senior (₹/month) |
|
||||
|------|------------------|---------------|------------------|
|
||||
| Backend Engineer | ₹45,000 – ₹70,000 | ₹80,000 – ₹1,40,000 | ₹1,60,000 – ₹2,80,000 |
|
||||
| Frontend Engineer | ₹40,000 – ₹65,000 | ₹75,000 – ₹1,30,000 | ₹1,50,000 – ₹2,50,000 |
|
||||
| QA / Test Engineer | ₹35,000 – ₹55,000 | ₹60,000 – ₹1,00,000 | ₹1,10,000 – ₹1,80,000 |
|
||||
| Project Manager | — | ₹90,000 – ₹1,50,000 | ₹1,60,000 – ₹3,00,000 |
|
||||
| UI/UX Designer | ₹40,000 – ₹60,000 | ₹70,000 – ₹1,20,000 | ₹1,30,000 – ₹2,20,000 |
|
||||
|
||||
A typical Growth-tier project runs a squad of 4–6 people for 5 months. At blended mid-level rates, that alone accounts for ₹25L–₹40L of the budget — which is why the Growth range sits where it does.
|
||||
|
||||
## Build vs Buy: When Custom Actually Pays Off
|
||||
|
||||
Custom is not always the right call. Off-the-shelf and SaaS ERPs (Zoho, TallyPrime, SAP Business One, Odoo) have real advantages for standard workflows. Here is the honest comparison for an Indian mid-market buyer.
|
||||
|
||||
| Factor | Custom ERP | SaaS / Off-the-Shelf |
|
||||
|--------|-----------|----------------------|
|
||||
| Upfront cost | ₹18L – ₹60L+ | ₹0 – ₹5L setup |
|
||||
| Recurring cost | ~15–20% of build/year (AMC) | ₹500 – ₹3,000 per user / month |
|
||||
| Fit to your process | Exact | Adapt your process to the tool |
|
||||
| Time to first value | 3–7 months | Days to weeks |
|
||||
| Data ownership | Full | Vendor-hosted |
|
||||
| Scaling cost | Predictable (you own it) | Grows with every seat |
|
||||
|
||||
The crossover maths matters. A 100-user SaaS ERP at ₹1,500/user/month costs **₹18,00,000 per year** — recurring, forever. A custom build at ₹40,00,000 with ₹7,00,000/year AMC breaks even against that SaaS bill in roughly **2.5 years**, and is cheaper every year after. Custom wins when your headcount is large, your processes are genuinely non-standard, or data sovereignty is non-negotiable (common in government and BFSI).
|
||||
|
||||
## Hidden Costs Buyers Forget to Budget
|
||||
|
||||
The build quote is not the total cost of ownership. Plan for these:
|
||||
|
||||
- **Annual Maintenance Contract (AMC):** budget **15–20% of build cost per year** for bug fixes, security patches, and minor enhancements.
|
||||
- **Cloud hosting:** a mid-size ERP on AWS/Azure in the Mumbai region typically costs **₹15,000 – ₹60,000/month** depending on load and redundancy.
|
||||
- **Third-party APIs:** SMS (₹0.12–₹0.25/message), WhatsApp Business, payment gateway (~2% per transaction), GST e-invoicing add-ons.
|
||||
- **Data migration:** cleaning and importing legacy data is often underestimated — budget **₹1,50,000 – ₹6,00,000**.
|
||||
- **Training and change management:** for 50+ users, this is a real line item, not an afterthought.
|
||||
- **Post-launch enhancements:** the first three months after go-live always surface new requirements.
|
||||
|
||||
## A Simple Way to Estimate Your Own Cost
|
||||
|
||||
You can get within ±25% of a real quote with this back-of-envelope method:
|
||||
|
||||
1. **List your modules.** Count them honestly.
|
||||
2. **Multiply by ₹4L–₹9L per module** depending on complexity (simple CRUD = lower; workflow + integrations = higher).
|
||||
3. **Add 20% for integrations** if you need GST, payments, or government APIs.
|
||||
4. **Add 15% for multi-tenant or offline-resilient** architecture.
|
||||
5. **Add the first year of AMC** (15–20%) for a true year-one budget.
|
||||
|
||||
Example: a 6-module mid-market ERP with payment + GST integration → (6 × ₹6L) = ₹36L, +20% integrations = ₹43.2L, +18% AMC = roughly **₹51L for year one**. That lands squarely in the Growth tier, which is the reality check you want before any vendor conversation.
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
**What is the minimum realistic budget for a custom ERP in India in 2026?**
|
||||
For a genuinely custom, production-grade single-department system, expect a floor of around **₹6,00,000**. Below that you are buying a configured off-the-shelf tool or a prototype, not a custom ERP.
|
||||
|
||||
**How long does a custom ERP take to build?**
|
||||
A Starter system ships in 6–10 weeks. A Growth-tier multi-module ERP takes 4–7 months. Enterprise and government platforms run 8–24 months. Timeline scales with module count and integration depth, not just budget.
|
||||
|
||||
**Is custom ERP cheaper than SaaS over time?**
|
||||
Often, yes — above roughly 75–100 users or 2.5 years of use. A custom build has a high upfront cost but a flat recurring cost (AMC), while SaaS charges per seat forever. The more you grow, the better custom looks.
|
||||
|
||||
**Why do ERP quotes vary so much between vendors?**
|
||||
Because "ERP" is undefined until you fix scope. A ₹15L and a ₹60L quote are usually answering two different questions about modules, roles, and integrations. Always compare on a fixed, written scope — never on the headline number alone.
|
||||
|
||||
**What ongoing costs should I plan for after launch?**
|
||||
Budget 15–20% of build cost per year for AMC, plus cloud hosting (₹15K–₹60K/month for mid-size systems), third-party API usage, and periodic enhancements. Total cost of ownership over five years is typically 1.7–2x the initial build.
|
||||
|
||||
**Should I pay fixed-price or time-and-materials?**
|
||||
Fixed-price works when scope is locked and well-documented. Time-and-materials fits evolving requirements and longer builds. For most mid-market ERPs, a hybrid — fixed-price per milestone with a change-request process — gives the best balance of predictability and flexibility.
|
||||
|
||||
## Getting an Accurate Number for Your Project
|
||||
|
||||
The ranges above will get your budget into the right order of magnitude, but every project has specifics — a single complex integration or a strict uptime SLA can move the number significantly. At WorkRoot we have delivered ERP and management systems across Indian government and enterprise clients, and we are happy to turn your requirements into a fixed, written estimate. [Tell us about your project](/contact) and we will give you a real number, not a vague "it depends."
|
||||
@@ -5,6 +5,13 @@ const blog = defineCollection({
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
// Optional SEO overrides. When set they drive the <title> tag and
|
||||
// <meta name="description"> without changing the on-page article
|
||||
// heading/excerpt, letting us keep descriptive H1s while tuning the
|
||||
// search snippet to the 50–60 / 150–160 character sweet spots.
|
||||
// `seoTitle` is the text BEFORE the " | WorkRoot" brand suffix.
|
||||
seoTitle: z.string().optional(),
|
||||
seoDescription: z.string().optional(),
|
||||
pubDate: z.coerce.date(),
|
||||
updatedDate: z.coerce.date().optional(),
|
||||
heroImage: z.string().optional(),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import Header from '../components/Header.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
import SEO from '../components/SEO.astro';
|
||||
import Analytics from '../components/Analytics.astro';
|
||||
import PWAInstallPrompt from '../components/PWAInstallPrompt.astro';
|
||||
import WhatsAppFab from '../components/WhatsAppFab.astro';
|
||||
@@ -26,8 +27,15 @@ const {
|
||||
noIndex = false,
|
||||
} = Astro.props;
|
||||
|
||||
// Full brand name — used for og:site_name and structured data.
|
||||
const siteName = 'WorkRoot IT Solutions LLP';
|
||||
const fullTitle = title === 'Home' ? siteName : `${title} | ${siteName}`;
|
||||
// Short brand suffix for the <title> tag so titles stay within the
|
||||
// 50–60 character SEO sweet spot (the full LLP name eats 25+ chars).
|
||||
const brand = 'WorkRoot';
|
||||
// The homepage gets a dedicated, query-focused title instead of the bare
|
||||
// brand name. Pages pass `title="Home"` to opt into it.
|
||||
const homeTitle = 'Custom Software & Government IT Development | WorkRoot';
|
||||
const fullTitle = title === 'Home' ? homeTitle : `${title} | ${brand}`;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
@@ -48,7 +56,9 @@ const fullTitle = title === 'Home' ? siteName : `${title} | ${siteName}`;
|
||||
<meta name="rating" content="general" />
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||
<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 -->
|
||||
@@ -101,111 +111,14 @@ const fullTitle = title === 'Home' ? siteName : `${title} | ${siteName}`;
|
||||
<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"
|
||||
})} />
|
||||
<!-- JSON-LD Structured Data — site-wide base schema (Organization +
|
||||
LocalBusiness and WebSite). Centralised in SEO.astro and emitted here
|
||||
in `base` mode so it appears exactly once on EVERY route, including
|
||||
pages that don't add their own page-specific <SEO> (404, privacy,
|
||||
terms, cookies, offline). Page-specific schema (Service, FAQ,
|
||||
BlogPosting, breadcrumbs, …) is opt-in via the slotted <SEO> each
|
||||
page renders into the head. -->
|
||||
<SEO base />
|
||||
|
||||
<!-- Preconnect to Google Fonts (must come before font stylesheet) -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
@@ -268,6 +181,17 @@ const fullTitle = title === 'Home' ? siteName : `${title} | ${siteName}`;
|
||||
<!-- Analytics (GA4 or Plausible) -->
|
||||
<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 -->
|
||||
<slot name="head" />
|
||||
</head>
|
||||
|
||||
@@ -61,6 +61,18 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
||||
const method = context.request.method;
|
||||
const path = context.url.pathname;
|
||||
|
||||
// Normalize paths corrupted by a trailing stray ")" before routing.
|
||||
// These come from monitors/links that captured a URL together with a
|
||||
// closing parenthesis — e.g. "https://workroot.in/)" copied out of the
|
||||
// markdown/shell form "(https://workroot.in/)". Without this, every such
|
||||
// probe hits a non-existent route and 404s on a loop (recurring "GET /)").
|
||||
// Stripping the trailing paren(s) lets the request resolve to the intended
|
||||
// route (e.g. "/)" -> "/"), so the monitor gets a 301 -> 200 instead.
|
||||
const normalizedPath = path.replace(/\)+$/, '');
|
||||
if (normalizedPath !== path && normalizedPath.length > 0) {
|
||||
return context.redirect(`${normalizedPath}${context.url.search}`, 301);
|
||||
}
|
||||
|
||||
// CSRF origin validation for API mutations
|
||||
if (!validateCsrfOrigin(context.request, context.url)) {
|
||||
logger.warn('middleware', 'CSRF origin check failed', {
|
||||
@@ -106,7 +118,26 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
||||
if (status >= 500) {
|
||||
logger.error('middleware', `SSR error ${status}`, { method, path, status, durationMs });
|
||||
} else if (status === 404) {
|
||||
logger.warn('middleware', `Not found`, { method, path, status, durationMs });
|
||||
// 404s are dominated by bots/scanners probing malformed or garbage paths
|
||||
// (e.g. "/)"), which would otherwise flood warn-level logs and trip alerts.
|
||||
// Treat a 404 as a warning only when it likely originated from our own site
|
||||
// (same-origin referer) — that indicates a real broken internal link worth
|
||||
// fixing. Everything else (no referer / external referer) is logged at info.
|
||||
const referer = context.request.headers.get('referer');
|
||||
let internalNavigation = false;
|
||||
if (referer) {
|
||||
try {
|
||||
internalNavigation = new URL(referer).hostname === context.url.hostname;
|
||||
} catch {
|
||||
// Malformed referer header → treat as external/untrusted
|
||||
}
|
||||
}
|
||||
const notFoundMeta = { method, path, status, durationMs, referer: referer ?? undefined };
|
||||
if (internalNavigation) {
|
||||
logger.warn('middleware', `Not found (broken internal link)`, notFoundMeta);
|
||||
} else {
|
||||
logger.info('middleware', `Not found`, notFoundMeta);
|
||||
}
|
||||
} else if (status >= 400) {
|
||||
logger.warn('middleware', `Client error ${status}`, { method, path, status, durationMs });
|
||||
} else {
|
||||
@@ -124,11 +155,13 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
||||
// - inline scripts/styles: Required for Astro framework
|
||||
// - https://www.googletagmanager.com & https://www.google-analytics.com: GA4
|
||||
// - 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)
|
||||
const isProduction = import.meta.env.PROD;
|
||||
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' 'unsafe-eval' 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 https://api.wrnexus.com";
|
||||
|
||||
const csp = [
|
||||
"default-src 'self'",
|
||||
@@ -136,11 +169,11 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
||||
"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",
|
||||
"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'",
|
||||
"manifest-src 'self'",
|
||||
"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'",
|
||||
"base-uri 'self'",
|
||||
"form-action 'self'",
|
||||
|
||||
@@ -6,8 +6,8 @@ export const prerender = false;
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="Page Not Found"
|
||||
description="The page you're looking for doesn't exist. Return to WorkRoot IT Solutions homepage."
|
||||
title="Page Not Found — Let's Get You Back on Track"
|
||||
description="The page you're looking for doesn't exist or has moved. Head back to the WorkRoot IT Solutions homepage, or explore our services, portfolio, and blog instead."
|
||||
noIndex={true}
|
||||
>
|
||||
<section class="min-h-[70vh] flex items-center justify-center py-20 px-4">
|
||||
|
||||
@@ -49,7 +49,7 @@ const teamMembers = [
|
||||
// stats counters) so the numbers never contradict across pages.
|
||||
const stats = [
|
||||
{ value: '20+', label: 'Projects' },
|
||||
{ value: '11', label: 'Districts Deployed' },
|
||||
{ value: '11+', label: 'Districts Deployed' },
|
||||
{ value: '50,000+', label: 'Records Managed' },
|
||||
{ value: '8+', label: 'Years Avg Experience' },
|
||||
];
|
||||
@@ -114,8 +114,8 @@ const howWeWork = [
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="About Us — Our Story & Team"
|
||||
description="WorkRoot IT Solutions LLP: founded 2022, senior in-house engineering team with 8+ years average experience, 20+ projects delivered. Meet the technologists behind your digital transformation. Innovation, integrity, excellence."
|
||||
title="About Our Software Engineering Team & Story"
|
||||
description="Meet WorkRoot IT Solutions: founded 2022, a senior in-house engineering team averaging 8+ years' experience across 20+ web, mobile, ERP, and govtech projects."
|
||||
>
|
||||
<SEO
|
||||
slot="head"
|
||||
|
||||
@@ -32,21 +32,27 @@ function formatDate(date: Date): string {
|
||||
}
|
||||
|
||||
const readingTime = getReadingTime(post.body);
|
||||
// SEO meta: prefer per-post overrides so the <title>/description land in the
|
||||
// 50–60 / 150–160 character ranges, while the on-page heading below keeps the
|
||||
// full, descriptive post title. `seoTitle` gets the " | WorkRoot" suffix from
|
||||
// BaseLayout, so it should be the text before that suffix.
|
||||
const metaTitle = post.data.seoTitle ?? post.data.title;
|
||||
const metaDescription = post.data.seoDescription ?? post.data.description;
|
||||
const shareUrl = encodeURIComponent(Astro.url.href);
|
||||
const shareTitle = encodeURIComponent(post.data.title);
|
||||
const shareDescription = encodeURIComponent(post.data.description);
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title={post.data.title}
|
||||
description={post.data.description}
|
||||
title={metaTitle}
|
||||
description={metaDescription}
|
||||
ogImage={post.data.heroImage}
|
||||
>
|
||||
<SEO
|
||||
slot="head"
|
||||
type="BlogPosting"
|
||||
title={post.data.title}
|
||||
description={post.data.description}
|
||||
title={metaTitle}
|
||||
description={metaDescription}
|
||||
breadcrumbs={[
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Blog', url: '/blog' },
|
||||
|
||||
@@ -24,8 +24,8 @@ function formatDate(date: Date): string {
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="Blog"
|
||||
description="Insights and articles on web development, AI, cloud computing, and digital transformation from the WorkRoot team."
|
||||
title="Software Development & Technology Insights"
|
||||
description="Insights on web development, ERP, cloud migration, AI, and government IT from the WorkRoot engineering team — field notes from real, large-scale deployments."
|
||||
>
|
||||
<SEO
|
||||
slot="head"
|
||||
|
||||
@@ -80,11 +80,28 @@ const trustStats = [
|
||||
{ value: '< 24h', label: 'Response Time' },
|
||||
{ value: '3+', label: 'Years Experience' },
|
||||
];
|
||||
|
||||
// Service options — rendered as large, tappable radio tiles (replacing the
|
||||
// old native <select>) so the choice is thumb-friendly on mobile. `value`
|
||||
// is the code submitted to /api/contact (unchanged from the previous
|
||||
// <option> values); `label` is the display text.
|
||||
const serviceOptions = [
|
||||
{ value: 'erp-solutions', label: 'ERP / Enterprise Systems' },
|
||||
{ value: 'government-systems', label: 'Government IT' },
|
||||
{ value: 'web-development', label: 'Web Development' },
|
||||
{ value: 'mobile-development', label: 'Mobile Development' },
|
||||
{ value: 'ui-ux', label: 'UI/UX Design' },
|
||||
{ value: 'consulting', label: 'IT Consulting / DevOps' },
|
||||
{ value: 'other', label: 'Other / General Inquiry' },
|
||||
];
|
||||
|
||||
// Budget options — also rendered as large tappable tiles.
|
||||
const budgetOptions = ['< ₹1L', '₹1L – ₹5L', '₹5L – ₹15L', '₹15L+', 'Not sure yet'];
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="Contact Us — Get a Free Quote"
|
||||
description="Start your project with WorkRoot IT Solutions LLP. 20+ projects delivered, 100% client satisfaction, response within 24 hours. Reach us at admin@workroot.in or call +91 9561417403."
|
||||
title="Contact Us — Get a Free Project Quote Today"
|
||||
description="Start your web, mobile, ERP, or government IT project with WorkRoot. 20+ projects delivered, replies within 1 business day. Email admin@workroot.in to begin."
|
||||
>
|
||||
<SEO
|
||||
slot="head"
|
||||
@@ -273,107 +290,84 @@ const trustStats = [
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Phone + Subject row -->
|
||||
<div class="grid sm:grid-cols-2 gap-5">
|
||||
<!-- Phone Field -->
|
||||
<div class="form-group">
|
||||
<label for="phone" class="block text-sm font-semibold text-secondary-700 dark:text-secondary-300 mb-1.5">
|
||||
Phone Number
|
||||
<span class="text-secondary-400 font-normal">(optional)</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none" aria-hidden="true">
|
||||
<svg class="w-4 h-4 text-secondary-400 field-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone"
|
||||
name="phone"
|
||||
autocomplete="tel"
|
||||
aria-describedby="phone-error"
|
||||
class="form-input w-full pl-10 pr-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all duration-200 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-sm bg-secondary-50 dark:bg-secondary-700 focus:bg-white dark:focus:bg-secondary-700"
|
||||
placeholder="+1 (555) 123-4567"
|
||||
/>
|
||||
<div class="valid-check absolute inset-y-0 right-3 flex items-center hidden" aria-label="Valid">
|
||||
<svg class="w-4 h-4 text-green-700 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="invalid-check absolute inset-y-0 right-3 flex items-center hidden" aria-label="Invalid">
|
||||
<svg class="w-4 h-4 text-red-800 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</div>
|
||||
<!-- Phone Field (full width) -->
|
||||
<div class="form-group">
|
||||
<label for="phone" class="block text-sm font-semibold text-secondary-700 dark:text-secondary-300 mb-1.5">
|
||||
Phone Number
|
||||
<span class="text-secondary-400 font-normal">(optional)</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none" aria-hidden="true">
|
||||
<svg class="w-4 h-4 text-secondary-400 field-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="mt-1.5 text-xs text-red-500 hidden flex items-center gap-1" id="phone-error" data-error="phone" role="alert">
|
||||
<svg class="w-3.5 h-3.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>
|
||||
Please enter a valid phone number.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Subject Field -->
|
||||
<div class="form-group">
|
||||
<label for="subject" class="block text-sm font-semibold text-secondary-700 dark:text-secondary-300 mb-1.5">
|
||||
Service Needed <span class="text-red-500" aria-hidden="true">*</span>
|
||||
<span class="sr-only">(required)</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none" aria-hidden="true">
|
||||
<svg class="w-4 h-4 text-secondary-400 field-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
||||
</svg>
|
||||
</div>
|
||||
<select
|
||||
id="subject"
|
||||
name="subject"
|
||||
required
|
||||
aria-required="true"
|
||||
aria-describedby="subject-error"
|
||||
class="form-input w-full pl-10 pr-8 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all duration-200 text-secondary-900 dark:text-secondary-100 text-sm bg-secondary-50 dark:bg-secondary-700 focus:bg-white dark:focus:bg-secondary-700 appearance-none cursor-pointer"
|
||||
>
|
||||
<option value="">Select a service...</option>
|
||||
<option value="erp-solutions">ERP / Enterprise Systems</option>
|
||||
<option value="government-systems">Government IT</option>
|
||||
<option value="web-development">Web Development</option>
|
||||
<option value="mobile-development">Mobile Development</option>
|
||||
<option value="ui-ux">UI/UX Design</option>
|
||||
<option value="consulting">IT Consulting / DevOps</option>
|
||||
<option value="other">Other / General Inquiry</option>
|
||||
</select>
|
||||
<div class="valid-check absolute inset-y-0 right-9 flex items-center hidden" aria-label="Valid">
|
||||
<svg class="w-4 h-4 text-green-700 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="invalid-check absolute inset-y-0 right-9 flex items-center hidden" aria-label="Invalid">
|
||||
<svg class="w-4 h-4 text-red-800 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="absolute inset-y-0 right-3 flex items-center pointer-events-none" aria-hidden="true">
|
||||
<svg class="w-4 h-4 text-secondary-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone"
|
||||
name="phone"
|
||||
autocomplete="tel"
|
||||
aria-describedby="phone-error"
|
||||
class="form-input w-full pl-10 pr-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all duration-200 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-sm bg-secondary-50 dark:bg-secondary-700 focus:bg-white dark:focus:bg-secondary-700"
|
||||
placeholder="+1 (555) 123-4567"
|
||||
/>
|
||||
<div class="valid-check absolute inset-y-0 right-3 flex items-center hidden" aria-label="Valid">
|
||||
<svg class="w-4 h-4 text-green-700 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="invalid-check absolute inset-y-0 right-3 flex items-center hidden" aria-label="Invalid">
|
||||
<svg class="w-4 h-4 text-red-800 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="mt-1.5 text-xs text-red-500 hidden flex items-center gap-1" id="subject-error" data-error="subject" role="alert">
|
||||
<svg class="w-3.5 h-3.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>
|
||||
Please select a service.
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-1.5 text-xs text-red-500 hidden flex items-center gap-1" id="phone-error" data-error="phone" role="alert">
|
||||
<svg class="w-3.5 h-3.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>
|
||||
Please enter a valid phone number.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Budget range (new field) -->
|
||||
<!-- Service Needed — large tappable radio tiles (replaces the
|
||||
old native <select>). Renders 2 columns on mobile (each tile
|
||||
a comfortable thumb target) and 3 from sm up. The hidden
|
||||
radios share name="subject" so the form submission and
|
||||
analytics read the same value the <select> used to provide. -->
|
||||
<div class="form-group">
|
||||
<span id="subject-label" class="block text-sm font-semibold text-secondary-700 dark:text-secondary-300 mb-2">
|
||||
Service Needed <span class="text-red-500" aria-hidden="true">*</span>
|
||||
<span class="sr-only">(required)</span>
|
||||
</span>
|
||||
<div
|
||||
id="subject-group"
|
||||
role="radiogroup"
|
||||
aria-labelledby="subject-label"
|
||||
aria-describedby="subject-error"
|
||||
class="grid grid-cols-2 sm:grid-cols-3 gap-2.5"
|
||||
>
|
||||
{serviceOptions.map((opt) => (
|
||||
<label class="subject-option group flex items-center justify-center text-center min-h-[52px] px-3 py-2.5 rounded-xl border border-secondary-200 dark:border-secondary-600 text-sm font-medium text-secondary-700 dark:text-secondary-300 cursor-pointer hover:border-primary hover:bg-primary/5 dark:hover:bg-primary/10 transition-all duration-150 has-[:checked]:border-primary has-[:checked]:bg-primary/10 has-[:checked]:text-primary-700 dark:has-[:checked]:text-primary-300 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-primary/40">
|
||||
<input type="radio" name="subject" value={opt.value} class="sr-only" aria-describedby="subject-error" />
|
||||
<span class="leading-snug">{opt.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<p class="mt-1.5 text-xs text-red-500 hidden flex items-center gap-1" id="subject-error" data-error="subject" role="alert">
|
||||
<svg class="w-3.5 h-3.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>
|
||||
Please select a service.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Project Budget — large tappable radio tiles. 2 cols on mobile
|
||||
so each is a full thumb target; auto-fit row from sm up. -->
|
||||
<div class="form-group">
|
||||
<label class="block text-sm font-semibold text-secondary-700 dark:text-secondary-300 mb-2">
|
||||
Project Budget
|
||||
<span class="text-secondary-400 dark:text-secondary-500 font-normal">(optional)</span>
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2" role="group" aria-label="Budget range options">
|
||||
{['< ₹1L', '₹1L – ₹5L', '₹5L – ₹15L', '₹15L+', 'Not sure yet'].map((range, i) => (
|
||||
<label class="budget-option flex items-center gap-1.5 px-3 py-2 rounded-lg border border-secondary-200 dark:border-secondary-600 text-sm text-secondary-600 dark:text-secondary-400 cursor-pointer hover:border-primary hover:bg-primary/5 dark:hover:bg-primary/10 transition-all duration-150 has-[:checked]:border-primary has-[:checked]:bg-primary/10 has-[:checked]:text-primary-700 dark:has-[:checked]:text-primary-300">
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-2.5" role="group" aria-label="Budget range options">
|
||||
{budgetOptions.map((range) => (
|
||||
<label class="budget-option flex items-center justify-center text-center min-h-[48px] px-3 py-2.5 rounded-xl border border-secondary-200 dark:border-secondary-600 text-sm font-medium text-secondary-600 dark:text-secondary-400 cursor-pointer hover:border-primary hover:bg-primary/5 dark:hover:bg-primary/10 transition-all duration-150 has-[:checked]:border-primary has-[:checked]:bg-primary/10 has-[:checked]:text-primary-700 dark:has-[:checked]:text-primary-300 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-primary/40">
|
||||
<input type="radio" name="budget" value={range} class="sr-only" />
|
||||
{range}
|
||||
</label>
|
||||
@@ -806,6 +800,13 @@ const trustStats = [
|
||||
color: #0e7490;
|
||||
}
|
||||
|
||||
/* Service tiles: invalid-group highlight (shown only after a failed submit
|
||||
attempt with no service chosen). Subtle red ring around the whole group. */
|
||||
.subject-group-invalid {
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 0 0 2px rgb(220 38 38 / 0.35);
|
||||
}
|
||||
|
||||
/* FAQ item */
|
||||
.faq-item summary::-webkit-details-marker { display: none; }
|
||||
.faq-item summary::marker { display: none; }
|
||||
@@ -909,7 +910,7 @@ const trustStats = [
|
||||
});
|
||||
}
|
||||
|
||||
// ── Budget toggle (radio-as-chip) ──────────────────────────
|
||||
// ── Budget toggle (radio-as-tile) ──────────────────────────
|
||||
const budgetOptions = document.querySelectorAll('.budget-option');
|
||||
budgetOptions.forEach((opt) => {
|
||||
const radio = opt.querySelector('input[type="radio"]') as HTMLInputElement;
|
||||
@@ -921,6 +922,31 @@ const trustStats = [
|
||||
}
|
||||
});
|
||||
|
||||
// ── Service tiles (radio group replacing the old native <select>) ──
|
||||
// The hidden radios share name="subject" so `form.elements.subject` resolves
|
||||
// to a RadioNodeList whose `.value` is the checked option — exactly what the
|
||||
// submit handler and analytics previously read from the <select>.
|
||||
const subjectErrorEl = document.querySelector('[data-error="subject"]');
|
||||
const subjectGroupEl = document.getElementById('subject-group');
|
||||
const subjectRadios = Array.from(
|
||||
document.querySelectorAll<HTMLInputElement>('input[name="subject"]')
|
||||
);
|
||||
|
||||
function getSubjectValue(): string {
|
||||
return subjectRadios.find((r) => r.checked)?.value ?? '';
|
||||
}
|
||||
|
||||
function validateSubject(touched = true): boolean {
|
||||
const valid = getSubjectValue() !== '';
|
||||
if (subjectErrorEl) subjectErrorEl.classList.toggle('hidden', valid || !touched);
|
||||
subjectGroupEl?.classList.toggle('subject-group-invalid', !valid && touched);
|
||||
return valid;
|
||||
}
|
||||
|
||||
subjectRadios.forEach((radio) => {
|
||||
radio.addEventListener('change', () => validateSubject());
|
||||
});
|
||||
|
||||
// ── Form Validation ────────────────────────────────────────
|
||||
const form = document.getElementById('contact-form') as HTMLFormElement;
|
||||
const successState = document.getElementById('success-state');
|
||||
@@ -1012,18 +1038,25 @@ const trustStats = [
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate all fields
|
||||
// Validate all fields. `subject` is now a radio group, so it is checked
|
||||
// separately from the text/textarea fields handled by validateField.
|
||||
let isValid = true;
|
||||
['name', 'email', 'phone', 'subject', 'message'].forEach((fieldName) => {
|
||||
['name', 'email', 'phone', 'message'].forEach((fieldName) => {
|
||||
const field = form.elements.namedItem(fieldName) as HTMLInputElement;
|
||||
if (field && !validateField(field)) isValid = false;
|
||||
});
|
||||
if (!validateSubject()) isValid = false;
|
||||
|
||||
if (!isValid) {
|
||||
showToast('Please fix the highlighted fields before submitting.', 'error');
|
||||
// Focus first invalid field
|
||||
// Focus the first invalid text field, or the service tiles if that's
|
||||
// the only thing missing.
|
||||
const firstInvalid = form.querySelector('.is-invalid') as HTMLElement | null;
|
||||
firstInvalid?.focus();
|
||||
if (firstInvalid) {
|
||||
firstInvalid.focus();
|
||||
} else if (getSubjectValue() === '') {
|
||||
subjectRadios[0]?.focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1057,7 +1090,7 @@ const trustStats = [
|
||||
name: (form.elements.namedItem('name') as HTMLInputElement)?.value.trim(),
|
||||
email: (form.elements.namedItem('email') as HTMLInputElement)?.value.trim(),
|
||||
phone: (form.elements.namedItem('phone') as HTMLInputElement)?.value.trim(),
|
||||
subject: (form.elements.namedItem('subject') as HTMLSelectElement)?.value,
|
||||
subject: getSubjectValue(),
|
||||
message: (form.elements.namedItem('message') as HTMLTextAreaElement)?.value.trim(),
|
||||
website: honeypot,
|
||||
}),
|
||||
@@ -1087,7 +1120,7 @@ const trustStats = [
|
||||
}
|
||||
|
||||
// Track analytics
|
||||
const subject = (form.elements.namedItem('subject') as HTMLSelectElement)?.value;
|
||||
const subject = getSubjectValue();
|
||||
trackFormSubmit('contact', success);
|
||||
if (success) trackEvent('contact_form_submit', { subject_category: subject });
|
||||
|
||||
@@ -1122,6 +1155,7 @@ const trustStats = [
|
||||
form.querySelectorAll('[data-error]').forEach((el) => el.classList.add('hidden'));
|
||||
if (charCount) charCount.textContent = '0 / 2000';
|
||||
budgetOptions.forEach((o) => o.classList.remove('selected'));
|
||||
subjectGroupEl?.classList.remove('subject-group-invalid');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ const lastUpdated = 'March 20, 2026';
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="Cookie Policy"
|
||||
description="WorkRoot IT Solutions Cookie Policy - Learn how we use cookies and similar tracking technologies on our website."
|
||||
title="Cookie Policy — How We Use Cookies & Tracking"
|
||||
description="Learn how WorkRoot IT Solutions uses cookies and similar tracking technologies, what each cookie does, and how to manage or disable them in your browser."
|
||||
noIndex={true}
|
||||
>
|
||||
<!-- Hero Section -->
|
||||
@@ -299,7 +299,7 @@ const lastUpdated = 'March 20, 2026';
|
||||
<div class="bg-secondary-900/50 border border-secondary-800 rounded-xl p-6">
|
||||
<p class="text-secondary-300"><strong class="text-white">WorkRoot IT Solutions LLP</strong></p>
|
||||
<p class="text-secondary-300">Email: admin@workroot.in</p>
|
||||
<p class="text-secondary-300">Phone: +91 9561417403</p>
|
||||
<p class="text-secondary-300">Phone: <a href="tel:+919561417403" class="text-primary-400 hover:underline">+91 9561417403</a></p>
|
||||
<p class="text-secondary-300">Address: At Post Rajapur Shantinagar, Taluka Khatav, Dist Satara, MH 415503</p>
|
||||
</div>
|
||||
<p class="text-secondary-300 mt-4">
|
||||
|
||||
@@ -19,6 +19,7 @@ const services = [
|
||||
outcome: 'Ship a fast, SEO-ready web product your customers actually convert on.',
|
||||
description: 'Angular, React, Next.js, Astro frontends backed by Spring Boot APIs — built for speed, scale, and search visibility.',
|
||||
features: ['Angular/React/Next.js', 'Spring Boot APIs', 'Astro Static Sites'],
|
||||
cta: 'Discuss your web project',
|
||||
},
|
||||
{
|
||||
icon: 'smartphone',
|
||||
@@ -27,6 +28,7 @@ const services = [
|
||||
outcome: 'Launch on iOS and Android from a single codebase — without the native-team overhead.',
|
||||
description: 'Cross-platform React Native and Expo apps with offline-first sync, push notifications, and store-ready builds.',
|
||||
features: ['React Native / Expo', 'iOS & Android', 'Offline-First Apps'],
|
||||
cta: 'Discuss your mobile app',
|
||||
},
|
||||
{
|
||||
icon: 'brain',
|
||||
@@ -35,14 +37,16 @@ const services = [
|
||||
outcome: 'Replace spreadsheets and ageing tools with one system your operations team trusts.',
|
||||
description: 'Custom ERP, CRM, and admin platforms designed around your real workflows — already running in universities and government bodies.',
|
||||
features: ['University Management', 'Police Management', 'Crematorium SCMS'],
|
||||
cta: 'Discuss your ERP project',
|
||||
},
|
||||
{
|
||||
icon: 'cloud',
|
||||
slug: 'government-systems',
|
||||
title: 'Government IT Solutions',
|
||||
outcome: 'Roll out secure, compliant platforms across districts without rewriting them for each one.',
|
||||
description: 'Multi-district deployments with role-based access, audit trails, and the uptime public services require.',
|
||||
features: ['Multi-District Deployments', 'Secure & Compliant', 'High Availability'],
|
||||
description: 'GeM-empanelled and already live across 11+ districts at 99.9% uptime — role-based access, audit trails, and the security public services demand.',
|
||||
features: ['Live in 11+ Districts', 'GeM-Empanelled & Compliant', '99.9% Uptime'],
|
||||
cta: 'Discuss your government project',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -120,7 +124,7 @@ const testimonials = [
|
||||
];
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
@@ -167,11 +171,42 @@ const techStack = [
|
||||
// "Common Questions" block that used to live above the tech-stack section
|
||||
// was removed; its unique questions were merged into HomepageFaq's
|
||||
// `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 vendor’s 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
|
||||
title="Home"
|
||||
description="WorkRoot IT Solutions LLP builds production-grade web, mobile, ERP, and government IT systems for enterprises and growing teams. Free consultation, reply within 1 business day."
|
||||
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
|
||||
slot="head"
|
||||
@@ -179,7 +214,7 @@ const techStack = [
|
||||
/>
|
||||
|
||||
<!-- Hero Section - Enhanced -->
|
||||
<section class="relative min-h-screen flex items-center overflow-hidden bg-gradient-to-br from-secondary-900 via-secondary-800 to-primary-900">
|
||||
<section class="relative lg:min-h-[85vh] flex items-center overflow-hidden bg-gradient-to-br from-secondary-900 via-secondary-800 to-primary-900">
|
||||
<!-- Background decorative elements -->
|
||||
<div class="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
|
||||
<div class="absolute -top-40 -right-40 w-[600px] h-[600px] bg-primary/20 rounded-full blur-3xl animate-pulse-slow"></div>
|
||||
@@ -191,32 +226,38 @@ const techStack = [
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-secondary-900/50"></div>
|
||||
</div>
|
||||
|
||||
<div class="container-wrapper relative z-10 py-24">
|
||||
<div class="max-w-5xl mx-auto text-center">
|
||||
<!-- Full-bleed hero content: the <section> gradient already spans the full
|
||||
viewport; this wrapper stretches edge-to-edge (no max-width cap) with
|
||||
responsive horizontal padding so the copy + lead form stay padded and
|
||||
readable from 360px up to ultra-wide displays. -->
|
||||
<div class="w-full px-4 sm:px-6 lg:px-8 xl:px-12 relative z-10 py-14 sm:py-20 lg:py-32">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-center">
|
||||
<!-- Left column — copy + CTAs -->
|
||||
<div class="text-center lg:text-left">
|
||||
<!-- Trust badge -->
|
||||
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-white/10 backdrop-blur-sm border border-white/20 mb-8 opacity-0 animate-slide-up">
|
||||
<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"/>
|
||||
</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>
|
||||
|
||||
<!-- Main heading — benefit-led: outcome + audience -->
|
||||
<h1 class="text-5xl sm:text-6xl lg:text-7xl xl:text-8xl font-bold text-white leading-[1.1] tracking-tight">
|
||||
<h1 class="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold text-white leading-[1.1] tracking-tight">
|
||||
<span class="block opacity-0 animate-slide-up" style="animation-delay: 0.2s;">Custom Software That</span>
|
||||
<span class="block opacity-0 animate-slide-up" style="animation-delay: 0.4s;">Powers <span class="text-transparent bg-clip-text bg-gradient-to-r from-primary-400 via-primary-300 to-accent-400">Enterprises & Governments</span></span>
|
||||
</h1>
|
||||
|
||||
<p class="mt-8 text-xl sm:text-2xl text-secondary-200 max-w-3xl mx-auto opacity-0 animate-slide-up leading-relaxed" style="animation-delay: 0.6s;">
|
||||
<p class="mt-6 text-lg sm:text-xl text-secondary-200 max-w-xl mx-auto lg:mx-0 opacity-0 animate-slide-up leading-relaxed" style="animation-delay: 0.6s;">
|
||||
We design, build, and run production-grade web, mobile, and ERP systems for startups, enterprises, and government bodies.
|
||||
</p>
|
||||
|
||||
<!-- Dual CTA Buttons — action verb + clear secondary -->
|
||||
<div class="mt-12 flex flex-wrap justify-center gap-4 opacity-0 animate-slide-up" style="animation-delay: 0.8s;">
|
||||
<div class="mt-8 sm:mt-10 flex flex-col sm:flex-row sm:flex-wrap justify-center lg:justify-start gap-3 sm:gap-4 opacity-0 animate-slide-up" style="animation-delay: 0.8s;">
|
||||
<a
|
||||
href="/contact?source=hero"
|
||||
data-cta="hero-primary"
|
||||
class="group inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-primary-500 to-primary-600 px-10 py-5 text-lg font-bold text-white transition-all hover:shadow-2xl hover:shadow-primary/50 hover:scale-105 focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-secondary-900"
|
||||
class="group inline-flex w-full sm:w-auto items-center justify-center rounded-xl bg-gradient-to-r from-primary-500 to-primary-600 px-8 sm:px-10 py-4 sm:py-5 text-lg font-bold text-white transition-all hover:shadow-2xl hover:shadow-primary/50 sm:hover:scale-105 focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-secondary-900"
|
||||
>
|
||||
Get my free proposal
|
||||
<svg class="ml-2 w-5 h-5 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
@@ -226,7 +267,7 @@ const techStack = [
|
||||
<a
|
||||
href="/portfolio"
|
||||
data-cta="hero-secondary"
|
||||
class="group inline-flex items-center justify-center rounded-xl border-2 border-white/30 bg-white/5 backdrop-blur-sm px-10 py-5 text-lg font-bold text-white transition-all hover:bg-white/10 hover:border-white/50 focus:outline-none focus:ring-2 focus:ring-white/50 focus:ring-offset-2 focus:ring-offset-secondary-900"
|
||||
class="group inline-flex w-full sm:w-auto items-center justify-center rounded-xl border-2 border-white/30 bg-white/5 backdrop-blur-sm px-8 sm:px-10 py-4 sm:py-5 text-lg font-bold text-white transition-all hover:bg-white/10 hover:border-white/50 focus:outline-none focus:ring-2 focus:ring-white/50 focus:ring-offset-2 focus:ring-offset-secondary-900"
|
||||
>
|
||||
<svg class="mr-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
@@ -236,8 +277,48 @@ const techStack = [
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Named-client credibility line — concrete, attributable social
|
||||
proof sitting directly under the primary CTAs so a visitor sees
|
||||
WHO we've delivered for above the fold, not just generic trust
|
||||
adjectives. Names mirror the client roster in the TrustStrip logo
|
||||
bar immediately below the hero (see TrustStrip.astro). -->
|
||||
<p
|
||||
class="mt-6 flex flex-wrap items-center justify-center lg:justify-start gap-x-2 gap-y-1 text-sm sm:text-base text-secondary-200 opacity-0 animate-slide-up"
|
||||
style="animation-delay: 0.85s;"
|
||||
aria-label="Selected clients"
|
||||
>
|
||||
<svg class="w-4 h-4 flex-shrink-0 text-accent-400" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 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>
|
||||
<span>
|
||||
Trusted by
|
||||
<span class="font-semibold text-white">IIT Patna</span>,
|
||||
<span class="font-semibold text-white">PLNR</span>,
|
||||
<span class="font-semibold text-white">RENTEC</span> &
|
||||
<span class="font-semibold text-white">MOC Soft</span>
|
||||
<span class="text-secondary-300">— 20+ systems delivered across enterprise & government teams.</span>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<!-- Tertiary click-to-call — one-tap dial for visitors who'd rather
|
||||
talk now. Min 48px tap target per WCAG 2.5.5 / Apple HIG. -->
|
||||
<p class="mt-6 text-base sm:text-lg text-secondary-200 opacity-0 animate-slide-up flex flex-wrap items-center justify-center lg:justify-start gap-x-2 gap-y-1" style="animation-delay: 0.9s;">
|
||||
<span>Prefer to talk now?</span>
|
||||
<a
|
||||
href="tel:+919561417403"
|
||||
data-cta="hero-call"
|
||||
aria-label="Call WorkRoot at +91 95614 17403"
|
||||
class="inline-flex min-h-[48px] items-center gap-2 rounded-lg px-2 font-semibold text-white underline decoration-primary-400/60 underline-offset-4 transition-colors hover:text-primary-200 hover:decoration-primary-300 focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-secondary-900"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
|
||||
</svg>
|
||||
+91 95614 17403
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<!-- Micro-trust line under CTAs -->
|
||||
<p class="mt-6 text-sm sm:text-base text-secondary-300 opacity-0 animate-slide-up flex flex-wrap items-center justify-center gap-x-3 gap-y-2" style="animation-delay: 0.9s;" aria-label="Trust signals">
|
||||
<p class="mt-6 text-sm sm:text-base text-secondary-300 opacity-0 animate-slide-up flex flex-wrap items-center justify-center lg:justify-start gap-x-3 gap-y-2" style="animation-delay: 0.95s;" aria-label="Trust signals">
|
||||
<span class="inline-flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-accent-400" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 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"/>
|
||||
@@ -249,14 +330,16 @@ const techStack = [
|
||||
<span class="hidden sm:inline text-secondary-500" aria-hidden="true">·</span>
|
||||
<span>No commitment</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- /Left column -->
|
||||
|
||||
<!-- Product preview — framed dashboard mock so the hero leads with a
|
||||
<!-- Right column — framed dashboard mock so the hero leads with a
|
||||
concrete product visual, not text alone. The image is a polished
|
||||
placeholder UI (public/images/product-dashboard.svg); swap in a
|
||||
real product screenshot or screen-recording poster frame
|
||||
(≥1200×720, .webp/.png) at the same path — no template change
|
||||
needed. Explicit width/height keep CLS at zero while it loads. -->
|
||||
<figure class="mt-16 sm:mt-20 max-w-4xl mx-auto opacity-0 animate-slide-up" style="animation-delay: 1s;">
|
||||
<figure class="mt-4 lg:mt-0 w-full max-w-xl mx-auto lg:mx-0 lg:ml-auto opacity-0 animate-slide-up" style="animation-delay: 1s;">
|
||||
<div class="relative rounded-2xl border border-white/15 bg-white/5 backdrop-blur-sm shadow-2xl shadow-primary/20 overflow-hidden">
|
||||
<!-- Glow behind the frame -->
|
||||
<div class="absolute -inset-px rounded-2xl bg-gradient-to-tr from-primary-500/10 via-transparent to-accent-400/10 pointer-events-none" aria-hidden="true"></div>
|
||||
@@ -368,17 +451,31 @@ const techStack = [
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<!-- Learn more arrow -->
|
||||
<a
|
||||
href={`/services#${service.slug}`}
|
||||
class="mt-6 inline-flex items-center text-primary-600 dark:text-primary-400 font-semibold group-hover:text-primary-700 dark:group-hover:text-primary-300 transition-colors focus:outline-none focus:underline"
|
||||
aria-label={`Learn more about ${service.title}`}
|
||||
>
|
||||
<span>Learn more</span>
|
||||
<svg class="w-5 h-5 ml-2 group-hover:translate-x-2 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<!-- Per-service CTAs — a primary conversion link that routes
|
||||
straight to the contact/quote form (tagged per service so
|
||||
the lead source is attributable), plus a secondary
|
||||
"Learn more" link into the matching /services detail
|
||||
section for visitors who want depth before they reach out. -->
|
||||
<div class="mt-7 flex flex-wrap items-center gap-x-6 gap-y-3">
|
||||
<a
|
||||
href={`/contact?source=service-${service.slug}`}
|
||||
data-cta={`service-${service.slug}`}
|
||||
class="inline-flex items-center rounded-xl bg-primary-50 dark:bg-primary-900/30 px-5 py-3 text-base font-bold text-primary-700 dark:text-primary-300 transition-all hover:bg-primary-100 dark:hover:bg-primary-900/50 hover:scale-105 focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-white dark:focus:ring-offset-secondary-800"
|
||||
aria-label={`${service.cta} — get a free quote`}
|
||||
>
|
||||
<span>{service.cta}</span>
|
||||
<svg class="w-5 h-5 ml-2 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href={`/services#${service.slug}`}
|
||||
class="inline-flex items-center text-secondary-500 dark:text-secondary-400 font-semibold hover:text-primary-600 dark:hover:text-primary-300 transition-colors focus:outline-none focus:underline"
|
||||
aria-label={`Learn more about ${service.title}`}
|
||||
>
|
||||
<span>Learn more</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Number badge -->
|
||||
@@ -400,6 +497,52 @@ const techStack = [
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Mid-page Conversion CTA — sits directly after the Services section so
|
||||
mobile visitors hit the funnel early instead of having to scroll past
|
||||
How-It-Works, testimonials, the tech stack and the FAQ to reach the
|
||||
inline lead form. Primary action jumps straight to the on-page inline
|
||||
lead form (#inline-lead-form, smooth-scrolled by the anchor handler at
|
||||
the bottom of this page); secondary action is a one-tap phone call. -->
|
||||
<section
|
||||
aria-labelledby="midpage-cta-heading"
|
||||
class="bg-gradient-to-r from-primary-600 to-primary-700 dark:from-primary-700 dark:to-primary-800"
|
||||
>
|
||||
<div class="container-wrapper py-10 sm:py-12">
|
||||
<div class="flex flex-col items-center gap-5 text-center md:flex-row md:justify-between md:text-left md:gap-8 max-w-5xl mx-auto">
|
||||
<div class="md:flex-1">
|
||||
<h2 id="midpage-cta-heading" class="text-2xl sm:text-3xl font-bold text-white leading-tight">
|
||||
Have a project in mind? Get a free proposal in 24 hours.
|
||||
</h2>
|
||||
<p class="mt-2 text-primary-100 text-sm sm:text-base">
|
||||
No obligation · Free 30-min discovery call · NDA on request.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex w-full flex-col sm:flex-row md:w-auto md:flex-shrink-0 gap-3">
|
||||
<a
|
||||
href="#inline-lead-form"
|
||||
data-cta="midpage-primary"
|
||||
class="inline-flex items-center justify-center rounded-xl bg-white px-7 py-4 text-base font-bold text-primary-700 shadow-lg transition-all hover:bg-primary-50 hover:scale-105 focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-primary-700"
|
||||
>
|
||||
Get my free proposal
|
||||
<svg class="ml-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M17 8l4 4m0 0l-4 4m4-4H3"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="tel:+919561417403"
|
||||
data-cta="midpage-call"
|
||||
class="inline-flex items-center justify-center rounded-xl border-2 border-white/40 bg-white/5 px-7 py-4 text-base font-bold text-white transition-all hover:bg-white/15 hover:border-white/60 focus:outline-none focus:ring-2 focus:ring-white/60 focus:ring-offset-2 focus:ring-offset-primary-700"
|
||||
>
|
||||
<svg class="mr-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
|
||||
</svg>
|
||||
Call now
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- How It Works Section - NEW -->
|
||||
<section class="py-24 bg-gradient-to-br from-secondary-900 via-secondary-800 to-primary-900 relative overflow-hidden">
|
||||
<!-- Background pattern -->
|
||||
@@ -569,6 +712,80 @@ const techStack = [
|
||||
</div>
|
||||
</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
|
||||
leads above the fold. Kept intentionally subdued (border-only cards,
|
||||
neutral grayscale icons) so it reads as supporting context rather than
|
||||
@@ -632,9 +849,11 @@ const techStack = [
|
||||
|
||||
<!-- Inline Lead Form — sits between the tech-stack social-proof block
|
||||
and the final CTA band so visitors who are ready to start a project can
|
||||
submit a 3-field enquiry without leaving the homepage. Submits to the
|
||||
same /api/contact endpoint and is tagged `source: 'homepage-inline'` so
|
||||
the lead can be segmented downstream. -->
|
||||
submit a compact enquiry (name, email, phone, project type, message)
|
||||
without leaving the homepage. Required fields are kept to name + email +
|
||||
project type for a low-friction mobile flow. Submits to the same
|
||||
/api/contact endpoint and is tagged `source: 'homepage-inline'` so the
|
||||
lead can be segmented downstream. -->
|
||||
<InlineLeadForm />
|
||||
|
||||
<!-- Homepage FAQ — single canonical FAQ section for /. Covers the cost /
|
||||
|
||||
@@ -3,8 +3,8 @@ import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="You're Offline"
|
||||
description="It looks like you're offline. Please check your internet connection."
|
||||
title="You're Offline — Reconnect to Keep Browsing"
|
||||
description="You appear to be offline. Check your internet connection and try again — many WorkRoot IT Solutions pages stay available once you have visited them before."
|
||||
noIndex={true}
|
||||
>
|
||||
<section class="min-h-[60vh] flex items-center justify-center px-4">
|
||||
|
||||
@@ -173,8 +173,8 @@ const featuredProjects = projects.filter(p => p.featured);
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="Portfolio — Case Studies & Projects"
|
||||
description="Explore WorkRoot IT Solutions' portfolio: 20+ successful projects across web, mobile, ERP & government IT. From PLNR Financing to Bihar Government systems. Real results: 100% client satisfaction, 99.9% uptime."
|
||||
title="Software Project Portfolio & Case Studies"
|
||||
description="Explore WorkRoot's portfolio of 20+ web, mobile, ERP, and government IT projects — from PLNR Financing to Bihar police systems, with 99.9% uptime delivered."
|
||||
>
|
||||
<SEO
|
||||
slot="head"
|
||||
@@ -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">
|
||||
{[
|
||||
{ value: '20+', label: 'Projects' },
|
||||
{ value: '11', label: 'Districts Deployed' },
|
||||
{ value: '11+', label: 'Districts Deployed' },
|
||||
{ value: '50,000+', label: 'Records Managed' },
|
||||
{ value: '8+', label: 'Years Avg Experience' },
|
||||
].map((stat) => (
|
||||
@@ -348,8 +348,11 @@ const featuredProjects = projects.filter(p => p.featured);
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Category Filter Tabs -->
|
||||
<div class="flex flex-wrap justify-center gap-3" role="tablist" aria-label="Filter projects by category">
|
||||
<!-- Category Filter Tabs
|
||||
Mobile (<sm): a single horizontally-scrollable row so the five
|
||||
chips never wrap into a tall stack that pushes the project grid
|
||||
below the fold. ≥sm: centered wrapping row as before. -->
|
||||
<div class="filter-scroll flex flex-nowrap gap-3 overflow-x-auto pb-1 snap-x sm:flex-wrap sm:justify-center sm:overflow-visible sm:pb-0" role="tablist" aria-label="Filter projects by category">
|
||||
<button
|
||||
data-filter="all"
|
||||
class="filter-btn active rounded-full bg-primary px-6 py-2.5 font-semibold text-white transition-all duration-300 hover:shadow-lg hover:shadow-primary/25 focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2"
|
||||
@@ -1190,4 +1193,28 @@ const featuredProjects = projects.filter(p => p.featured);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Horizontally-scrollable category filter row on mobile.
|
||||
Chips keep their intrinsic width (no shrinking) and snap as the user
|
||||
swipes; the scrollbar is hidden for a clean native-app feel. At >=sm the
|
||||
row wraps normally so the scroll affordance is mobile-only. */
|
||||
.filter-scroll {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scroll-snap-type: x proximity;
|
||||
}
|
||||
.filter-scroll > .filter-btn {
|
||||
flex: 0 0 auto;
|
||||
white-space: nowrap;
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
/* Hide the scrollbar (still scrollable) only while in the mobile scroll mode */
|
||||
@media (max-width: 639px) {
|
||||
.filter-scroll {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE/Edge legacy */
|
||||
}
|
||||
.filter-scroll::-webkit-scrollbar {
|
||||
display: none; /* Chrome/Safari */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,8 +8,8 @@ const lastUpdated = 'March 20, 2026';
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="Privacy Policy"
|
||||
description="WorkRoot IT Solutions Privacy Policy - Learn how we collect, use, and protect your personal information."
|
||||
title="Privacy Policy — How We Protect Your Data"
|
||||
description="Read the WorkRoot IT Solutions privacy policy to learn what personal data we collect, how we use and store it, and the choices and rights you have over it."
|
||||
noIndex={true}
|
||||
>
|
||||
<!-- Hero Section -->
|
||||
@@ -211,7 +211,7 @@ const lastUpdated = 'March 20, 2026';
|
||||
<div class="bg-secondary-900/50 border border-secondary-800 rounded-xl p-6">
|
||||
<p class="text-secondary-300"><strong class="text-white">WorkRoot IT Solutions</strong></p>
|
||||
<p class="text-secondary-300">Email: admin@workroot.in</p>
|
||||
<p class="text-secondary-300">Phone: +91 9561417403</p>
|
||||
<p class="text-secondary-300">Phone: <a href="tel:+919561417403" class="text-primary-400 hover:underline">+91 9561417403</a></p>
|
||||
<p class="text-secondary-300">Address: At Post Rajapur Shantinagar, Taluka Khatav, Dist Satara, MH 415503</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -246,6 +246,23 @@ const caseStudies = [
|
||||
},
|
||||
];
|
||||
|
||||
// Dedicated, keyword-focused landing pages that deep-dive a service. Keyed by
|
||||
// the service `id` so each detail section can surface its related page(s)
|
||||
// directly in the on-page navigation flow.
|
||||
const landingPages: Record<string, { label: string; href: string }[]> = {
|
||||
'government-systems': [
|
||||
{ label: 'Government IT & GeM vendor', href: '/services/government-it-solutions-gem-vendor' },
|
||||
{ label: 'Police Management System', href: '/services/police-management-system' },
|
||||
],
|
||||
'erp-solutions': [
|
||||
{ label: 'Custom ERP development', href: '/services/custom-erp-development' },
|
||||
{ label: 'University ERP', href: '/services/university-erp' },
|
||||
],
|
||||
'mobile-development': [
|
||||
{ label: 'React Native app development', href: '/services/react-native-app-development' },
|
||||
],
|
||||
};
|
||||
|
||||
// Process steps
|
||||
const processSteps = [
|
||||
{
|
||||
@@ -276,8 +293,8 @@ const processSteps = [
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="IT Services & Solutions"
|
||||
description="Expert web development, mobile apps, ERP systems, government IT solutions & UI/UX design from WorkRoot IT Solutions LLP. 10+ projects delivered, 8+ happy clients. From ₹1,00,000 — get a free quote today."
|
||||
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."
|
||||
>
|
||||
<SEO
|
||||
slot="head"
|
||||
@@ -545,6 +562,28 @@ const processSteps = [
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" />
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
{/* Deep-dive landing pages related to this service */}
|
||||
{landingPages[service.id] && (
|
||||
<div class="mt-6 pt-5 border-t border-secondary-200 dark:border-secondary-700">
|
||||
<p class="text-xs font-semibold text-secondary-400 dark:text-secondary-500 uppercase tracking-wider mb-2">Explore in depth</p>
|
||||
<ul class="flex flex-wrap gap-x-5 gap-y-2">
|
||||
{landingPages[service.id].map((page) => (
|
||||
<li>
|
||||
<a
|
||||
href={page.href}
|
||||
class={`inline-flex items-center gap-1.5 text-sm font-semibold ${service.iconColor} hover:underline focus:outline-none focus:underline`}
|
||||
>
|
||||
{page.label}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<!-- Visual element -->
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
---
|
||||
// Custom ERP Development — keyword-focused service landing page.
|
||||
// Reuses the shared section components (GuaranteeBand, InlineLeadForm,
|
||||
// ServiceFaq) and feeds Service + FAQ schema into SEO.astro.
|
||||
//
|
||||
// All company stats mirror the canonical site-wide set used in
|
||||
// ProofNumbers.astro, /portfolio, and SEO.astro — 20+ projects · 11+ districts ·
|
||||
// 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.
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import SEO from '../../components/SEO.astro';
|
||||
import GuaranteeBand from '../../components/GuaranteeBand.astro';
|
||||
import InlineLeadForm from '../../components/InlineLeadForm.astro';
|
||||
import ServiceFaq from '../../components/ServiceFaq.astro';
|
||||
|
||||
const pageTitle = 'Custom ERP Development Services';
|
||||
const pageDescription =
|
||||
'Custom ERP development by WorkRoot — bespoke enterprise systems on Spring Boot, Angular & Next.js. Already running for IIT Patna and across 11 government districts. Get a free scope.';
|
||||
|
||||
const serviceName = 'Custom ERP Development';
|
||||
const serviceDescription =
|
||||
'Bespoke ERP and enterprise resource planning systems built around your real workflows — modules for operations, personnel, finance, inventory, reporting and role-based access. Delivered with Spring Boot, Angular, and Next.js, and proven in university and multi-district government deployments.';
|
||||
|
||||
// Capabilities — what a WorkRoot custom ERP build typically covers.
|
||||
const modules = [
|
||||
{ title: 'Operations & workflow', body: 'Model your real approval chains, hand-offs, and SLAs instead of bending the business to a rigid template.' },
|
||||
{ title: 'Role-based access control', body: 'Granular permissions per role, department, and district — the same RBAC layer running in our government deployments.' },
|
||||
{ title: 'Personnel & HR', body: 'Staff records, duty rosters, leave, and audit trails — the backbone of our 15,000+ officer police deployment.' },
|
||||
{ title: 'Inventory & assets', body: 'Stock, procurement, and asset lifecycle tracking with reconciliation and low-stock alerting.' },
|
||||
{ title: 'Dashboards & reporting', body: 'Real-time operational dashboards and scheduled exports for leadership oversight and compliance.' },
|
||||
{ title: 'Integrations & APIs', body: 'Clean REST APIs so your ERP talks to payment gateways, SMS/email, and existing legacy systems.' },
|
||||
];
|
||||
|
||||
// Comparison table — off-the-shelf ERP vs a WorkRoot custom build.
|
||||
const comparison = [
|
||||
{ dimension: 'Fit to your workflow', offShelf: 'You adapt your process to the software', custom: 'The software is built around your process' },
|
||||
{ dimension: 'Per-seat licensing', offShelf: 'Recurring per-user fees that scale with headcount', custom: 'You own the source — no per-seat tax' },
|
||||
{ dimension: 'Source-code ownership', offShelf: 'Vendor-locked, closed source', custom: 'Full IP transfer on delivery' },
|
||||
{ dimension: 'Customisation', offShelf: 'Limited to vendor-exposed settings', custom: 'Any module, any rule, any report' },
|
||||
{ dimension: 'Data residency', offShelf: 'Often the vendor’s cloud / region', custom: 'Your servers, your region, your control' },
|
||||
{ dimension: 'Hosting model', offShelf: 'SaaS only', custom: 'On-prem, your VPC, or managed by us' },
|
||||
];
|
||||
|
||||
const stats = [
|
||||
{ value: '5+', label: 'ERP & enterprise deployments' },
|
||||
{ value: '20,000+', label: 'Users across systems' },
|
||||
{ value: '11+', label: 'Districts running our platforms' },
|
||||
{ value: '99.9%', label: 'Uptime on critical systems' },
|
||||
];
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: 'How much does a custom ERP cost?',
|
||||
answer:
|
||||
'Custom ERP engagements typically run ₹2,00,000–₹15,00,000 depending on the number of modules, user roles, and integrations. We offer fixed-price quotes for well-defined scopes and time-and-materials for evolving ones. Every estimate is free and itemised.',
|
||||
},
|
||||
{
|
||||
question: 'How long does it take to build a custom ERP?',
|
||||
answer:
|
||||
'Most ERP builds ship a usable first module in 6–8 weeks and reach full rollout in 3–6 months. 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 big-bang launch.',
|
||||
},
|
||||
{
|
||||
question: 'Do we own the source code?',
|
||||
answer:
|
||||
'Yes. Full IP transfer is included — you receive the repositories, deployment access, and documentation on delivery. There are no per-seat licence fees, and you are free to host, extend, or hand the system to another team at any time.',
|
||||
},
|
||||
{
|
||||
question: 'Can you integrate with our existing systems?',
|
||||
answer:
|
||||
'Yes. We build clean REST APIs and have integrated ERPs with payment gateways, SMS and email providers, government identity systems, and legacy MySQL/PostgreSQL databases. If your current tool can export data, we can migrate and connect to it.',
|
||||
},
|
||||
{
|
||||
question: 'Is a custom ERP secure enough for sensitive data?',
|
||||
answer:
|
||||
'Security is built into every layer: role-based access control, encrypted data at rest and in transit, full audit logging, and OWASP-aligned reviews. The same architecture currently runs a Bihar government police platform across 11 districts under strict security compliance.',
|
||||
},
|
||||
{
|
||||
question: 'What technology stack do you use for ERP?',
|
||||
answer:
|
||||
'We build on Spring Boot for the backend, Angular or Next.js for the admin and user interfaces, and PostgreSQL or MySQL for the data layer — the same stack behind the IIT Patna University Management System and our multi-district government deployments.',
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<BaseLayout title={pageTitle} description={pageDescription}>
|
||||
<SEO
|
||||
slot="head"
|
||||
type="Service"
|
||||
breadcrumbs={[
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Services', url: '/services' },
|
||||
{ name: 'Custom ERP Development', url: '/services/custom-erp-development' },
|
||||
]}
|
||||
service={{ name: serviceName, description: serviceDescription }}
|
||||
faq={faqs}
|
||||
/>
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="relative bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900 py-20 lg:py-28 overflow-hidden">
|
||||
<div class="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
|
||||
<div class="absolute top-1/4 left-0 w-80 h-80 bg-primary-500/10 rounded-full blur-3xl animate-pulse-slower"></div>
|
||||
<div class="absolute bottom-0 right-1/4 w-96 h-96 bg-purple-500/10 rounded-full blur-3xl animate-pulse-slow"></div>
|
||||
<div class="absolute inset-0 opacity-5" style="background-image: linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px); background-size: 60px 60px;"></div>
|
||||
</div>
|
||||
<div class="container-wrapper relative">
|
||||
<div class="max-w-3xl">
|
||||
<nav aria-label="Breadcrumb" class="mb-6">
|
||||
<ol class="flex flex-wrap items-center gap-2 text-sm text-secondary-400">
|
||||
<li><a href="/" class="hover:text-primary-300">Home</a></li>
|
||||
<li aria-hidden="true">/</li>
|
||||
<li><a href="/services" class="hover:text-primary-300">Services</a></li>
|
||||
<li aria-hidden="true">/</li>
|
||||
<li class="text-secondary-200" aria-current="page">Custom ERP Development</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-6 text-sm font-semibold text-primary-300 bg-primary-500/10 rounded-full border border-primary-500/20">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-primary-400 animate-pulse" aria-hidden="true"></span>
|
||||
ERP & Enterprise Systems
|
||||
</span>
|
||||
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white mb-6 leading-tight">
|
||||
Custom ERP development that fits
|
||||
<span class="text-transparent bg-clip-text bg-gradient-to-r from-primary-400 via-primary-300 to-accent-400"> your workflow, not a template.</span>
|
||||
</h1>
|
||||
<p class="text-lg sm:text-xl text-secondary-300 max-w-2xl mb-8 leading-relaxed">
|
||||
Replace scattered spreadsheets and disconnected tools with one bespoke system your team actually trusts —
|
||||
built on Spring Boot, Angular, and Next.js, and already running for IIT Patna and across 11 government districts.
|
||||
</p>
|
||||
<div class="flex flex-col sm:flex-row gap-4">
|
||||
<a href="#inline-lead-form" data-cta="erp-hero-primary" class="inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-primary-500 to-primary-600 px-8 py-4 text-base font-bold text-white shadow-lg hover:shadow-xl hover:scale-105 transition-all focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-secondary-900">
|
||||
Get my free ERP scope
|
||||
<svg class="ml-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
|
||||
</a>
|
||||
<a href="tel:+919561417403" data-cta="erp-hero-call" class="inline-flex items-center justify-center rounded-xl border-2 border-white/30 bg-white/5 px-8 py-4 text-base font-bold text-white hover:bg-white/10 transition-all focus:outline-none focus:ring-2 focus:ring-white/50">
|
||||
Talk to an engineer
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stat block -->
|
||||
<div class="mt-14 grid grid-cols-2 sm:grid-cols-4 gap-4 lg:gap-6">
|
||||
{stats.map((stat) => (
|
||||
<div class="bg-white/5 backdrop-blur-sm rounded-xl p-4 lg:p-5 text-center border border-white/10">
|
||||
<p class="text-2xl lg:text-3xl font-bold text-white">{stat.value}</p>
|
||||
<p class="text-secondary-400 text-sm mt-1">{stat.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Overview / prose -->
|
||||
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<span class="text-primary-500 dark:text-primary-400 font-semibold text-sm uppercase tracking-wider">What we build</span>
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mt-2 mb-6">Enterprise software shaped around how you actually operate</h2>
|
||||
<div class="space-y-5 text-lg text-secondary-700 dark:text-secondary-300 leading-relaxed">
|
||||
<p>
|
||||
An off-the-shelf ERP forces every organisation into the same boxes. That works until your approval chain, your
|
||||
district structure, or your compliance rules don’t fit the template — and then you are paying per seat for software
|
||||
your team quietly works around in spreadsheets. A custom ERP flips that relationship: the system is built around the
|
||||
way you already run, so adoption is high and the workarounds disappear.
|
||||
</p>
|
||||
<p>
|
||||
WorkRoot has been delivering bespoke enterprise systems since 2022, and the engineers on our team carry 8+ years of
|
||||
experience building software that has to stay up. We have shipped enterprise platforms for an IIT, for a state
|
||||
government across 11 districts, and for private operators handling tens of thousands of users. Across those systems
|
||||
we manage <strong>50,000+ records</strong> and hold <strong>99.9% uptime</strong> on the deployments that cannot
|
||||
afford to go dark.
|
||||
</p>
|
||||
<p>
|
||||
Every engagement starts with your workflow, not our codebase. We map the real process — who requests what, who
|
||||
approves it, where it gets stuck — and then build modules that remove the friction: operations and workflow,
|
||||
role-based access, personnel and HR, inventory and assets, finance, and the dashboards leadership needs to see it
|
||||
all. The result is one system of record instead of five disconnected tools, and a single source of truth your
|
||||
auditors will thank you for.
|
||||
</p>
|
||||
<p>
|
||||
Because the system is yours, there are no per-seat licence fees and no vendor lock-in. We hand over the full source
|
||||
code, the repositories, the deployment pipeline, and the documentation. You can host it on-premise, in your own
|
||||
cloud account, or let us run it for you under a maintenance retainer — the choice stays with you.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Modules grid -->
|
||||
<section class="py-16 lg:py-24 bg-secondary-50 dark:bg-secondary-800/50">
|
||||
<div class="container-wrapper">
|
||||
<div class="text-center max-w-2xl mx-auto mb-12">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">Modules we build into custom ERPs</h2>
|
||||
<p class="text-secondary-600 dark:text-secondary-400 text-lg">Mix and match the pieces your operation needs — each one production-proven in a live deployment.</p>
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{modules.map((m) => (
|
||||
<div class="bg-white dark:bg-secondary-800 rounded-2xl p-7 border border-secondary-100 dark:border-secondary-700 shadow-sm hover:shadow-lg transition-shadow">
|
||||
<div class="w-12 h-12 rounded-xl bg-primary-50 dark:bg-primary-900/30 text-primary-600 dark:text-primary-300 flex items-center justify-center mb-4" aria-hidden="true">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /></svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-bold text-secondary-900 dark:text-secondary-100 mb-2">{m.title}</h3>
|
||||
<p class="text-secondary-600 dark:text-secondary-400 text-sm leading-relaxed">{m.body}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Comparison table -->
|
||||
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="text-center mb-10">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">Off-the-shelf ERP vs a custom build</h2>
|
||||
<p class="text-secondary-600 dark:text-secondary-400 text-lg">Where a bespoke system pays you back.</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 a custom ERP built by WorkRoot</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 ERP (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>
|
||||
|
||||
<!-- Case study -->
|
||||
<section class="py-16 lg:py-24 bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900">
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-5 text-sm font-semibold text-purple-300 bg-purple-500/10 rounded-full border border-purple-500/20">Case study · Enterprise ERP</span>
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">University Management System — IIT Patna</h2>
|
||||
<p class="text-lg text-secondary-300 leading-relaxed mb-8">
|
||||
IIT Patna needed a single platform to replace fragmented legacy systems across admissions, course registration,
|
||||
examination scheduling, grade management, and faculty administration. We built an end-to-end ERP on a
|
||||
<strong class="text-white"> Next.js</strong> front end with a <strong class="text-white">Spring Boot</strong> backend
|
||||
and a <strong class="text-white">MySQL</strong> data layer — designed for scalability and high availability across the
|
||||
entire academic community.
|
||||
</p>
|
||||
<div class="grid grid-cols-3 gap-4 mb-8">
|
||||
{[{ v: '3,000+', l: 'Students served' }, { v: '500+', l: 'Courses managed' }, { v: '200+', l: 'Faculty onboarded' }].map((s) => (
|
||||
<div class="bg-white/5 border border-white/10 rounded-xl p-5 text-center">
|
||||
<p class="text-2xl sm:text-3xl font-bold text-white">{s.v}</p>
|
||||
<p class="text-secondary-400 text-xs sm:text-sm mt-1">{s.l}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<blockquote class="border-l-4 border-primary-500 pl-5 text-secondary-200 italic leading-relaxed">
|
||||
“WorkRoot delivered our University Management System for IIT Patna on time and within budget. The Next.js + Spring Boot
|
||||
architecture is robust and scalable. They are a reliable technology partner for complex enterprise projects.”
|
||||
<footer class="mt-3 not-italic text-sm text-secondary-400">— Dilip Raj, Director, MOC Soft Technologies Pvt Ltd</footer>
|
||||
</blockquote>
|
||||
<div class="mt-8 flex flex-wrap gap-4">
|
||||
<a href="/portfolio#university-management-iitp" class="inline-flex items-center gap-2 text-primary-300 font-semibold hover:text-primary-200">
|
||||
Read the full case study
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
|
||||
</a>
|
||||
<a href="/services/university-erp" class="inline-flex items-center gap-2 text-secondary-300 font-semibold hover:text-white">
|
||||
Explore our University ERP page
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" /></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- More deployments strip -->
|
||||
<section class="py-12 bg-secondary-50 dark:bg-secondary-800/50 border-y border-secondary-100 dark:border-secondary-800">
|
||||
<div class="container-wrapper">
|
||||
<p class="text-center text-sm font-semibold uppercase tracking-wider text-secondary-500 dark:text-secondary-400 mb-6">More enterprise systems we’ve shipped</p>
|
||||
<div class="flex flex-wrap items-center justify-center gap-3">
|
||||
<a href="/services/police-management-system" class="px-4 py-2 rounded-full bg-white dark:bg-secondary-800 border border-secondary-200 dark:border-secondary-700 text-sm font-medium text-secondary-700 dark:text-secondary-300 hover:border-primary-300 transition-colors">Police Management System · 11 districts</a>
|
||||
<a href="/services/government-it-solutions-gem-vendor" class="px-4 py-2 rounded-full bg-white dark:bg-secondary-800 border border-secondary-200 dark:border-secondary-700 text-sm font-medium text-secondary-700 dark:text-secondary-300 hover:border-primary-300 transition-colors">SCMS for PSCL, Bihar Govt · 50,000+ records</a>
|
||||
<a href="/portfolio#plnr-financing" class="px-4 py-2 rounded-full bg-white dark:bg-secondary-800 border border-secondary-200 dark:border-secondary-700 text-sm font-medium text-secondary-700 dark:text-secondary-300 hover:border-primary-300 transition-colors">PLNR Financing · 5,000+ users</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ServiceFaq
|
||||
faqs={faqs}
|
||||
heading="Custom ERP — your questions answered"
|
||||
subheading="Cost, timelines, ownership, integrations, and security for a bespoke ERP build."
|
||||
/>
|
||||
|
||||
<GuaranteeBand />
|
||||
<InlineLeadForm />
|
||||
</BaseLayout>
|
||||
@@ -0,0 +1,309 @@
|
||||
---
|
||||
// Government IT Solutions / GeM Vendor — keyword-focused service landing page.
|
||||
// Reuses GuaranteeBand, InlineLeadForm and ServiceFaq, and feeds Service + FAQ
|
||||
// schema into SEO.astro.
|
||||
//
|
||||
// Canonical stats mirror ProofNumbers.astro / /portfolio / SEO.astro:
|
||||
// 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 SEO from '../../components/SEO.astro';
|
||||
import GuaranteeBand from '../../components/GuaranteeBand.astro';
|
||||
import InlineLeadForm from '../../components/InlineLeadForm.astro';
|
||||
import ServiceFaq from '../../components/ServiceFaq.astro';
|
||||
|
||||
const pageTitle = 'Government IT Solutions & GeM Vendor';
|
||||
const pageDescription =
|
||||
'GeM-empanelled government IT vendor. WorkRoot builds secure, multi-district public-service platforms — live across 11 Bihar districts at 99.9% uptime. Get a free scope for your tender.';
|
||||
|
||||
const serviceName = 'Government IT Solutions';
|
||||
const serviceDescription =
|
||||
'Secure, compliant government IT systems for public bodies and smart-city missions — multi-district deployment, role-based access, audit logging, and data residency. Delivered by a GeM-empanelled vendor with live platforms across 11 districts in Bihar at 99.9% uptime.';
|
||||
|
||||
const capabilities = [
|
||||
{ title: 'Multi-district deployment', body: 'One platform rolled out across districts without rewriting it for each — proven across 11 Bihar districts.' },
|
||||
{ title: 'Citizen-facing portals', body: 'Public service portals for bookings, applications, and status tracking, built for low-bandwidth access.' },
|
||||
{ title: 'Audit & compliance', body: 'Audit logging baked into the data layer, role-based access control, and tamper-evident record-keeping.' },
|
||||
{ title: 'Smart-city systems (SCMS)', body: 'Operational systems for civic facilities — slot booking, real-time availability, and oversight dashboards.' },
|
||||
{ title: 'Data residency & security', body: 'Hosting in your region under strict security standards — encrypted at rest and in transit.' },
|
||||
{ title: 'High availability', body: 'Infrastructure engineered for 99.9% uptime on mission-critical public services.' },
|
||||
];
|
||||
|
||||
// Capability / readiness stat block presented as a checklist table.
|
||||
const readiness = [
|
||||
{ dimension: 'GeM empanelment', detail: 'Empanelled for relevant IT service categories on the Government e-Marketplace.' },
|
||||
{ dimension: 'Live government deployments', detail: 'Smart Crematorium Management System for Patna Smart City Limited (PSCL), Bihar Government.' },
|
||||
{ dimension: 'Multi-district experience', detail: 'Police Management System deployed across 11 districts for the Bihar State Government.' },
|
||||
{ dimension: 'Records under management', detail: '50,000+ government records managed across live deployments.' },
|
||||
{ dimension: 'Security posture', detail: 'Role-based access control, full audit trails, OWASP-aligned reviews, encrypted data.' },
|
||||
{ dimension: 'Uptime track record', detail: '99.9% uptime sustained on critical public-service systems.' },
|
||||
];
|
||||
|
||||
const stats = [
|
||||
{ value: '11', label: 'Districts deployed' },
|
||||
{ value: '50,000+', label: 'Government records' },
|
||||
{ value: '99.9%', label: 'Uptime' },
|
||||
{ value: 'GeM', label: 'Empanelled vendor' },
|
||||
];
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: 'Is WorkRoot a GeM-empanelled vendor?',
|
||||
answer:
|
||||
'Yes. WorkRoot is empanelled for relevant IT service categories on the Government e-Marketplace (GeM), and we have delivered platforms used across 11 districts in Bihar. We can respond to GeM bids and government tenders for web, ERP, and smart-city systems.',
|
||||
},
|
||||
{
|
||||
question: 'Have you delivered for government clients before?',
|
||||
answer:
|
||||
'Yes. We built the Smart Crematorium Management System (SCMS) for Patna Smart City Limited under the Bihar Government, and a Police Management System deployed across 11 districts for the Bihar State Government, handling personnel, case tracking, and FIR digitisation for 15,000+ officers.',
|
||||
},
|
||||
{
|
||||
question: 'How do you handle data residency and security for government systems?',
|
||||
answer:
|
||||
'Government data stays in your chosen region on infrastructure you control. We apply role-based access control, encrypt data at rest and in transit, log every action for audit, and run OWASP-aligned security reviews. The same controls run our live Bihar government deployments under strict compliance.',
|
||||
},
|
||||
{
|
||||
question: 'Can a single platform serve multiple districts?',
|
||||
answer:
|
||||
'Yes — that is exactly what our multi-district architecture is built for. We use a database-per-district model with central aggregation and district-specific feature configuration, so one codebase rolls out across districts without a separate rewrite for each.',
|
||||
},
|
||||
{
|
||||
question: 'What uptime can you commit to for public services?',
|
||||
answer:
|
||||
'We sustain 99.9% uptime on our mission-critical government deployments through high-availability infrastructure, monitoring, and blue-green deployments. We can align an SLA to the criticality of your service during scoping.',
|
||||
},
|
||||
{
|
||||
question: 'What technology do you use for government platforms?',
|
||||
answer:
|
||||
'We build on Spring Boot, Astro, and Golang with PostgreSQL and MySQL — the stack behind our SCMS and police deployments. The choice favours long-term maintainability, security, and performance under real public load.',
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<BaseLayout title={pageTitle} description={pageDescription}>
|
||||
<SEO
|
||||
slot="head"
|
||||
type="Service"
|
||||
breadcrumbs={[
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Services', url: '/services' },
|
||||
{ name: 'Government IT Solutions', url: '/services/government-it-solutions-gem-vendor' },
|
||||
]}
|
||||
service={{ name: serviceName, description: serviceDescription }}
|
||||
faq={faqs}
|
||||
/>
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="relative bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900 py-20 lg:py-28 overflow-hidden">
|
||||
<div class="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
|
||||
<div class="absolute top-1/4 left-0 w-80 h-80 bg-red-500/10 rounded-full blur-3xl animate-pulse-slower"></div>
|
||||
<div class="absolute bottom-0 right-1/4 w-96 h-96 bg-orange-500/10 rounded-full blur-3xl animate-pulse-slow"></div>
|
||||
<div class="absolute inset-0 opacity-5" style="background-image: linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px); background-size: 60px 60px;"></div>
|
||||
</div>
|
||||
<div class="container-wrapper relative">
|
||||
<div class="max-w-3xl">
|
||||
<nav aria-label="Breadcrumb" class="mb-6">
|
||||
<ol class="flex flex-wrap items-center gap-2 text-sm text-secondary-400">
|
||||
<li><a href="/" class="hover:text-primary-300">Home</a></li>
|
||||
<li aria-hidden="true">/</li>
|
||||
<li><a href="/services" class="hover:text-primary-300">Services</a></li>
|
||||
<li aria-hidden="true">/</li>
|
||||
<li class="text-secondary-200" aria-current="page">Government IT Solutions</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-6 text-sm font-semibold text-red-300 bg-red-500/10 rounded-full border border-red-500/20">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-red-400 animate-pulse" aria-hidden="true"></span>
|
||||
GeM-empanelled · Government IT
|
||||
</span>
|
||||
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white mb-6 leading-tight">
|
||||
Government IT systems that
|
||||
<span class="text-transparent bg-clip-text bg-gradient-to-r from-red-400 via-orange-300 to-amber-300"> roll out across districts and stay up.</span>
|
||||
</h1>
|
||||
<p class="text-lg sm:text-xl text-secondary-300 max-w-2xl mb-8 leading-relaxed">
|
||||
A GeM-empanelled vendor with secure, audit-ready platforms already live across 11 Bihar districts at 99.9% uptime.
|
||||
Built on Spring Boot, Astro, Golang and PostgreSQL — with the role-based access, data residency, and compliance public
|
||||
services demand.
|
||||
</p>
|
||||
<div class="flex flex-col sm:flex-row gap-4">
|
||||
<a href="#inline-lead-form" data-cta="gov-hero-primary" class="inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-primary-500 to-primary-600 px-8 py-4 text-base font-bold text-white shadow-lg hover:shadow-xl hover:scale-105 transition-all focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-secondary-900">
|
||||
Scope my government project
|
||||
<svg class="ml-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
|
||||
</a>
|
||||
<a href="tel:+919561417403" data-cta="gov-hero-call" class="inline-flex items-center justify-center rounded-xl border-2 border-white/30 bg-white/5 px-8 py-4 text-base font-bold text-white hover:bg-white/10 transition-all focus:outline-none focus:ring-2 focus:ring-white/50">
|
||||
Talk to our team
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-14 grid grid-cols-2 sm:grid-cols-4 gap-4 lg:gap-6">
|
||||
{stats.map((stat) => (
|
||||
<div class="bg-white/5 backdrop-blur-sm rounded-xl p-4 lg:p-5 text-center border border-white/10">
|
||||
<p class="text-2xl lg:text-3xl font-bold text-white">{stat.value}</p>
|
||||
<p class="text-secondary-400 text-sm mt-1">{stat.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Overview -->
|
||||
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<span class="text-red-500 dark:text-red-400 font-semibold text-sm uppercase tracking-wider">Why public bodies choose us</span>
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mt-2 mb-6">Digitise public services without the rewrite-for-each-district tax</h2>
|
||||
<div class="space-y-5 text-lg text-secondary-700 dark:text-secondary-300 leading-relaxed">
|
||||
<p>
|
||||
Public-sector technology fails in predictable ways: a system built for one office can’t scale to the next district,
|
||||
paper processes survive alongside half-finished portals, and security or audit requirements get bolted on too late.
|
||||
WorkRoot builds government platforms the other way around — multi-district from day one, with audit logging and
|
||||
role-based access designed into the data layer rather than retrofitted.
|
||||
</p>
|
||||
<p>
|
||||
We are empanelled on the Government e-Marketplace (GeM) and have delivered for real public clients. For
|
||||
<strong> Patna Smart City Limited</strong> under the Bihar Government we built and shipped a Smart Crematorium
|
||||
Management System that now manages <strong>50,000+ records</strong> across 10+ facilities. For the Bihar State
|
||||
Government we delivered a Police Management System deployed across <strong>11 districts</strong>, digitising FIRs,
|
||||
case tracking, and personnel administration for <strong>15,000+ officers</strong>. Both run at 99.9% uptime.
|
||||
</p>
|
||||
<p>
|
||||
Our architecture is deliberately conservative where it counts. We favour a database-per-district model with central
|
||||
aggregation so each district’s data stays isolated while leadership still gets a consolidated view. Offline-resilient
|
||||
design keeps field-critical modules working through patchy connectivity. Every sensitive action is written to an
|
||||
immutable audit log, access is governed by role-based permissions, and data is encrypted both at rest and in transit.
|
||||
Data residency stays under your control — on-premise, in your government cloud, or managed by us within your region.
|
||||
</p>
|
||||
<p>
|
||||
Just as importantly, you own what we build. Full source-code ownership, documentation, and deployment access are part
|
||||
of delivery, so your department is never hostage to a single vendor. That combination — GeM empanelment, a real
|
||||
multi-district track record, and clean handover — is why public bodies trust us with services citizens depend on.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Capabilities grid -->
|
||||
<section class="py-16 lg:py-24 bg-secondary-50 dark:bg-secondary-800/50">
|
||||
<div class="container-wrapper">
|
||||
<div class="text-center max-w-2xl mx-auto mb-12">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">What we deliver for government</h2>
|
||||
<p class="text-secondary-600 dark:text-secondary-400 text-lg">Every capability below is running in a live public-sector deployment today.</p>
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{capabilities.map((c) => (
|
||||
<div class="bg-white dark:bg-secondary-800 rounded-2xl p-7 border border-secondary-100 dark:border-secondary-700 shadow-sm hover:shadow-lg transition-shadow">
|
||||
<div class="w-12 h-12 rounded-xl bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-300 flex items-center justify-center mb-4" aria-hidden="true">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-bold text-secondary-900 dark:text-secondary-100 mb-2">{c.title}</h3>
|
||||
<p class="text-secondary-600 dark:text-secondary-400 text-sm leading-relaxed">{c.body}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Readiness table -->
|
||||
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="text-center mb-10">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">Tender-ready at a glance</h2>
|
||||
<p class="text-secondary-600 dark:text-secondary-400 text-lg">What we bring to a government engagement.</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-[560px]">
|
||||
<caption class="sr-only">WorkRoot government IT readiness and track record</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">Criterion</th>
|
||||
<th scope="col" class="px-5 py-4 text-sm font-bold text-primary-700 dark:text-primary-300">WorkRoot</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{readiness.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 w-1/3">{row.dimension}</th>
|
||||
<td class="px-5 py-4 text-sm text-secondary-700 dark:text-secondary-200 align-top">
|
||||
<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.detail}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Case studies (two government projects) -->
|
||||
<section class="py-16 lg:py-24 bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900">
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="text-center mb-12">
|
||||
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-4 text-sm font-semibold text-red-300 bg-red-500/10 rounded-full border border-red-500/20">Government case studies</span>
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-white">Live public-sector deployments</h2>
|
||||
</div>
|
||||
<div class="grid lg:grid-cols-2 gap-8">
|
||||
<!-- SCMS -->
|
||||
<article class="bg-white/5 border border-white/10 rounded-2xl p-8">
|
||||
<h3 class="text-xl font-bold text-white mb-2">SCMS for PSCL, Bihar Government</h3>
|
||||
<p class="text-sm text-secondary-400 mb-4">Patna Smart City Limited · Astro · Golang · PostgreSQL</p>
|
||||
<p class="text-secondary-300 leading-relaxed mb-6">
|
||||
A Smart Crematorium Management System digitising slot booking, real-time availability, digital record-keeping, and
|
||||
automated reporting — cutting booking from hours to minutes across 10+ facilities.
|
||||
</p>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
{[{ v: '10+', l: 'Facilities' }, { v: '50,000+', l: 'Records' }, { v: '99.9%', l: 'Uptime' }].map((s) => (
|
||||
<div class="bg-white/5 rounded-lg p-3 text-center">
|
||||
<p class="text-xl font-bold text-white">{s.v}</p>
|
||||
<p class="text-secondary-400 text-xs mt-1">{s.l}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<a href="/portfolio#scms-pscl" class="mt-6 inline-flex items-center gap-2 text-primary-300 font-semibold hover:text-primary-200 text-sm">
|
||||
Full case study
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" /></svg>
|
||||
</a>
|
||||
</article>
|
||||
<!-- Police -->
|
||||
<article class="bg-white/5 border border-white/10 rounded-2xl p-8">
|
||||
<h3 class="text-xl font-bold text-white mb-2">Police Management System — Bihar</h3>
|
||||
<p class="text-sm text-secondary-400 mb-4">Bihar State Government · Spring Boot · Angular · MySQL</p>
|
||||
<p class="text-secondary-300 leading-relaxed mb-6">
|
||||
A centralised system across 11 districts handling personnel management, FIR digitisation, case tracking, and duty
|
||||
roster scheduling — with role-based access and strict security compliance for thousands of concurrent users.
|
||||
</p>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
{[{ v: '11', l: 'Districts' }, { v: '15,000+', l: 'Officers' }, { v: '100,000+', l: 'Cases' }].map((s) => (
|
||||
<div class="bg-white/5 rounded-lg p-3 text-center">
|
||||
<p class="text-xl font-bold text-white">{s.v}</p>
|
||||
<p class="text-secondary-400 text-xs mt-1">{s.l}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<a href="/services/police-management-system" class="mt-6 inline-flex items-center gap-2 text-primary-300 font-semibold hover:text-primary-200 text-sm">
|
||||
Police system details
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" /></svg>
|
||||
</a>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ServiceFaq
|
||||
faqs={faqs}
|
||||
eyebrow="For government buyers"
|
||||
heading="Government IT & GeM — your questions answered"
|
||||
subheading="Empanelment, track record, security, data residency, and multi-district rollout."
|
||||
/>
|
||||
|
||||
<GuaranteeBand />
|
||||
<InlineLeadForm />
|
||||
</BaseLayout>
|
||||
@@ -0,0 +1,285 @@
|
||||
---
|
||||
// Police Management System — keyword-focused service landing page.
|
||||
// Reuses GuaranteeBand, InlineLeadForm and ServiceFaq, and feeds Service + FAQ
|
||||
// schema into SEO.astro.
|
||||
//
|
||||
// Case-study numbers are taken verbatim from the Police Management System
|
||||
// entry on /portfolio: 11 districts · 15,000+ officers · 100,000+ cases.
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import SEO from '../../components/SEO.astro';
|
||||
import GuaranteeBand from '../../components/GuaranteeBand.astro';
|
||||
import InlineLeadForm from '../../components/InlineLeadForm.astro';
|
||||
import ServiceFaq from '../../components/ServiceFaq.astro';
|
||||
|
||||
const pageTitle = 'Police Management System Development';
|
||||
const pageDescription =
|
||||
'Custom Police Management System development — FIR digitisation, case tracking, personnel & duty rosters. WorkRoot’s platform runs across 11 Bihar districts for 15,000+ officers.';
|
||||
|
||||
const serviceName = 'Police Management System Development';
|
||||
const serviceDescription =
|
||||
'Custom Police Management System software for state and district law-enforcement bodies — FIR digitisation, case tracking, personnel management, duty-roster scheduling, and real-time reporting with role-based access control. Built on Spring Boot and Angular, deployed across 11 districts in Bihar for 15,000+ officers.';
|
||||
|
||||
const modules = [
|
||||
{ title: 'FIR digitisation', body: 'Digital First Information Reports with structured capture, search, and status tracking — replacing paper registers.' },
|
||||
{ title: 'Case tracking', body: 'End-to-end case lifecycle from registration to disposal, with 100,000+ cases tracked in production.' },
|
||||
{ title: 'Personnel management', body: 'Officer records, postings, transfers, and service history for 15,000+ personnel.' },
|
||||
{ title: 'Duty roster scheduling', body: 'Automated duty rosters and shift planning that remove manual scheduling errors.' },
|
||||
{ title: 'Reporting dashboards', body: 'Real-time dashboards for district and HQ oversight, with scheduled compliance exports.' },
|
||||
{ title: 'Role-based access control', body: 'Granular permissions by rank, role, and district, with full audit logging of sensitive actions.' },
|
||||
];
|
||||
|
||||
const stats = [
|
||||
{ value: '11', label: 'Districts deployed' },
|
||||
{ value: '15,000+', label: 'Officers managed' },
|
||||
{ value: '100,000+', label: 'Cases tracked' },
|
||||
{ value: '99.9%', label: 'Uptime' },
|
||||
];
|
||||
|
||||
// Before/after comparison stat-table: manual processes vs the digital system.
|
||||
const comparison = [
|
||||
{ dimension: 'FIR filing', before: 'Paper registers, no central search', after: 'Digital FIRs, searchable across districts' },
|
||||
{ dimension: 'Case status', before: 'Manual file movement, opaque', after: 'Real-time tracking from registration to disposal' },
|
||||
{ dimension: 'Personnel records', before: 'District-siloed paper files', after: 'Unified service records for 15,000+ officers' },
|
||||
{ dimension: 'Duty rosters', before: 'Hand-written, error-prone', after: 'Automated scheduling with conflict checks' },
|
||||
{ dimension: 'Oversight', before: 'Periodic manual compilation', after: 'Live dashboards for HQ and districts' },
|
||||
{ dimension: 'Data consistency', before: 'Inconsistent across 11 districts', after: 'Single source of truth with audit trail' },
|
||||
];
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: 'Have you built a police management system before?',
|
||||
answer:
|
||||
'Yes. WorkRoot developed and deployed a Police Management System for the Bihar State Government across 11 districts. It handles personnel management, FIR digitisation, case tracking, and duty-roster scheduling for 15,000+ officers and 100,000+ cases at 99.9% uptime.',
|
||||
},
|
||||
{
|
||||
question: 'What modules does a police management system include?',
|
||||
answer:
|
||||
'Core modules are FIR digitisation, case tracking, personnel management, duty-roster scheduling, real-time reporting dashboards, and role-based access control with audit logging. We tailor the module set to your jurisdiction’s workflow during scoping.',
|
||||
},
|
||||
{
|
||||
question: 'How do you secure sensitive law-enforcement data?',
|
||||
answer:
|
||||
'Through defence-in-depth: role-based access control by rank and district, encryption at rest and in transit, immutable audit logging of every sensitive action, and OWASP-aligned security reviews. The system is built to strict security compliance for thousands of concurrent users.',
|
||||
},
|
||||
{
|
||||
question: 'Can the system scale across many districts?',
|
||||
answer:
|
||||
'Yes. Our multi-district architecture uses a database-per-district model with central aggregation, so each district’s data stays isolated while HQ gets a consolidated view. The live Bihar deployment already spans 11 districts on a single codebase.',
|
||||
},
|
||||
{
|
||||
question: 'How long does deployment take?',
|
||||
answer:
|
||||
'Timelines depend on the number of modules and districts. We deliver a usable first module in weeks and roll out district-by-district to manage change and training. We share a detailed plan after the discovery phase.',
|
||||
},
|
||||
{
|
||||
question: 'What technology stack do you use?',
|
||||
answer:
|
||||
'The police platform is built on Spring Boot for the backend, Angular for the interface, and MySQL for the data layer — chosen for security, maintainability, and performance under heavy concurrent load.',
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<BaseLayout title={pageTitle} description={pageDescription}>
|
||||
<SEO
|
||||
slot="head"
|
||||
type="Service"
|
||||
breadcrumbs={[
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Services', url: '/services' },
|
||||
{ name: 'Police Management System', url: '/services/police-management-system' },
|
||||
]}
|
||||
service={{ name: serviceName, description: serviceDescription }}
|
||||
faq={faqs}
|
||||
/>
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="relative bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900 py-20 lg:py-28 overflow-hidden">
|
||||
<div class="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
|
||||
<div class="absolute top-1/4 left-0 w-80 h-80 bg-red-500/10 rounded-full blur-3xl animate-pulse-slower"></div>
|
||||
<div class="absolute bottom-0 right-1/4 w-96 h-96 bg-orange-500/10 rounded-full blur-3xl animate-pulse-slow"></div>
|
||||
<div class="absolute inset-0 opacity-5" style="background-image: linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px); background-size: 60px 60px;"></div>
|
||||
</div>
|
||||
<div class="container-wrapper relative">
|
||||
<div class="max-w-3xl">
|
||||
<nav aria-label="Breadcrumb" class="mb-6">
|
||||
<ol class="flex flex-wrap items-center gap-2 text-sm text-secondary-400">
|
||||
<li><a href="/" class="hover:text-primary-300">Home</a></li>
|
||||
<li aria-hidden="true">/</li>
|
||||
<li><a href="/services" class="hover:text-primary-300">Services</a></li>
|
||||
<li aria-hidden="true">/</li>
|
||||
<li class="text-secondary-200" aria-current="page">Police Management System</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-6 text-sm font-semibold text-red-300 bg-red-500/10 rounded-full border border-red-500/20">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-red-400 animate-pulse" aria-hidden="true"></span>
|
||||
Law-enforcement software
|
||||
</span>
|
||||
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white mb-6 leading-tight">
|
||||
Police Management System
|
||||
<span class="text-transparent bg-clip-text bg-gradient-to-r from-red-400 via-orange-300 to-amber-300"> proven across 11 districts.</span>
|
||||
</h1>
|
||||
<p class="text-lg sm:text-xl text-secondary-300 max-w-2xl mb-8 leading-relaxed">
|
||||
Digitise FIRs, case tracking, personnel records, and duty rosters in one secure platform — the system we built for the
|
||||
Bihar State Government already serves 15,000+ officers and tracks 100,000+ cases at 99.9% uptime.
|
||||
</p>
|
||||
<div class="flex flex-col sm:flex-row gap-4">
|
||||
<a href="#inline-lead-form" data-cta="police-hero-primary" class="inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-primary-500 to-primary-600 px-8 py-4 text-base font-bold text-white shadow-lg hover:shadow-xl hover:scale-105 transition-all focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-secondary-900">
|
||||
Discuss your deployment
|
||||
<svg class="ml-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
|
||||
</a>
|
||||
<a href="tel:+919561417403" data-cta="police-hero-call" class="inline-flex items-center justify-center rounded-xl border-2 border-white/30 bg-white/5 px-8 py-4 text-base font-bold text-white hover:bg-white/10 transition-all focus:outline-none focus:ring-2 focus:ring-white/50">
|
||||
Talk to our team
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-14 grid grid-cols-2 sm:grid-cols-4 gap-4 lg:gap-6">
|
||||
{stats.map((stat) => (
|
||||
<div class="bg-white/5 backdrop-blur-sm rounded-xl p-4 lg:p-5 text-center border border-white/10">
|
||||
<p class="text-2xl lg:text-3xl font-bold text-white">{stat.value}</p>
|
||||
<p class="text-secondary-400 text-sm mt-1">{stat.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Overview -->
|
||||
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<span class="text-red-500 dark:text-red-400 font-semibold text-sm uppercase tracking-wider">Built for the field</span>
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mt-2 mb-6">From paper registers to a single digital system of record</h2>
|
||||
<div class="space-y-5 text-lg text-secondary-700 dark:text-secondary-300 leading-relaxed">
|
||||
<p>
|
||||
Before digitisation, police departments across Bihar’s districts relied on paper-based processes for FIR filing, case
|
||||
tracking, and personnel management. The result was the familiar pattern: delays moving files between offices,
|
||||
inconsistent records from one district to the next, and no way for headquarters to see the current picture without a
|
||||
manual compilation. WorkRoot was engaged to replace that with one centralised, secure system.
|
||||
</p>
|
||||
<p>
|
||||
We built a Police Management System on a <strong>Spring Boot</strong> backend and an <strong>Angular</strong>
|
||||
interface, backed by <strong>MySQL</strong>, and deployed it across <strong>11 districts</strong> for the Bihar State
|
||||
Government. The platform digitises First Information Reports, tracks cases through their full lifecycle, manages
|
||||
personnel records and postings for <strong>15,000+ officers</strong>, and automates duty-roster scheduling. To date it
|
||||
has handled <strong>100,000+ cases</strong> while sustaining 99.9% uptime under thousands of concurrent users.
|
||||
</p>
|
||||
<p>
|
||||
Security and accountability were non-negotiable. Access is governed by role-based permissions tied to rank, role, and
|
||||
district; every sensitive action is written to an immutable audit log; and data is encrypted both at rest and in
|
||||
transit. The architecture keeps each district’s data isolated while still rolling up a real-time view for district and
|
||||
HQ leadership through reporting dashboards — so oversight is continuous rather than periodic.
|
||||
</p>
|
||||
<p>
|
||||
The project spanned four years of delivery and iteration (2018–2022), and the lessons from running at that scale —
|
||||
offline resilience for field-critical modules, audit logging baked into the data layer, and district-by-district
|
||||
rollout to manage training and change — now inform every law-enforcement and government engagement we take on. If your
|
||||
department needs to retire paper and unify records across jurisdictions, we have done exactly that in production.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Modules grid -->
|
||||
<section class="py-16 lg:py-24 bg-secondary-50 dark:bg-secondary-800/50">
|
||||
<div class="container-wrapper">
|
||||
<div class="text-center max-w-2xl mx-auto mb-12">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">Core modules</h2>
|
||||
<p class="text-secondary-600 dark:text-secondary-400 text-lg">Each module is running in the live Bihar deployment.</p>
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{modules.map((m) => (
|
||||
<div class="bg-white dark:bg-secondary-800 rounded-2xl p-7 border border-secondary-100 dark:border-secondary-700 shadow-sm hover:shadow-lg transition-shadow">
|
||||
<div class="w-12 h-12 rounded-xl bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-300 flex items-center justify-center mb-4" aria-hidden="true">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-bold text-secondary-900 dark:text-secondary-100 mb-2">{m.title}</h3>
|
||||
<p class="text-secondary-600 dark:text-secondary-400 text-sm leading-relaxed">{m.body}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Before/after comparison -->
|
||||
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="text-center mb-10">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">Before and after digitisation</h2>
|
||||
<p class="text-secondary-600 dark:text-secondary-400 text-lg">The operational change a unified system delivers.</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">Police operations before and after deploying the WorkRoot Police Management System</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">Area</th>
|
||||
<th scope="col" class="px-5 py-4 text-sm font-bold text-secondary-500 dark:text-secondary-400">Before (manual)</th>
|
||||
<th scope="col" class="px-5 py-4 text-sm font-bold text-primary-700 dark:text-primary-300">After (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.before}</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.after}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Case study highlight -->
|
||||
<section class="py-16 lg:py-24 bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900">
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-5 text-sm font-semibold text-red-300 bg-red-500/10 rounded-full border border-red-500/20">Case study · Government</span>
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">Bihar State Government — Police Management System</h2>
|
||||
<p class="text-lg text-secondary-300 leading-relaxed mb-8">
|
||||
A four-year engagement (2018–2022) delivering a centralised platform across 11 districts, built to handle thousands of
|
||||
concurrent users with role-based access control and strict security compliance. It remains a reference point for how we
|
||||
approach multi-district public-sector software.
|
||||
</p>
|
||||
<div class="grid grid-cols-3 gap-4 mb-8">
|
||||
{[{ v: '11', l: 'Districts' }, { v: '15,000+', l: 'Officers' }, { v: '100,000+', l: 'Cases' }].map((s) => (
|
||||
<div class="bg-white/5 border border-white/10 rounded-xl p-5 text-center">
|
||||
<p class="text-2xl sm:text-3xl font-bold text-white">{s.v}</p>
|
||||
<p class="text-secondary-400 text-xs sm:text-sm mt-1">{s.l}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<a href="/portfolio#police-management" class="inline-flex items-center gap-2 text-primary-300 font-semibold hover:text-primary-200">
|
||||
Read the full case study
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
|
||||
</a>
|
||||
<a href="/services/government-it-solutions-gem-vendor" class="inline-flex items-center gap-2 text-secondary-300 font-semibold hover:text-white">
|
||||
See all government IT solutions
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" /></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ServiceFaq
|
||||
faqs={faqs}
|
||||
eyebrow="For law-enforcement buyers"
|
||||
heading="Police Management System — your questions answered"
|
||||
subheading="Track record, modules, security, multi-district scale, and the stack we use."
|
||||
/>
|
||||
|
||||
<GuaranteeBand />
|
||||
<InlineLeadForm />
|
||||
</BaseLayout>
|
||||
@@ -0,0 +1,280 @@
|
||||
---
|
||||
// React Native App Development — keyword-focused service landing page.
|
||||
// Reuses GuaranteeBand, InlineLeadForm and ServiceFaq, and feeds Service + FAQ
|
||||
// schema into SEO.astro.
|
||||
//
|
||||
// Case-study numbers are taken verbatim from the Pophealth entry on
|
||||
// /portfolio: 10,000+ sessions · 100+ providers · 4.8★ rating.
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import SEO from '../../components/SEO.astro';
|
||||
import GuaranteeBand from '../../components/GuaranteeBand.astro';
|
||||
import InlineLeadForm from '../../components/InlineLeadForm.astro';
|
||||
import ServiceFaq from '../../components/ServiceFaq.astro';
|
||||
|
||||
const pageTitle = 'React Native App Development';
|
||||
const pageDescription =
|
||||
'Cross-platform React Native & Expo app development — one codebase for iOS and Android, offline-first sync, push notifications, store-ready builds. WorkRoot ships apps rated 4.8★.';
|
||||
|
||||
const serviceName = 'React Native App Development';
|
||||
const serviceDescription =
|
||||
'Cross-platform mobile app development with React Native and Expo — a single codebase shipped to both the App Store and Play Store, with offline-first architecture, push notifications, and Spring Boot backend integration. Proven with a health-sessions app handling 10,000+ sessions at a 4.8★ rating.';
|
||||
|
||||
const capabilities = [
|
||||
{ title: 'One codebase, both stores', body: 'A single React Native/Expo codebase published to iOS and Android — no separate native teams to fund.' },
|
||||
{ title: 'Offline-first sync', body: 'Apps that keep working through patchy connectivity and sync cleanly when back online — proven in the field.' },
|
||||
{ title: 'Push notifications', body: 'Native push on both platforms for re-engagement, reminders, and transactional alerts.' },
|
||||
{ title: 'Backend integration', body: 'Secure REST integration with Spring Boot and PostgreSQL/MySQL — the same backends behind our web platforms.' },
|
||||
{ title: 'Store submission', body: 'We handle App Store and Play Store builds, review, and release — not just the code.' },
|
||||
{ title: 'Native-like performance', body: 'Smooth, responsive UIs that feel native, with platform-aware navigation and gestures.' },
|
||||
];
|
||||
|
||||
const stats = [
|
||||
{ value: '3+', label: 'Mobile apps shipped' },
|
||||
{ value: '10,000+', label: 'Sessions handled' },
|
||||
{ value: '4.8★', label: 'User rating' },
|
||||
{ value: '2', label: 'Stores, 1 codebase' },
|
||||
];
|
||||
|
||||
// Comparison: cross-platform React Native vs two separate native builds.
|
||||
const comparison = [
|
||||
{ dimension: 'Codebases to maintain', native: 'Two (Swift + Kotlin)', rn: 'One (React Native/Expo)' },
|
||||
{ dimension: 'Time to both stores', native: 'Two parallel builds', rn: 'Single build, ship together' },
|
||||
{ dimension: 'Team needed', native: 'Separate iOS + Android engineers', rn: 'One cross-platform team' },
|
||||
{ dimension: 'Feature parity', native: 'Drifts between platforms', rn: 'Identical features by default' },
|
||||
{ dimension: 'Ongoing cost', native: 'Two maintenance tracks', rn: 'One maintenance track' },
|
||||
{ dimension: 'Offline support', native: 'Built twice', rn: 'Built once, shared' },
|
||||
];
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: 'Why choose React Native over fully native apps?',
|
||||
answer:
|
||||
'React Native lets you ship one codebase to both the App Store and Play Store, so you fund a single cross-platform team instead of separate iOS and Android engineers, get identical features on both platforms, and maintain one codebase. For most products it is faster and cheaper to launch and to keep running, with native-like performance.',
|
||||
},
|
||||
{
|
||||
question: 'Do you publish to both the App Store and Play Store?',
|
||||
answer:
|
||||
'Yes. We handle the full release path — production builds with Expo/EAS, store metadata, review submission, and the first release — for both Apple’s App Store and Google Play. We can publish under your developer accounts or guide you through setting them up.',
|
||||
},
|
||||
{
|
||||
question: 'Can the app work offline?',
|
||||
answer:
|
||||
'Yes. We build offline-first architecture with local storage and background sync, so the app stays usable through poor connectivity and reconciles cleanly when the connection returns. Our Pophealth health-sessions app uses exactly this approach for field use.',
|
||||
},
|
||||
{
|
||||
question: 'How does the app connect to a backend?',
|
||||
answer:
|
||||
'Through secure REST APIs. We typically pair React Native apps with a Spring Boot backend and a PostgreSQL or MySQL database — the same backend stack behind our web and enterprise platforms — so your mobile and web products share one source of truth.',
|
||||
},
|
||||
{
|
||||
question: 'How long does it take to build a React Native app?',
|
||||
answer:
|
||||
'A focused MVP typically ships in around 6–12 weeks depending on the feature set and integrations. We work in 2-week sprints with a live build available early, so you can test on real devices and steer the roadmap as it develops.',
|
||||
},
|
||||
{
|
||||
question: 'What does a React Native app cost?',
|
||||
answer:
|
||||
'Mobile builds generally fall in the ₹2,00,000–₹15,00,000 range depending on scope, integrations, and whether a backend is included. Every quote is free, itemised, and fixed-price for well-defined scopes.',
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<BaseLayout title={pageTitle} description={pageDescription}>
|
||||
<SEO
|
||||
slot="head"
|
||||
type="Service"
|
||||
breadcrumbs={[
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Services', url: '/services' },
|
||||
{ name: 'React Native App Development', url: '/services/react-native-app-development' },
|
||||
]}
|
||||
service={{ name: serviceName, description: serviceDescription }}
|
||||
faq={faqs}
|
||||
/>
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="relative bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900 py-20 lg:py-28 overflow-hidden">
|
||||
<div class="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
|
||||
<div class="absolute top-1/4 left-0 w-80 h-80 bg-green-500/10 rounded-full blur-3xl animate-pulse-slower"></div>
|
||||
<div class="absolute bottom-0 right-1/4 w-96 h-96 bg-emerald-500/10 rounded-full blur-3xl animate-pulse-slow"></div>
|
||||
<div class="absolute inset-0 opacity-5" style="background-image: linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px); background-size: 60px 60px;"></div>
|
||||
</div>
|
||||
<div class="container-wrapper relative">
|
||||
<div class="max-w-3xl">
|
||||
<nav aria-label="Breadcrumb" class="mb-6">
|
||||
<ol class="flex flex-wrap items-center gap-2 text-sm text-secondary-400">
|
||||
<li><a href="/" class="hover:text-primary-300">Home</a></li>
|
||||
<li aria-hidden="true">/</li>
|
||||
<li><a href="/services" class="hover:text-primary-300">Services</a></li>
|
||||
<li aria-hidden="true">/</li>
|
||||
<li class="text-secondary-200" aria-current="page">React Native App Development</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-6 text-sm font-semibold text-green-300 bg-green-500/10 rounded-full border border-green-500/20">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-green-400 animate-pulse" aria-hidden="true"></span>
|
||||
Cross-platform mobile
|
||||
</span>
|
||||
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white mb-6 leading-tight">
|
||||
React Native apps for
|
||||
<span class="text-transparent bg-clip-text bg-gradient-to-r from-green-400 via-emerald-300 to-teal-300"> iOS and Android — from one codebase.</span>
|
||||
</h1>
|
||||
<p class="text-lg sm:text-xl text-secondary-300 max-w-2xl mb-8 leading-relaxed">
|
||||
Launch on both stores without the native-team overhead. We build offline-first React Native and Expo apps with push
|
||||
notifications and Spring Boot backends — and ship them store-ready. Our last health app holds a 4.8★ rating across
|
||||
10,000+ sessions.
|
||||
</p>
|
||||
<div class="flex flex-col sm:flex-row gap-4">
|
||||
<a href="#inline-lead-form" data-cta="rn-hero-primary" class="inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-primary-500 to-primary-600 px-8 py-4 text-base font-bold text-white shadow-lg hover:shadow-xl hover:scale-105 transition-all focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-secondary-900">
|
||||
Get my free app scope
|
||||
<svg class="ml-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
|
||||
</a>
|
||||
<a href="tel:+919561417403" data-cta="rn-hero-call" class="inline-flex items-center justify-center rounded-xl border-2 border-white/30 bg-white/5 px-8 py-4 text-base font-bold text-white hover:bg-white/10 transition-all focus:outline-none focus:ring-2 focus:ring-white/50">
|
||||
Talk to a developer
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-14 grid grid-cols-2 sm:grid-cols-4 gap-4 lg:gap-6">
|
||||
{stats.map((stat) => (
|
||||
<div class="bg-white/5 backdrop-blur-sm rounded-xl p-4 lg:p-5 text-center border border-white/10">
|
||||
<p class="text-2xl lg:text-3xl font-bold text-white">{stat.value}</p>
|
||||
<p class="text-secondary-400 text-sm mt-1">{stat.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Overview -->
|
||||
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<span class="text-green-600 dark:text-green-400 font-semibold text-sm uppercase tracking-wider">Why cross-platform</span>
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mt-2 mb-6">One codebase in every customer’s pocket</h2>
|
||||
<div class="space-y-5 text-lg text-secondary-700 dark:text-secondary-300 leading-relaxed">
|
||||
<p>
|
||||
Building separate native apps for iOS and Android means funding two engineering teams, two release pipelines, and two
|
||||
sets of bugs — and watching feature parity quietly drift apart over time. React Native collapses that into a single
|
||||
codebase that ships to both the App Store and Google Play. For the vast majority of products, that means launching
|
||||
sooner, spending less, and maintaining one track instead of two — without giving up the native-like feel users expect.
|
||||
</p>
|
||||
<p>
|
||||
WorkRoot builds cross-platform apps with <strong>React Native and Expo</strong>, backed by <strong>Spring Boot</strong>
|
||||
APIs and <strong>PostgreSQL</strong>. We design offline-first from the start, because real users lose signal — in
|
||||
transit, in basements, in rural areas — and an app that freezes the moment connectivity drops is an app people delete.
|
||||
Local storage plus background sync keeps the experience smooth and reconciles data the moment the connection returns.
|
||||
</p>
|
||||
<p>
|
||||
Push notifications, secure authentication, deep links, and clean backend integration come standard, and we don’t stop
|
||||
at the code: we handle the production builds, store metadata, and review submission to get you live on both stores.
|
||||
Because the backend stack matches our web and enterprise platforms, your mobile app and any web product can share one
|
||||
source of truth rather than drifting into separate data silos.
|
||||
</p>
|
||||
<p>
|
||||
The proof is in production. Our Pophealth health-sessions app — built with Expo and Spring Boot — went live for a
|
||||
healthcare client, onboarded <strong>100+ providers</strong>, handled <strong>10,000+ sessions</strong> with
|
||||
offline-first sync for areas of poor connectivity, and earned a <strong>4.8★</strong> user rating. If you have a mobile
|
||||
product to launch, we can take it from idea to both stores.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Capabilities grid -->
|
||||
<section class="py-16 lg:py-24 bg-secondary-50 dark:bg-secondary-800/50">
|
||||
<div class="container-wrapper">
|
||||
<div class="text-center max-w-2xl mx-auto mb-12">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">What we ship</h2>
|
||||
<p class="text-secondary-600 dark:text-secondary-400 text-lg">Everything a production mobile app needs, on both platforms.</p>
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{capabilities.map((c) => (
|
||||
<div class="bg-white dark:bg-secondary-800 rounded-2xl p-7 border border-secondary-100 dark:border-secondary-700 shadow-sm hover:shadow-lg transition-shadow">
|
||||
<div class="w-12 h-12 rounded-xl bg-green-50 dark:bg-green-900/30 text-green-600 dark:text-green-300 flex items-center justify-center mb-4" aria-hidden="true">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" /></svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-bold text-secondary-900 dark:text-secondary-100 mb-2">{c.title}</h3>
|
||||
<p class="text-secondary-600 dark:text-secondary-400 text-sm leading-relaxed">{c.body}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Comparison table -->
|
||||
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="text-center mb-10">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">React Native vs two native builds</h2>
|
||||
<p class="text-secondary-600 dark:text-secondary-400 text-lg">Why one codebase usually wins.</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 separate native iOS and Android development versus cross-platform React Native</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">Two native apps</th>
|
||||
<th scope="col" class="px-5 py-4 text-sm font-bold text-primary-700 dark:text-primary-300">React Native (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.native}</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.rn}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Case study -->
|
||||
<section class="py-16 lg:py-24 bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900">
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-5 text-sm font-semibold text-green-300 bg-green-500/10 rounded-full border border-green-500/20">Case study · Mobile</span>
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">Pophealth — Health Sessions App</h2>
|
||||
<p class="text-lg text-secondary-300 leading-relaxed mb-8">
|
||||
A healthcare client needed a mobile-first tool to manage patient sessions and health data, with offline capability for
|
||||
areas of poor connectivity. We built it with <strong class="text-white">Expo (React Native)</strong> and a
|
||||
<strong class="text-white"> Spring Boot</strong> backend over <strong class="text-white">PostgreSQL</strong>, using an
|
||||
offline-first architecture with seamless sync for session management, appointment booking, and patient tracking.
|
||||
</p>
|
||||
<div class="grid grid-cols-3 gap-4 mb-8">
|
||||
{[{ v: '10,000+', l: 'Sessions managed' }, { v: '100+', l: 'Providers onboarded' }, { v: '4.8★', l: 'User rating' }].map((s) => (
|
||||
<div class="bg-white/5 border border-white/10 rounded-xl p-5 text-center">
|
||||
<p class="text-2xl sm:text-3xl font-bold text-white">{s.v}</p>
|
||||
<p class="text-secondary-400 text-xs sm:text-sm mt-1">{s.l}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<a href="/portfolio#pophealth" class="inline-flex items-center gap-2 text-primary-300 font-semibold hover:text-primary-200">
|
||||
Read the full case study
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ServiceFaq
|
||||
faqs={faqs}
|
||||
eyebrow="Before you build"
|
||||
heading="React Native app development — your questions answered"
|
||||
subheading="Cross-platform trade-offs, store publishing, offline support, timelines, and cost."
|
||||
/>
|
||||
|
||||
<GuaranteeBand />
|
||||
<InlineLeadForm />
|
||||
</BaseLayout>
|
||||
@@ -0,0 +1,294 @@
|
||||
---
|
||||
// University ERP — keyword-focused service landing page.
|
||||
// Reuses GuaranteeBand, InlineLeadForm and ServiceFaq, and feeds Service + FAQ
|
||||
// schema into SEO.astro.
|
||||
//
|
||||
// Case-study numbers are taken verbatim from the IIT Patna University
|
||||
// Management System entry on /portfolio: 3,000+ students · 500+ courses ·
|
||||
// 200+ faculty.
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import SEO from '../../components/SEO.astro';
|
||||
import GuaranteeBand from '../../components/GuaranteeBand.astro';
|
||||
import InlineLeadForm from '../../components/InlineLeadForm.astro';
|
||||
import ServiceFaq from '../../components/ServiceFaq.astro';
|
||||
|
||||
const pageTitle = 'University ERP & Management System';
|
||||
const pageDescription =
|
||||
'University ERP development — admissions, registration, examinations, grading, and faculty management in one platform. WorkRoot built the IIT Patna University Management System.';
|
||||
|
||||
const serviceName = 'University ERP & Management System';
|
||||
const serviceDescription =
|
||||
'Custom university ERP and management systems covering the full student lifecycle — admissions, course registration, examination scheduling, grade management, and faculty administration. Built on Next.js and Spring Boot, delivered for the Indian Institute of Technology, Patna.';
|
||||
|
||||
const modules = [
|
||||
{ title: 'Admissions', body: 'Online applications, document handling, and seat allocation that replace fragmented manual intake.' },
|
||||
{ title: 'Course registration', body: 'Self-service registration with prerequisite checks, capacity limits, and timetable clash detection.' },
|
||||
{ title: 'Examination scheduling', body: 'Exam planning, hall and invigilator allocation, and conflict-free timetabling.' },
|
||||
{ title: 'Grade management', body: 'Mark entry, moderation, GPA/CGPA computation, and transcript generation.' },
|
||||
{ title: 'Faculty administration', body: 'Faculty profiles, course assignments, and workload tracking for 200+ faculty in production.' },
|
||||
{ title: 'Student lifecycle', body: 'A unified record from admission to graduation, with role-based access for staff and students.' },
|
||||
];
|
||||
|
||||
const stats = [
|
||||
{ value: '3,000+', label: 'Students served' },
|
||||
{ value: '500+', label: 'Courses managed' },
|
||||
{ value: '200+', label: 'Faculty onboarded' },
|
||||
{ value: '1', label: 'Unified platform' },
|
||||
];
|
||||
|
||||
// Fragmented legacy vs unified university ERP comparison.
|
||||
const comparison = [
|
||||
{ dimension: 'Admissions', legacy: 'Separate intake tools and spreadsheets', erp: 'One online admissions pipeline' },
|
||||
{ dimension: 'Registration', legacy: 'Manual forms, clash errors', erp: 'Self-service with prerequisite & clash checks' },
|
||||
{ dimension: 'Examinations', legacy: 'Standalone scheduling, hall conflicts', erp: 'Conflict-free exam timetabling' },
|
||||
{ dimension: 'Grading', legacy: 'Disconnected mark sheets', erp: 'Automated GPA/CGPA and transcripts' },
|
||||
{ dimension: 'Faculty data', legacy: 'Siloed department records', erp: 'Central faculty administration' },
|
||||
{ dimension: 'Student record', legacy: 'Scattered across systems', erp: 'Single lifecycle record of truth' },
|
||||
];
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: 'Have you built a university management system before?',
|
||||
answer:
|
||||
'Yes. WorkRoot built the University Management System for the Indian Institute of Technology, Patna. It manages admissions, course registration, examination scheduling, grade management, faculty profiles, and administrative workflows for 3,000+ students, 500+ courses, and 200+ faculty.',
|
||||
},
|
||||
{
|
||||
question: 'What modules does a university ERP include?',
|
||||
answer:
|
||||
'A typical build covers admissions, course registration, examination scheduling, grade and transcript management, faculty administration, and the overall student lifecycle — all under role-based access for staff and students. We tailor the exact module set to your institution during scoping.',
|
||||
},
|
||||
{
|
||||
question: 'Can it replace our existing fragmented systems?',
|
||||
answer:
|
||||
'Yes — that is the usual reason institutions engage us. IIT Patna needed to replace fragmented legacy systems with one unified platform, which is exactly what we delivered. We migrate existing data and consolidate intake, registration, examinations, and records into a single source of truth.',
|
||||
},
|
||||
{
|
||||
question: 'Will it scale for our whole campus?',
|
||||
answer:
|
||||
'Yes. The IIT Patna platform was designed for scalability and high availability to serve the entire academic community. Our architecture handles concurrent registration and exam periods — the peak-load moments that break smaller systems.',
|
||||
},
|
||||
{
|
||||
question: 'How long does a university ERP take to deliver?',
|
||||
answer:
|
||||
'We deliver module by module so value lands early — a first module in weeks, with full rollout over a few months depending on scope. Working in 2-week sprints with a live staging environment, your team sees and steers the system as it is built.',
|
||||
},
|
||||
{
|
||||
question: 'What technology do you use for university ERPs?',
|
||||
answer:
|
||||
'The IIT Patna system is built on Next.js for the interface and Spring Boot for the backend, with MySQL for the data layer — chosen for scalability, maintainability, and high availability across the academic community.',
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<BaseLayout title={pageTitle} description={pageDescription}>
|
||||
<SEO
|
||||
slot="head"
|
||||
type="Service"
|
||||
breadcrumbs={[
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Services', url: '/services' },
|
||||
{ name: 'University ERP', url: '/services/university-erp' },
|
||||
]}
|
||||
service={{ name: serviceName, description: serviceDescription }}
|
||||
faq={faqs}
|
||||
/>
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="relative bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900 py-20 lg:py-28 overflow-hidden">
|
||||
<div class="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
|
||||
<div class="absolute top-1/4 left-0 w-80 h-80 bg-purple-500/10 rounded-full blur-3xl animate-pulse-slower"></div>
|
||||
<div class="absolute bottom-0 right-1/4 w-96 h-96 bg-indigo-500/10 rounded-full blur-3xl animate-pulse-slow"></div>
|
||||
<div class="absolute inset-0 opacity-5" style="background-image: linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px); background-size: 60px 60px;"></div>
|
||||
</div>
|
||||
<div class="container-wrapper relative">
|
||||
<div class="max-w-3xl">
|
||||
<nav aria-label="Breadcrumb" class="mb-6">
|
||||
<ol class="flex flex-wrap items-center gap-2 text-sm text-secondary-400">
|
||||
<li><a href="/" class="hover:text-primary-300">Home</a></li>
|
||||
<li aria-hidden="true">/</li>
|
||||
<li><a href="/services" class="hover:text-primary-300">Services</a></li>
|
||||
<li aria-hidden="true">/</li>
|
||||
<li class="text-secondary-200" aria-current="page">University ERP</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-6 text-sm font-semibold text-purple-300 bg-purple-500/10 rounded-full border border-purple-500/20">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-purple-400 animate-pulse" aria-hidden="true"></span>
|
||||
Higher-education software
|
||||
</span>
|
||||
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white mb-6 leading-tight">
|
||||
University ERP that runs
|
||||
<span class="text-transparent bg-clip-text bg-gradient-to-r from-purple-400 via-indigo-300 to-primary-300"> the whole student lifecycle.</span>
|
||||
</h1>
|
||||
<p class="text-lg sm:text-xl text-secondary-300 max-w-2xl mb-8 leading-relaxed">
|
||||
Admissions, registration, examinations, grading, and faculty — one platform instead of a dozen disconnected tools.
|
||||
We built exactly this for the Indian Institute of Technology, Patna, on Next.js and Spring Boot.
|
||||
</p>
|
||||
<div class="flex flex-col sm:flex-row gap-4">
|
||||
<a href="#inline-lead-form" data-cta="uni-hero-primary" class="inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-primary-500 to-primary-600 px-8 py-4 text-base font-bold text-white shadow-lg hover:shadow-xl hover:scale-105 transition-all focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-secondary-900">
|
||||
Scope my university ERP
|
||||
<svg class="ml-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
|
||||
</a>
|
||||
<a href="tel:+919561417403" data-cta="uni-hero-call" class="inline-flex items-center justify-center rounded-xl border-2 border-white/30 bg-white/5 px-8 py-4 text-base font-bold text-white hover:bg-white/10 transition-all focus:outline-none focus:ring-2 focus:ring-white/50">
|
||||
Talk to our team
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-14 grid grid-cols-2 sm:grid-cols-4 gap-4 lg:gap-6">
|
||||
{stats.map((stat) => (
|
||||
<div class="bg-white/5 backdrop-blur-sm rounded-xl p-4 lg:p-5 text-center border border-white/10">
|
||||
<p class="text-2xl lg:text-3xl font-bold text-white">{stat.value}</p>
|
||||
<p class="text-secondary-400 text-sm mt-1">{stat.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Overview -->
|
||||
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<span class="text-purple-600 dark:text-purple-400 font-semibold text-sm uppercase tracking-wider">Built for institutions</span>
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mt-2 mb-6">One platform for admissions through graduation</h2>
|
||||
<div class="space-y-5 text-lg text-secondary-700 dark:text-secondary-300 leading-relaxed">
|
||||
<p>
|
||||
Universities accumulate software the way they accumulate buildings — one tool for admissions, another for
|
||||
registration, a spreadsheet for grades, a separate system for faculty. Each works in isolation, but the seams show at
|
||||
exactly the wrong moments: registration week, exam season, results day. A university ERP replaces that patchwork with a
|
||||
single platform that follows each student from application to graduation, and gives staff one place to work.
|
||||
</p>
|
||||
<p>
|
||||
WorkRoot delivered precisely this for the <strong>Indian Institute of Technology, Patna</strong>. The institution
|
||||
needed to replace fragmented legacy systems with a unified platform spanning admissions, course registration,
|
||||
examination scheduling, grade management, faculty profiles, and administrative workflows. We built it on a
|
||||
<strong> Next.js</strong> front end with a <strong>Spring Boot</strong> backend and a <strong>MySQL</strong> data
|
||||
layer, engineered for scalability and high availability so it holds up when the whole campus logs in at once. In
|
||||
production it serves <strong>3,000+ students</strong>, <strong>500+ courses</strong>, and <strong>200+ faculty</strong>.
|
||||
</p>
|
||||
<p>
|
||||
The hard part of academic software is the peak. Registration and examinations concentrate enormous load into a few
|
||||
days, and the modules have to coordinate — prerequisites gate registration, registration drives exam scheduling,
|
||||
exams feed grading, grades roll into transcripts. We model those dependencies explicitly so the system stays
|
||||
conflict-free under pressure: capacity limits and clash detection at registration, hall and invigilator allocation at
|
||||
exam time, and automated GPA/CGPA computation and transcript generation at results.
|
||||
</p>
|
||||
<p>
|
||||
Role-based access keeps students, faculty, and administrators each seeing only what they should, and the unified
|
||||
student record becomes a single source of truth that ends the reconciliation scramble between departments. The
|
||||
platform is yours on delivery — full source code, documentation, and deployment access — so your institution stays in
|
||||
control of its most important data. If you are ready to retire a fragmented stack, we have built the replacement
|
||||
before, at an IIT.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Modules grid -->
|
||||
<section class="py-16 lg:py-24 bg-secondary-50 dark:bg-secondary-800/50">
|
||||
<div class="container-wrapper">
|
||||
<div class="text-center max-w-2xl mx-auto mb-12">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">University ERP modules</h2>
|
||||
<p class="text-secondary-600 dark:text-secondary-400 text-lg">The full academic lifecycle, in one connected system.</p>
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{modules.map((m) => (
|
||||
<div class="bg-white dark:bg-secondary-800 rounded-2xl p-7 border border-secondary-100 dark:border-secondary-700 shadow-sm hover:shadow-lg transition-shadow">
|
||||
<div class="w-12 h-12 rounded-xl bg-purple-50 dark:bg-purple-900/30 text-purple-600 dark:text-purple-300 flex items-center justify-center mb-4" aria-hidden="true">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 14l9-5-9-5-9 5 9 5z" /><path stroke-linecap="round" stroke-linejoin="round" d="M12 14l6.16-3.422A12.083 12.083 0 0112 21.5a12.083 12.083 0 01-6.16-10.922L12 14z" /></svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-bold text-secondary-900 dark:text-secondary-100 mb-2">{m.title}</h3>
|
||||
<p class="text-secondary-600 dark:text-secondary-400 text-sm leading-relaxed">{m.body}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Comparison table -->
|
||||
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="text-center mb-10">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">Fragmented tools vs a unified ERP</h2>
|
||||
<p class="text-secondary-600 dark:text-secondary-400 text-lg">What changes when everything connects.</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 fragmented academic tools versus a unified WorkRoot university ERP</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">Area</th>
|
||||
<th scope="col" class="px-5 py-4 text-sm font-bold text-secondary-500 dark:text-secondary-400">Fragmented tools</th>
|
||||
<th scope="col" class="px-5 py-4 text-sm font-bold text-primary-700 dark:text-primary-300">Unified ERP (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.legacy}</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.erp}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Case study -->
|
||||
<section class="py-16 lg:py-24 bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900">
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-5 text-sm font-semibold text-purple-300 bg-purple-500/10 rounded-full border border-purple-500/20">Case study · Higher education</span>
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">University Management System — IIT Patna</h2>
|
||||
<p class="text-lg text-secondary-300 leading-relaxed mb-8">
|
||||
An end-to-end University Management System for the Indian Institute of Technology, Patna — managing student admissions,
|
||||
course registration, examination scheduling, grade management, faculty profiles, and administrative workflows in one
|
||||
scalable, high-availability platform.
|
||||
</p>
|
||||
<div class="grid grid-cols-3 gap-4 mb-8">
|
||||
{[{ v: '3,000+', l: 'Students served' }, { v: '500+', l: 'Courses managed' }, { v: '200+', l: 'Faculty onboarded' }].map((s) => (
|
||||
<div class="bg-white/5 border border-white/10 rounded-xl p-5 text-center">
|
||||
<p class="text-2xl sm:text-3xl font-bold text-white">{s.v}</p>
|
||||
<p class="text-secondary-400 text-xs sm:text-sm mt-1">{s.l}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<blockquote class="border-l-4 border-primary-500 pl-5 text-secondary-200 italic leading-relaxed">
|
||||
“WorkRoot delivered our University Management System for IIT Patna on time and within budget. The Next.js + Spring Boot
|
||||
architecture is robust and scalable. They are a reliable technology partner for complex enterprise projects.”
|
||||
<footer class="mt-3 not-italic text-sm text-secondary-400">— Dilip Raj, Director, MOC Soft Technologies Pvt Ltd</footer>
|
||||
</blockquote>
|
||||
<div class="mt-8 flex flex-wrap gap-4">
|
||||
<a href="/portfolio#university-management-iitp" class="inline-flex items-center gap-2 text-primary-300 font-semibold hover:text-primary-200">
|
||||
Read the full case study
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
|
||||
</a>
|
||||
<a href="/services/custom-erp-development" class="inline-flex items-center gap-2 text-secondary-300 font-semibold hover:text-white">
|
||||
Explore custom ERP development
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" /></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ServiceFaq
|
||||
faqs={faqs}
|
||||
eyebrow="For institutions"
|
||||
heading="University ERP — your questions answered"
|
||||
subheading="Track record, modules, migration, scale, timelines, and the stack we use."
|
||||
/>
|
||||
|
||||
<GuaranteeBand />
|
||||
<InlineLeadForm />
|
||||
</BaseLayout>
|
||||
@@ -22,10 +22,22 @@ const sitemapSections = [
|
||||
title: 'Services',
|
||||
icon: 'briefcase',
|
||||
links: [
|
||||
{ name: 'All Services', url: '/services', description: 'Our full range of IT service offerings' },
|
||||
{ name: 'Web Development', url: '/services#web-development', description: 'Custom web applications and e-commerce' },
|
||||
{ name: 'Mobile Development', url: '/services#mobile-development', description: 'iOS, Android, and cross-platform apps' },
|
||||
{ name: 'AI & Machine Learning', url: '/services#ai-ml', description: 'Intelligent solutions and automation' },
|
||||
{ name: 'Cloud Services', url: '/services#cloud', description: 'Cloud migration and infrastructure' },
|
||||
{ name: 'UI/UX Design', url: '/services#ui-ux-design', description: 'Accessible, conversion-focused interfaces' },
|
||||
{ name: 'IT Consulting & DevOps', url: '/services#it-consulting', description: 'Architecture, cloud, and CI/CD' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Solutions',
|
||||
icon: 'briefcase',
|
||||
links: [
|
||||
{ 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: '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: 'React Native App Development', url: '/services/react-native-app-development', description: 'One codebase for iOS and Android' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -78,8 +90,8 @@ const icons: Record<string, string> = {
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="Sitemap"
|
||||
description="WorkRoot IT Solutions Sitemap - Navigate to all pages and resources on our website."
|
||||
title="Sitemap — Browse All Pages and Resources"
|
||||
description="Browse every page on WorkRoot IT Solutions in one place — services, portfolio, blog, about, contact, and our web, mobile, ERP, and government IT resources."
|
||||
>
|
||||
<!-- Hero Section -->
|
||||
<section class="relative bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900 py-16 lg:py-20">
|
||||
|
||||
@@ -25,6 +25,12 @@ export const GET: APIRoute = async () => {
|
||||
const staticPages: SitemapEntry[] = [
|
||||
{ url: '/', changefreq: 'weekly', priority: 1.0 },
|
||||
{ url: '/services/', changefreq: 'monthly', priority: 0.9 },
|
||||
// Keyword-focused service landing pages
|
||||
{ url: '/services/custom-erp-development/', changefreq: 'monthly', priority: 0.8 },
|
||||
{ url: '/services/government-it-solutions-gem-vendor/', changefreq: 'monthly', priority: 0.8 },
|
||||
{ url: '/services/police-management-system/', changefreq: 'monthly', priority: 0.8 },
|
||||
{ url: '/services/react-native-app-development/', changefreq: 'monthly', priority: 0.8 },
|
||||
{ url: '/services/university-erp/', changefreq: 'monthly', priority: 0.8 },
|
||||
{ url: '/portfolio/', changefreq: 'weekly', priority: 0.8 },
|
||||
{ url: '/about/', changefreq: 'monthly', priority: 0.8 },
|
||||
{ url: '/blog/', changefreq: 'weekly', priority: 0.8 },
|
||||
|
||||
@@ -8,8 +8,8 @@ const lastUpdated = 'March 20, 2026';
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="Terms of Service"
|
||||
description="WorkRoot IT Solutions Terms of Service - Read our terms and conditions for using our website and services."
|
||||
title="Terms of Service — Website & Project Terms"
|
||||
description="Read the WorkRoot IT Solutions terms of service covering acceptable use of our website, project engagements, intellectual property, liability, and warranties."
|
||||
noIndex={true}
|
||||
>
|
||||
<!-- Hero Section -->
|
||||
@@ -297,7 +297,7 @@ const lastUpdated = 'March 20, 2026';
|
||||
<div class="bg-secondary-900/50 border border-secondary-800 rounded-xl p-6">
|
||||
<p class="text-secondary-300"><strong class="text-white">WorkRoot IT Solutions</strong></p>
|
||||
<p class="text-secondary-300">Email: admin@workroot.in</p>
|
||||
<p class="text-secondary-300">Phone: +91 9561417403</p>
|
||||
<p class="text-secondary-300">Phone: <a href="tel:+919561417403" class="text-primary-400 hover:underline">+91 9561417403</a></p>
|
||||
<p class="text-secondary-300">Address: At Post Rajapur Shantinagar, Taluka Khatav, Dist Satara, MH 415503</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -84,7 +84,7 @@ export function generateArticleSchema(
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'WorkRoot IT Solutions',
|
||||
name: 'WorkRoot IT Solutions LLP',
|
||||
logo: {
|
||||
'@type': 'ImageObject',
|
||||
url: 'https://workroot.in/logo.png',
|
||||
@@ -132,7 +132,7 @@ export function generateServiceSchema(
|
||||
description: description,
|
||||
provider: {
|
||||
'@type': 'Organization',
|
||||
name: 'WorkRoot IT Solutions',
|
||||
name: 'WorkRoot IT Solutions LLP',
|
||||
url: siteUrl,
|
||||
},
|
||||
areaServed: {
|
||||
|
||||
@@ -6,7 +6,10 @@ import { test, expect } from '@playwright/test';
|
||||
* Covers:
|
||||
* - The form is mounted on the homepage between FAQ/tech-stack and the
|
||||
* final CTA band.
|
||||
* - Filling all three fields and submitting calls /api/contact and shows
|
||||
* - It exposes the compact 5-field layout (name, email, phone, project type,
|
||||
* message) with the correct mobile keyboard hints per field. Only name,
|
||||
* email and project type are required; phone and message are optional.
|
||||
* - Filling the required fields and submitting calls /api/contact and shows
|
||||
* an in-place thank-you state without a full-page navigation.
|
||||
* - The submission is tagged `source: 'homepage-inline'` so leads can be
|
||||
* segmented downstream.
|
||||
@@ -15,7 +18,7 @@ import { test, expect } from '@playwright/test';
|
||||
*/
|
||||
|
||||
test.describe('Inline Lead Form (homepage)', () => {
|
||||
test('form is visible on the homepage and has the three required fields', async ({ page }) => {
|
||||
test('form is visible on the homepage with the compact field set and mobile keyboards', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
const form = page.getByTestId('inline-lead-form');
|
||||
@@ -23,8 +26,10 @@ test.describe('Inline Lead Form (homepage)', () => {
|
||||
await expect(form).toBeVisible();
|
||||
|
||||
await expect(page.locator('#inline-lead-name')).toBeVisible();
|
||||
await expect(page.locator('#inline-lead-contact')).toBeVisible();
|
||||
await expect(page.locator('#inline-lead-email')).toBeVisible();
|
||||
await expect(page.locator('#inline-lead-phone')).toBeVisible();
|
||||
await expect(page.locator('#inline-lead-project-type')).toBeVisible();
|
||||
await expect(page.locator('#inline-lead-message')).toBeVisible();
|
||||
|
||||
// Touch-target sanity: the submit button must be at least 48px tall.
|
||||
const submit = page.locator('#inline-lead-submit');
|
||||
@@ -32,9 +37,17 @@ test.describe('Inline Lead Form (homepage)', () => {
|
||||
expect(submitBox).not.toBeNull();
|
||||
expect(submitBox!.height).toBeGreaterThanOrEqual(48);
|
||||
|
||||
// Correct mobile-friendly attributes on the contact field.
|
||||
await expect(page.locator('#inline-lead-contact')).toHaveAttribute('autocomplete', 'email tel');
|
||||
await expect(page.locator('#inline-lead-contact')).toHaveAttribute('inputmode', 'email');
|
||||
// Correct mobile keyboard per field.
|
||||
await expect(page.locator('#inline-lead-email')).toHaveAttribute('type', 'email');
|
||||
await expect(page.locator('#inline-lead-email')).toHaveAttribute('inputmode', 'email');
|
||||
await expect(page.locator('#inline-lead-email')).toHaveAttribute('autocomplete', 'email');
|
||||
await expect(page.locator('#inline-lead-phone')).toHaveAttribute('type', 'tel');
|
||||
await expect(page.locator('#inline-lead-phone')).toHaveAttribute('inputmode', 'tel');
|
||||
await expect(page.locator('#inline-lead-phone')).toHaveAttribute('autocomplete', 'tel');
|
||||
|
||||
// Phone + message are optional (no `required` attribute).
|
||||
await expect(page.locator('#inline-lead-phone')).not.toHaveAttribute('required', /.*/);
|
||||
await expect(page.locator('#inline-lead-message')).not.toHaveAttribute('required', /.*/);
|
||||
});
|
||||
|
||||
test('blocks submit when required fields are empty', async ({ page }) => {
|
||||
@@ -57,12 +70,12 @@ test.describe('Inline Lead Form (homepage)', () => {
|
||||
|
||||
// The blank-field errors should surface and the network should NOT be hit.
|
||||
await expect(page.locator('#inline-lead-name-error')).toBeVisible();
|
||||
await expect(page.locator('#inline-lead-contact-error')).toBeVisible();
|
||||
await expect(page.locator('#inline-lead-email-error')).toBeVisible();
|
||||
await expect(page.locator('#inline-lead-project-type-error')).toBeVisible();
|
||||
expect(apiCalled).toBe(false);
|
||||
});
|
||||
|
||||
test('fills and submits the form successfully with an email contact', async ({ page }) => {
|
||||
test('fills the required fields and submits successfully', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
const form = page.getByTestId('inline-lead-form');
|
||||
await form.scrollIntoViewIfNeeded();
|
||||
@@ -85,7 +98,7 @@ test.describe('Inline Lead Form (homepage)', () => {
|
||||
});
|
||||
|
||||
await page.locator('#inline-lead-name').fill('Inline Lead Tester');
|
||||
await page.locator('#inline-lead-contact').fill('inline-lead@example.com');
|
||||
await page.locator('#inline-lead-email').fill('inline-lead@example.com');
|
||||
await page.locator('#inline-lead-project-type').selectOption('Web App');
|
||||
await page.locator('#inline-lead-submit').click();
|
||||
|
||||
@@ -103,11 +116,14 @@ test.describe('Inline Lead Form (homepage)', () => {
|
||||
expect(captured.name).toBe('Inline Lead Tester');
|
||||
expect(captured.email).toBe('inline-lead@example.com');
|
||||
expect(captured.subject).toBe('web-development');
|
||||
// Message is synthesized when left blank so the backend's >=10 char rule passes.
|
||||
expect(typeof captured.message).toBe('string');
|
||||
expect(captured.message.length).toBeGreaterThanOrEqual(10);
|
||||
// Honeypot must be empty for a real user submission.
|
||||
expect(captured.website ?? '').toBe('');
|
||||
});
|
||||
|
||||
test('routes a phone-shaped contact value into the phone field', async ({ page }) => {
|
||||
test('sends the phone and a typed message through their own fields', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
const form = page.getByTestId('inline-lead-form');
|
||||
await form.scrollIntoViewIfNeeded();
|
||||
@@ -127,17 +143,19 @@ test.describe('Inline Lead Form (homepage)', () => {
|
||||
});
|
||||
|
||||
await page.locator('#inline-lead-name').fill('Phone Lead');
|
||||
await page.locator('#inline-lead-contact').fill('+91 95614 17403');
|
||||
await page.locator('#inline-lead-email').fill('phone-lead@example.com');
|
||||
await page.locator('#inline-lead-phone').fill('+91 95614 17403');
|
||||
await page.locator('#inline-lead-project-type').selectOption('Government / GeM');
|
||||
await page.locator('#inline-lead-message').fill('We need a GeM-ready procurement portal.');
|
||||
await page.locator('#inline-lead-submit').click();
|
||||
|
||||
await expect(page.getByTestId('inline-lead-success')).toBeVisible();
|
||||
|
||||
expect(captured).toBeTruthy();
|
||||
// The real email and phone are sent as-is in their own fields.
|
||||
expect(captured.email).toBe('phone-lead@example.com');
|
||||
expect(captured.phone).toBe('+91 95614 17403');
|
||||
// The client synthesizes a placeholder email so the backend's strict email
|
||||
// validator accepts phone-only contacts. The real phone is what matters.
|
||||
expect(captured.email).toMatch(/^lead-\d+@homepage\.workroot\.in$/);
|
||||
expect(captured.message).toBe('We need a GeM-ready procurement portal.');
|
||||
expect(captured.subject).toBe('government-systems');
|
||||
expect(captured.source).toBe('homepage-inline');
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 814 KiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 511 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 512 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 511 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 455 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 384 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 384 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 386 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 157 KiB |