Compare commits

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:08:31 +05:30
platform-mcp 19a0720fe8 pre-agent: Apply WorkRoot logo sitewide and as favicon 2026-06-17 15:03:27 +05:30
platform-mcp 5c05779541 agent: Fix mobile responsiveness and conversion across pa 2026-06-17 14:51:43 +05:30
platform-mcp ce67f90f06 pre-agent: Fix mobile responsiveness and conversion across pa 2026-06-17 14:42:24 +05:30
platform-mcp fb5e1ea281 agent: Improve blog content and lead funnel 2026-06-17 14:41:57 +05:30
platform-mcp 03c37a623f pre-agent: Improve blog content and lead funnel 2026-06-17 14:34:26 +05:30
16 changed files with 1003 additions and 497 deletions

No files matched your search

+39 -34
View File
@@ -1,11 +1,11 @@
# Graph Report - 2e709500-79ab-403f-9ca0-3f027ac9f36e (2026-06-17) # Graph Report - 2e709500-79ab-403f-9ca0-3f027ac9f36e (2026-06-17)
## Corpus Check ## Corpus Check
- 57 files · ~323,606 words - 58 files · ~324,818 words
- Verdict: corpus is large enough that graph structure adds value. - Verdict: corpus is large enough that graph structure adds value.
## Summary ## Summary
- 522 nodes · 567 edges · 93 communities (86 shown, 7 thin omitted) - 536 nodes · 580 edges · 94 communities (87 shown, 7 thin omitted)
- Extraction: 99% EXTRACTED · 1% INFERRED · 0% AMBIGUOUS · INFERRED: 4 edges (avg confidence: 0.8) - Extraction: 99% EXTRACTED · 1% INFERRED · 0% AMBIGUOUS · INFERRED: 4 edges (avg confidence: 0.8)
- Token cost: 0 input · 0 output - Token cost: 0 input · 0 output
@@ -23,9 +23,9 @@
- [[_COMMUNITY_Community 5|Community 5]] - [[_COMMUNITY_Community 5|Community 5]]
- [[_COMMUNITY_Community 6|Community 6]] - [[_COMMUNITY_Community 6|Community 6]]
- [[_COMMUNITY_Community 7|Community 7]] - [[_COMMUNITY_Community 7|Community 7]]
- [[_COMMUNITY_Community 9|Community 9]] - [[_COMMUNITY_Community 8|Community 8]]
- [[_COMMUNITY_Community 10|Community 10]] - [[_COMMUNITY_Community 10|Community 10]]
- [[_COMMUNITY_Community 12|Community 12]] - [[_COMMUNITY_Community 11|Community 11]]
- [[_COMMUNITY_Community 13|Community 13]] - [[_COMMUNITY_Community 13|Community 13]]
- [[_COMMUNITY_Community 14|Community 14]] - [[_COMMUNITY_Community 14|Community 14]]
- [[_COMMUNITY_Community 15|Community 15]] - [[_COMMUNITY_Community 15|Community 15]]
@@ -49,7 +49,8 @@
- [[_COMMUNITY_Community 33|Community 33]] - [[_COMMUNITY_Community 33|Community 33]]
- [[_COMMUNITY_Community 34|Community 34]] - [[_COMMUNITY_Community 34|Community 34]]
- [[_COMMUNITY_Community 35|Community 35]] - [[_COMMUNITY_Community 35|Community 35]]
- [[_COMMUNITY_Community 40|Community 40]] - [[_COMMUNITY_Community 36|Community 36]]
- [[_COMMUNITY_Community 41|Community 41]]
## God Nodes (most connected - your core abstractions) ## God Nodes (most connected - your core abstractions)
1. `Performance Testing Scripts` - 12 edges 1. `Performance Testing Scripts` - 12 edges
@@ -73,7 +74,7 @@
- `POST()` --calls--> `logApiRequest()` [INFERRED] - `POST()` --calls--> `logApiRequest()` [INFERRED]
src/pages/api/newsletter.ts → src/utils/logger.ts src/pages/api/newsletter.ts → src/utils/logger.ts
## Communities (93 total, 7 thin omitted) ## Communities (94 total, 7 thin omitted)
### Community 0 - "Community 0" ### Community 0 - "Community 0"
Cohesion: 0.05 Cohesion: 0.05
@@ -105,90 +106,94 @@ Nodes (15): 1. Modular Monolith Over Premature Microservices, 2. Role-Based Acce
### Community 7 - "Community 7" ### Community 7 - "Community 7"
Cohesion: 0.14 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" ### Community 8 - "Community 8"
Cohesion: 0.17 Cohesion: 0.14
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) 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" ### Community 10 - "Community 10"
Cohesion: 0.17 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) 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 Cohesion: 0.18
Nodes (10): code:hcl (# Define AWS EC2 instance), code:dockerfile (# Multi-stage build for a Go application), code:yaml (# AWS Cost allocation tags), Common Pitfalls, Conclusion, Containerization as a Migration Strategy, Cost Optimization Strategies, Infrastructure as Code (+2 more) Nodes (10): code:hcl (# Define AWS EC2 instance), code:dockerfile (# Multi-stage build for a Go application), code:yaml (# AWS Cost allocation tags), Common Pitfalls, Conclusion, Containerization as a Migration Strategy, Cost Optimization Strategies, Infrastructure as Code (+2 more)
### Community 17 - "Community 17" ### Community 18 - "Community 18"
Cohesion: 0.43 Cohesion: 0.43
Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter() Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter()
### Community 18 - "Community 18" ### Community 19 - "Community 19"
Cohesion: 0.36 Cohesion: 0.36
Nodes (7): extract_scores(), get_issues(), main(), Run Lighthouse audit on a URL, Extract key scores from Lighthouse data, Extract issues from Lighthouse audit, run_lighthouse() Nodes (7): extract_scores(), get_issues(), main(), Run Lighthouse audit on a URL, Extract key scores from Lighthouse data, Extract issues from Lighthouse audit, run_lighthouse()
### Community 19 - "Community 19" ### Community 20 - "Community 20"
Cohesion: 0.46 Cohesion: 0.46
Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady() Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady()
### Community 20 - "Community 20" ### Community 21 - "Community 21"
Cohesion: 0.36 Cohesion: 0.36
Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue() Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue()
### Community 21 - "Community 21" ### Community 22 - "Community 22"
Cohesion: 0.46 Cohesion: 0.46
Nodes (7): checkRateLimit(), corsHeaders(), escapeHtml(), OPTIONS(), POST(), sendContactEmail(), validateContactForm() Nodes (7): checkRateLimit(), corsHeaders(), escapeHtml(), OPTIONS(), POST(), sendContactEmail(), validateContactForm()
### Community 22 - "Community 22" ### Community 23 - "Community 23"
Cohesion: 0.43 Cohesion: 0.43
Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter() Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter()
### Community 23 - "Community 23" ### Community 24 - "Community 24"
Cohesion: 0.36 Cohesion: 0.36
Nodes (7): extract_scores(), get_issues(), main(), Run Lighthouse audit on a URL, Extract key scores from Lighthouse data, Extract issues from Lighthouse audit, run_lighthouse() Nodes (7): extract_scores(), get_issues(), main(), Run Lighthouse audit on a URL, Extract key scores from Lighthouse data, Extract issues from Lighthouse audit, run_lighthouse()
### Community 24 - "Community 24" ### Community 25 - "Community 25"
Cohesion: 0.46 Cohesion: 0.46
Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady() Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady()
### Community 25 - "Community 25" ### Community 26 - "Community 26"
Cohesion: 0.36 Cohesion: 0.36
Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue() Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue()
### Community 26 - "Community 26"
Cohesion: 0.48
Nodes (5): initAllAnimations(), initButtonRipple(), initCounters(), initProgressBars(), initScrollReveal()
### Community 27 - "Community 27" ### Community 27 - "Community 27"
Cohesion: 0.48 Cohesion: 0.48
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard() Nodes (5): initAllAnimations(), initButtonRipple(), initCounters(), initProgressBars(), initScrollReveal()
### Community 28 - "Community 28" ### Community 28 - "Community 28"
Cohesion: 0.48 Cohesion: 0.48
Nodes (5): initAllAnimations(), initButtonRipple(), initCounters(), initProgressBars(), initScrollReveal() Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard()
### Community 29 - "Community 29" ### Community 29 - "Community 29"
Cohesion: 0.48 Cohesion: 0.48
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard() Nodes (5): initAllAnimations(), initButtonRipple(), initCounters(), initProgressBars(), initScrollReveal()
### Community 30 - "Community 30" ### Community 30 - "Community 30"
Cohesion: 0.48
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard()
### Community 31 - "Community 31"
Cohesion: 0.47 Cohesion: 0.47
Nodes (5): main(), measure_response_time(), print_stats(), Measure response time for a URL over multiple iterations, Print statistics for response times Nodes (5): main(), measure_response_time(), print_stats(), Measure response time for a URL over multiple iterations, Print statistics for response times
### Community 32 - "Community 32" ### Community 33 - "Community 33"
Cohesion: 0.47 Cohesion: 0.47
Nodes (5): main(), measure_response_time(), print_stats(), Measure response time for a URL over multiple iterations, Print statistics for response times Nodes (5): main(), measure_response_time(), print_stats(), Measure response time for a URL over multiple iterations, Print statistics for response times
### Community 34 - "Community 34"
Cohesion: 0.6
Nodes (3): formatUptime(), GET(), percentile()
### Community 35 - "Community 35" ### Community 35 - "Community 35"
Cohesion: 0.6 Cohesion: 0.6
Nodes (3): formatUptime(), GET(), percentile() Nodes (3): formatUptime(), GET(), percentile()
### Community 36 - "Community 36"
Cohesion: 0.6
Nodes (3): formatUptime(), GET(), percentile()
## Knowledge Gaps ## 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) - **120 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` (+115 more)
These have ≤1 connection - possible missing edges or undocumented components. 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. - **7 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
@@ -198,7 +203,7 @@ _Questions this graph is uniquely positioned to answer:_
- **Are the 2 inferred relationships involving `captureException()` (e.g. with `sendContactEmail()` and `subscribeToNewsletter()`) actually correct?** - **Are the 2 inferred relationships involving `captureException()` (e.g. with `sendContactEmail()` and `subscribeToNewsletter()`) actually correct?**
_`captureException()` has 2 INFERRED edges - model-reasoned connections that need verification._ _`captureException()` has 2 INFERRED edges - model-reasoned connections that need verification._
- **What connects `Run Lighthouse audit on a URL`, `Extract key scores from Lighthouse data`, `Extract issues from Lighthouse audit` to the rest of the system?** - **What connects `Run Lighthouse audit on a URL`, `Extract key scores from Lighthouse data`, `Extract issues from Lighthouse audit` to the rest of the system?**
_111 weakly-connected nodes found - possible documentation gaps or missing edges._ _120 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `Community 0` be split into smaller, more focused modules?** - **Should `Community 0` be split into smaller, more focused modules?**
_Cohesion score 0.05 - nodes in this community are weakly interconnected._ _Cohesion score 0.05 - nodes in this community are weakly interconnected._
- **Should `Community 1` be split into smaller, more focused modules?** - **Should `Community 1` be split into smaller, more focused modules?**
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_api_contact_ts", "label": "contact.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/contact.ts", "source_location": "L1"}, {"id": "api_contact_checkratelimit", "label": "checkRateLimit()", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/contact.ts", "source_location": "L17"}, {"id": "api_contact_validatecontactform", "label": "validateContactForm()", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/contact.ts", "source_location": "L84"}, {"id": "api_contact_readsmtpconfig", "label": "readSmtpConfig()", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/contact.ts", "source_location": "L147"}, {"id": "api_contact_gettransporter", "label": "getTransporter()", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/contact.ts", "source_location": "L188"}, {"id": "api_contact_sendcontactemail", "label": "sendContactEmail()", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/contact.ts", "source_location": "L295"}, {"id": "api_contact_escapehtml", "label": "escapeHtml()", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/contact.ts", "source_location": "L468"}, {"id": "api_contact_corsheaders", "label": "corsHeaders()", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/contact.ts", "source_location": "L482"}, {"id": "api_contact_options", "label": "OPTIONS()", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/contact.ts", "source_location": "L507"}, {"id": "api_contact_post", "label": "POST()", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/contact.ts", "source_location": "L514"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_api_contact_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/api/contact.ts", "source_location": "L1", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_api_contact_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/pages/api/contact.ts", "source_location": "L2", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_api_contact_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/pages/api/contact.ts", "source_location": "L2", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_api_contact_ts", "target": "utils_logger_logapirequest", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/contact.ts", "source_location": "L2", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_api_contact_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/pages/api/contact.ts", "source_location": "L3", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_api_contact_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/pages/api/contact.ts", "source_location": "L3", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_api_contact_ts", "target": "api_contact_checkratelimit", "relation": "contaLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_content_blog_deploying_police_management_system_bihar_md", "label": "deploying-police-management-system-bihar.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/blog/deploying-police-management-system-bihar.md", "source_location": "L1"}, {"id": "blog_deploying_police_management_system_bihar_the_starting_point", "label": "The Starting Point", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/blog/deploying-police-management-system-bihar.md", "source_location": "L18"}, {"id": "blog_deploying_police_management_system_bihar_the_architecture_we_committed_to", "label": "The Architecture We Committed To", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/blog/deploying-police-management-system-bihar.md", "source_location": "L29"}, {"id": "blog_deploying_police_management_system_bihar_1_database_per_district_with_central_aggregation", "label": "1. Database-per-district with central aggregation", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/blog/deploying-police-management-system-bihar.md", "source_location": "L35"}, {"id": "blog_deploying_police_management_system_bihar_codeblock_1", "label": "code:yaml (# Per-district deployment descriptor)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/blog/deploying-police-management-system-bihar.md", "source_location": "L39"}, {"id": "blog_deploying_police_management_system_bihar_2_offline_first_for_field_critical_modules", "label": "2. Offline-first for field-critical modules", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/blog/deploying-police-management-system-bihar.md", "source_location": "L51"}, {"id": "blog_deploying_police_management_system_bihar_codeblock_2", "label": "code:java (@Scheduled(fixedDelay = 300_000) // every 5 minutes)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/blog/deploying-police-management-system-bihar.md", "source_location": "L55"}, {"id": "blog_deploying_police_management_system_bihar_3_audit_logging_baked_into_the_data_layer", "label": "3. Audit logging baked into the data layer", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/blog/deploying-police-management-system-bihar.md", "source_location": "L70"}, {"id": "blog_deploying_police_management_system_bihar_the_rollout_plan_that_actually_held_up", "label": "The Rollout Plan That Actually Held Up", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/blog/deploying-police-management-system-bihar.md", "source_location": "L74"}, {"id": "blog_deploying_police_management_system_bihar_codeblock_3", "label": "code:yaml (features:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/blog/deploying-police-management-system-bihar.md", "source_location": "L82"}, {"id": "blog_deploying_police_management_system_bihar_what_broke_because_something_always_does", "label": "What Broke (Because Something Always Does)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/blog/deploying-police-management-system-bihar.md", "source_location": "L95"}, {"id": "blog_deploying_police_management_system_bihar_the_numbers", "label": "The Numbers", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/blog/deploying-police-management-system-bihar.md", "source_location": "L103"}, {"id": "blog_deploying_police_management_system_bihar_what_we_d_tell_anyone_attempting_this", "label": "What We'd Tell Anyone Attempting This", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/blog/deploying-police-management-system-bihar.md", "source_location": "L113"}, {"id": "blog_deploying_police_management_system_bihar_building_something_similar", "label": "Building something similar?", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/blog/deploying-police-management-system-bihar.md", "source_location": "L121"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_content_blog_deploying_police_managemLine truncated
+4 -4
View File
@@ -61,12 +61,12 @@
</div> </div>
<div id="legend"></div> <div id="legend"></div>
</div> </div>
<div id="stats">522 nodes &middot; 567 edges &middot; 93 communities</div> <div id="stats">536 nodes &middot; 580 edges &middot; 94 communities</div>
</div> </div>
<script> <script>
const RAW_NODES = [{"id": "astro_config_mjs", "label": "astro.config.mjs", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 12.3, "font": {"size": 0, "color": "#ffffff"}, "title": "astro.config.mjs", "community": 41, "community_name": "Community 41", "source_file": "astro.config.mjs", "file_type": "code", "degree": 1}, {"id": "playwright_config_ts", "label": "playwright.config.ts", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "playwright.config.ts", "community": 48, "community_name": "Community 48", "source_file": "playwright.config.ts", "file_type": "code", "degree": 0}, {"id": "tailwind_config_mjs", "label": "tailwind.config.mjs", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "tailwind.config.mjs", "community": 49, "community_name": "Community 49", "source_file": "tailwind.config.mjs", "file_type": "code", "degree": 0}, {"id": "server_mjs", "label": "server.mjs", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "server.mjs", "community": 50, "community_name": "Community 50", "source_file": "server.mjs", "file_type": "code", "degree": 0}, {"id": "src_env_d_ts", "label": "env.d.ts", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "env.d.ts", "community": 51, "community_name": "Community 51", "source_file": "src/env.d.ts", "file_type": "code", "degree": 0}, {"id": "src_middleware_ts", "label": "middleware.ts", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 19.2, "font": {"size": 12, "color": "#ffffff"}, "title": "middleware.ts", "community": 2, "community_name": "Community 2", "source_file": "src/middleware.ts", "file_type": "code", "degree": 4}, {"id": "src_middleware_validatecsrforigin", "label": "validateCsrfOrigin()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 12.3, "font": {"size": 0, "color": "#ffffff"}, "title": "validateCsrfOrigin()", "community": 2, "community_name": "Community 2", "source_file": "src/middleware.ts", "file_type": "code", "degree": 1}, {"id": "src_content_config_ts", "label": "config.ts", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "config.ts", "community": 52, "community_name": "Community 52", "source_file": "src/content/config.ts", "file_type": "code", "degree": 0}, {"id": "src_pages_sitemap_xml_ts", "label": "sitemap.xml.ts", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 12.3, "font": {"size": 0, "color": "#ffffff"}, "title": "sitemap.xml.ts", "community": 42, "community_name": "Community 42", "source_file": "src/pages/sitemap.xml.ts", "file_type": "code", "degree": 1}, {"id": "pages_sitemap_xml_get", "label": "GET()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 12.3, "font": {"size": 0, "color": "#ffffff"}, "title": "GET()", "community": 42, "community_name": "Community 42", "source_file": "src/pages/sitemap.xml.ts", "file_type": "code", "degree": 1}, {"id": "src_pages_sitemap_index_xml_ts", "label": "sitemap-index.xml.ts", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 12.3, "font": {"size": 0, "color": "#ffffff"}, "title": "sitemap-index.xml.ts", "community": 43, "community_name": "Community 43", "source_file": "src/pages/sitemap-index.xml.ts", "file_type": "code", "degree": 1}, {"id": "pages_sitemap_index_xml_get", "label": "GET()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 12.3, "font": {"size": 0, "color": "#ffffff"}, "title": "GET()", "community": 43, "community_name": "Community 43", "source_file": "src/pages/sitemap-index.xml.ts", "file_type": "code", "degree": 1}, {"id": "src_pages_api_contact_ts", "label": "contact.ts", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 40.0, "font": {"size": 12, "color": "#ffffff"}, "title": "contact.ts", "community": 2, "community_name": "Community 2", "source_file": "src/pages/api/contact.ts", "file_type": "code", "degree": 13}, {"id": "api_Line truncated const RAW_NODES = [{"id": "astro_config_mjs", "label": "astro.config.mjs", "color": {"background": "#EDC948", "border": "#EDC948", "highlight": {"background": "#ffffff", "border": "#EDC948"}}, "size": 12.3, "font": {"size": 0, "color": "#ffffff"}, "title": "astro.config.mjs", "community": 45, "community_name": "Community 45", "source_file": "astro.config.mjs", "file_type": "code", "degree": 1}, {"id": "playwright_config_ts", "label": "playwright.config.ts", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "playwright.config.ts", "community": 49, "community_name": "Community 49", "source_file": "playwright.config.ts", "file_type": "code", "degree": 0}, {"id": "tailwind_config_mjs", "label": "tailwind.config.mjs", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "tailwind.config.mjs", "community": 50, "community_name": "Community 50", "source_file": "tailwind.config.mjs", "file_type": "code", "degree": 0}, {"id": "server_mjs", "label": "server.mjs", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "server.mjs", "community": 51, "community_name": "Community 51", "source_file": "server.mjs", "file_type": "code", "degree": 0}, {"id": "src_env_d_ts", "label": "env.d.ts", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "env.d.ts", "community": 52, "community_name": "Community 52", "source_file": "src/env.d.ts", "file_type": "code", "degree": 0}, {"id": "src_middleware_ts", "label": "middleware.ts", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 19.2, "font": {"size": 12, "color": "#ffffff"}, "title": "middleware.ts", "community": 2, "community_name": "Community 2", "source_file": "src/middleware.ts", "file_type": "code", "degree": 4}, {"id": "src_middleware_validatecsrforigin", "label": "validateCsrfOrigin()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 12.3, "font": {"size": 0, "color": "#ffffff"}, "title": "validateCsrfOrigin()", "community": 2, "community_name": "Community 2", "source_file": "src/middleware.ts", "file_type": "code", "degree": 1}, {"id": "src_content_config_ts", "label": "config.ts", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "config.ts", "community": 53, "community_name": "Community 53", "source_file": "src/content/config.ts", "file_type": "code", "degree": 0}, {"id": "src_pages_sitemap_xml_ts", "label": "sitemap.xml.ts", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 12.3, "font": {"size": 0, "color": "#ffffff"}, "title": "sitemap.xml.ts", "community": 42, "community_name": "Community 42", "source_file": "src/pages/sitemap.xml.ts", "file_type": "code", "degree": 1}, {"id": "pages_sitemap_xml_get", "label": "GET()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 12.3, "font": {"size": 0, "color": "#ffffff"}, "title": "GET()", "community": 42, "community_name": "Community 42", "source_file": "src/pages/sitemap.xml.ts", "file_type": "code", "degree": 1}, {"id": "src_pages_sitemap_index_xml_ts", "label": "sitemap-index.xml.ts", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 12.3, "font": {"size": 0, "color": "#ffffff"}, "title": "sitemap-index.xml.ts", "community": 43, "community_name": "Community 43", "source_file": "src/pages/sitemap-index.xml.ts", "file_type": "code", "degree": 1}, {"id": "pages_sitemap_index_xml_get", "label": "GET()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 12.3, "font": {"size": 0, "color": "#ffffff"}, "title": "GET()", "community": 43, "community_name": "Community 43", "source_file": "src/pages/sitemap-index.xml.ts", "file_type": "code", "degree": 1}, {"id": "src_pages_api_contact_ts", "label": "contact.ts", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 40.0, "font": {"size": 12, "color": "#ffffff"}, "title": "contact.ts", "community": 2, "community_name": "Community 2", "source_file": "src/pages/api/contact.ts", "file_type": "code", "degree": 13}, {"id": "api_Line truncated
const RAW_EDGES = [{"from": "astro_config_mjs", "to": "config", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_middleware_ts", "to": "src_utils_logger_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_middleware_ts", "to": "src_utils_sentry_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_middleware_ts", "to": "utils_sentry_captureexception", "label": "imports", "title": "imports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_middleware_ts", "to": "src_middleware_validatecsrforigin", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_sitemap_xml_ts", "to": "pages_sitemap_xml_get", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_sitemap_index_xml_ts", "to": "pages_sitemap_index_xml_get", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "src_utils_logger_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "utils_logger_logapirequest", "label": "imports", "title": "imports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "src_utils_sentry_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "utils_sentry_captureexception", "label": "imports", "title": "imports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_checkratelimit", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_validatecontactform", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_readsmtpconfig", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_gettransporter", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_sendcontactemail", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_escapehtml", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_corsheaders", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_options", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_post", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "api_contact_post", "to": "api_contact_checkratelimit", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "api_contact_post", "to": "api_contact_validatecontactform", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "api_contact_sendcontactemail", "to": "api_contact_readsmtpconfig", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "api_contact_sendcontactemail", "to": "api_contact_gettransporter", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTELine truncated const RAW_EDGES = [{"from": "astro_config_mjs", "to": "config", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_middleware_ts", "to": "src_utils_logger_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_middleware_ts", "to": "src_utils_sentry_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_middleware_ts", "to": "utils_sentry_captureexception", "label": "imports", "title": "imports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_middleware_ts", "to": "src_middleware_validatecsrforigin", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_sitemap_xml_ts", "to": "pages_sitemap_xml_get", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_sitemap_index_xml_ts", "to": "pages_sitemap_index_xml_get", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "src_utils_logger_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "utils_logger_logapirequest", "label": "imports", "title": "imports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "src_utils_sentry_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "utils_sentry_captureexception", "label": "imports", "title": "imports [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_checkratelimit", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_validatecontactform", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_readsmtpconfig", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_gettransporter", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_sendcontactemail", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_escapehtml", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_corsheaders", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_options", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_pages_api_contact_ts", "to": "api_contact_post", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "api_contact_post", "to": "api_contact_checkratelimit", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "api_contact_post", "to": "api_contact_validatecontactform", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "api_contact_sendcontactemail", "to": "api_contact_readsmtpconfig", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "api_contact_sendcontactemail", "to": "api_contact_gettransporter", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTELine truncated
const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "Community 0", "count": 38}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 38}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 35}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 25}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 23}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 22}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 16}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 14}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 12}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 12}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 12}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 12}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 11}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 11}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 11}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 9}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 9}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 8}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 8}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 8}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 8}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 8}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 8}, {"cid": 23, "color": "#76B7B2", "label": "Community 23", "count": 8}, {"cid": 24, "color": "#59A14F", "label": "Community 24", "count": 8}, {"cid": 25, "color": "#EDC948", "label": "Community 25", "count": 8}, {"cid": 26, "color": "#B07AA1", "label": "Community 26", "count": 7}, {"cid": 27, "color": "#FF9DA7", "label": "Community 27", "count": 7}, {"cid": 28, "color": "#9C755F", "label": "Community 28", "count": 7}, {"cid": 29, "color": "#BAB0AC", "label": "Community 29", "count": 7}, {"cid": 30, "color": "#4E79A7", "label": "Community 30", "count": 6}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 6}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 6}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 6}, {"cid": 34, "color": "#59A14F", "label": "Community 34", "count": 5}, {"cid": 35, "color": "#EDC948", "label": "Community 35", "count": 5}, {"cid": 36, "color": "#B07AA1", "label": "Community 36", "count": 4}, {"cid": 37, "color": "#FF9DA7", "label": "Community 37", "count": 4}, {"cid": 38, "color": "#9C755F", "label": "Community 38", "count": 4}, {"cid": 39, "color": "#BAB0AC", "label": "Community 39", "count": 4}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 3}, {"cid": 41, "color": "#F28E2B", "label": "Community 41", "count": 2}, {"cid": 42, "color": "#E15759", "label": "Community 42", "count": 2}, {"cid": 43, "color": "#76B7B2", "label": "Community 43", "count": 2}, {"cid": 44, "color": "#59A14F", "label": "Community 44", "count": 2}, {"cid": 45, "color": "#EDC948", "label": "Community 45", "count": 2}, {"cid": 46, "color": "#B07AA1", "label": "Community 46", "count": 2}, {"cid": 47, "color": "#FF9DA7", "label": "Community 47", "count": 2}, {"cid": 48, "color": "#9C755F", "label": "Community 48", "count": 1}, {"cid": 49, "color": "#BAB0AC", "label": "Community 49", "count": 1}, {"cid": 50, "color": "#4E79A7", "label": "Community 50", "count": 1}, {"cid": 51, "color": "#F28E2B", "label": "Community 51", "count": 1}, {"cid": 52, "color": "#E15759", "label": "Community 52", "count": 1}, {"cid": 53, "color": "#76B7B2", "label": "Community 53", "count": 1}, {"cid": 54, "color": "#59A14F", "label": "Community 54", "count": 1}, {"cid": 55, "color": "#EDC948", "label": "Community 55", "count": 1}, {"cid": 56, "color": "#B07AA1", "label": "Community 56", "count": 1}, {"cid": 57, "color": "#FF9DA7", "label": "Community 57", "count": 1}, {"cid": 58, "color": "#9C755F", "label": "Community 58", "count": 1}, {"cid": 59, "color": "#BAB0AC", "label": "Community 59", "count": 1}, {"cid": 60, "color": "#4E79A7", "label": "Community 60", "count": 1}, {"cid": 61, "color": "#F28E2B", "label": "Community 61", "count": 1}, {"cid": 62, "color": "#E15759", "label": "Community 62", "count": 1}, {"cid": 63, "color": "#76B7B2", "label": "Community 63", "count": 1}, {"cid": 64, "color": "#59A14F", "label": "Community 64", "count": 1}, {"cid": 65, "color": "#EDC948", "label": "Community 65", "count": 1}, {"cid": 66, "color": "#B07AA1", "label": "Community 66", "count": 1}, {"cid": 67, "color": "#FF9DA7", "label": "Community 67", "count": 1}, {"cid": 68, "color": "#9C755F", "label": "Community 68", "count": 1}, {"cid": 69, "color": "#BAB0AC", "label": "Community 69", "count": 1}, {"cid": 70, "color": "#4E79A7", "label": "Community 70", "count": 1}, {"cid": 71, "colorLine truncated const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "Community 0", "count": 38}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 38}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 35}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 25}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 23}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 22}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 16}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 14}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 14}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 12}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 12}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 12}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 12}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 11}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 11}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 11}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 9}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 9}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 8}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 8}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 8}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 8}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 8}, {"cid": 23, "color": "#76B7B2", "label": "Community 23", "count": 8}, {"cid": 24, "color": "#59A14F", "label": "Community 24", "count": 8}, {"cid": 25, "color": "#EDC948", "label": "Community 25", "count": 8}, {"cid": 26, "color": "#B07AA1", "label": "Community 26", "count": 8}, {"cid": 27, "color": "#FF9DA7", "label": "Community 27", "count": 7}, {"cid": 28, "color": "#9C755F", "label": "Community 28", "count": 7}, {"cid": 29, "color": "#BAB0AC", "label": "Community 29", "count": 7}, {"cid": 30, "color": "#4E79A7", "label": "Community 30", "count": 7}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 6}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 6}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 6}, {"cid": 34, "color": "#59A14F", "label": "Community 34", "count": 6}, {"cid": 35, "color": "#EDC948", "label": "Community 35", "count": 5}, {"cid": 36, "color": "#B07AA1", "label": "Community 36", "count": 5}, {"cid": 37, "color": "#FF9DA7", "label": "Community 37", "count": 4}, {"cid": 38, "color": "#9C755F", "label": "Community 38", "count": 4}, {"cid": 39, "color": "#BAB0AC", "label": "Community 39", "count": 4}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 4}, {"cid": 41, "color": "#F28E2B", "label": "Community 41", "count": 3}, {"cid": 42, "color": "#E15759", "label": "Community 42", "count": 2}, {"cid": 43, "color": "#76B7B2", "label": "Community 43", "count": 2}, {"cid": 44, "color": "#59A14F", "label": "Community 44", "count": 2}, {"cid": 45, "color": "#EDC948", "label": "Community 45", "count": 2}, {"cid": 46, "color": "#B07AA1", "label": "Community 46", "count": 2}, {"cid": 47, "color": "#FF9DA7", "label": "Community 47", "count": 2}, {"cid": 48, "color": "#9C755F", "label": "Community 48", "count": 2}, {"cid": 49, "color": "#BAB0AC", "label": "Community 49", "count": 1}, {"cid": 50, "color": "#4E79A7", "label": "Community 50", "count": 1}, {"cid": 51, "color": "#F28E2B", "label": "Community 51", "count": 1}, {"cid": 52, "color": "#E15759", "label": "Community 52", "count": 1}, {"cid": 53, "color": "#76B7B2", "label": "Community 53", "count": 1}, {"cid": 54, "color": "#59A14F", "label": "Community 54", "count": 1}, {"cid": 55, "color": "#EDC948", "label": "Community 55", "count": 1}, {"cid": 56, "color": "#B07AA1", "label": "Community 56", "count": 1}, {"cid": 57, "color": "#FF9DA7", "label": "Community 57", "count": 1}, {"cid": 58, "color": "#9C755F", "label": "Community 58", "count": 1}, {"cid": 59, "color": "#BAB0AC", "label": "Community 59", "count": 1}, {"cid": 60, "color": "#4E79A7", "label": "Community 60", "count": 1}, {"cid": 61, "color": "#F28E2B", "label": "Community 61", "count": 1}, {"cid": 62, "color": "#E15759", "label": "Community 62", "count": 1}, {"cid": 63, "color": "#76B7B2", "label": "Community 63", "count": 1}, {"cid": 64, "color": "#59A14F", "label": "Community 64", "count": 1}, {"cid": 65, "color": "#EDC948", "label": "Community 65", "count": 1}, {"cid": 66, "color": "#B07AA1", "label": "Community 66", "count": 1}, {"cid": 67, "color": "#FF9DA7", "label": "Community 67", "count": 1}, {"cid": 68, "color": "#9C755F", "label": "Community 68", "count": 1}, {"cid": 69, "color": "#BAB0AC", "label": "Community 69", "count": 1}, {"cid": 70, "color": "#4E79A7", "label": "Community 70", "count": 1}, {"cid": 71, "coloLine truncated
// HTML-escape helper — prevents XSS when injecting graph data into innerHTML // HTML-escape helper — prevents XSS when injecting graph data into innerHTML
function esc(s) { function esc(s) {
+583 -327
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 B

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

+1 -6
View File
@@ -1,6 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none"> <?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="9.69 10.69 1174.657 1169.222" width="1174.657pt" height="1169.222pt"><g><path d=" M 802.821 170.332 C 802.821 170.332 1267.11 329.733 1033.382 836.126 C 1033.382 836.126 1068.608 894.546 1062.286 928.783 C 1062.286 928.783 1228.11 732.085 1116.291 450.953 C 1116.291 450.953 1033.443 215.785 802.821 170.332 Z " fill="rgb(71,75,81)"/><path d=" M 647.701 1145.387 C 647.701 1145.387 574.007 1100.145 560.316 1070.941 C 560.316 1070.941 131.203 1008.875 149.074 564.461 C 149.074 564.461 66.351 1096.347 647.701 1145.387 Z " fill="rgb(71,75,81)"/><path d=" M 255.995 474.5 C 265.237 453.996 279.919 438.66 300.413 429.264 C 347.247 407.793 400.771 425.264 425.703 470.006 C 444.285 503.346 449.123 539.854 449.002 577.085 C 448.882 614.62 447.523 652.139 440.998 689.294 C 439.711 696.631 436.128 706.924 444.652 710.489 C 453.253 714.075 458.041 704.152 462.271 698.136 C 479.615 673.448 496.269 648.281 513.373 623.424 C 522.215 610.577 531.457 598.007 540.534 585.323 C 541.52 583.943 542.517 582.557 543.635 581.286 C 552.236 571.515 560.81 560.483 575.798 563.412 C 591.587 566.504 596.276 580.28 599.832 593.486 C 603.99 608.914 604.094 624.81 603.398 640.771 C 602.166 669.073 601.514 697.408 600.834 725.732 C 600.697 731.476 600.834 737.856 607.342 740.546 C 613.571 743.117 618.65 739.372 623.148 735.709 C 634.4 726.536 641.221 713.842 649.564 702.342 C 690.137 646.433 731.252 590.872 793.064 555.57 C 833.817 532.295 877.162 522.214 923.903 535.121 C 967.768 547.234 993.155 579.253 992.716 622.158 C 992.448 648.531 977.892 668.193 960.52 685.881 C 944.315 702.38 927.069 717.82 915.549 738.269 C 886.913 789.075 912.656 842.153 954.987 859.494 C 971.318 866.189 987.304 862.755 1002.068 853.967 C 1008.752 849.989 1014.898 845.12 1021.467 840.523 C 1021.817 844.202 1019.023 846.848 1017.139 849.691 C 987.605 894.09 933.786 902.638 890.387 869.896 C 850.702 839.952 839.581 789.249 861.516 741.932 C 872.719 717.771 890.836 699.038 908.69 680.175 C 920.205 668.008 930.932 655.21 938.881 640.809 C 953.009 615.224 943.762 585.623 917.702 570.694 C 892.6 556.315 865.487 553.478 837.515 560.619 C 804.897 568.95 778.015 587.514 753.264 609.202 C 714.187 643.444 682.368 683.893 652.977 726.368 C 637.523 748.703 622.178 771.158 602.894 790.559 C 597.838 795.646 592.469 800.395 586.01 803.656 C 576.341 808.536 568.709 805.819 564.573 795.738 C 558.766 781.592 558.668 766.571 559.084 751.692 C 560.108 714.841 561.697 678.013 562.869 641.167 C 563.132 632.76 562.53 624.332 562.469 615.914 C 562.431 610.588 561.445 604.686 556.777 602.578 C 551.277 600.089 547.157 605.828 544.413 609.441 C 533.566 623.723 523.491 638.57 513.17 653.237 C 487.411 689.843 462.786 727.243 435.361 762.67 C 431.203 768.044 426.009 772.593 419.983 775.951 C 409.169 781.978 400.754 779.185 396.163 767.675 C 391.414 755.768 390.806 743.182 392.159 730.634 C 395.928 695.631 403.104 661.074 405.531 625.875 C 408.084 588.899 407.876 552.163 398.656 515.937 C 395.04 501.721 389.989 488.097 381.755 475.821 C 362.022 446.404 328.533 436.193 295.499 449.426 C 280.773 455.322 268.354 464.751 255.995 474.5 Z " fill="rgb(41,175,182)"/><path d=" M 255.995 474.5 C 265.237 453.996 279.919 438.66 300.413 429.264 C 347.247 407.793 400.771 425.264 425.703 470.006 C 444.285 503.346 449.123 539.854 449.002 577.085 C 448.882 614.62 447.523 652.139 440.998 689.294 C 439.711 696.631 436.128 706.924 444.652 710.489 C 453.253 714.075 458.041 704.152 462.271 698.136 C 479.615 673.448 496.269 648.281 513.373 623.424 C 522.215 610.577 531.457 598.007 540.534 585.323 C 541.52 583.943 542.517 582.557 543.635 581.286 C 552.236 571.515 560.81 560.483 575.798 563.412 C 591.587 566.504 596.276 580.28 599.832 593.486 C 603.99 608.914 604.094 624.81 603.398 640.771 C 602.166 669.073 601.514 697.408 600.834 725.732 C 600.697 731.476 600.834 737.856 607.342 740.546 C 613.571 743.117 618.65 739.372 623.148 735.709 C 634.4 726.536 641.221 713.842 649.564 702.342 C 690.137 646.433 731.252 590.872 793.064 555.57 C 833.817 532.295 877.162 522.214 923.903 535.121 C 967.768 547.234 993.155 579.253 992.716 622.158 C 992.448 648.531 977.892 668.193 960.52 685.881 C 944.315 702.38 927.069 717.82 915.549 738.269 C 886.913 789.075 912.656 842.153 954.987 859.494 C 971.318 866.189 987.304 862.755 1002.068 853.967 C 1008.752 849.989 1014.898 845.12 1021.467 840.523 C 1021.817 844.202 1019.023 846.848 1017.139 849.691 C 987.605 894.09 933.786 902.638 890.387 869.896 C 850.702 839.952 839.581 789.249 861.516 741.932 C 872.719 717.771 890.836 699.038 908.69 680.175 C 920.205 668.008 930.932 655.21 938.881 640.809 C 953.009 615.224 943.762 585.623 917.702 570.694 C 892.6 556.315 865.487 553.478 837.515 560.619 C 804.897 568.95 778.015 587.514 753.264 609.202 C 714.187 643.444 682.368 683.893 652.977 726.368 C 637.523 Line truncated
<rect width="32" height="32" rx="6" fill="#0891b2"/>
<path d="M8 12L14 20L8 20" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M24 12L18 20L24 20" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="16" cy="16" r="2" fill="#f59e0b"/>
</svg>

Before

Width:  |  Height:  |  Size: 410 B

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 31 KiB

+1 -3
View File
@@ -44,9 +44,7 @@ const currentYear = new Date().getFullYear();
<!-- Company Info --> <!-- Company Info -->
<div class="lg:col-span-1"> <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"> <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"> <img src="/favicon.svg" alt="WorkRoot" width="40" height="40" class="w-10 h-10 transition-transform group-hover:scale-105" />
<span class="text-white font-bold text-lg">W</span>
</div>
<span class="text-xl font-bold text-white">WorkRoot</span> <span class="text-xl font-bold text-white">WorkRoot</span>
</a> </a>
<p class="text-secondary-400 text-sm leading-relaxed mb-6"> <p class="text-secondary-400 text-sm leading-relaxed mb-6">
+42 -23
View File
@@ -33,9 +33,13 @@ const currentPath = Astro.url.pathname;
<nav class="flex items-center justify-between h-16 lg:h-20" aria-label="Main navigation"> <nav class="flex items-center justify-between h-16 lg:h-20" aria-label="Main navigation">
<!-- Logo --> <!-- Logo -->
<a href="/" class="flex items-center gap-2 group" aria-label="WorkRoot IT Solutions LLP - Home"> <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"> <img
<span class="text-white font-bold text-xl">W</span> src="/favicon.svg"
</div> alt="WorkRoot"
width="40"
height="40"
class="w-10 h-10 transition-transform group-hover:scale-105"
/>
<div class="hidden sm:block"> <div class="hidden sm:block">
<span class="font-bold text-xl text-secondary dark:text-white">WorkRoot</span> <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> <span class="text-primary font-semibold text-sm block -mt-1">IT Solutions</span>
@@ -85,23 +89,40 @@ const currentPath = Astro.url.pathname;
</a> </a>
</div> </div>
<!-- Mobile Menu Button --> <!-- Mobile actions: prominent click-to-call + hamburger (shown <lg on every page) -->
<button <div class="flex items-center gap-1 lg:hidden">
type="button" <!-- Click-to-call — always visible in the mobile header so visitors on
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" any page can reach us in one tap without opening the menu. -->
id="mobile-menu-toggle" <a
aria-label="Toggle mobile menu" href="tel:+919561417403"
aria-expanded="false" data-cta="header-mobile-call"
aria-controls="mobile-menu" 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"
<span class="sr-only">Open main menu</span> >
<!-- Hamburger Icon --> <svg class="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<div class="hamburger-icon" 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" />
<span class="hamburger-line hamburger-line-1"></span> </svg>
<span class="hamburger-line hamburger-line-2"></span> <span class="hidden xs:inline">Call</span>
<span class="hamburger-line hamburger-line-3"></span> </a>
</div>
</button> <!-- 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> </nav>
</div> </div>
@@ -124,9 +145,7 @@ const currentPath = Astro.url.pathname;
<!-- Mobile Menu Header --> <!-- Mobile Menu Header -->
<div class="flex items-center justify-between p-4 border-b border-secondary-100 dark:border-secondary-800"> <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"> <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"> <img src="/favicon.svg" alt="WorkRoot" width="32" height="32" class="w-8 h-8" />
<span class="text-white font-bold text-lg">W</span>
</div>
<span class="font-bold text-lg text-secondary dark:text-white">WorkRoot</span> <span class="font-bold text-lg text-secondary dark:text-white">WorkRoot</span>
</a> </a>
<button <button
@@ -0,0 +1,123 @@
---
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."
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.
+1
View File
@@ -49,6 +49,7 @@ const fullTitle = title === 'Home' ? siteName : `${title} | ${siteName}`;
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <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" /> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<!-- PWA Manifest --> <!-- PWA Manifest -->
+132 -98
View File
@@ -80,6 +80,23 @@ const trustStats = [
{ value: '< 24h', label: 'Response Time' }, { value: '< 24h', label: 'Response Time' },
{ value: '3+', label: 'Years Experience' }, { 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 <BaseLayout
@@ -273,107 +290,84 @@ const trustStats = [
</div> </div>
</div> </div>
<!-- Phone + Subject row --> <!-- Phone Field (full width) -->
<div class="grid sm:grid-cols-2 gap-5"> <div class="form-group">
<!-- Phone Field --> <label for="phone" class="block text-sm font-semibold text-secondary-700 dark:text-secondary-300 mb-1.5">
<div class="form-group"> Phone Number
<label for="phone" class="block text-sm font-semibold text-secondary-700 dark:text-secondary-300 mb-1.5"> <span class="text-secondary-400 font-normal">(optional)</span>
Phone Number </label>
<span class="text-secondary-400 font-normal">(optional)</span> <div class="relative">
</label> <div class="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none" aria-hidden="true">
<div class="relative"> <svg class="w-4 h-4 text-secondary-400 field-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div class="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none" 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 class="w-4 h-4 text-secondary-400 field-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"> </svg>
<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>
</div> </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"> <input
<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> type="tel"
Please enter a valid phone number. id="phone"
</p> name="phone"
</div> autocomplete="tel"
aria-describedby="phone-error"
<!-- Subject Field --> 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"
<div class="form-group"> placeholder="+1 (555) 123-4567"
<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> <div class="valid-check absolute inset-y-0 right-3 flex items-center hidden" aria-label="Valid">
<span class="sr-only">(required)</span> <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">
</label> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7" />
<div class="relative"> </svg>
<div class="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none" aria-hidden="true"> </div>
<svg class="w-4 h-4 text-secondary-400 field-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <div class="invalid-check absolute inset-y-0 right-3 flex items-center hidden" aria-label="Invalid">
<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 class="w-4 h-4 text-red-800 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
</svg> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12" />
</div> </svg>
<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>
</div> </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> </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> </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"> <div class="form-group">
<label class="block text-sm font-semibold text-secondary-700 dark:text-secondary-300 mb-2"> <label class="block text-sm font-semibold text-secondary-700 dark:text-secondary-300 mb-2">
Project Budget Project Budget
<span class="text-secondary-400 dark:text-secondary-500 font-normal">(optional)</span> <span class="text-secondary-400 dark:text-secondary-500 font-normal">(optional)</span>
</label> </label>
<div class="flex flex-wrap gap-2" role="group" aria-label="Budget range options"> <div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-2.5" role="group" aria-label="Budget range options">
{['< ₹1L', '₹1L – ₹5L', '₹5L – ₹15L', '₹15L+', 'Not sure yet'].map((range, i) => ( {budgetOptions.map((range) => (
<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"> <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" /> <input type="radio" name="budget" value={range} class="sr-only" />
{range} {range}
</label> </label>
@@ -806,6 +800,13 @@ const trustStats = [
color: #0e7490; 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 */
.faq-item summary::-webkit-details-marker { display: none; } .faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::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'); const budgetOptions = document.querySelectorAll('.budget-option');
budgetOptions.forEach((opt) => { budgetOptions.forEach((opt) => {
const radio = opt.querySelector('input[type="radio"]') as HTMLInputElement; 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 ──────────────────────────────────────── // ── Form Validation ────────────────────────────────────────
const form = document.getElementById('contact-form') as HTMLFormElement; const form = document.getElementById('contact-form') as HTMLFormElement;
const successState = document.getElementById('success-state'); const successState = document.getElementById('success-state');
@@ -1012,18 +1038,25 @@ const trustStats = [
return; 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; let isValid = true;
['name', 'email', 'phone', 'subject', 'message'].forEach((fieldName) => { ['name', 'email', 'phone', 'message'].forEach((fieldName) => {
const field = form.elements.namedItem(fieldName) as HTMLInputElement; const field = form.elements.namedItem(fieldName) as HTMLInputElement;
if (field && !validateField(field)) isValid = false; if (field && !validateField(field)) isValid = false;
}); });
if (!validateSubject()) isValid = false;
if (!isValid) { if (!isValid) {
showToast('Please fix the highlighted fields before submitting.', 'error'); 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; const firstInvalid = form.querySelector('.is-invalid') as HTMLElement | null;
firstInvalid?.focus(); if (firstInvalid) {
firstInvalid.focus();
} else if (getSubjectValue() === '') {
subjectRadios[0]?.focus();
}
return; return;
} }
@@ -1057,7 +1090,7 @@ const trustStats = [
name: (form.elements.namedItem('name') as HTMLInputElement)?.value.trim(), name: (form.elements.namedItem('name') as HTMLInputElement)?.value.trim(),
email: (form.elements.namedItem('email') as HTMLInputElement)?.value.trim(), email: (form.elements.namedItem('email') as HTMLInputElement)?.value.trim(),
phone: (form.elements.namedItem('phone') 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(), message: (form.elements.namedItem('message') as HTMLTextAreaElement)?.value.trim(),
website: honeypot, website: honeypot,
}), }),
@@ -1087,7 +1120,7 @@ const trustStats = [
} }
// Track analytics // Track analytics
const subject = (form.elements.namedItem('subject') as HTMLSelectElement)?.value; const subject = getSubjectValue();
trackFormSubmit('contact', success); trackFormSubmit('contact', success);
if (success) trackEvent('contact_form_submit', { subject_category: subject }); 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')); form.querySelectorAll('[data-error]').forEach((el) => el.classList.add('hidden'));
if (charCount) charCount.textContent = '0 / 2000'; if (charCount) charCount.textContent = '0 / 2000';
budgetOptions.forEach((o) => o.classList.remove('selected')); budgetOptions.forEach((o) => o.classList.remove('selected'));
subjectGroupEl?.classList.remove('subject-group-invalid');
}); });
} }
} }
+46
View File
@@ -400,6 +400,52 @@ const techStack = [
</div> </div>
</section> </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 --> <!-- 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"> <section class="py-24 bg-gradient-to-br from-secondary-900 via-secondary-800 to-primary-900 relative overflow-hidden">
<!-- Background pattern --> <!-- Background pattern -->
+29 -2
View File
@@ -348,8 +348,11 @@ const featuredProjects = projects.filter(p => p.featured);
</div> </div>
</div> </div>
<!-- Category Filter Tabs --> <!-- Category Filter Tabs
<div class="flex flex-wrap justify-center gap-3" role="tablist" aria-label="Filter projects by category"> 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 <button
data-filter="all" 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" 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; display: flex;
flex-wrap: wrap; 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> </style>