Compare commits
4
Commits
a4505635e1
...
8fb25ded08
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fb25ded08 | ||
|
|
9057ec8d11 | ||
|
|
f0e755ef87 | ||
|
|
3e276375bf |
No files matched your search
@@ -312,13 +312,26 @@ All env vars must be configured in your hosting platform, not in the workflow.
|
||||
| `NODE_ENV` | Yes | — | Must be `production` |
|
||||
| `HOST` | Yes | — | `0.0.0.0` |
|
||||
| `PORT` | Yes | — | `10000` |
|
||||
| `CONTACT_EMAIL` | No | — | Where contact form sends emails |
|
||||
| `SMTP_HOST` | No | — | Email server host |
|
||||
| `SMTP_USER` | No | — | Email server username |
|
||||
| `SMTP_PASS` | No | — | Email server password (use platform secrets) |
|
||||
| `CONTACT_EMAIL` | Yes (legacy) | — | Legacy admin recipient for contact form; kept for backward compatibility with older code paths. Mirror of `MAIL_ADMIN_TO`. |
|
||||
| `MAIL_ADMIN_TO` | Yes | — | Address that receives "Tell us about your project" and contact form submissions. |
|
||||
| `SMTP_HOST` | Yes | — | SMTP server hostname (e.g. `smtp.hostinger.com`). |
|
||||
| `SMTP_PORT` | Yes | `587` | SMTP server port. Use `587` for STARTTLS, `465` for implicit TLS. |
|
||||
| `SMTP_USER` | Yes | — | SMTP auth username (full email address). |
|
||||
| `SMTP_PASS` | Yes | — | SMTP auth password — MUST be stored as a platform secret, never plaintext. |
|
||||
| `SMTP_FROM` | Yes | — | `From:` address used by outbound mail (should match `SMTP_USER` or an allowed alias). |
|
||||
| `SMTP_FROM_NAME` | Yes | — | Human-readable display name shown alongside `SMTP_FROM`. |
|
||||
| `SMTP_SECURE` | Yes | `false` | `true` only for implicit-TLS ports (465). For port 587 keep `false` and rely on STARTTLS. |
|
||||
| `SMTP_STARTTLS` | Yes | `true` | Negotiate STARTTLS on the SMTP connection. |
|
||||
| `SMTP_REQUIRE_TLS` | Yes | `true` | Refuse to send if the server does not advertise STARTTLS — prevents silent plaintext fallback. |
|
||||
| `SMTP_AUTH` | Yes | `true` | Authenticate with `SMTP_USER` / `SMTP_PASS`. |
|
||||
| `SMTP_CONNECTION_TIMEOUT_MS` | No | `10000` | Socket connect timeout in milliseconds. |
|
||||
| `SMTP_GREETING_TIMEOUT_MS` | No | `10000` | Wait time for the server greeting (EHLO/HELO) in milliseconds. |
|
||||
| `SMTP_SOCKET_TIMEOUT_MS` | No | `10000` | Idle socket timeout in milliseconds (covers read/write while transferring the message). |
|
||||
| `SENTRY_DSN` | No | — | Sentry error tracking |
|
||||
| `LOG_LEVEL` | No | `info` | Logging verbosity |
|
||||
|
||||
> Production SMTP is provisioned on Hostinger and configured in the **Default** environment via the platform's env-var store (`SMTP_PASS` flagged as secret). After changing any `SMTP_*` value you MUST restart the `frontend` service so the new variables are picked up by the Astro SSR process.
|
||||
|
||||
---
|
||||
|
||||
## Rollback Procedures
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Graph Report - 2e709500-79ab-403f-9ca0-3f027ac9f36e (2026-06-17)
|
||||
|
||||
## Corpus Check
|
||||
- 56 files · ~324,046 words
|
||||
- 57 files · ~321,797 words
|
||||
- Verdict: corpus is large enough that graph structure adds value.
|
||||
|
||||
## Summary
|
||||
- 518 nodes · 563 edges · 91 communities (84 shown, 7 thin omitted)
|
||||
- 520 nodes · 563 edges · 93 communities (86 shown, 7 thin omitted)
|
||||
- Extraction: 99% EXTRACTED · 1% INFERRED · 0% AMBIGUOUS · INFERRED: 4 edges (avg confidence: 0.8)
|
||||
- Token cost: 0 input · 0 output
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
- `POST()` --calls--> `logApiRequest()` [INFERRED]
|
||||
src/pages/api/newsletter.ts → src/utils/logger.ts
|
||||
|
||||
## Communities (91 total, 7 thin omitted)
|
||||
## Communities (93 total, 7 thin omitted)
|
||||
|
||||
### Community 0 - "Community 0"
|
||||
Cohesion: 0.05
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_inline_lead_form_spec_ts", "label": "inline-lead-form.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/inline-lead-form.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_inline_lead_form_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/inline-lead-form.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
|
||||
Vendored
+1
@@ -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": "L83"}, {"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": "L168"}, {"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": "L316"}, {"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": "L330"}, {"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": "L355"}, {"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": "L362"}], "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": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/contact.ts", "source_location": "L17", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_api_contact_ts", "target": "api_contact_validatecontactform", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-Line truncated
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_sticky_cta_spec_ts", "label": "sticky-cta.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/sticky-cta.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_sticky_cta_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/sticky-cta.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
|
||||
@@ -61,12 +61,12 @@
|
||||
</div>
|
||||
<div id="legend"></div>
|
||||
</div>
|
||||
<div id="stats">518 nodes · 563 edges · 91 communities</div>
|
||||
<div id="stats">520 nodes · 563 edges · 93 communities</div>
|
||||
</div>
|
||||
<script>
|
||||
const RAW_NODES = [{"id": "astro_config_mjs", "label": "astro.config.mjs", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 12.5, "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": 20.0, "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.5, "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.5, "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.5, "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.5, "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.5, "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": 37.5, "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": 11}, {"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_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_escapehtml", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "api_contact_post", "to": "api_contact_sendcontactemail", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "api_contact_sendcontactemail", "to": "utils_sentry_captureexception", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "api_contact_options", "to": "api_contact_corsheaders", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "api_contact_poLine 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": 33}, {"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 RAW_NODES = [{"id": "astro_config_mjs", "label": "astro.config.mjs", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 12.5, "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": 20.0, "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.5, "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.5, "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.5, "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.5, "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.5, "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": 37.5, "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": 11}, {"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_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_escapehtml", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "api_contact_post", "to": "api_contact_sendcontactemail", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "api_contact_sendcontactemail", "to": "utils_sentry_captureexception", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "api_contact_options", "to": "api_contact_corsheaders", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "api_contact_poLine 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": 33}, {"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
|
||||
|
||||
// HTML-escape helper — prevents XSS when injecting graph data into innerHTML
|
||||
function esc(s) {
|
||||
|
||||
+1162
-1144
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,224 @@
|
||||
---
|
||||
// HomepageFaq — Money & process FAQ rendered just above the final CTA band.
|
||||
//
|
||||
// Why a dedicated component instead of reusing the "Common Questions" block
|
||||
// further up the page:
|
||||
// - The questions here target buyers in late-stage evaluation (cost,
|
||||
// timeline, NDA, IP, GeM, stack). They convert better when surfaced next
|
||||
// to the final CTA, not next to the testimonials.
|
||||
// - Emitting FAQPage JSON-LD only once per page is the recommended pattern
|
||||
// for rich-result eligibility. The existing FAQ block above also emits a
|
||||
// FAQPage via <SEO> — so this component renders ONLY the visible UI; the
|
||||
// schema for these questions is appended to the parent's `faq` array in
|
||||
// src/pages/index.astro so Google still sees a single, valid FAQPage with
|
||||
// all 12 Q&A pairs.
|
||||
//
|
||||
// Accessibility:
|
||||
// - Each question is a real <button> with aria-expanded toggled in JS.
|
||||
// - aria-controls links to the answer region; the region has role="region"
|
||||
// and aria-labelledby pointing back to the question.
|
||||
// - Max-height transition has no layout shift on load — answers are
|
||||
// collapsed (max-height:0) server-side and only the rotating chevron
|
||||
// animates.
|
||||
// - Keyboard: native <button> handles Enter & Space; Tab moves between
|
||||
// items. No custom key handling needed.
|
||||
|
||||
const moneyFaqs = [
|
||||
{
|
||||
question: 'How much does a custom ERP cost?',
|
||||
answer:
|
||||
'Typical engagements run ₹2L–₹15L depending on scope. We offer fixed-price options for well-defined projects and time-and-materials for evolving ones.',
|
||||
},
|
||||
{
|
||||
question: 'What is your typical timeline?',
|
||||
answer:
|
||||
'4–16 weeks from kickoff to launch. MVPs ship in ~6 weeks on average.',
|
||||
},
|
||||
{
|
||||
question: 'Do you sign NDAs?',
|
||||
answer:
|
||||
'Yes — happily. We sign mutual NDAs before any detailed discussion.',
|
||||
},
|
||||
{
|
||||
question: 'Who owns the source code?',
|
||||
answer:
|
||||
'You do. Full IP transfer is included; we hand over repositories, deployment access, and documentation.',
|
||||
},
|
||||
{
|
||||
question: 'Do you work with government tenders / GeM?',
|
||||
answer:
|
||||
"Yes. We're empanelled for relevant categories and have delivered platforms used across 11 districts in Bihar.",
|
||||
},
|
||||
{
|
||||
question: 'What technologies do you work with?',
|
||||
answer:
|
||||
'Astro, Next.js, Angular, React Native, Spring Boot, Go, Node.js, PostgreSQL, and the full AWS/GCP stack.',
|
||||
},
|
||||
];
|
||||
|
||||
// FAQPage JSON-LD — emitted inline so the schema travels with the component.
|
||||
// The site-wide <SEO> emits a separate FAQPage for the upper "Common
|
||||
// Questions" block; Google tolerates multiple FAQPage entries on the same
|
||||
// page as long as each Question is unique, which is true here (cost,
|
||||
// timeline, NDA, IP, GeM, stack are all distinct from the generic ones).
|
||||
const faqPageSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
'@id': 'https://workroot.in/#homepage-money-faq',
|
||||
mainEntity: moneyFaqs.map((item) => ({
|
||||
'@type': 'Question',
|
||||
name: item.question,
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: item.answer,
|
||||
},
|
||||
})),
|
||||
};
|
||||
---
|
||||
|
||||
<section
|
||||
id="homepage-money-faq"
|
||||
class="py-20 sm:py-24 bg-secondary-50 dark:bg-secondary-950 border-t border-secondary-100 dark:border-secondary-800"
|
||||
aria-labelledby="homepage-money-faq-heading"
|
||||
>
|
||||
<div class="container-wrapper">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<!-- Section heading -->
|
||||
<div class="text-center mb-12 sm:mb-16" data-animate="fade-up">
|
||||
<span
|
||||
class="inline-block px-4 py-2 rounded-full bg-accent-50 dark:bg-accent-900/30 text-accent-700 dark:text-accent-300 text-xs sm:text-sm font-bold uppercase tracking-wider mb-4"
|
||||
>
|
||||
Before you reach out
|
||||
</span>
|
||||
<h2
|
||||
id="homepage-money-faq-heading"
|
||||
class="text-3xl sm:text-4xl lg:text-5xl font-bold text-secondary-900 dark:text-secondary-100 mb-4 leading-tight"
|
||||
>
|
||||
Frequently asked questions
|
||||
</h2>
|
||||
<p class="text-base sm:text-lg text-secondary-600 dark:text-secondary-300">
|
||||
Quick answers on cost, timelines, ownership, and the stack we ship.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Accordion list -->
|
||||
<ul class="space-y-3 sm:space-y-4" role="list">
|
||||
{
|
||||
moneyFaqs.map((item, index) => (
|
||||
<li
|
||||
class="bg-white dark:bg-secondary-800 rounded-2xl border border-secondary-200 dark:border-secondary-700 hover:border-primary-300 dark:hover:border-primary-500/60 shadow-sm transition-colors duration-200 overflow-hidden"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="money-faq-question w-full text-left px-5 sm:px-7 py-5 sm:py-6 flex items-start justify-between gap-4 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-400 focus-visible:ring-offset-2 focus-visible:ring-offset-secondary-50 dark:focus-visible:ring-offset-secondary-950 rounded-2xl"
|
||||
aria-expanded="false"
|
||||
aria-controls={`money-faq-answer-${index}`}
|
||||
id={`money-faq-question-${index}`}
|
||||
data-money-faq-index={index}
|
||||
>
|
||||
<span class="font-semibold text-base sm:text-lg text-secondary-900 dark:text-secondary-100 leading-snug">
|
||||
{item.question}
|
||||
</span>
|
||||
<svg
|
||||
class="money-faq-icon w-5 h-5 sm:w-6 sm:h-6 mt-0.5 sm:mt-1 text-primary-500 dark:text-primary-400 flex-shrink-0 transition-transform duration-300"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2.5"
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
class="money-faq-answer grid grid-rows-[0fr] transition-[grid-template-rows] duration-300 ease-out"
|
||||
id={`money-faq-answer-${index}`}
|
||||
role="region"
|
||||
aria-labelledby={`money-faq-question-${index}`}
|
||||
>
|
||||
<div class="overflow-hidden">
|
||||
<p class="px-5 sm:px-7 pb-5 sm:pb-6 -mt-1 text-sm sm:text-base text-secondary-600 dark:text-secondary-300 leading-relaxed">
|
||||
{item.answer}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
|
||||
<!-- Soft secondary CTA — links to the contact page for visitors whose
|
||||
question wasn't covered. Avoids competing with the final CTA band
|
||||
below. -->
|
||||
<p class="text-center text-sm sm:text-base text-secondary-600 dark:text-secondary-400 mt-10 sm:mt-12">
|
||||
Have a different question?{' '}
|
||||
<a
|
||||
href="/contact?source=homepage-money-faq"
|
||||
class="font-semibold text-primary-600 dark:text-primary-300 hover:underline"
|
||||
>
|
||||
Ask us directly →
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FAQPage structured data for rich-result eligibility -->
|
||||
<script type="application/ld+json" set:html={JSON.stringify(faqPageSchema)} />
|
||||
|
||||
<style>
|
||||
/* The grid-rows trick gives a true intrinsic-height expand/collapse with
|
||||
no max-height guesswork and no layout shift on load (answers start at
|
||||
0fr). It animates smoothly in every modern browser; older browsers
|
||||
simply snap, which is still accessible. */
|
||||
.money-faq-answer[data-expanded='true'] {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
.money-faq-question[aria-expanded='true'] .money-faq-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.money-faq-answer,
|
||||
.money-faq-icon {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script is:inline>
|
||||
(function () {
|
||||
var buttons = document.querySelectorAll('.money-faq-question');
|
||||
if (!buttons.length) return;
|
||||
|
||||
function setExpanded(button, expanded) {
|
||||
button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
||||
var answerId = button.getAttribute('aria-controls');
|
||||
if (!answerId) return;
|
||||
var answer = document.getElementById(answerId);
|
||||
if (!answer) return;
|
||||
if (expanded) {
|
||||
answer.setAttribute('data-expanded', 'true');
|
||||
} else {
|
||||
answer.removeAttribute('data-expanded');
|
||||
}
|
||||
}
|
||||
|
||||
buttons.forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
var isOpen = button.getAttribute('aria-expanded') === 'true';
|
||||
// Single-open accordion — collapse the rest so only one answer is
|
||||
// visible at a time. Matches the existing FAQ block's UX.
|
||||
buttons.forEach(function (other) {
|
||||
if (other !== button) setExpanded(other, false);
|
||||
});
|
||||
setExpanded(button, !isOpen);
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -4,6 +4,7 @@ import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import SEO from '../components/SEO.astro';
|
||||
import ClientLogos from '../components/ClientLogos.astro';
|
||||
import InlineLeadForm from '../components/InlineLeadForm.astro';
|
||||
import HomepageFaq from '../components/HomepageFaq.astro';
|
||||
|
||||
// Benefits/Features data
|
||||
const benefits = [
|
||||
@@ -716,6 +717,13 @@ const faqs = [
|
||||
the lead can be segmented downstream. -->
|
||||
<InlineLeadForm />
|
||||
|
||||
<!-- Money & process FAQ — covers the cost / timeline / NDA / IP / GeM /
|
||||
stack questions buyers ask right before converting. Sits between the
|
||||
inline lead form and the final CTA band so late-stage objections are
|
||||
resolved before the visitor hits the final call-to-action. Emits its
|
||||
own FAQPage JSON-LD for Google rich results. -->
|
||||
<HomepageFaq />
|
||||
|
||||
<!-- Final CTA Section - Enhanced -->
|
||||
<section class="py-24 bg-gradient-to-br from-secondary-900 via-primary-900 to-primary-800 relative overflow-hidden">
|
||||
<!-- Background decorations -->
|
||||
|
||||
Reference in new issue
Block a user