Compare commits
12 Commits
fix/cloudf
...
feat/issue
| Author | SHA1 | Date | |
|---|---|---|---|
| d12826b314 | |||
| b7e051ec34 | |||
| c6d13418f7 | |||
| 01e3815820 | |||
| 0714ec1335 | |||
| ddab32d3ef | |||
| f33d29fe7b | |||
| 9017c933f4 | |||
| 856e20c19c | |||
| 6ca0256bf7 | |||
| 70759d6c1c | |||
| 5ab329adbf |
13
.env.example
Normal file
13
.env.example
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
PUBLIC_CONTACT_FORM_ACTION_URL=/api/contact
|
||||||
|
PUBLIC_CONTACT_FORM_SUCCESS_REDIRECT=http://localhost:4321/#contatti
|
||||||
|
PUBLIC_CONTACT_FORM_MAILTO=
|
||||||
|
PUBLIC_CONTACT_TURNSTILE_SITE_KEY=
|
||||||
|
|
||||||
|
CONTACT_FORM_MODE=dev
|
||||||
|
CONTACT_FORM_RESEND_API_KEY=
|
||||||
|
CONTACT_FORM_FROM_EMAIL=no-reply@nexstudio.com
|
||||||
|
CONTACT_FORM_TO_EMAIL=info@nexstudio.com
|
||||||
|
CONTACT_FORM_TO_EMAIL_INFO=info@nexstudio.com
|
||||||
|
CONTACT_FORM_TO_EMAIL_PRIVACY=
|
||||||
|
CONTACT_FORM_TO_EMAIL_CAREERS=
|
||||||
|
CONTACT_TURNSTILE_SECRET_KEY=
|
||||||
137
README.md
137
README.md
@ -1,52 +1,123 @@
|
|||||||
# Astro Starter Kit: Minimal
|
# NexStudio Website Runbook
|
||||||
|
|
||||||
```sh
|
Sito istituzionale NexStudio basato su Astro, con deploy su Cloudflare e form contatti protetto da Turnstile.
|
||||||
npm create astro@latest -- --template minimal
|
|
||||||
```
|
|
||||||
|
|
||||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
## Obiettivo del repository
|
||||||
|
|
||||||
## 🚀 Project Structure
|
- Presentazione aziendale e servizi (homepage e pagine legali).
|
||||||
|
- Entry point unico per richieste commerciali e supporto.
|
||||||
|
- Base pronta per contenuti multilingua IT/EN/TH.
|
||||||
|
- Pipeline di deploy stabile su Cloudflare.
|
||||||
|
|
||||||
Inside of your Astro project, you'll see the following folders and files:
|
## Stack tecnico
|
||||||
|
|
||||||
```text
|
- Astro + Tailwind CSS
|
||||||
/
|
- Adapter Cloudflare (`@astrojs/cloudflare`)
|
||||||
├── public/
|
- Sitemap (`@astrojs/sitemap`)
|
||||||
├── src/
|
- Form backend su endpoint `/api/contact`
|
||||||
│ └── pages/
|
- Turnstile + Resend per anti-spam e invio email
|
||||||
│ └── index.astro
|
|
||||||
└── package.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
## Struttura essenziale
|
||||||
|
|
||||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
- `src/pages/`: pagine pubbliche e route API (`src/pages/api/contact.ts`)
|
||||||
|
- `src/components/`: componenti UI condivisi
|
||||||
|
- `src/data/`: contenuti testuali e configurazioni pagine
|
||||||
|
- `public/`: asset statici (loghi, immagini, robots)
|
||||||
|
- `.env.example`: variabili locali di riferimento
|
||||||
|
|
||||||
Any static assets, like images, can be placed in the `public/` directory.
|
## Prerequisiti
|
||||||
|
|
||||||
## 🧞 Commands
|
- Node.js 20+
|
||||||
|
- npm 10+
|
||||||
|
- Account Cloudflare (Pages/Workers + Turnstile)
|
||||||
|
- Account Resend (API key + sender verificato)
|
||||||
|
|
||||||
All commands are run from the root of the project, from a terminal:
|
## Avvio locale
|
||||||
|
|
||||||
| Command | Action |
|
1. Installazione dipendenze:
|
||||||
| :------------------------ | :----------------------------------------------- |
|
- `npm install`
|
||||||
| `npm install` | Installs dependencies |
|
2. Copia variabili di ambiente:
|
||||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
- copia `.env.example` in `.env`
|
||||||
| `npm run build` | Build your production site to `./dist/` |
|
3. Avvio sviluppo:
|
||||||
| `npm run preview` | Preview your build locally, before deploying |
|
- `npm run dev`
|
||||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
4. Se Vite resta in cache incoerente:
|
||||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
- `npm run dev:fresh`
|
||||||
|
|
||||||
## 👀 Want to learn more?
|
## Comandi utili
|
||||||
|
|
||||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
- `npm run dev`: sviluppo locale
|
||||||
|
- `npm run dev:fresh`: reset cache `.vite` e `.astro` + avvio dev server
|
||||||
|
- `npm run build`: build produzione
|
||||||
|
- `npm run preview`: preview locale della build
|
||||||
|
- `npm run deploy:cf`: build + deploy con wrangler su output Astro
|
||||||
|
|
||||||
## Cloudflare deploy notes
|
## Configurazione contatti
|
||||||
|
|
||||||
If you deploy with Wrangler in CI/Cloudflare, build first and deploy using Astro's generated Worker config:
|
Variabili pubbliche (frontend):
|
||||||
|
|
||||||
|
- `PUBLIC_CONTACT_FORM_ACTION_URL` (default consigliato: `/api/contact`)
|
||||||
|
- `PUBLIC_CONTACT_FORM_SUCCESS_REDIRECT` (URL assoluto della pagina post-submit)
|
||||||
|
- `PUBLIC_CONTACT_FORM_MAILTO` (fallback se action URL non disponibile)
|
||||||
|
- `PUBLIC_CONTACT_TURNSTILE_SITE_KEY`
|
||||||
|
|
||||||
|
Variabili server (Cloudflare/local):
|
||||||
|
|
||||||
|
- `CONTACT_FORM_MODE` (`dev` oppure `live`)
|
||||||
|
- `CONTACT_FORM_RESEND_API_KEY`
|
||||||
|
- `CONTACT_FORM_FROM_EMAIL` (consigliato: `no-reply@nexstudio.ai`)
|
||||||
|
- `CONTACT_FORM_TO_EMAIL` (default destinatario, es. `info@nexstudio.ai`)
|
||||||
|
- `CONTACT_FORM_TO_EMAIL_INFO` (opzionale, override esplicito per area "info")
|
||||||
|
- `CONTACT_FORM_TO_EMAIL_PRIVACY`
|
||||||
|
- `CONTACT_FORM_TO_EMAIL_CAREERS`
|
||||||
|
- `CONTACT_TURNSTILE_SECRET_KEY`
|
||||||
|
|
||||||
|
Comportamento:
|
||||||
|
|
||||||
|
- `dev`: accetta submit e scrive payload nei log (senza invio reale)
|
||||||
|
- `live`: valida Turnstile e invia email via Resend
|
||||||
|
|
||||||
|
Routing pubblico consigliato (portale vetrina):
|
||||||
|
|
||||||
|
- `info`: informazioni generali/commerciali
|
||||||
|
- `privacy`: richieste privacy e diritti interessato
|
||||||
|
- `careers`: candidature e collaborazioni
|
||||||
|
|
||||||
|
Indirizzi consigliati portale:
|
||||||
|
|
||||||
|
- `info@nexstudio.ai`
|
||||||
|
- `privacy@nexstudio.ai`
|
||||||
|
- `careers@nexstudio.ai`
|
||||||
|
- `no-reply@nexstudio.ai` (solo invio comunicazioni/newsletter, non destinazione inbound)
|
||||||
|
|
||||||
|
## Deploy Cloudflare
|
||||||
|
|
||||||
|
Impostazioni consigliate:
|
||||||
|
|
||||||
- Build command: `npm run build`
|
- Build command: `npm run build`
|
||||||
- Deploy command: `npm run deploy:cf`
|
- Deploy command: `npm run deploy:cf`
|
||||||
|
|
||||||
This avoids entrypoint errors like `@astrojs/cloudflare/entrypoints/server` not found.
|
Questa configurazione evita errori tipo:
|
||||||
|
`The entry-point file at "@astrojs/cloudflare/entrypoints/server" was not found.`
|
||||||
|
|
||||||
|
## Dominio e sicurezza
|
||||||
|
|
||||||
|
- Dominio ufficiale: `https://nexstudio.ai`
|
||||||
|
- Hostname Turnstile da mantenere allineati:
|
||||||
|
- `nexstudio.ai`
|
||||||
|
- `www.nexstudio.ai` (se usato)
|
||||||
|
- `localhost` (test locale)
|
||||||
|
- Accesso pre-go-live gestito con Cloudflare Zero Trust Access
|
||||||
|
|
||||||
|
## Stato operativo attuale
|
||||||
|
|
||||||
|
- Issue #1 (contact form live): implementata
|
||||||
|
- Issue #2 (anti-spam Turnstile): implementata
|
||||||
|
- Issue #3 (SEO base): implementata (sitemap, robots, canonical/OG/Twitter)
|
||||||
|
- Issue #4: in avanzamento (contenuti non placeholder)
|
||||||
|
- Issue #5: da completare (localizzazione reale IT/EN/TH)
|
||||||
|
|
||||||
|
## Note operative
|
||||||
|
|
||||||
|
- Per modifiche non minori usare branch dedicato (`feat/*`, `fix/*`) e merge su `main` dopo verifica.
|
||||||
|
- Evitare commit su `main` per attivita complesse.
|
||||||
|
- Rigenerare eventuali API key esposte e aggiornare subito i secret su Cloudflare.
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { defineConfig } from 'astro/config';
|
|||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
import cloudflare from '@astrojs/cloudflare';
|
import cloudflare from '@astrojs/cloudflare';
|
||||||
import alpinejs from '@astrojs/alpinejs';
|
import alpinejs from '@astrojs/alpinejs';
|
||||||
|
import sitemap from '@astrojs/sitemap';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mitiga race su richieste SSR parallele durante l’ottimizzazione dipendenze
|
* Mitiga race su richieste SSR parallele durante l’ottimizzazione dipendenze
|
||||||
@ -34,7 +35,7 @@ function viteSsrOptimizeIgnoreOutdated() {
|
|||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
/** Usato da Astro per URL assoluti (es. sitemap). Imposta il dominio di produzione. */
|
/** Usato da Astro per URL assoluti (es. sitemap). Imposta il dominio di produzione. */
|
||||||
// site: 'https://www.tuodominio.com',
|
site: 'https://nexstudio.ai',
|
||||||
|
|
||||||
output: 'server', // per il deployment su Cloudflare Pages
|
output: 'server', // per il deployment su Cloudflare Pages
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
adapter: cloudflare(),
|
adapter: cloudflare(),
|
||||||
integrations: [alpinejs()],
|
integrations: [alpinejs(), sitemap()],
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: 'it',
|
defaultLocale: 'it',
|
||||||
locales: ['it', 'en', 'th'],
|
locales: ['it', 'en', 'th'],
|
||||||
|
|||||||
67
package-lock.json
generated
67
package-lock.json
generated
@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/alpinejs": "^0.5.0",
|
"@astrojs/alpinejs": "^0.5.0",
|
||||||
"@astrojs/cloudflare": "^13.1.10",
|
"@astrojs/cloudflare": "^13.1.10",
|
||||||
|
"@astrojs/sitemap": "^3.7.2",
|
||||||
"@tailwindcss/vite": "^4.2.2",
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
"@types/alpinejs": "^3.13.11",
|
"@types/alpinejs": "^3.13.11",
|
||||||
"alpinejs": "^3.15.11",
|
"alpinejs": "^3.15.11",
|
||||||
@ -105,6 +106,17 @@
|
|||||||
"node": ">=22.12.0"
|
"node": ">=22.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@astrojs/sitemap": {
|
||||||
|
"version": "3.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.2.tgz",
|
||||||
|
"integrity": "sha512-PqkzkcZTb5ICiyIR8VoKbIAP/laNRXi5tw616N1Ckk+40oNB8Can1AzVV56lrbC5GKSZFCyJYUVYqVivMisvpA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"sitemap": "^9.0.0",
|
||||||
|
"stream-replace-string": "^2.0.0",
|
||||||
|
"zod": "^4.3.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@astrojs/telemetry": {
|
"node_modules/@astrojs/telemetry": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.1.tgz",
|
||||||
@ -2095,6 +2107,24 @@
|
|||||||
"@types/unist": "*"
|
"@types/unist": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "24.12.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz",
|
||||||
|
"integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~7.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/sax": {
|
||||||
|
"version": "1.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz",
|
||||||
|
"integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/unist": {
|
"node_modules/@types/unist": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
||||||
@ -2156,6 +2186,12 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/arg": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/argparse": {
|
"node_modules/argparse": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
@ -4992,6 +5028,25 @@
|
|||||||
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
|
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/sitemap": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^24.9.2",
|
||||||
|
"@types/sax": "^1.2.1",
|
||||||
|
"arg": "^5.0.0",
|
||||||
|
"sax": "^1.4.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"sitemap": "dist/esm/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.19.5",
|
||||||
|
"npm": ">=10.8.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/smol-toml": {
|
"node_modules/smol-toml": {
|
||||||
"version": "1.6.1",
|
"version": "1.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz",
|
||||||
@ -5023,6 +5078,12 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stream-replace-string": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/stringify-entities": {
|
"node_modules/stringify-entities": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
|
||||||
@ -5207,6 +5268,12 @@
|
|||||||
"node": ">=20.18.1"
|
"node": ">=20.18.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||||
|
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/unenv": {
|
"node_modules/unenv": {
|
||||||
"version": "2.0.0-rc.24",
|
"version": "2.0.0-rc.24",
|
||||||
"resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz",
|
"resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz",
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/alpinejs": "^0.5.0",
|
"@astrojs/alpinejs": "^0.5.0",
|
||||||
"@astrojs/cloudflare": "^13.1.10",
|
"@astrojs/cloudflare": "^13.1.10",
|
||||||
|
"@astrojs/sitemap": "^3.7.2",
|
||||||
"@tailwindcss/vite": "^4.2.2",
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
"@types/alpinejs": "^3.13.11",
|
"@types/alpinejs": "^3.13.11",
|
||||||
"alpinejs": "^3.15.11",
|
"alpinejs": "^3.15.11",
|
||||||
|
|||||||
4
public/robots.txt
Normal file
4
public/robots.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
Sitemap: https://nexstudio.ai/sitemap-index.xml
|
||||||
@ -1,5 +1,10 @@
|
|||||||
---
|
---
|
||||||
import { cta } from '../data/home';
|
import { getCta } from '../data/home';
|
||||||
|
|
||||||
|
const path = Astro.url.pathname;
|
||||||
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
|
const currentLocale = (localeMatch?.[1] ?? 'it') as 'it' | 'en' | 'th';
|
||||||
|
const cta = getCta(currentLocale);
|
||||||
|
|
||||||
const cf = cta.contactForm;
|
const cf = cta.contactForm;
|
||||||
const model = {
|
const model = {
|
||||||
@ -11,8 +16,18 @@ const model = {
|
|||||||
fieldName: cf.fieldName,
|
fieldName: cf.fieldName,
|
||||||
fieldCompany: cf.fieldCompany,
|
fieldCompany: cf.fieldCompany,
|
||||||
fieldCountry: cf.fieldCountry,
|
fieldCountry: cf.fieldCountry,
|
||||||
|
fieldDepartment: cf.fieldDepartment,
|
||||||
fieldEmail: cf.fieldEmail,
|
fieldEmail: cf.fieldEmail,
|
||||||
fieldMessage: cf.fieldMessage,
|
fieldMessage: cf.fieldMessage,
|
||||||
|
turnstileError: cf.errors.turnstile,
|
||||||
|
configError: cf.errors.config,
|
||||||
|
mailtoFallbackSubject: cf.mailtoFallbackSubject,
|
||||||
|
mailtoLabelName: cf.mailtoLabels.name,
|
||||||
|
mailtoLabelCompany: cf.mailtoLabels.company,
|
||||||
|
mailtoLabelEmail: cf.mailtoLabels.email,
|
||||||
|
mailtoLabelCountry: cf.mailtoLabels.country,
|
||||||
|
mailtoLabelDepartment: cf.mailtoLabels.department,
|
||||||
|
mailtoDefaultDepartment: cf.mailtoLabels.defaultDepartment,
|
||||||
};
|
};
|
||||||
const hasFormspree =
|
const hasFormspree =
|
||||||
typeof cf.actionUrl === 'string' && cf.actionUrl.length > 0;
|
typeof cf.actionUrl === 'string' && cf.actionUrl.length > 0;
|
||||||
@ -161,6 +176,20 @@ const fieldClass =
|
|||||||
class={fieldClass}
|
class={fieldClass}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-medium text-nx-muted" for="nx-cf-department">
|
||||||
|
{cf.departmentLabel}
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="nx-cf-department"
|
||||||
|
name={cf.fieldDepartment}
|
||||||
|
class={fieldClass}
|
||||||
|
>
|
||||||
|
{cf.departmentOptions.map((opt) => (
|
||||||
|
<option value={opt.value}>{opt.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs font-medium text-nx-muted" for="nx-cf-msg">
|
<label class="block text-xs font-medium text-nx-muted" for="nx-cf-msg">
|
||||||
{cf.messageLabel}
|
{cf.messageLabel}
|
||||||
@ -188,11 +217,11 @@ const fieldClass =
|
|||||||
class="cf-turnstile"
|
class="cf-turnstile"
|
||||||
data-sitekey={cf.turnstileSiteKey}
|
data-sitekey={cf.turnstileSiteKey}
|
||||||
data-theme="dark"
|
data-theme="dark"
|
||||||
data-language="it"
|
data-language={cf.turnstileLanguage}
|
||||||
data-size="normal"
|
data-size="normal"
|
||||||
></div>
|
></div>
|
||||||
<p class="mt-1.5 text-xs leading-relaxed text-nx-muted">
|
<p class="mt-1.5 text-xs leading-relaxed text-nx-muted">
|
||||||
Verifica anti-spam: completa il controllo prima di inviare il messaggio.
|
{cf.turnstileHint}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : null
|
) : null
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
---
|
---
|
||||||
import { footer } from '../data/home';
|
import { getFooter } from '../data/home';
|
||||||
|
|
||||||
|
const path = Astro.url.pathname;
|
||||||
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
|
const currentLocale = (localeMatch?.[1] ?? 'it') as 'it' | 'en' | 'th';
|
||||||
|
const footer = getFooter(currentLocale);
|
||||||
|
|
||||||
const hasNewsletterAction =
|
const hasNewsletterAction =
|
||||||
typeof footer.newsletter.actionUrl === 'string' &&
|
typeof footer.newsletter.actionUrl === 'string' &&
|
||||||
@ -17,9 +22,6 @@ const notifySubject =
|
|||||||
|
|
||||||
const linkClass =
|
const linkClass =
|
||||||
'text-sm text-sky-400/90 transition-colors hover:text-sky-300';
|
'text-sm text-sky-400/90 transition-colors hover:text-sky-300';
|
||||||
const path = Astro.url.pathname;
|
|
||||||
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
|
||||||
const currentLocale = localeMatch?.[1] ?? 'it';
|
|
||||||
const withLocale = (href: string) => {
|
const withLocale = (href: string) => {
|
||||||
if (currentLocale === 'it') return href;
|
if (currentLocale === 'it') return href;
|
||||||
if (href.startsWith(`/${currentLocale}`)) return href;
|
if (href.startsWith(`/${currentLocale}`)) return href;
|
||||||
@ -91,7 +93,7 @@ const withLocale = (href: string) => {
|
|||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
class="cursor-not-allowed text-nx-muted opacity-50"
|
class="cursor-not-allowed text-nx-muted opacity-50"
|
||||||
title="URL da configurare in footer.ts"
|
title={footer.labels.socialPlaceholderTitle}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</span>
|
</span>
|
||||||
@ -115,7 +117,7 @@ const withLocale = (href: string) => {
|
|||||||
{link.label}
|
{link.label}
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<span class="text-sm text-nx-muted" title="Collegamento in arrivo">
|
<span class="text-sm text-nx-muted" title={footer.labels.companyPlaceholderTitle}>
|
||||||
{link.label}
|
{link.label}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@ -131,7 +133,7 @@ const withLocale = (href: string) => {
|
|||||||
>
|
>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<p class="text-xs font-semibold uppercase tracking-wider text-nx-fg">
|
<p class="text-xs font-semibold uppercase tracking-wider text-nx-fg">
|
||||||
Impostazioni cookie
|
{footer.labels.cookieTitle}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1.5 max-w-xl text-xs leading-relaxed text-nx-muted">
|
<p class="mt-1.5 max-w-xl text-xs leading-relaxed text-nx-muted">
|
||||||
{footer.cookiePreferences.hint}
|
{footer.cookiePreferences.hint}
|
||||||
@ -204,7 +206,7 @@ const withLocale = (href: string) => {
|
|||||||
{nl.buttonLabel}
|
{nl.buttonLabel}
|
||||||
</button>
|
</button>
|
||||||
<p id="nx-newsletter-hint" class="text-xs leading-relaxed text-nx-muted">
|
<p id="nx-newsletter-hint" class="text-xs leading-relaxed text-nx-muted">
|
||||||
Iscrizione in arrivo: configura l’endpoint nel progetto per attivare il modulo.
|
{footer.labels.newsletterComingSoon}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,49 +1,169 @@
|
|||||||
/** Call-to-action (blocco contatti / chiusura pagina) */
|
/** Call-to-action (blocco contatti / chiusura pagina) */
|
||||||
export const cta = {
|
const env = import.meta.env;
|
||||||
/** Riga sezione (stesso ruolo di FAQ, Stack, …) */
|
const contactActionUrl = (env.PUBLIC_CONTACT_FORM_ACTION_URL ?? '/api/contact').trim();
|
||||||
eyebrow: 'Contatti',
|
const contactSuccessRedirect = (env.PUBLIC_CONTACT_FORM_SUCCESS_REDIRECT ?? '').trim();
|
||||||
|
const contactMailto = (env.PUBLIC_CONTACT_FORM_MAILTO ?? '').trim();
|
||||||
|
const contactTurnstileSiteKey = (env.PUBLIC_CONTACT_TURNSTILE_SITE_KEY ?? '').trim();
|
||||||
|
|
||||||
/** Titolo CTA su due righe (dopo il punto) */
|
import type { SupportedLocale } from './navigation';
|
||||||
titleLine1: 'Automatizza il complesso.',
|
|
||||||
titleLine2: 'Velocizza il processo.',
|
|
||||||
body:
|
|
||||||
'Dai modelli LLM alla distribuzione globale in pochi millisecondi. La directory definitiva per il LegalTech e MedTech. Utilizza il modulo contatto qui a fianco e ti forniremo tutti i chiarimenti che ti servono.',
|
|
||||||
|
|
||||||
/**
|
const ctaByLocale = {
|
||||||
* Modulo contatti in pagina (sezione Contatti).
|
it: {
|
||||||
*
|
eyebrow: 'Contatti',
|
||||||
* **Formspree:** crea un form su https://formspree.io e incolla l’URL in `actionUrl`.
|
titleLine1: 'Automatizza il complesso.',
|
||||||
* **Solo mailto:** lascia `actionUrl` vuoto e imposta `mailto` con l’indirizzo destinatario
|
titleLine2: 'Velocizza il processo.',
|
||||||
* (si apre il client di posta dell’utente).
|
body:
|
||||||
*/
|
'Dai modelli LLM alla distribuzione globale in pochi millisecondi. La directory definitiva per il LegalTech e MedTech. Utilizza il modulo contatto qui a fianco e ti forniremo tutti i chiarimenti che ti servono.',
|
||||||
contactForm: {
|
contactForm: {
|
||||||
/** Lasciare vuoti endpoint/chiavi nelle fasi di sola UI. */
|
title: 'Parliamone',
|
||||||
title: 'Parliamone',
|
description:
|
||||||
description:
|
'Lascia i tuoi dati e il messaggio: ti risponde il team NexStudio.',
|
||||||
'Lascia i tuoi dati e il messaggio: ti risponde il team NexStudio.',
|
nameLabel: 'Nome',
|
||||||
nameLabel: 'Nome',
|
companyLabel: 'Azienda (opzionale)',
|
||||||
companyLabel: 'Azienda (opzionale)',
|
countryLabel: 'Paese',
|
||||||
countryLabel: 'Paese',
|
departmentLabel: 'Area',
|
||||||
emailLabel: 'Email',
|
emailLabel: 'Email',
|
||||||
messageLabel: 'Scrivi qui il tuo messaggio',
|
messageLabel: 'Scrivi qui il tuo messaggio',
|
||||||
submitLabel: 'Invia messaggio',
|
submitLabel: 'Invia messaggio',
|
||||||
/** Formspree: `https://formspree.io/f/xxxx` — vuoto = prova `mailto` */
|
actionUrl: contactActionUrl,
|
||||||
actionUrl: '',
|
successRedirect: contactSuccessRedirect,
|
||||||
/** Formspree: redirect dopo invio (`_next`) — URL **assoluta** in produzione */
|
turnstileSiteKey: contactTurnstileSiteKey,
|
||||||
successRedirect: '',
|
turnstileLanguage: 'it',
|
||||||
/**
|
turnstileHint:
|
||||||
* Cloudflare Turnstile (anti-spam): inserisci qui la site key pubblica.
|
'Verifica anti-spam: completa il controllo prima di inviare il messaggio.',
|
||||||
* Se vuota, il CAPTCHA non viene mostrato.
|
notifySubject: 'Richiesta contatto — sito NexStudio',
|
||||||
*/
|
mailto: contactMailto,
|
||||||
turnstileSiteKey: '',
|
mailtoFallbackSubject: 'Contatto sito',
|
||||||
/** Formspree `_subject` e oggetto mailto */
|
mailtoLabels: {
|
||||||
notifySubject: 'Richiesta contatto — sito NexStudio',
|
name: 'Nome',
|
||||||
/** Se `actionUrl` è vuoto: email per `mailto:` (es. `info@tuodominio.com`) */
|
company: 'Azienda',
|
||||||
mailto: '',
|
email: 'Email',
|
||||||
fieldName: 'name',
|
country: 'Paese',
|
||||||
fieldCompany: 'company',
|
department: 'Area',
|
||||||
fieldCountry: 'country',
|
defaultDepartment: 'general',
|
||||||
fieldEmail: 'email',
|
},
|
||||||
fieldMessage: 'message',
|
errors: {
|
||||||
|
turnstile: 'Completa il controllo anti-spam prima di inviare il messaggio.',
|
||||||
|
config:
|
||||||
|
'Configura `contactForm.actionUrl` (Formspree) o `contactForm.mailto` in `src/data/home/cta.ts`.',
|
||||||
|
},
|
||||||
|
fieldName: 'name',
|
||||||
|
fieldCompany: 'company',
|
||||||
|
fieldCountry: 'country',
|
||||||
|
fieldDepartment: 'department',
|
||||||
|
fieldEmail: 'email',
|
||||||
|
fieldMessage: 'message',
|
||||||
|
departmentOptions: [
|
||||||
|
{ value: 'info', label: 'Informazioni generali e commerciali' },
|
||||||
|
{ value: 'privacy', label: 'Privacy' },
|
||||||
|
{ value: 'careers', label: 'Careers / Collaborazioni' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
eyebrow: 'Contacts',
|
||||||
|
titleLine1: 'Automate complexity.',
|
||||||
|
titleLine2: 'Accelerate operations.',
|
||||||
|
body:
|
||||||
|
'From LLM models to global distribution in milliseconds. Use the contact form and our team will provide all the details you need.',
|
||||||
|
contactForm: {
|
||||||
|
title: 'Let’s talk',
|
||||||
|
description: 'Share your details and message: the NexStudio team will reply.',
|
||||||
|
nameLabel: 'Name',
|
||||||
|
companyLabel: 'Company (optional)',
|
||||||
|
countryLabel: 'Country',
|
||||||
|
departmentLabel: 'Area',
|
||||||
|
emailLabel: 'Email',
|
||||||
|
messageLabel: 'Write your message',
|
||||||
|
submitLabel: 'Send message',
|
||||||
|
actionUrl: contactActionUrl,
|
||||||
|
successRedirect: contactSuccessRedirect,
|
||||||
|
turnstileSiteKey: contactTurnstileSiteKey,
|
||||||
|
turnstileLanguage: 'en',
|
||||||
|
turnstileHint:
|
||||||
|
'Anti-spam verification: complete the challenge before sending your message.',
|
||||||
|
notifySubject: 'Contact request — NexStudio website',
|
||||||
|
mailto: contactMailto,
|
||||||
|
mailtoFallbackSubject: 'Website contact',
|
||||||
|
mailtoLabels: {
|
||||||
|
name: 'Name',
|
||||||
|
company: 'Company',
|
||||||
|
email: 'Email',
|
||||||
|
country: 'Country',
|
||||||
|
department: 'Area',
|
||||||
|
defaultDepartment: 'general',
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
turnstile: 'Complete the anti-spam check before sending your message.',
|
||||||
|
config:
|
||||||
|
'Configure `contactForm.actionUrl` (Formspree) or `contactForm.mailto` in `src/data/home/cta.ts`.',
|
||||||
|
},
|
||||||
|
fieldName: 'name',
|
||||||
|
fieldCompany: 'company',
|
||||||
|
fieldCountry: 'country',
|
||||||
|
fieldDepartment: 'department',
|
||||||
|
fieldEmail: 'email',
|
||||||
|
fieldMessage: 'message',
|
||||||
|
departmentOptions: [
|
||||||
|
{ value: 'info', label: 'General and commercial information' },
|
||||||
|
{ value: 'privacy', label: 'Privacy' },
|
||||||
|
{ value: 'careers', label: 'Careers / Collaborations' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
eyebrow: 'ติดต่อ',
|
||||||
|
titleLine1: 'ทำงานซับซ้อนให้อัตโนมัติ',
|
||||||
|
titleLine2: 'เร่งความเร็วกระบวนการ',
|
||||||
|
body:
|
||||||
|
'จากโมเดล LLM สู่การกระจายระบบระดับโลกภายในไม่กี่มิลลิวินาที ใช้ฟอร์มติดต่อด้านข้าง แล้วทีม NexStudio จะตอบกลับพร้อมข้อมูลที่คุณต้องการ',
|
||||||
|
contactForm: {
|
||||||
|
title: 'คุยกับเรา',
|
||||||
|
description: 'กรอกข้อมูลและข้อความ แล้วทีม NexStudio จะติดต่อกลับ',
|
||||||
|
nameLabel: 'ชื่อ',
|
||||||
|
companyLabel: 'บริษัท (ไม่บังคับ)',
|
||||||
|
countryLabel: 'ประเทศ',
|
||||||
|
departmentLabel: 'แผนก',
|
||||||
|
emailLabel: 'อีเมล',
|
||||||
|
messageLabel: 'พิมพ์ข้อความของคุณ',
|
||||||
|
submitLabel: 'ส่งข้อความ',
|
||||||
|
actionUrl: contactActionUrl,
|
||||||
|
successRedirect: contactSuccessRedirect,
|
||||||
|
turnstileSiteKey: contactTurnstileSiteKey,
|
||||||
|
turnstileLanguage: 'th',
|
||||||
|
turnstileHint: 'การยืนยันป้องกันสแปม: กรุณาทำขั้นตอนให้เสร็จก่อนส่งข้อความ',
|
||||||
|
notifySubject: 'คำขอติดต่อ — เว็บไซต์ NexStudio',
|
||||||
|
mailto: contactMailto,
|
||||||
|
mailtoFallbackSubject: 'ติดต่อจากเว็บไซต์',
|
||||||
|
mailtoLabels: {
|
||||||
|
name: 'ชื่อ',
|
||||||
|
company: 'บริษัท',
|
||||||
|
email: 'อีเมล',
|
||||||
|
country: 'ประเทศ',
|
||||||
|
department: 'แผนก',
|
||||||
|
defaultDepartment: 'general',
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
turnstile: 'กรุณายืนยันการป้องกันสแปมก่อนส่งข้อความ',
|
||||||
|
config:
|
||||||
|
'กรุณาตั้งค่า `contactForm.actionUrl` (Formspree) หรือ `contactForm.mailto` ใน `src/data/home/cta.ts`',
|
||||||
|
},
|
||||||
|
fieldName: 'name',
|
||||||
|
fieldCompany: 'company',
|
||||||
|
fieldCountry: 'country',
|
||||||
|
fieldDepartment: 'department',
|
||||||
|
fieldEmail: 'email',
|
||||||
|
fieldMessage: 'message',
|
||||||
|
departmentOptions: [
|
||||||
|
{ value: 'info', label: 'ข้อมูลทั่วไปและเชิงพาณิชย์' },
|
||||||
|
{ value: 'privacy', label: 'ความเป็นส่วนตัว' },
|
||||||
|
{ value: 'careers', label: 'Careers / Collaborations' },
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const getCta = (locale: SupportedLocale) => ctaByLocale[locale];
|
||||||
|
|
||||||
|
/** Retro-compatibilità: fallback italiano. */
|
||||||
|
export const cta = ctaByLocale.it;
|
||||||
|
|||||||
@ -1,47 +1,148 @@
|
|||||||
|
import type { SupportedLocale } from './navigation';
|
||||||
|
|
||||||
/** FAQ — domande/risposte allineate a hero, prodotti, servizi e stack */
|
/** FAQ — domande/risposte allineate a hero, prodotti, servizi e stack */
|
||||||
export const faq = {
|
const faqByLocale = {
|
||||||
eyebrow: 'FAQ',
|
it: {
|
||||||
title: 'Domande frequenti',
|
eyebrow: 'FAQ',
|
||||||
items: [
|
title: 'Domande frequenti',
|
||||||
{
|
items: [
|
||||||
question: 'Che tipo di organizzazioni seguite con LexAura e MediAura?',
|
{
|
||||||
answer:
|
question: 'Che tipo di organizzazioni seguite con LexAura e MediAura?',
|
||||||
'Lavoriamo con studi legali e strutture sanitarie — dal professionista singolo a team e sedi multi‑unità — che gestiscono volumi elevati di informazione e hanno bisogno di strumenti affidabili in ambiti regolamentati. La verticalità su Legal Tech e Health Tech è parte della nostra proposta: meno genericismo, più requisiti operativi, tracciabilità e continuità del servizio.',
|
answer:
|
||||||
},
|
'Lavoriamo con studi legali e strutture sanitarie — dal professionista singolo a team e sedi multi‑unità — che gestiscono volumi elevati di informazione e hanno bisogno di strumenti affidabili in ambiti regolamentati. La verticalità su Legal Tech e Health Tech è parte della nostra proposta: meno genericismo, più requisiti operativi, tracciabilità e continuità del servizio.',
|
||||||
{
|
},
|
||||||
question: 'L’intelligenza artificiale sostituisce avvocati o medici?',
|
{
|
||||||
answer:
|
question: 'L’intelligenza artificiale sostituisce avvocati o medici?',
|
||||||
'No. I nostri prodotti sono pensati per integrarsi nei flussi professionali: sintesi, ricerca, drafting assistito, automazione della documentazione e interfacce vocali liberano tempo, ma la decisione finale e la responsabilità restano del professionista. L’obiettivo è ridurre il carico ripetitivo e offrire una base analitica solida, non sostituire il giudizio umano.',
|
answer:
|
||||||
},
|
'No. I nostri prodotti sono pensati per integrarsi nei flussi professionali: sintesi, ricerca, drafting assistito, automazione della documentazione e interfacce vocali liberano tempo, ma la decisione finale e la responsabilità restano del professionista. L’obiettivo è ridurre il carico ripetitivo e offrire una base analitica solida, non sostituire il giudizio umano.',
|
||||||
{
|
},
|
||||||
question: 'Cosa succede dopo l’adozione del software?',
|
{
|
||||||
answer:
|
question: 'Cosa succede dopo l’adozione del software?',
|
||||||
'Il modello di servizio va oltre la licenza: onboarding e formazione specialistica per team legali e sanitari, supporto tecnico e consulenza operativa continuativi, aggiornamento dei motori di analisi e manutenzione evolutiva rispetto a normative e contesto di mercato. Per criticità urgenti sono previsti protocolli di intervento prioritario, così che studi e strutture non restino isolati dai propri strumenti.',
|
answer:
|
||||||
},
|
'Il modello di servizio va oltre la licenza: onboarding e formazione specialistica per team legali e sanitari, supporto tecnico e consulenza operativa continuativi, aggiornamento dei motori di analisi e manutenzione evolutiva rispetto a normative e contesto di mercato. Per criticità urgenti sono previsti protocolli di intervento prioritario, così che studi e strutture non restino isolati dai propri strumenti.',
|
||||||
{
|
},
|
||||||
question: 'Potete collegare LexAura o MediAura ai nostri sistemi già in uso?',
|
{
|
||||||
answer:
|
question: 'Potete collegare LexAura o MediAura ai nostri sistemi già in uso?',
|
||||||
'Sì, quando serve. Oltre ai prodotti standard supportiamo integrazioni tramite API e moduli personalizzati nel perimetro dei nostri SaaS, per collegare gestionali o flussi documentali preesistenti senza reinventare l’intero stack informativo.',
|
answer:
|
||||||
},
|
'Sì, quando serve. Oltre ai prodotti standard supportiamo integrazioni tramite API e moduli personalizzati nel perimetro dei nostri SaaS, per collegare gestionali o flussi documentali preesistenti senza reinventare l’intero stack informativo.',
|
||||||
{
|
},
|
||||||
question: 'Come gestite sicurezza, disponibilità e dati sensibili?',
|
{
|
||||||
answer:
|
question: 'Come gestite sicurezza, disponibilità e dati sensibili?',
|
||||||
'Progettiamo su architetture cloud‑native ad alta disponibilità, con attenzione a crittografia, protezione di perimetro (es. WAF, mitigazione DDoS) e continuità operativa. Il trattamento di informazioni legali e sanitarie è affrontato con criteri di integrità dei dati e conformità alle normative applicabili, inclusa la chiarezza su dove risiedono i dati e sui requisiti di due diligence che i settori regolamentati si attendono.',
|
answer:
|
||||||
},
|
'Progettiamo su architetture cloud‑native ad alta disponibilità, con attenzione a crittografia, protezione di perimetro (es. WAF, mitigazione DDoS) e continuità operativa. Il trattamento di informazioni legali e sanitarie è affrontato con criteri di integrità dei dati e conformità alle normative applicabili, inclusa la chiarezza su dove risiedono i dati e sui requisiti di due diligence che i settori regolamentati si attendono.',
|
||||||
{
|
},
|
||||||
question: 'Lavorate solo in loco o anche con clienti in Italia e all’estero?',
|
{
|
||||||
answer:
|
question: 'Lavorate solo in loco o anche con clienti in Italia e all’estero?',
|
||||||
'Siamo una software company con base operativa a Bangkok e collaboriamo con clienti a livello internazionale. Workshop e incontri possono essere online o in presenza in base al progetto; supporto e comunicazioni operative in italiano, inglese, tedesco, francese, spagnolo o tailandese, con orari concordati.',
|
answer:
|
||||||
},
|
'Siamo una software company con base operativa a Bangkok e collaboriamo con clienti a livello internazionale. Workshop e incontri possono essere online o in presenza in base al progetto; supporto e comunicazioni operative in italiano, inglese, tedesco, francese, spagnolo o tailandese, con orari concordati.',
|
||||||
{
|
},
|
||||||
question: 'Diritto d’uso e proprietà del software: come le regolate?',
|
{
|
||||||
answer:
|
question: 'Diritto d’uso e proprietà del software: come le regolate?',
|
||||||
'Dipende dal contratto: in genere cediamo l’esclusiva sul deliverable a saldo, con clausole esplicite su componenti open source e librerie di terze parti. Per licenze SaaS, permessi d’uso e responsabilità sono definiti nei termini commerciali e nel contratto quadro.',
|
answer:
|
||||||
},
|
'Dipende dal contratto: in genere cediamo l’esclusiva sul deliverable a saldo, con clausole esplicite su componenti open source e librerie di terze parti. Per licenze SaaS, permessi d’uso e responsabilità sono definiti nei termini commerciali e nel contratto quadro.',
|
||||||
{
|
},
|
||||||
question: 'Qual è il modo più rapido per parlarne con voi?',
|
{
|
||||||
answer:
|
question: 'Qual è il modo più rapido per parlarne con voi?',
|
||||||
'Il punto di ingresso principale è la chat dal pulsante «Chattiamo» in header: prima vedi se il canale è con operatore umano, solo chatbot o misto, poi lasci email (e facoltativamente il nome) per ricevere un link o un codice monouso e aprire la sessione senza perdite di tempo. In chat operatore umano e chatbot collaborano per orientamento iniziale, requisiti e passi successivi. Per il supporto via web ci interfacciamo in italiano, inglese, tedesco, francese, spagnolo o tailandese. Per materiale sensibile useremo sempre i canali concordati in fase di rapporto commerciale. Se ci si sente più a proprio agio usando un form di contatto, in fondo alla homepage nella sezione Contatti trovate il modulo a destra del testo introduttivo.',
|
answer:
|
||||||
},
|
'Il punto di ingresso principale è la chat dal pulsante «Chattiamo» in header: prima vedi se il canale è con operatore umano, solo chatbot o misto, poi lasci email (e facoltativamente il nome) per ricevere un link o un codice monouso e aprire la sessione senza perdite di tempo. In chat operatore umano e chatbot collaborano per orientamento iniziale, requisiti e passi successivi. Per il supporto via web ci interfacciamo in italiano, inglese, tedesco, francese, spagnolo o tailandese. Per materiale sensibile useremo sempre i canali concordati in fase di rapporto commerciale. Se ci si sente più a proprio agio usando un form di contatto, in fondo alla homepage nella sezione Contatti trovate il modulo a destra del testo introduttivo.',
|
||||||
] as const,
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
eyebrow: 'FAQ',
|
||||||
|
title: 'Frequently asked questions',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
question: 'What organizations do you support with LexAura and MediAura?',
|
||||||
|
answer:
|
||||||
|
'We work with law firms and healthcare organizations, from solo professionals to multi-site teams, that manage high information volumes and need reliable tools in regulated environments.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Does AI replace lawyers or doctors?',
|
||||||
|
answer:
|
||||||
|
'No. Our products support professional workflows with summarization, research, assisted drafting, and automation. Final decisions and responsibility always remain with the professional.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'What happens after software adoption?',
|
||||||
|
answer:
|
||||||
|
'Our service model goes beyond licensing: specialist onboarding and training, continuous technical support and consulting, and ongoing engine updates aligned with regulations and market context.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Can you integrate LexAura or MediAura with our existing systems?',
|
||||||
|
answer:
|
||||||
|
'Yes, when needed. We support API integrations and custom modules within our SaaS perimeter to connect existing management or document flows.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'How do you handle security, availability, and sensitive data?',
|
||||||
|
answer:
|
||||||
|
'We design for high-availability cloud-native architectures, encryption, perimeter protection, and operational continuity, with clear data-governance practices aligned with applicable regulations.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Do you work only locally or also with international clients?',
|
||||||
|
answer:
|
||||||
|
'We are based in Bangkok and collaborate internationally. Workshops can be remote or on-site depending on the project.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'How do you handle software usage rights and ownership?',
|
||||||
|
answer:
|
||||||
|
'This depends on the contract. In many engagements, exclusive rights on deliverables are transferred after final payment, with explicit clauses for open source and third-party components.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'What is the fastest way to talk with your team?',
|
||||||
|
answer:
|
||||||
|
'The primary entry point is the chat button in the header. You provide your email to receive a one-time link or code, then start the session without delay.',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
eyebrow: 'คำถามที่พบบ่อย',
|
||||||
|
title: 'คำถามที่พบบ่อย',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
question: 'องค์กรแบบไหนที่เหมาะกับ LexAura และ MediAura?',
|
||||||
|
answer:
|
||||||
|
'เราทำงานกับสำนักงานกฎหมายและองค์กรสุขภาพ ตั้งแต่ผู้เชี่ยวชาญเดี่ยวไปจนถึงทีมหลายสาขา ที่ต้องจัดการข้อมูลจำนวนมากในบริบทที่มีข้อกำกับสูง',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'AI จะแทนที่ทนายหรือแพทย์หรือไม่?',
|
||||||
|
answer:
|
||||||
|
'ไม่ใช่ เป้าหมายของเราคือช่วยงานเชิงวิเคราะห์ งานเอกสาร และเวิร์กโฟลว์ซ้ำ ๆ โดยการตัดสินใจสุดท้ายยังคงเป็นหน้าที่ของผู้เชี่ยวชาญ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'หลังเริ่มใช้งานซอฟต์แวร์แล้วจะเกิดอะไรขึ้น?',
|
||||||
|
answer:
|
||||||
|
'เรามีบริการหลังการใช้งานครบวงจร ทั้ง onboarding การอบรมเฉพาะทาง การสนับสนุนทางเทคนิค และการอัปเดตระบบอย่างต่อเนื่อง',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'สามารถเชื่อม LexAura หรือ MediAura กับระบบเดิมของเราได้ไหม?',
|
||||||
|
answer:
|
||||||
|
'ได้ เรารองรับการเชื่อมต่อผ่าน API และโมดูลแบบกำหนดเองภายในขอบเขต SaaS ของเรา',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'จัดการความปลอดภัย ความพร้อมใช้งาน และข้อมูลอ่อนไหวอย่างไร?',
|
||||||
|
answer:
|
||||||
|
'เราใช้สถาปัตยกรรมคลาวด์ที่พร้อมใช้งานสูง การเข้ารหัส การป้องกันระดับเครือข่าย และแนวปฏิบัติด้านการกำกับข้อมูลให้สอดคล้องข้อกำกับที่เกี่ยวข้อง',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'รับงานเฉพาะในพื้นที่หรือรับลูกค้าต่างประเทศด้วย?',
|
||||||
|
answer:
|
||||||
|
'สำนักงานใหญ่ปฏิบัติการของเราอยู่ที่กรุงเทพฯ และทำงานกับลูกค้าต่างประเทศได้ ทั้งออนไลน์และออนไซต์ตามความเหมาะสมของโครงการ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'สิทธิการใช้งานและความเป็นเจ้าของซอฟต์แวร์กำหนดอย่างไร?',
|
||||||
|
answer:
|
||||||
|
'ขึ้นกับสัญญา โดยทั่วไปจะระบุเงื่อนไขการใช้งาน ความรับผิดชอบ และสิทธิในชิ้นงานอย่างชัดเจน รวมถึงส่วนประกอบโอเพนซอร์สและ third-party',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'ถ้าต้องการคุยเร็วที่สุดควรติดต่อช่องทางไหน?',
|
||||||
|
answer:
|
||||||
|
'กดปุ่มแชทบนส่วนหัวเว็บไซต์ ใส่อีเมลเพื่อรับลิงก์หรือโค้ดแบบใช้ครั้งเดียว แล้วเริ่มเซสชันได้ทันที',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const getFaq = (locale: SupportedLocale) => faqByLocale[locale];
|
||||||
|
|
||||||
|
/** Retro-compatibilità: fallback italiano. */
|
||||||
|
export const faq = faqByLocale.it;
|
||||||
|
|||||||
@ -1,87 +1,178 @@
|
|||||||
|
import type { SupportedLocale } from './navigation';
|
||||||
|
|
||||||
/** Footer — griglia 4 colonne (link); newsletter in blocco unico sotto, allineata a sinistra */
|
/** Footer — griglia 4 colonne (link); newsletter in blocco unico sotto, allineata a sinistra */
|
||||||
export const footer = {
|
const footerByLocale = {
|
||||||
brandName: 'NexStudio',
|
it: {
|
||||||
tagline: 'Ingegneria del software e IA per settori regolamentati.',
|
brandName: 'NexStudio',
|
||||||
legalLine: 'P.IVA e note legali: da inserire.',
|
tagline: 'Ingegneria del software e IA per settori regolamentati.',
|
||||||
|
legalLine: 'P.IVA e note legali: da inserire.',
|
||||||
columnTitles: {
|
columnTitles: {
|
||||||
sections: 'Sezioni',
|
sections: 'Sezioni',
|
||||||
legal: 'Normative',
|
legal: 'Normative',
|
||||||
social: 'Reti sociali',
|
social: 'Reti sociali',
|
||||||
company: 'Azienda',
|
company: 'Azienda',
|
||||||
} as const,
|
} as const,
|
||||||
|
homeNav: [
|
||||||
/** Colonna 1 — ancore sulla homepage */
|
{ label: 'Prodotti', href: '/#prodotti' },
|
||||||
homeNav: [
|
{ label: 'Servizi', href: '/#servizi' },
|
||||||
{ label: 'Prodotti', href: '/#prodotti' },
|
{ label: 'Stack', href: '/#stack' },
|
||||||
{ label: 'Servizi', href: '/#servizi' },
|
{ label: 'FAQ', href: '/#faq' },
|
||||||
{ label: 'Stack', href: '/#stack' },
|
] as const,
|
||||||
{ label: 'FAQ', href: '/#faq' },
|
cookiePreferences: {
|
||||||
] as const,
|
label: 'Impostazioni cookie',
|
||||||
|
hint: 'Preferenze di consenso sul tuo browser (non è un testo legale).',
|
||||||
/** Pannello preferenze (CMP): non è un documento legale; tenuto fuori dalla colonna Normative */
|
} as const,
|
||||||
cookiePreferences: {
|
legalColumnNav: [
|
||||||
label: 'Impostazioni cookie',
|
{ label: 'Condizioni d’uso', href: '/terms' },
|
||||||
hint: 'Preferenze di consenso sul tuo browser (non è un testo legale).',
|
{ label: 'Informativa cookie', href: '/cookies' },
|
||||||
} as const,
|
{ label: 'Privacy', href: '/privacy' },
|
||||||
|
{ label: 'Diritti privacy (GDPR)', href: '/gdpr' },
|
||||||
/** Colonna 2 — documenti legali (testi bozza nelle rispettive pagine) */
|
] as const,
|
||||||
legalColumnNav: [
|
socialLinks: [
|
||||||
{ label: 'Condizioni d’uso', href: '/terms' },
|
{ label: 'Facebook', href: '' },
|
||||||
{ label: 'Informativa cookie', href: '/cookies' },
|
{ label: 'LinkedIn', href: '' },
|
||||||
{ label: 'Privacy', href: '/privacy' },
|
{ label: 'Twitter / X', href: '' },
|
||||||
{ label: 'Diritti privacy (GDPR)', href: '/gdpr' },
|
{ label: 'GitHub', href: '' },
|
||||||
] as const,
|
] as const,
|
||||||
|
companyNav: [
|
||||||
/**
|
{ label: 'Chi siamo', href: '/about' },
|
||||||
* Colonna 3 — social (sostituire gli URL con i profili reali).
|
{ label: 'Codice etico', href: '/codice-etico' },
|
||||||
* Lasciare `href` vuoto per mostrare solo etichetta disabilitata finché non configurato.
|
{ label: 'Modello organizzativo', href: '/modello-organizzativo' },
|
||||||
*/
|
{ label: 'Dove siamo', href: '/dove-siamo' },
|
||||||
socialLinks: [
|
] as const,
|
||||||
{ label: 'Facebook', href: '' },
|
newsletter: {
|
||||||
{ label: 'LinkedIn', href: '' },
|
title: 'Newsletter',
|
||||||
{ label: 'Twitter / X', href: '' },
|
description: 'Aggiornamenti su prodotti e roadmap.',
|
||||||
{ label: 'GitHub', href: '' },
|
placeholder: 'La tua email',
|
||||||
] as const,
|
buttonLabel: 'Iscriviti',
|
||||||
|
actionUrl: '',
|
||||||
/** Colonna 4 — pagine istituzionali e compliance */
|
fieldName: 'email',
|
||||||
companyNav: [
|
successRedirect: '',
|
||||||
{ label: 'Chi siamo', href: '/about' },
|
notifySubject: 'Nuova iscrizione newsletter — NexStudio',
|
||||||
{ label: 'Codice etico', href: '/codice-etico' },
|
},
|
||||||
{ label: 'Modello organizzativo', href: '/modello-organizzativo' },
|
labels: {
|
||||||
{ label: 'Dove siamo', href: '/dove-siamo' },
|
cookieTitle: 'Impostazioni cookie',
|
||||||
] as const,
|
socialPlaceholderTitle: 'URL da configurare in footer.ts',
|
||||||
|
companyPlaceholderTitle: 'Collegamento in arrivo',
|
||||||
/**
|
newsletterComingSoon:
|
||||||
* Newsletter — invio via POST a un endpoint esterno.
|
'Iscrizione in arrivo: configura l’endpoint nel progetto per attivare il modulo.',
|
||||||
*
|
},
|
||||||
* Opzione A (consigliata per partire): Formspree
|
},
|
||||||
* 1. Crea account su https://formspree.io e un nuovo form.
|
en: {
|
||||||
* 2. Copia l’URL tipo `https://formspree.io/f/abcdefgh` in `actionUrl`.
|
brandName: 'NexStudio',
|
||||||
* 3. Opzionale: `successRedirect` = URL assoluta della pagina di ringraziamento
|
tagline: 'Software engineering and AI for regulated industries.',
|
||||||
* (es. `https://tuodominio.com/newsletter/grazie` — vedi `src/pages/newsletter/grazie.astro`).
|
legalLine: 'VAT number and legal notes: to be added.',
|
||||||
* 4. In Formspree collega l’invio a email / Slack / Google Sheets.
|
columnTitles: {
|
||||||
*
|
sections: 'Sections',
|
||||||
* Opzione B: Brevo, Mailchimp, Buttondown, ConvertKit — usano spesso un URL
|
legal: 'Legal',
|
||||||
* dedicato o un embed; adatta `fieldName` al nome campo richiesto dalla doc.
|
social: 'Social',
|
||||||
*
|
company: 'Company',
|
||||||
* Opzione C: Worker Cloudflare + lista in KV/D1 (più controllo, più codice).
|
} as const,
|
||||||
*/
|
homeNav: [
|
||||||
newsletter: {
|
{ label: 'Products', href: '/#prodotti' },
|
||||||
title: 'Newsletter',
|
{ label: 'Services', href: '/#servizi' },
|
||||||
description: 'Aggiornamenti su prodotti e roadmap.',
|
{ label: 'Stack', href: '/#stack' },
|
||||||
placeholder: 'La tua email',
|
{ label: 'FAQ', href: '/#faq' },
|
||||||
buttonLabel: 'Iscriviti',
|
] as const,
|
||||||
/** Formspree: `https://formspree.io/f/xxxx` — vuoto = modulo disabilitato */
|
cookiePreferences: {
|
||||||
actionUrl: '',
|
label: 'Cookie settings',
|
||||||
/** Nome campo email richiesto dal provider (Formspree: di solito `email`) */
|
hint: 'Consent preferences in your browser (this is not a legal document).',
|
||||||
fieldName: 'email',
|
} as const,
|
||||||
/**
|
legalColumnNav: [
|
||||||
* Formspree: redirect dopo invio (`_next`). Deve essere URL **assoluta**
|
{ label: 'Terms of use', href: '/terms' },
|
||||||
* (es. produzione). Vuoto = pagina di ringrazio predefinita Formspree.
|
{ label: 'Cookie policy', href: '/cookies' },
|
||||||
*/
|
{ label: 'Privacy', href: '/privacy' },
|
||||||
successRedirect: '',
|
{ label: 'Privacy rights (GDPR)', href: '/gdpr' },
|
||||||
/** Formspree: oggetto della mail di notifica (`_subject`) */
|
] as const,
|
||||||
notifySubject: 'Nuova iscrizione newsletter — NexStudio',
|
socialLinks: [
|
||||||
|
{ label: 'Facebook', href: '' },
|
||||||
|
{ label: 'LinkedIn', href: '' },
|
||||||
|
{ label: 'Twitter / X', href: '' },
|
||||||
|
{ label: 'GitHub', href: '' },
|
||||||
|
] as const,
|
||||||
|
companyNav: [
|
||||||
|
{ label: 'About us', href: '/about' },
|
||||||
|
{ label: 'Code of ethics', href: '/codice-etico' },
|
||||||
|
{ label: 'Organizational model', href: '/modello-organizzativo' },
|
||||||
|
{ label: 'Where we are', href: '/dove-siamo' },
|
||||||
|
] as const,
|
||||||
|
newsletter: {
|
||||||
|
title: 'Newsletter',
|
||||||
|
description: 'Updates on products and roadmap.',
|
||||||
|
placeholder: 'Your email',
|
||||||
|
buttonLabel: 'Subscribe',
|
||||||
|
actionUrl: '',
|
||||||
|
fieldName: 'email',
|
||||||
|
successRedirect: '',
|
||||||
|
notifySubject: 'New newsletter signup — NexStudio',
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
cookieTitle: 'Cookie settings',
|
||||||
|
socialPlaceholderTitle: 'URL to configure in footer.ts',
|
||||||
|
companyPlaceholderTitle: 'Coming soon',
|
||||||
|
newsletterComingSoon:
|
||||||
|
'Newsletter signups coming soon: configure the endpoint in the project to enable this form.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
brandName: 'NexStudio',
|
||||||
|
tagline: 'วิศวกรรมซอฟต์แวร์และ AI สำหรับอุตสาหกรรมที่มีข้อกำกับ',
|
||||||
|
legalLine: 'เลขประจำตัวผู้เสียภาษีและข้อมูลทางกฎหมาย: รอเพิ่มภายหลัง',
|
||||||
|
columnTitles: {
|
||||||
|
sections: 'ส่วนต่าง ๆ',
|
||||||
|
legal: 'กฎหมาย',
|
||||||
|
social: 'โซเชียล',
|
||||||
|
company: 'บริษัท',
|
||||||
|
} as const,
|
||||||
|
homeNav: [
|
||||||
|
{ label: 'ผลิตภัณฑ์', href: '/#prodotti' },
|
||||||
|
{ label: 'บริการ', href: '/#servizi' },
|
||||||
|
{ label: 'สแตก', href: '/#stack' },
|
||||||
|
{ label: 'คำถามที่พบบ่อย', href: '/#faq' },
|
||||||
|
] as const,
|
||||||
|
cookiePreferences: {
|
||||||
|
label: 'ตั้งค่าคุกกี้',
|
||||||
|
hint: 'การตั้งค่าความยินยอมในเบราว์เซอร์ของคุณ (ไม่ใช่เอกสารทางกฎหมาย)',
|
||||||
|
} as const,
|
||||||
|
legalColumnNav: [
|
||||||
|
{ label: 'เงื่อนไขการใช้งาน', href: '/terms' },
|
||||||
|
{ label: 'นโยบายคุกกี้', href: '/cookies' },
|
||||||
|
{ label: 'ความเป็นส่วนตัว', href: '/privacy' },
|
||||||
|
{ label: 'สิทธิความเป็นส่วนตัว (GDPR)', href: '/gdpr' },
|
||||||
|
] as const,
|
||||||
|
socialLinks: [
|
||||||
|
{ label: 'Facebook', href: '' },
|
||||||
|
{ label: 'LinkedIn', href: '' },
|
||||||
|
{ label: 'Twitter / X', href: '' },
|
||||||
|
{ label: 'GitHub', href: '' },
|
||||||
|
] as const,
|
||||||
|
companyNav: [
|
||||||
|
{ label: 'เกี่ยวกับเรา', href: '/about' },
|
||||||
|
{ label: 'จรรยาบรรณ', href: '/codice-etico' },
|
||||||
|
{ label: 'โมเดลองค์กร', href: '/modello-organizzativo' },
|
||||||
|
{ label: 'ที่ตั้ง', href: '/dove-siamo' },
|
||||||
|
] as const,
|
||||||
|
newsletter: {
|
||||||
|
title: 'จดหมายข่าว',
|
||||||
|
description: 'อัปเดตเกี่ยวกับผลิตภัณฑ์และโรดแมป',
|
||||||
|
placeholder: 'อีเมลของคุณ',
|
||||||
|
buttonLabel: 'สมัครรับข่าวสาร',
|
||||||
|
actionUrl: '',
|
||||||
|
fieldName: 'email',
|
||||||
|
successRedirect: '',
|
||||||
|
notifySubject: 'มีผู้สมัครจดหมายข่าวใหม่ — NexStudio',
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
cookieTitle: 'ตั้งค่าคุกกี้',
|
||||||
|
socialPlaceholderTitle: 'ต้องตั้งค่า URL ใน footer.ts',
|
||||||
|
companyPlaceholderTitle: 'กำลังจะเปิดใช้งาน',
|
||||||
|
newsletterComingSoon:
|
||||||
|
'ระบบสมัครจดหมายข่าวกำลังจะเปิดใช้งาน: ตั้งค่า endpoint ในโปรเจกต์เพื่อเปิดฟอร์มนี้',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const getFooter = (locale: SupportedLocale) => footerByLocale[locale];
|
||||||
|
|
||||||
|
/** Retro-compatibilità: fallback italiano. */
|
||||||
|
export const footer = footerByLocale.it;
|
||||||
|
|||||||
@ -1,29 +1,85 @@
|
|||||||
|
import type { SupportedLocale } from './navigation';
|
||||||
|
|
||||||
/** Sezione hero */
|
/** Sezione hero */
|
||||||
export const hero = {
|
const heroByLocale = {
|
||||||
badge: 'Restituiamo valore al tuo tempo',
|
it: {
|
||||||
/** Parte prima dell’evidenziazione nel titolo */
|
badge: 'Restituiamo valore al tuo tempo',
|
||||||
titleBefore: "Ingegneria del software per l'era dell'",
|
titleBefore: "Ingegneria del software per l'era dell'",
|
||||||
/** Segmento con gradiente nel titolo */
|
titleHighlight: 'Intelligenza Artificiale',
|
||||||
titleHighlight: 'Intelligenza Artificiale',
|
titleAfter: '.',
|
||||||
titleAfter: '.',
|
subtitle:
|
||||||
subtitle:
|
'Sviluppiamo soluzioni SaaS ad alta complessità per settori regolamentati.',
|
||||||
'Sviluppiamo soluzioni SaaS ad alta complessità per settori regolamentati.',
|
paragraphs: [
|
||||||
/** Paragrafi introduttivi */
|
"Ci occupiamo di progettazione di ecosistemi digitali evoluti. La nostra specializzazione risiede nell'integrazione di modelli di Intelligenza Artificiale all'interno di flussi di lavoro professionali dove precisione, sicurezza e affidabilità sono requisiti non negoziabili.",
|
||||||
paragraphs: [
|
'Il nostro approccio si concentra sulla trasformazione di dati complessi in strumenti operativi fluidi, attraverso lo sviluppo di algoritmi proprietari, interfacce vocali e architetture cloud scalabili.',
|
||||||
"Ci occupiamo di progettazione di ecosistemi digitali evoluti. La nostra specializzazione risiede nell'integrazione di modelli di Intelligenza Artificiale all'interno di flussi di lavoro professionali dove precisione, sicurezza e affidabilità sono requisiti non negoziabili.",
|
] as const,
|
||||||
'Il nostro approccio si concentra sulla trasformazione di dati complessi in strumenti operativi fluidi, attraverso lo sviluppo di algoritmi proprietari, interfacce vocali e architetture cloud scalabili.',
|
businessUnitsHeading: 'Le nostre Business Unit:',
|
||||||
] as const,
|
businessUnitsIntro:
|
||||||
businessUnitsHeading: 'Le nostre Business Unit:',
|
'Operiamo attraverso divisioni specializzate per garantire la massima verticalità e competenza di dominio:',
|
||||||
businessUnitsIntro:
|
businessUnits: [
|
||||||
'Operiamo attraverso divisioni specializzate per garantire la massima verticalità e competenza di dominio:',
|
{
|
||||||
businessUnits: [
|
name: 'Legal Tech',
|
||||||
{
|
description: "Soluzioni per l'automazione e l'analisi normativa.",
|
||||||
name: 'Legal Tech',
|
},
|
||||||
description: "Soluzioni per l'automazione e l'analisi normativa.",
|
{
|
||||||
},
|
name: 'Health Tech',
|
||||||
{
|
description: 'Sistemi avanzati per la gestione e l’automazione sanitaria.',
|
||||||
name: 'Health Tech',
|
},
|
||||||
description: 'Sistemi avanzati per la gestione e l’automazione sanitaria.',
|
] as const,
|
||||||
},
|
},
|
||||||
] as const,
|
en: {
|
||||||
|
badge: 'We give your time back',
|
||||||
|
titleBefore: "Software engineering for the era of ",
|
||||||
|
titleHighlight: 'Artificial Intelligence',
|
||||||
|
titleAfter: '.',
|
||||||
|
subtitle:
|
||||||
|
'We build high-complexity SaaS solutions for regulated industries.',
|
||||||
|
paragraphs: [
|
||||||
|
'We design advanced digital ecosystems. Our specialization is integrating AI models into professional workflows where accuracy, security, and reliability are non-negotiable requirements.',
|
||||||
|
'Our approach turns complex data into smooth operational tools through proprietary algorithms, voice interfaces, and scalable cloud-native architectures.',
|
||||||
|
] as const,
|
||||||
|
businessUnitsHeading: 'Our Business Units:',
|
||||||
|
businessUnitsIntro:
|
||||||
|
'We operate through specialized divisions to ensure deep domain expertise and vertical focus:',
|
||||||
|
businessUnits: [
|
||||||
|
{
|
||||||
|
name: 'Legal Tech',
|
||||||
|
description: 'Solutions for legal automation and regulatory analysis.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Health Tech',
|
||||||
|
description:
|
||||||
|
'Advanced systems for healthcare workflow management and automation.',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
badge: 'คืนคุณค่าให้กับเวลาของคุณ',
|
||||||
|
titleBefore: 'วิศวกรรมซอฟต์แวร์สำหรับยุคของ',
|
||||||
|
titleHighlight: 'ปัญญาประดิษฐ์',
|
||||||
|
titleAfter: '.',
|
||||||
|
subtitle: 'เราพัฒนาโซลูชัน SaaS ที่ซับซ้อนสูงสำหรับอุตสาหกรรมที่มีข้อกำกับ',
|
||||||
|
paragraphs: [
|
||||||
|
'เราออกแบบระบบนิเวศดิจิทัลขั้นสูง โดยเชี่ยวชาญในการผสานโมเดล AI เข้ากับเวิร์กโฟลว์ระดับมืออาชีพที่ต้องการความแม่นยำ ความปลอดภัย และความน่าเชื่อถือ',
|
||||||
|
'แนวทางของเราคือเปลี่ยนข้อมูลที่ซับซ้อนให้เป็นเครื่องมือการทำงานที่ลื่นไหล ผ่านอัลกอริทึมเฉพาะทาง อินเทอร์เฟซเสียง และสถาปัตยกรรมคลาวด์ที่ขยายได้',
|
||||||
|
] as const,
|
||||||
|
businessUnitsHeading: 'หน่วยธุรกิจของเรา:',
|
||||||
|
businessUnitsIntro:
|
||||||
|
'เราดำเนินงานผ่านทีมเฉพาะทางเพื่อให้ได้ความเชี่ยวชาญเชิงลึกในแต่ละโดเมน:',
|
||||||
|
businessUnits: [
|
||||||
|
{
|
||||||
|
name: 'Legal Tech',
|
||||||
|
description: 'โซลูชันสำหรับงานกฎหมายอัตโนมัติและการวิเคราะห์ข้อกำกับ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Health Tech',
|
||||||
|
description: 'ระบบขั้นสูงสำหรับการจัดการและทำงานอัตโนมัติในภาคสุขภาพ',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const getHero = (locale: SupportedLocale) => heroByLocale[locale];
|
||||||
|
|
||||||
|
/** Retro-compatibilità: fallback italiano. */
|
||||||
|
export const hero = heroByLocale.it;
|
||||||
|
|||||||
@ -3,13 +3,21 @@
|
|||||||
* Modifica i file in questa cartella per aggiornare testi senza toccare il markup.
|
* Modifica i file in questa cartella per aggiornare testi senza toccare il markup.
|
||||||
*/
|
*/
|
||||||
export { siteMeta } from './site-meta';
|
export { siteMeta } from './site-meta';
|
||||||
|
export { getSiteMeta } from './site-meta';
|
||||||
export { chat } from './chat';
|
export { chat } from './chat';
|
||||||
export { navigation } from './navigation';
|
export { navigation } from './navigation';
|
||||||
|
export { getNavigation } from './navigation';
|
||||||
export { hero } from './hero';
|
export { hero } from './hero';
|
||||||
|
export { getHero } from './hero';
|
||||||
export { stats } from './stats';
|
export { stats } from './stats';
|
||||||
export { services } from './services';
|
export { services } from './services';
|
||||||
|
export { getServices } from './services';
|
||||||
export { stackSection } from './stack';
|
export { stackSection } from './stack';
|
||||||
|
export { getStackSection } from './stack';
|
||||||
export { productsSection } from './products';
|
export { productsSection } from './products';
|
||||||
export { faq } from './faq';
|
export { faq } from './faq';
|
||||||
|
export { getFaq } from './faq';
|
||||||
export { cta } from './cta';
|
export { cta } from './cta';
|
||||||
|
export { getCta } from './cta';
|
||||||
export { footer } from './footer';
|
export { footer } from './footer';
|
||||||
|
export { getFooter } from './footer';
|
||||||
|
|||||||
@ -1,19 +1,77 @@
|
|||||||
import { chat } from './chat';
|
import { chat } from './chat';
|
||||||
|
|
||||||
/** Header — CTA unico: ingresso chat live (config in `chat.ts`) */
|
export type SupportedLocale = 'it' | 'en' | 'th';
|
||||||
export const navigation = {
|
|
||||||
brandName: 'NexStudio',
|
const navigationByLocale = {
|
||||||
items: [
|
it: {
|
||||||
{ label: 'Prodotti', href: '/#prodotti' },
|
brandName: 'NexStudio',
|
||||||
{ label: 'Servizi', href: '/#servizi' },
|
items: [
|
||||||
{ label: 'Stack', href: '/#stack' },
|
{ label: 'Prodotti', href: '/#prodotti' },
|
||||||
{ label: 'FAQ', href: '/#faq' },
|
{ label: 'Servizi', href: '/#servizi' },
|
||||||
{ label: 'Contattaci', href: '/#contatti' },
|
{ label: 'Stack', href: '/#stack' },
|
||||||
] as const,
|
{ label: 'FAQ', href: '/#faq' },
|
||||||
cta: {
|
{ label: 'Contattaci', href: '/#contatti' },
|
||||||
label: 'Chattiamo',
|
] as const,
|
||||||
linkTitle: chat.linkTitle,
|
cta: {
|
||||||
ariaLabel:
|
label: 'Chattiamo',
|
||||||
'Richiedi accesso alla chat NexStudio: stato operatore e chatbot, identificazione con email (link o codice) prima della sessione.',
|
linkTitle: chat.linkTitle,
|
||||||
|
ariaLabel:
|
||||||
|
'Richiedi accesso alla chat NexStudio: stato operatore e chatbot, identificazione con email (link o codice) prima della sessione.',
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
mainNavAria: 'Principale',
|
||||||
|
mobileNavAria: 'Navigazione mobile',
|
||||||
|
languageSelectorAria: 'Seleziona lingua',
|
||||||
|
openMenuAria: 'Apri menu',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
brandName: 'NexStudio',
|
||||||
|
items: [
|
||||||
|
{ label: 'Products', href: '/#prodotti' },
|
||||||
|
{ label: 'Services', href: '/#servizi' },
|
||||||
|
{ label: 'Stack', href: '/#stack' },
|
||||||
|
{ label: 'FAQ', href: '/#faq' },
|
||||||
|
{ label: 'Contact', href: '/#contatti' },
|
||||||
|
] as const,
|
||||||
|
cta: {
|
||||||
|
label: 'Let’s chat',
|
||||||
|
linkTitle: 'Open NexStudio chat access request.',
|
||||||
|
ariaLabel:
|
||||||
|
'Request access to NexStudio chat: operator and chatbot availability, email verification link or code before session start.',
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
mainNavAria: 'Main navigation',
|
||||||
|
mobileNavAria: 'Mobile navigation',
|
||||||
|
languageSelectorAria: 'Select language',
|
||||||
|
openMenuAria: 'Open menu',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
brandName: 'NexStudio',
|
||||||
|
items: [
|
||||||
|
{ label: 'ผลิตภัณฑ์', href: '/#prodotti' },
|
||||||
|
{ label: 'บริการ', href: '/#servizi' },
|
||||||
|
{ label: 'สแตก', href: '/#stack' },
|
||||||
|
{ label: 'คำถามที่พบบ่อย', href: '/#faq' },
|
||||||
|
{ label: 'ติดต่อเรา', href: '/#contatti' },
|
||||||
|
] as const,
|
||||||
|
cta: {
|
||||||
|
label: 'เริ่มแชท',
|
||||||
|
linkTitle: 'เปิดคำขอเข้าใช้งานแชทของ NexStudio',
|
||||||
|
ariaLabel:
|
||||||
|
'ขอเข้าใช้งานแชท NexStudio: ตรวจสอบสถานะผู้ดูแลและแชทบอต พร้อมยืนยันตัวตนด้วยอีเมลก่อนเริ่มใช้งาน',
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
mainNavAria: 'เมนูหลัก',
|
||||||
|
mobileNavAria: 'เมนูบนมือถือ',
|
||||||
|
languageSelectorAria: 'เลือกภาษา',
|
||||||
|
openMenuAria: 'เปิดเมนู',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const getNavigation = (locale: SupportedLocale) => navigationByLocale[locale];
|
||||||
|
|
||||||
|
/** Retro-compatibilità: fallback italiano. */
|
||||||
|
export const navigation = navigationByLocale.it;
|
||||||
|
|||||||
@ -2,51 +2,153 @@
|
|||||||
* Servizi (griglia bento).
|
* Servizi (griglia bento).
|
||||||
* layout: wide = 2 colonne, tall = 2 righe + elenco puntato
|
* layout: wide = 2 colonne, tall = 2 righe + elenco puntato
|
||||||
*/
|
*/
|
||||||
export const services = {
|
import type { SupportedLocale } from './navigation';
|
||||||
eyebrow: 'I nostri servizi',
|
|
||||||
title: 'Implementazione, evoluzione e supporto',
|
const servicesByLocale = {
|
||||||
lead:
|
it: {
|
||||||
'In NexStudio, il nostro impegno non si esaurisce con la fornitura della licenza software. Il nostro modello di servizio è progettato per garantire che ogni professionista e struttura possa estrarre il massimo valore dalle potenzialità dell’Intelligenza Artificiale integrata in LexAura e MediAura.',
|
eyebrow: 'I nostri servizi',
|
||||||
cards: [
|
title: 'Implementazione, evoluzione e supporto',
|
||||||
{
|
lead:
|
||||||
layout: 'tall' as const,
|
'In NexStudio, il nostro impegno non si esaurisce con la fornitura della licenza software. Il nostro modello di servizio è progettato per garantire che ogni professionista e struttura possa estrarre il massimo valore dalle potenzialità dell’Intelligenza Artificiale integrata in LexAura e MediAura.',
|
||||||
/** Sfera geodetica vettoriale (SVG) in basso a destra */
|
cards: [
|
||||||
decoration: 'wireframe-globe' as const,
|
{
|
||||||
title: 'Onboarding e formazione specialistica',
|
layout: 'tall' as const,
|
||||||
body:
|
decoration: 'wireframe-globe' as const,
|
||||||
'L’adozione di un sistema basato su IA richiede un passaggio metodologico, non solo tecnico. Offriamo programmi di formazione dedicati per team legali e medici, finalizzati a:',
|
title: 'Onboarding e formazione specialistica',
|
||||||
bullets: [
|
|
||||||
'Ottimizzare l’interazione con gli algoritmi di ricerca e sintesi.',
|
|
||||||
'Padroneggiare i comandi vocali e l’automazione dei flussi documentali.',
|
|
||||||
'Ridurre drasticamente la curva di apprendimento tecnologico.',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
layout: 'wide' as const,
|
|
||||||
title: 'Supporto Tecnico e Consulenza Operativa',
|
|
||||||
body:
|
|
||||||
'Forniamo un’assistenza continua e di alto profilo per garantire la costante continuità operativa dei vostri sistemi. NexStudio non agisce come un semplice fornitore di supporto tecnico, ma come un partner strategico che affianca lo studio o la struttura sanitaria nell’ottimizzazione dei processi interni attraverso l’uso dei nostri SaaS.',
|
|
||||||
callout: {
|
|
||||||
title: 'Intervento «Breaking Glass»',
|
|
||||||
body:
|
body:
|
||||||
'Per le situazioni di estrema urgenza o criticità sistemica, garantiamo protocolli di accesso rapido e intervento prioritario. Questa procedura di emergenza permette di superare i normali flussi di assistenza per risolvere istantaneamente anomalie che potrebbero impattare sulla continuità del servizio, assicurando che il professionista non resti mai isolato dai propri dati o strumenti decisionali.',
|
'L’adozione di un sistema basato su IA richiede un passaggio metodologico, non solo tecnico. Offriamo programmi di formazione dedicati per team legali e medici, finalizzati a:',
|
||||||
|
bullets: [
|
||||||
|
'Ottimizzare l’interazione con gli algoritmi di ricerca e sintesi.',
|
||||||
|
'Padroneggiare i comandi vocali e l’automazione dei flussi documentali.',
|
||||||
|
'Ridurre drasticamente la curva di apprendimento tecnologico.',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
layout: 'wide' as const,
|
||||||
layout: 'default' as const,
|
title: 'Supporto Tecnico e Consulenza Operativa',
|
||||||
title: 'Estensioni e integrazioni personalizzate',
|
body:
|
||||||
body:
|
'Forniamo un’assistenza continua e di alto profilo per garantire la costante continuità operativa dei vostri sistemi. NexStudio non agisce come un semplice fornitore di supporto tecnico, ma come un partner strategico che affianca lo studio o la struttura sanitaria nell’ottimizzazione dei processi interni attraverso l’uso dei nostri SaaS.',
|
||||||
'Sebbene LexAura e MediAura siano prodotti finiti e pronti all’uso, comprendiamo che strutture complesse possano avere esigenze di interconnessione specifiche.',
|
callout: {
|
||||||
bullets: [
|
title: 'Intervento «Breaking Glass»',
|
||||||
'API integration: supportiamo l’integrazione dei nostri SaaS con sistemi gestionali preesistenti.',
|
body:
|
||||||
'Custom modules: progettiamo estensioni su misura che aggiungono funzionalità specifiche all’interno del perimetro dei nostri prodotti, per rispondere a flussi di lavoro unici.',
|
'Per le situazioni di estrema urgenza o criticità sistemica, garantiamo protocolli di accesso rapido e intervento prioritario. Questa procedura di emergenza permette di superare i normali flussi di assistenza per risolvere istantaneamente anomalie che potrebbero impattare sulla continuità del servizio, assicurando che il professionista non resti mai isolato dai propri dati o strumenti decisionali.',
|
||||||
],
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
layout: 'default' as const,
|
layout: 'default' as const,
|
||||||
title: 'Aggiornamento e manutenzione evolutiva',
|
title: 'Estensioni e integrazioni personalizzate',
|
||||||
body:
|
body:
|
||||||
'Il mondo dell’IA e delle normative (legali e sanitarie) è in costante mutamento. Il nostro servizio include l’aggiornamento continuo dei motori di analisi, garantendo che il software sia sempre allineato con le ultime tecnologie e le evoluzioni del mercato di riferimento.',
|
'Sebbene LexAura e MediAura siano prodotti finiti e pronti all’uso, comprendiamo che strutture complesse possano avere esigenze di interconnessione specifiche.',
|
||||||
},
|
bullets: [
|
||||||
],
|
'API integration: supportiamo l’integrazione dei nostri SaaS con sistemi gestionali preesistenti.',
|
||||||
|
'Custom modules: progettiamo estensioni su misura che aggiungono funzionalità specifiche all’interno del perimetro dei nostri prodotti, per rispondere a flussi di lavoro unici.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
layout: 'default' as const,
|
||||||
|
title: 'Aggiornamento e manutenzione evolutiva',
|
||||||
|
body:
|
||||||
|
'Il mondo dell’IA e delle normative (legali e sanitarie) è in costante mutamento. Il nostro servizio include l’aggiornamento continuo dei motori di analisi, garantendo che il software sia sempre allineato con le ultime tecnologie e le evoluzioni del mercato di riferimento.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
eyebrow: 'Our services',
|
||||||
|
title: 'Implementation, evolution, and support',
|
||||||
|
lead:
|
||||||
|
'At NexStudio, our commitment goes beyond software licensing. Our service model helps each professional and organization unlock the full value of AI integrated into LexAura and MediAura.',
|
||||||
|
cards: [
|
||||||
|
{
|
||||||
|
layout: 'tall' as const,
|
||||||
|
decoration: 'wireframe-globe' as const,
|
||||||
|
title: 'Onboarding and specialist training',
|
||||||
|
body:
|
||||||
|
'Adopting AI-powered systems is a methodological shift, not only a technical one. We provide dedicated programs for legal and medical teams to:',
|
||||||
|
bullets: [
|
||||||
|
'Improve interaction with search and summarization algorithms.',
|
||||||
|
'Master voice commands and document workflow automation.',
|
||||||
|
'Significantly reduce the technology learning curve.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
layout: 'wide' as const,
|
||||||
|
title: 'Technical support and operational consulting',
|
||||||
|
body:
|
||||||
|
'We provide continuous, high-level assistance to ensure operational continuity. NexStudio is not just a support vendor, but a strategic partner for legal firms and healthcare organizations.',
|
||||||
|
callout: {
|
||||||
|
title: '“Breaking Glass” intervention',
|
||||||
|
body:
|
||||||
|
'For critical urgency or systemic incidents, we offer rapid-access and priority intervention protocols. This emergency procedure bypasses standard support flows to restore continuity as fast as possible.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
layout: 'default' as const,
|
||||||
|
title: 'Custom extensions and integrations',
|
||||||
|
body:
|
||||||
|
'Although LexAura and MediAura are complete, ready-to-use products, complex organizations may still require specific interoperability needs.',
|
||||||
|
bullets: [
|
||||||
|
'API integration: we integrate our SaaS with your existing management systems.',
|
||||||
|
'Custom modules: we build tailored extensions inside our product perimeter to support unique workflows.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
layout: 'default' as const,
|
||||||
|
title: 'Evolutionary updates and maintenance',
|
||||||
|
body:
|
||||||
|
'AI and regulated frameworks evolve continuously. Our service includes ongoing analysis-engine updates to keep software aligned with new technologies and market requirements.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
eyebrow: 'บริการของเรา',
|
||||||
|
title: 'การติดตั้ง การพัฒนา และการสนับสนุน',
|
||||||
|
lead:
|
||||||
|
'ที่ NexStudio ความมุ่งมั่นของเราไม่ได้จบแค่การให้สิทธิ์ใช้งานซอฟต์แวร์ โมเดลบริการของเราช่วยให้ทีมงานดึงศักยภาพสูงสุดจาก AI ที่ผสานใน LexAura และ MediAura',
|
||||||
|
cards: [
|
||||||
|
{
|
||||||
|
layout: 'tall' as const,
|
||||||
|
decoration: 'wireframe-globe' as const,
|
||||||
|
title: 'การเริ่มใช้งานและการอบรมเฉพาะทาง',
|
||||||
|
body:
|
||||||
|
'การนำระบบ AI มาใช้ต้องอาศัยการเปลี่ยนผ่านเชิงวิธีการ ไม่ใช่แค่เทคนิค เรามีโปรแกรมอบรมสำหรับทีมกฎหมายและการแพทย์เพื่อ:',
|
||||||
|
bullets: [
|
||||||
|
'เพิ่มประสิทธิภาพการใช้งานอัลกอริทึมค้นหาและสรุปผล',
|
||||||
|
'ใช้งานคำสั่งเสียงและระบบงานเอกสารอัตโนมัติได้อย่างคล่องตัว',
|
||||||
|
'ลดช่วงเวลาเรียนรู้เทคโนโลยีอย่างมีนัยสำคัญ',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
layout: 'wide' as const,
|
||||||
|
title: 'สนับสนุนทางเทคนิคและที่ปรึกษาการปฏิบัติงาน',
|
||||||
|
body:
|
||||||
|
'เราให้การสนับสนุนอย่างต่อเนื่องเพื่อคงความต่อเนื่องในการดำเนินงาน NexStudio ไม่ได้เป็นเพียงผู้ให้บริการซัพพอร์ต แต่เป็นพาร์ตเนอร์เชิงกลยุทธ์สำหรับองค์กร',
|
||||||
|
callout: {
|
||||||
|
title: 'การแทรกแซงแบบ “Breaking Glass”',
|
||||||
|
body:
|
||||||
|
'เมื่อเกิดเหตุเร่งด่วนหรือความผิดปกติระดับวิกฤต เรามีโปรโตคอลเข้าถึงด่วนและจัดลำดับความสำคัญสูงสุด เพื่อแก้ปัญหาได้ทันทีและรักษาความต่อเนื่องของบริการ',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
layout: 'default' as const,
|
||||||
|
title: 'ส่วนขยายและการเชื่อมต่อแบบกำหนดเอง',
|
||||||
|
body:
|
||||||
|
'แม้ LexAura และ MediAura จะพร้อมใช้งานทันที แต่บางองค์กรอาจต้องการการเชื่อมต่อเฉพาะทางกับระบบเดิม',
|
||||||
|
bullets: [
|
||||||
|
'API integration: เชื่อม SaaS ของเราเข้ากับระบบจัดการที่ใช้อยู่แล้ว',
|
||||||
|
'Custom modules: ออกแบบส่วนขยายเฉพาะงานภายในขอบเขตผลิตภัณฑ์ของเรา',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
layout: 'default' as const,
|
||||||
|
title: 'อัปเดตและบำรุงรักษาเชิงพัฒนา',
|
||||||
|
body:
|
||||||
|
'โลกของ AI และข้อกำกับเปลี่ยนแปลงตลอดเวลา บริการของเรารวมถึงการอัปเดตเอนจินวิเคราะห์อย่างต่อเนื่องเพื่อให้ซอฟต์แวร์ทันสมัยและสอดคล้องบริบทตลาด',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const getServices = (locale: SupportedLocale) => servicesByLocale[locale];
|
||||||
|
|
||||||
|
/** Retro-compatibilità: fallback italiano. */
|
||||||
|
export const services = servicesByLocale.it;
|
||||||
|
|||||||
@ -1,6 +1,25 @@
|
|||||||
|
import type { SupportedLocale } from './navigation';
|
||||||
|
|
||||||
/** Meta pagina principale — usato da BaseLayout */
|
/** Meta pagina principale — usato da BaseLayout */
|
||||||
export const siteMeta = {
|
const siteMetaByLocale = {
|
||||||
title: 'NexStudio — Ingegneria del software e IA per settori regolamentati',
|
it: {
|
||||||
description:
|
title: 'NexStudio — Ingegneria del software e IA per settori regolamentati',
|
||||||
'NexStudio: software company con sede a Bangkok. SaaS ad alta complessità, integrazione di IA in flussi professionali, Legal Tech e Health Tech.',
|
description:
|
||||||
|
'NexStudio: software company con sede a Bangkok. SaaS ad alta complessità, integrazione di IA in flussi professionali, Legal Tech e Health Tech.',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: 'NexStudio — Software engineering and AI for regulated industries',
|
||||||
|
description:
|
||||||
|
'NexStudio is a software company based in Bangkok. We build high-complexity SaaS products and integrate AI into professional workflows across Legal Tech and Health Tech.',
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
title: 'NexStudio — วิศวกรรมซอฟต์แวร์และ AI สำหรับอุตสาหกรรมที่มีข้อกำกับ',
|
||||||
|
description:
|
||||||
|
'NexStudio คือบริษัทซอฟต์แวร์ในกรุงเทพฯ พัฒนา SaaS ความซับซ้อนสูง และผสาน AI เข้ากับเวิร์กโฟลว์ระดับมืออาชีพในสาย Legal Tech และ Health Tech',
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const getSiteMeta = (locale: SupportedLocale) => siteMetaByLocale[locale];
|
||||||
|
|
||||||
|
/** Retro-compatibilità: fallback italiano. */
|
||||||
|
export const siteMeta = siteMetaByLocale.it;
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
|
import type { SupportedLocale } from './navigation';
|
||||||
|
|
||||||
/** Sezione stack — infrastruttura Cloudflare e sicurezza */
|
/** Sezione stack — infrastruttura Cloudflare e sicurezza */
|
||||||
export const stackSection = {
|
const stackIt = {
|
||||||
eyebrow: 'Lo stack tecnologico',
|
eyebrow: 'Lo stack tecnologico',
|
||||||
title: 'Infrastruttura e sicurezza',
|
title: 'Infrastruttura e sicurezza',
|
||||||
lead:
|
lead:
|
||||||
'In NexStudio, la qualità dell’esperienza utente è inscindibile dalla solidità dell’infrastruttura. Per questo motivo ci affidiamo a partner globali ad alta efficienza che garantiscono i più elevati standard di disponibilità e protezione dei dati.',
|
'In NexStudio, la qualità dell’esperienza utente è inscindibile dalla solidità dell’infrastruttura. Per questo motivo ci affidiamo a partner globali ad alta efficienza che garantiscono i più elevati standard di disponibilità e protezione dei dati.',
|
||||||
|
infrastructureHeading: 'Infrastruttura',
|
||||||
cloudflare: {
|
cloudflare: {
|
||||||
title: 'Ecosistema Cloudflare',
|
title: 'Ecosistema Cloudflare',
|
||||||
intro:
|
intro:
|
||||||
@ -145,3 +148,168 @@ export const stackSection = {
|
|||||||
] as const,
|
] as const,
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
const stackByLocale = {
|
||||||
|
it: stackIt,
|
||||||
|
en: {
|
||||||
|
...stackIt,
|
||||||
|
eyebrow: 'Technology stack',
|
||||||
|
title: 'Infrastructure and security',
|
||||||
|
lead:
|
||||||
|
'At NexStudio, user experience quality is inseparable from infrastructure resilience. That is why we rely on high-efficiency global partners that provide strong availability and data protection standards.',
|
||||||
|
infrastructureHeading: 'Infrastructure',
|
||||||
|
cloudflare: {
|
||||||
|
...stackIt.cloudflare,
|
||||||
|
title: 'Cloudflare ecosystem',
|
||||||
|
intro:
|
||||||
|
'"Cloudflare helps accelerate performance, secure app availability, and optimize web experiences globally."',
|
||||||
|
points: [
|
||||||
|
{
|
||||||
|
title: 'Performance without compromise',
|
||||||
|
body:
|
||||||
|
'Access to our services remains low-latency worldwide, ensuring fluid usage even during complex data-processing operations.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Advanced security',
|
||||||
|
body:
|
||||||
|
'We apply modern protection layers including end-to-end encryption, DDoS mitigation, and Web Application Firewall controls.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Compliance and data integrity',
|
||||||
|
body:
|
||||||
|
'We use geographic data-control capabilities to align legal and medical data processing with local and international privacy requirements.',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
continuity: {
|
||||||
|
title: 'Continuity and scalability',
|
||||||
|
body:
|
||||||
|
'We provide professional-grade SLA uptime. Our cloud-native architecture scales instantly for workload peaks, large offices, and multi-site organizations.',
|
||||||
|
},
|
||||||
|
whyThisChoice: {
|
||||||
|
title: 'Why NexStudio',
|
||||||
|
points: [
|
||||||
|
'Regulated vertical focus: we design for Legal Tech and Health Tech, where traceability, privacy, and service continuity are core product requirements.',
|
||||||
|
'AI in real professional workflows: we embed models and automation with attention to accuracy, auditability, and human oversight.',
|
||||||
|
'End-to-end responsibility: architecture, development, and evolution stay under one ownership model for clearer priorities and delivery.',
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
cloudflareBadge: {
|
||||||
|
...stackIt.cloudflareBadge,
|
||||||
|
label: 'Cloudflare ecosystem',
|
||||||
|
slogan: '"Helping build a better Internet."',
|
||||||
|
},
|
||||||
|
awsBadge: {
|
||||||
|
...stackIt.awsBadge,
|
||||||
|
label: 'AWS ecosystem',
|
||||||
|
slogan: '"The world’s most comprehensive and broadly adopted cloud platform."',
|
||||||
|
},
|
||||||
|
infrastructureCarousel: {
|
||||||
|
...stackIt.infrastructureCarousel,
|
||||||
|
ariaLabel: 'NexStudio infrastructure and technology partners',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
...stackIt.infrastructureCarousel.items[0],
|
||||||
|
label: 'Cloudflare ecosystem',
|
||||||
|
slogan: '"Helping build a better Internet."',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...stackIt.infrastructureCarousel.items[1],
|
||||||
|
label: 'AWS ecosystem',
|
||||||
|
slogan: '"The world’s most comprehensive and broadly adopted cloud platform."',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...stackIt.infrastructureCarousel.items[2],
|
||||||
|
label: 'GitHub ecosystem',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...stackIt.infrastructureCarousel.items[3],
|
||||||
|
label: 'OpenAI ecosystem',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
...stackIt,
|
||||||
|
eyebrow: 'เทคโนโลยีสแตก',
|
||||||
|
title: 'โครงสร้างพื้นฐานและความปลอดภัย',
|
||||||
|
lead:
|
||||||
|
'ที่ NexStudio คุณภาพประสบการณ์ผู้ใช้แยกไม่ออกจากความมั่นคงของโครงสร้างพื้นฐาน เราจึงเลือกพาร์ตเนอร์ระดับโลกที่มีประสิทธิภาพสูงเพื่อให้ได้มาตรฐานด้านความพร้อมใช้งานและความปลอดภัยของข้อมูล',
|
||||||
|
infrastructureHeading: 'โครงสร้างพื้นฐาน',
|
||||||
|
cloudflare: {
|
||||||
|
...stackIt.cloudflare,
|
||||||
|
title: 'ระบบนิเวศ Cloudflare',
|
||||||
|
intro:
|
||||||
|
'"Cloudflare ช่วยเร่งประสิทธิภาพ รับประกันความพร้อมใช้งานของแอป และยกระดับประสบการณ์เว็บทั่วโลก"',
|
||||||
|
points: [
|
||||||
|
{
|
||||||
|
title: 'ประสิทธิภาพที่ไม่ประนีประนอม',
|
||||||
|
body:
|
||||||
|
'เข้าถึงบริการของเราได้อย่างหน่วงต่ำจากทุกที่ทั่วโลก แม้ในงานประมวลผลข้อมูลที่ซับซ้อน',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ความปลอดภัยขั้นสูง',
|
||||||
|
body:
|
||||||
|
'เราใช้กลไกป้องกันสมัยใหม่ เช่น การเข้ารหัส end-to-end การป้องกัน DDoS และ Web Application Firewall',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'การปฏิบัติตามข้อกำกับและความถูกต้องของข้อมูล',
|
||||||
|
body:
|
||||||
|
'เราใช้ความสามารถในการกำกับตำแหน่งข้อมูลเพื่อให้การจัดการข้อมูลอ่อนไหวด้านกฎหมายและการแพทย์สอดคล้องกับข้อกำกับความเป็นส่วนตัว',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
continuity: {
|
||||||
|
title: 'ความต่อเนื่องและการขยายตัว',
|
||||||
|
body:
|
||||||
|
'เรารับประกัน uptime ระดับมืออาชีพตาม SLA และสถาปัตยกรรม cloud-native ที่ขยายได้ทันทีเมื่อมีโหลดสูง',
|
||||||
|
},
|
||||||
|
whyThisChoice: {
|
||||||
|
title: 'ทำไมต้อง NexStudio',
|
||||||
|
points: [
|
||||||
|
'เชี่ยวชาญอุตสาหกรรมที่มีข้อกำกับ: Legal Tech และ Health Tech',
|
||||||
|
'ผสาน AI เข้ากับงานจริง โดยคำนึงถึงความแม่นยำ การตรวจสอบย้อนหลัง และการกำกับโดยมนุษย์',
|
||||||
|
'รับผิดชอบแบบ end-to-end: สถาปัตยกรรม การพัฒนา และการต่อยอดอยู่ภายใต้ทีมเดียว',
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
cloudflareBadge: {
|
||||||
|
...stackIt.cloudflareBadge,
|
||||||
|
label: 'ระบบนิเวศ Cloudflare',
|
||||||
|
slogan: '"ช่วยกันสร้างอินเทอร์เน็ตที่ดีกว่า"',
|
||||||
|
},
|
||||||
|
awsBadge: {
|
||||||
|
...stackIt.awsBadge,
|
||||||
|
label: 'ระบบนิเวศ AWS',
|
||||||
|
slogan: '"แพลตฟอร์มคลาวด์ที่ครอบคลุมและได้รับการใช้งานอย่างกว้างขวางที่สุดในโลก"',
|
||||||
|
},
|
||||||
|
infrastructureCarousel: {
|
||||||
|
...stackIt.infrastructureCarousel,
|
||||||
|
ariaLabel: 'พาร์ตเนอร์โครงสร้างพื้นฐานและเทคโนโลยีของ NexStudio',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
...stackIt.infrastructureCarousel.items[0],
|
||||||
|
label: 'ระบบนิเวศ Cloudflare',
|
||||||
|
slogan: '"ช่วยกันสร้างอินเทอร์เน็ตที่ดีกว่า"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...stackIt.infrastructureCarousel.items[1],
|
||||||
|
label: 'ระบบนิเวศ AWS',
|
||||||
|
slogan: '"แพลตฟอร์มคลาวด์ที่ครอบคลุมและได้รับการใช้งานอย่างกว้างขวางที่สุดในโลก"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...stackIt.infrastructureCarousel.items[2],
|
||||||
|
label: 'ระบบนิเวศ GitHub',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...stackIt.infrastructureCarousel.items[3],
|
||||||
|
label: 'ระบบนิเวศ OpenAI',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const getStackSection = (locale: SupportedLocale) => stackByLocale[locale];
|
||||||
|
|
||||||
|
/** Retro-compatibilità: fallback italiano. */
|
||||||
|
export const stackSection = stackByLocale.it;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import '../styles/global.css';
|
import '../styles/global.css';
|
||||||
|
import globalStylesheetHref from '../styles/global.css?url';
|
||||||
import CookieConsent from '../components/CookieConsent.astro';
|
import CookieConsent from '../components/CookieConsent.astro';
|
||||||
import ChatGateModal from '../components/ChatGateModal.astro';
|
import ChatGateModal from '../components/ChatGateModal.astro';
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ const {
|
|||||||
const pathname = Astro.url.pathname;
|
const pathname = Astro.url.pathname;
|
||||||
const localeMatch = pathname.match(/^\/(en|th)(\/|$)/);
|
const localeMatch = pathname.match(/^\/(en|th)(\/|$)/);
|
||||||
const htmlLang = localeMatch?.[1] ?? 'it';
|
const htmlLang = localeMatch?.[1] ?? 'it';
|
||||||
|
const canonicalUrl = Astro.url.toString();
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
@ -23,9 +25,19 @@ const htmlLang = localeMatch?.[1] ?? 'it';
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
|
<link rel="canonical" href={canonicalUrl} />
|
||||||
<meta name="theme-color" content="#030712" />
|
<meta name="theme-color" content="#030712" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:site_name" content="NexStudio" />
|
||||||
|
<meta property="og:title" content={title} />
|
||||||
|
<meta property="og:description" content={description} />
|
||||||
|
<meta property="og:url" content={canonicalUrl} />
|
||||||
|
<meta name="twitter:card" content="summary" />
|
||||||
|
<meta name="twitter:title" content={title} />
|
||||||
|
<meta name="twitter:description" content={description} />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
<link rel="stylesheet" href={globalStylesheetHref} />
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from './BaseLayout.astro';
|
import BaseLayout from './BaseLayout.astro';
|
||||||
import SiteFooter from '../components/SiteFooter.astro';
|
import SiteFooter from '../components/SiteFooter.astro';
|
||||||
|
import { getNavigation } from '../data/home';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
@ -29,7 +30,8 @@ const printDate = new Intl.DateTimeFormat('it-IT', {
|
|||||||
const printSource = Astro.url.href;
|
const printSource = Astro.url.href;
|
||||||
const path = Astro.url.pathname;
|
const path = Astro.url.pathname;
|
||||||
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
const currentLocale = localeMatch?.[1] ?? 'it';
|
const currentLocale = (localeMatch?.[1] ?? 'it') as 'it' | 'en' | 'th';
|
||||||
|
const navigation = getNavigation(currentLocale);
|
||||||
const strippedPath = path.replace(/^\/(en|th)(?=\/|$)/, '') || '/';
|
const strippedPath = path.replace(/^\/(en|th)(?=\/|$)/, '') || '/';
|
||||||
const localeHref = (locale: 'it' | 'en' | 'th') => {
|
const localeHref = (locale: 'it' | 'en' | 'th') => {
|
||||||
if (locale === 'it') return strippedPath;
|
if (locale === 'it') return strippedPath;
|
||||||
@ -71,7 +73,7 @@ const langMeta = {
|
|||||||
<details class="relative" data-lang-dropdown>
|
<details class="relative" data-lang-dropdown>
|
||||||
<summary
|
<summary
|
||||||
class="inline-flex h-10 cursor-pointer list-none items-center gap-2 rounded-lg border border-nx-border/80 bg-nx-bg/50 px-3 text-xs font-medium text-nx-fg transition-colors hover:border-sky-500/35 hover:text-white"
|
class="inline-flex h-10 cursor-pointer list-none items-center gap-2 rounded-lg border border-nx-border/80 bg-nx-bg/50 px-3 text-xs font-medium text-nx-fg transition-colors hover:border-sky-500/35 hover:text-white"
|
||||||
aria-label="Seleziona lingua"
|
aria-label={navigation.labels.languageSelectorAria}
|
||||||
>
|
>
|
||||||
<img src={langMeta[currentLocale].flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
<img src={langMeta[currentLocale].flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||||
<span>{langMeta[currentLocale].code}</span>
|
<span>{langMeta[currentLocale].code}</span>
|
||||||
@ -129,7 +131,7 @@ const langMeta = {
|
|||||||
</details>
|
</details>
|
||||||
<a
|
<a
|
||||||
class="text-sm font-medium text-sky-400/90 hover:text-sky-300"
|
class="text-sm font-medium text-sky-400/90 hover:text-sky-300"
|
||||||
href={localize('/#contatti')}>Contattaci</a
|
href={localize('/#contatti')}>{navigation.items[4]?.label ?? 'Contattaci'}</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
163
src/pages/api/contact.ts
Normal file
163
src/pages/api/contact.ts
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import type { APIRoute } from 'astro';
|
||||||
|
|
||||||
|
export const prerender = false;
|
||||||
|
|
||||||
|
type RuntimeEnv = Record<string, unknown>;
|
||||||
|
|
||||||
|
function getRuntimeEnv(locals: unknown): RuntimeEnv {
|
||||||
|
if (
|
||||||
|
locals &&
|
||||||
|
typeof locals === 'object' &&
|
||||||
|
'runtime' in locals &&
|
||||||
|
locals.runtime &&
|
||||||
|
typeof locals.runtime === 'object' &&
|
||||||
|
'env' in locals.runtime &&
|
||||||
|
locals.runtime.env &&
|
||||||
|
typeof locals.runtime.env === 'object'
|
||||||
|
) {
|
||||||
|
return locals.runtime.env as RuntimeEnv;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readEnv(name: string, runtimeEnv: RuntimeEnv): string {
|
||||||
|
const runtimeValue = runtimeEnv[name];
|
||||||
|
if (typeof runtimeValue === 'string' && runtimeValue.trim().length > 0) {
|
||||||
|
return runtimeValue.trim();
|
||||||
|
}
|
||||||
|
const staticValue = import.meta.env[name];
|
||||||
|
return typeof staticValue === 'string' ? staticValue.trim() : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeRedirect(candidate: string, requestUrl: URL): string {
|
||||||
|
if (!candidate) return '';
|
||||||
|
try {
|
||||||
|
const u = new URL(candidate, requestUrl);
|
||||||
|
return u.toString();
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function required(value: FormDataEntryValue | null): string {
|
||||||
|
return typeof value === 'string' ? value.trim() : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyTurnstile(token: string, secret: string, ip?: string | null): Promise<boolean> {
|
||||||
|
const body = new URLSearchParams();
|
||||||
|
body.set('secret', secret);
|
||||||
|
body.set('response', token);
|
||||||
|
if (ip) body.set('remoteip', ip);
|
||||||
|
|
||||||
|
const response = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
|
||||||
|
method: 'POST',
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
if (!response.ok) return false;
|
||||||
|
const payload = (await response.json()) as { success?: boolean };
|
||||||
|
return payload.success === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendWithResend(apiKey: string, fromEmail: string, toEmail: string, subject: string, text: string) {
|
||||||
|
const response = await fetch('https://api.resend.com/emails', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
from: fromEmail,
|
||||||
|
to: [toEmail],
|
||||||
|
subject,
|
||||||
|
text,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const detail = await response.text();
|
||||||
|
throw new Error(`Resend error ${response.status}: ${detail}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveDepartmentRecipient(runtimeEnv: RuntimeEnv, defaultRecipient: string, department: string): string {
|
||||||
|
const normalized = department.trim().toLowerCase();
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
info: readEnv('CONTACT_FORM_TO_EMAIL_INFO', runtimeEnv) || defaultRecipient,
|
||||||
|
privacy: readEnv('CONTACT_FORM_TO_EMAIL_PRIVACY', runtimeEnv),
|
||||||
|
careers: readEnv('CONTACT_FORM_TO_EMAIL_CAREERS', runtimeEnv),
|
||||||
|
};
|
||||||
|
return map[normalized] || defaultRecipient;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const POST: APIRoute = async ({ request, locals, url }) => {
|
||||||
|
const runtimeEnv = getRuntimeEnv(locals);
|
||||||
|
const mode = readEnv('CONTACT_FORM_MODE', runtimeEnv).toLowerCase() || 'dev';
|
||||||
|
const resendApiKey = readEnv('CONTACT_FORM_RESEND_API_KEY', runtimeEnv);
|
||||||
|
const fromEmail = readEnv('CONTACT_FORM_FROM_EMAIL', runtimeEnv);
|
||||||
|
const toEmail = readEnv('CONTACT_FORM_TO_EMAIL', runtimeEnv);
|
||||||
|
const turnstileSecret = readEnv('CONTACT_TURNSTILE_SECRET_KEY', runtimeEnv);
|
||||||
|
|
||||||
|
const form = await request.formData();
|
||||||
|
const redirectTarget =
|
||||||
|
sanitizeRedirect(required(form.get('_next')), url) ||
|
||||||
|
sanitizeRedirect(readEnv('PUBLIC_CONTACT_FORM_SUCCESS_REDIRECT', runtimeEnv), url) ||
|
||||||
|
sanitizeRedirect(request.headers.get('referer') ?? '/', url) ||
|
||||||
|
'/';
|
||||||
|
|
||||||
|
const name = required(form.get('name'));
|
||||||
|
const company = required(form.get('company'));
|
||||||
|
const email = required(form.get('email'));
|
||||||
|
const country = required(form.get('country'));
|
||||||
|
const department = required(form.get('department')) || 'info';
|
||||||
|
const message = required(form.get('message'));
|
||||||
|
|
||||||
|
if (!name || !email || !country || !message) {
|
||||||
|
return new Response('Missing required contact fields.', { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const turnstileToken = required(form.get('cf-turnstile-response'));
|
||||||
|
if (turnstileSecret) {
|
||||||
|
const isHuman = await verifyTurnstile(
|
||||||
|
turnstileToken,
|
||||||
|
turnstileSecret,
|
||||||
|
request.headers.get('CF-Connecting-IP'),
|
||||||
|
);
|
||||||
|
if (!isHuman) {
|
||||||
|
return new Response('Turnstile verification failed.', { status: 400 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const subject = required(form.get('_subject')) || 'Richiesta contatto - sito NexStudio';
|
||||||
|
const text = [
|
||||||
|
`Nome: ${name}`,
|
||||||
|
company ? `Azienda: ${company}` : '',
|
||||||
|
`Email: ${email}`,
|
||||||
|
`Paese: ${country}`,
|
||||||
|
`Reparto: ${department}`,
|
||||||
|
'',
|
||||||
|
message,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
if (mode === 'live') {
|
||||||
|
if (!resendApiKey || !fromEmail || !toEmail) {
|
||||||
|
return new Response('Live mode requires CONTACT_FORM_RESEND_API_KEY, CONTACT_FORM_FROM_EMAIL and CONTACT_FORM_TO_EMAIL.', {
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const destination = resolveDepartmentRecipient(runtimeEnv, toEmail, department);
|
||||||
|
await sendWithResend(resendApiKey, fromEmail, destination, subject, text);
|
||||||
|
} else {
|
||||||
|
console.info('[contact:dev] Contact payload received', {
|
||||||
|
name,
|
||||||
|
company,
|
||||||
|
email,
|
||||||
|
country,
|
||||||
|
department,
|
||||||
|
subject,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.redirect(redirectTarget, 303);
|
||||||
|
};
|
||||||
@ -6,21 +6,33 @@ import ContactFormInline from '../components/ContactFormInline.astro';
|
|||||||
import CardBottomRightTexture from '../components/CardBottomRightTexture.astro';
|
import CardBottomRightTexture from '../components/CardBottomRightTexture.astro';
|
||||||
import SiteFooter from '../components/SiteFooter.astro';
|
import SiteFooter from '../components/SiteFooter.astro';
|
||||||
import {
|
import {
|
||||||
siteMeta,
|
getSiteMeta,
|
||||||
navigation,
|
getNavigation,
|
||||||
hero,
|
getHero,
|
||||||
stats,
|
stats,
|
||||||
services,
|
getServices,
|
||||||
stackSection,
|
getStackSection,
|
||||||
productsSection,
|
productsSection,
|
||||||
faq,
|
getFaq,
|
||||||
cta,
|
getCta,
|
||||||
} from '../data/home';
|
} from '../data/home';
|
||||||
|
|
||||||
const infrastructureCarouselItems = stackSection.infrastructureCarousel.items;
|
|
||||||
const path = Astro.url.pathname;
|
const path = Astro.url.pathname;
|
||||||
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
const currentLocale = localeMatch?.[1] ?? 'it';
|
const currentLocale = (localeMatch?.[1] ?? 'it') as 'it' | 'en' | 'th';
|
||||||
|
const navigation = getNavigation(currentLocale);
|
||||||
|
const siteMeta = getSiteMeta(currentLocale);
|
||||||
|
const hero = getHero(currentLocale);
|
||||||
|
const services = getServices(currentLocale);
|
||||||
|
const stackSection = getStackSection(currentLocale);
|
||||||
|
const faq = getFaq(currentLocale);
|
||||||
|
const cta = getCta(currentLocale);
|
||||||
|
const infrastructureCarouselItems = stackSection.infrastructureCarousel.items;
|
||||||
|
const pageLabels = {
|
||||||
|
it: { focus: 'Focus' },
|
||||||
|
en: { focus: 'Focus' },
|
||||||
|
th: { focus: 'จุดเน้น' },
|
||||||
|
}[currentLocale];
|
||||||
const localize = (href: string) => {
|
const localize = (href: string) => {
|
||||||
if (currentLocale === 'it') return href;
|
if (currentLocale === 'it') return href;
|
||||||
if (href.startsWith(`/${currentLocale}`)) return href;
|
if (href.startsWith(`/${currentLocale}`)) return href;
|
||||||
@ -64,7 +76,7 @@ const langMeta = {
|
|||||||
|
|
||||||
<nav
|
<nav
|
||||||
class="hidden items-center gap-8 text-sm md:flex"
|
class="hidden items-center gap-8 text-sm md:flex"
|
||||||
aria-label="Principale"
|
aria-label={navigation.labels.mainNavAria}
|
||||||
>
|
>
|
||||||
{navigation.items.map((item) => {
|
{navigation.items.map((item) => {
|
||||||
const sectionId = item.href.includes('#')
|
const sectionId = item.href.includes('#')
|
||||||
@ -87,7 +99,7 @@ const langMeta = {
|
|||||||
<details class="relative hidden sm:block" data-lang-dropdown>
|
<details class="relative hidden sm:block" data-lang-dropdown>
|
||||||
<summary
|
<summary
|
||||||
class="inline-flex h-10 cursor-pointer list-none items-center gap-2 rounded-lg border border-nx-border/80 bg-nx-bg/50 px-3 text-xs font-medium text-nx-fg transition-colors hover:border-sky-500/35 hover:text-white"
|
class="inline-flex h-10 cursor-pointer list-none items-center gap-2 rounded-lg border border-nx-border/80 bg-nx-bg/50 px-3 text-xs font-medium text-nx-fg transition-colors hover:border-sky-500/35 hover:text-white"
|
||||||
aria-label="Seleziona lingua"
|
aria-label={navigation.labels.languageSelectorAria}
|
||||||
>
|
>
|
||||||
<img src={langMeta[currentLocale].flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
<img src={langMeta[currentLocale].flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||||
<span>{langMeta[currentLocale].code}</span>
|
<span>{langMeta[currentLocale].code}</span>
|
||||||
@ -157,7 +169,7 @@ const langMeta = {
|
|||||||
@click="mobile = !mobile"
|
@click="mobile = !mobile"
|
||||||
:aria-expanded="mobile"
|
:aria-expanded="mobile"
|
||||||
aria-controls="mobile-nav"
|
aria-controls="mobile-nav"
|
||||||
aria-label="Apri menu"
|
aria-label={navigation.labels.openMenuAria}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
x-show="!mobile"
|
x-show="!mobile"
|
||||||
@ -191,7 +203,7 @@ const langMeta = {
|
|||||||
x-cloak
|
x-cloak
|
||||||
class="border-b border-nx-border bg-black px-4 py-4 md:hidden"
|
class="border-b border-nx-border bg-black px-4 py-4 md:hidden"
|
||||||
>
|
>
|
||||||
<nav class="flex flex-col gap-3 text-sm" aria-label="Mobile">
|
<nav class="flex flex-col gap-3 text-sm" aria-label={navigation.labels.mobileNavAria}>
|
||||||
{navigation.items.map((item) => {
|
{navigation.items.map((item) => {
|
||||||
const sectionId = item.href.includes('#')
|
const sectionId = item.href.includes('#')
|
||||||
? item.href.split('#').pop()!
|
? item.href.split('#').pop()!
|
||||||
@ -356,7 +368,7 @@ const langMeta = {
|
|||||||
pi === 0 ? 'text-sky-200/85' : 'text-emerald-200/85',
|
pi === 0 ? 'text-sky-200/85' : 'text-emerald-200/85',
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<span class="text-nx-muted font-normal">Focus — </span>
|
<span class="text-nx-muted font-normal">{pageLabels.focus} — </span>
|
||||||
{product.focus}
|
{product.focus}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-5 text-sm leading-relaxed text-nx-muted sm:text-base">
|
<p class="mt-5 text-sm leading-relaxed text-nx-muted sm:text-base">
|
||||||
@ -497,7 +509,7 @@ const langMeta = {
|
|||||||
class="relative z-[1] mt-12 rounded-2xl border border-orange-500/20 bg-gradient-to-br from-orange-950/20 to-nx-elevated/40 p-6 sm:p-8"
|
class="relative z-[1] mt-12 rounded-2xl border border-orange-500/20 bg-gradient-to-br from-orange-950/20 to-nx-elevated/40 p-6 sm:p-8"
|
||||||
>
|
>
|
||||||
<p class="text-xs font-medium uppercase tracking-wider text-orange-200/90">
|
<p class="text-xs font-medium uppercase tracking-wider text-orange-200/90">
|
||||||
Infrastruttura
|
{stackSection.infrastructureHeading}
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
class="nx-infra-carousel mt-5"
|
class="nx-infra-carousel mt-5"
|
||||||
|
|||||||
@ -6,13 +6,32 @@ import SubpageLayout from '../layouts/SubpageLayout.astro';
|
|||||||
title="Novità — NexStudio"
|
title="Novità — NexStudio"
|
||||||
description="Novità, release e articoli da NexStudio."
|
description="Novità, release e articoli da NexStudio."
|
||||||
heading="Novità"
|
heading="Novità"
|
||||||
lead="Hub comunicazioni: collegate qui il vostro blog CMS, oppure elencate manualmente le notizie."
|
lead="Aggiornamenti ufficiali su piattaforma, compliance, infrastruttura e contenuti editoriali."
|
||||||
>
|
>
|
||||||
<p>
|
<p class="rounded-xl border border-emerald-500/30 bg-emerald-500/10 px-4 py-3 text-emerald-100">
|
||||||
Quando avrete contenuti da pubblicare (release di prodotto, note di
|
Stato editoriale: canale attivo. Le prossime pubblicazioni verranno aggiornate su questa pagina con frequenza periodica.
|
||||||
conformità, eventi), potete sostituire questa pagina con una raccolta
|
|
||||||
articoli Astro, un redirect verso Medium/Substack, o un headless CMS.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<h2>Programma editoriale</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Release tecniche:</strong> aggiornamenti su funzionalita, integrazioni e miglioramenti di stabilita.</li>
|
||||||
|
<li><strong>Compliance:</strong> novita su policy, sicurezza applicativa e adempimenti normativi.</li>
|
||||||
|
<li><strong>Case e risultati:</strong> note operative su adozione, scalabilita e impatti di business.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Canali di distribuzione</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Questa pagina resta il punto di riferimento ufficiale per annunci e changelog sintetici.</li>
|
||||||
|
<li>La newsletter verra attivata appena disponibile il canale comunicazioni dedicato.</li>
|
||||||
|
<li>I contenuti long-form potranno essere pubblicati anche su canali esterni, con rimando qui.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<h2>Archivio</h2>
|
<h2>Archivio</h2>
|
||||||
<p class="text-nx-muted">Nessuna news pubblicata per il momento.</p>
|
<p class="text-nx-muted">
|
||||||
|
In questa fase il sito e in pre-lancio operativo. Il primo blocco di news verra pubblicato con il go-live ufficiale.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Per richieste stampa, partnership o approfondimenti tecnici e possibile usare il form nella sezione
|
||||||
|
<a href="/#contatti" class="text-sky-300 hover:text-sky-200">Contatti</a>.
|
||||||
|
</p>
|
||||||
</SubpageLayout>
|
</SubpageLayout>
|
||||||
|
|||||||
@ -8,8 +8,18 @@ interface ContactFormCfg {
|
|||||||
fieldName: string;
|
fieldName: string;
|
||||||
fieldCompany: string;
|
fieldCompany: string;
|
||||||
fieldCountry: string;
|
fieldCountry: string;
|
||||||
|
fieldDepartment: string;
|
||||||
fieldEmail: string;
|
fieldEmail: string;
|
||||||
fieldMessage: string;
|
fieldMessage: string;
|
||||||
|
turnstileError: string;
|
||||||
|
configError: string;
|
||||||
|
mailtoFallbackSubject: string;
|
||||||
|
mailtoLabelName: string;
|
||||||
|
mailtoLabelCompany: string;
|
||||||
|
mailtoLabelEmail: string;
|
||||||
|
mailtoLabelCountry: string;
|
||||||
|
mailtoLabelDepartment: string;
|
||||||
|
mailtoDefaultDepartment: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readCfg(): ContactFormCfg {
|
function readCfg(): ContactFormCfg {
|
||||||
@ -25,8 +35,18 @@ function readCfg(): ContactFormCfg {
|
|||||||
fieldName: 'name',
|
fieldName: 'name',
|
||||||
fieldCompany: 'company',
|
fieldCompany: 'company',
|
||||||
fieldCountry: 'country',
|
fieldCountry: 'country',
|
||||||
|
fieldDepartment: 'department',
|
||||||
fieldEmail: 'email',
|
fieldEmail: 'email',
|
||||||
fieldMessage: 'message',
|
fieldMessage: 'message',
|
||||||
|
turnstileError: 'Complete anti-spam verification before sending.',
|
||||||
|
configError: 'Contact form endpoint is not configured.',
|
||||||
|
mailtoFallbackSubject: 'Website contact',
|
||||||
|
mailtoLabelName: 'Name',
|
||||||
|
mailtoLabelCompany: 'Company',
|
||||||
|
mailtoLabelEmail: 'Email',
|
||||||
|
mailtoLabelCountry: 'Country',
|
||||||
|
mailtoLabelDepartment: 'Department',
|
||||||
|
mailtoDefaultDepartment: 'general',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return JSON.parse(raw) as ContactFormCfg;
|
return JSON.parse(raw) as ContactFormCfg;
|
||||||
@ -50,8 +70,7 @@ function bind(): void {
|
|||||||
if (!token) {
|
if (!token) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (err) {
|
if (err) {
|
||||||
err.textContent =
|
err.textContent = cfg.turnstileError;
|
||||||
'Completa il controllo anti-spam prima di inviare il messaggio.';
|
|
||||||
err.classList.remove('hidden');
|
err.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -67,19 +86,23 @@ function bind(): void {
|
|||||||
const company = String(fd.get(cfg.fieldCompany) ?? '').trim();
|
const company = String(fd.get(cfg.fieldCompany) ?? '').trim();
|
||||||
const email = String(fd.get(cfg.fieldEmail) ?? '').trim();
|
const email = String(fd.get(cfg.fieldEmail) ?? '').trim();
|
||||||
const country = String(fd.get(cfg.fieldCountry) ?? '').trim();
|
const country = String(fd.get(cfg.fieldCountry) ?? '').trim();
|
||||||
|
const department = String(fd.get(cfg.fieldDepartment) ?? '').trim();
|
||||||
const message = String(fd.get(cfg.fieldMessage) ?? '').trim();
|
const message = String(fd.get(cfg.fieldMessage) ?? '').trim();
|
||||||
if (cfg.mailto) {
|
if (cfg.mailto) {
|
||||||
const subject = encodeURIComponent(cfg.notifySubject || 'Contatto sito');
|
const subject = encodeURIComponent(
|
||||||
const companyLine = company ? `\nAzienda: ${company}` : '';
|
cfg.notifySubject || cfg.mailtoFallbackSubject || 'Website contact',
|
||||||
|
);
|
||||||
|
const companyLine = company
|
||||||
|
? `\n${cfg.mailtoLabelCompany}: ${company}`
|
||||||
|
: '';
|
||||||
const body = encodeURIComponent(
|
const body = encodeURIComponent(
|
||||||
`Nome: ${name}${companyLine}\nEmail: ${email}\nPaese: ${country}\n\n${message}`,
|
`${cfg.mailtoLabelName}: ${name}${companyLine}\n${cfg.mailtoLabelEmail}: ${email}\n${cfg.mailtoLabelCountry}: ${country}\n${cfg.mailtoLabelDepartment}: ${department || cfg.mailtoDefaultDepartment}\n\n${message}`,
|
||||||
);
|
);
|
||||||
window.location.href = `mailto:${cfg.mailto}?subject=${subject}&body=${body}`;
|
window.location.href = `mailto:${cfg.mailto}?subject=${subject}&body=${body}`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (err) {
|
if (err) {
|
||||||
err.textContent =
|
err.textContent = cfg.configError;
|
||||||
'Configura `contactForm.actionUrl` (Formspree) o `contactForm.mailto` in `src/data/home/cta.ts`.';
|
|
||||||
err.classList.remove('hidden');
|
err.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user