Compare commits
33 Commits
chore/test
...
feat/regis
| Author | SHA1 | Date | |
|---|---|---|---|
| 511ff97cf1 | |||
| 4d548b2b19 | |||
| 6704a9a6e1 | |||
| 60ccc4ca25 | |||
| 62e8f6fffc | |||
| 314011c17a | |||
| a0c9dd9141 | |||
| ee4bd0b2df | |||
| 3d43febe21 | |||
| b687bc8ac2 | |||
| ef343a31b5 | |||
| 04a1892c0d | |||
| 3b52e5815c | |||
| 7f4bb2f184 | |||
| 03aabd29cc | |||
| 866d07b8b2 | |||
| 5606ff696e | |||
| 6673843a00 | |||
| 9ac9139b35 | |||
| 6339325e6a | |||
| 4b70c940db | |||
| 2c3df8371d | |||
| daa2a245a5 | |||
| 770db3067d | |||
| 6ba58c3a86 | |||
| 8a99290209 | |||
| c04d43d9d7 | |||
| c4adc116d6 | |||
| 44007f99e1 | |||
| 0743521d48 | |||
| 2b470c778a | |||
| 7aff1abcfb | |||
| 9a8566a3f5 |
16
.env.example
16
.env.example
@ -2,12 +2,20 @@ PUBLIC_CONTACT_FORM_ACTION_URL=/api/contact
|
|||||||
PUBLIC_CONTACT_FORM_SUCCESS_REDIRECT=http://localhost:4321/#contatti
|
PUBLIC_CONTACT_FORM_SUCCESS_REDIRECT=http://localhost:4321/#contatti
|
||||||
PUBLIC_CONTACT_FORM_MAILTO=
|
PUBLIC_CONTACT_FORM_MAILTO=
|
||||||
PUBLIC_CONTACT_TURNSTILE_SITE_KEY=
|
PUBLIC_CONTACT_TURNSTILE_SITE_KEY=
|
||||||
|
PUBLIC_SITE_URL=http://localhost:4321
|
||||||
|
|
||||||
CONTACT_FORM_MODE=dev
|
CONTACT_FORM_MODE=dev
|
||||||
CONTACT_FORM_RESEND_API_KEY=
|
CONTACT_FORM_FROM_EMAIL=no-reply@nexstudio.ai
|
||||||
CONTACT_FORM_FROM_EMAIL=no-reply@nexstudio.com
|
CONTACT_FORM_TO_EMAIL=javaxman69@gmail.com
|
||||||
CONTACT_FORM_TO_EMAIL=info@nexstudio.com
|
CONTACT_FORM_TO_EMAIL_INFO=
|
||||||
CONTACT_FORM_TO_EMAIL_INFO=info@nexstudio.com
|
|
||||||
CONTACT_FORM_TO_EMAIL_PRIVACY=
|
CONTACT_FORM_TO_EMAIL_PRIVACY=
|
||||||
CONTACT_FORM_TO_EMAIL_CAREERS=
|
CONTACT_FORM_TO_EMAIL_CAREERS=
|
||||||
CONTACT_TURNSTILE_SECRET_KEY=
|
CONTACT_TURNSTILE_SECRET_KEY=
|
||||||
|
|
||||||
|
# Newsletter (custom D1 + Cloudflare Email Service, double opt-in)
|
||||||
|
# In produzione: NEWSLETTER_MODE=live (oppure CONTACT_FORM_MODE=live come fallback)
|
||||||
|
NEWSLETTER_MODE=dev
|
||||||
|
NEWSLETTER_FROM_EMAIL=no-reply@nexstudio.ai
|
||||||
|
NEWSLETTER_NOTIFY_TO_EMAIL=javaxman69@gmail.com
|
||||||
|
NEWSLETTER_ADMIN_TOKEN=
|
||||||
|
NEWSLETTER_TURNSTILE_SECRET_KEY=
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,6 +16,7 @@ pnpm-debug.log*
|
|||||||
# environment variables
|
# environment variables
|
||||||
.env
|
.env
|
||||||
.env.production
|
.env.production
|
||||||
|
.dev.vars
|
||||||
|
|
||||||
# macOS-specific files
|
# macOS-specific files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
51
README.md
51
README.md
@ -15,7 +15,7 @@ Sito istituzionale NexStudio basato su Astro, con deploy su Cloudflare e form co
|
|||||||
- Adapter Cloudflare (`@astrojs/cloudflare`)
|
- Adapter Cloudflare (`@astrojs/cloudflare`)
|
||||||
- Sitemap (`@astrojs/sitemap`)
|
- Sitemap (`@astrojs/sitemap`)
|
||||||
- Form backend su endpoint `/api/contact`
|
- Form backend su endpoint `/api/contact`
|
||||||
- Turnstile + Resend per anti-spam e invio email
|
- Turnstile + Cloudflare Email Service per anti-spam e invio email
|
||||||
|
|
||||||
## Struttura essenziale
|
## Struttura essenziale
|
||||||
|
|
||||||
@ -29,8 +29,7 @@ Sito istituzionale NexStudio basato su Astro, con deploy su Cloudflare e form co
|
|||||||
|
|
||||||
- Node.js 20+
|
- Node.js 20+
|
||||||
- npm 10+
|
- npm 10+
|
||||||
- Account Cloudflare (Pages/Workers + Turnstile)
|
- Account Cloudflare (Workers + Turnstile + Email Service con dominio onboardato, es. `nexstudio.ai`)
|
||||||
- Account Resend (API key + sender verificato)
|
|
||||||
|
|
||||||
## Avvio locale
|
## Avvio locale
|
||||||
|
|
||||||
@ -63,9 +62,8 @@ Variabili pubbliche (frontend):
|
|||||||
Variabili server (Cloudflare/local):
|
Variabili server (Cloudflare/local):
|
||||||
|
|
||||||
- `CONTACT_FORM_MODE` (`dev` oppure `live`)
|
- `CONTACT_FORM_MODE` (`dev` oppure `live`)
|
||||||
- `CONTACT_FORM_RESEND_API_KEY`
|
- `CONTACT_FORM_FROM_EMAIL` (consigliato: `no-reply@nexstudio.ai`, dominio verificato su Email Service)
|
||||||
- `CONTACT_FORM_FROM_EMAIL` (consigliato: `no-reply@nexstudio.ai`)
|
- `CONTACT_FORM_TO_EMAIL` (casella reale dove ricevi, es. Gmail; evita indirizzi solo Email Routing se DMARC è `p=reject`)
|
||||||
- `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_INFO` (opzionale, override esplicito per area "info")
|
||||||
- `CONTACT_FORM_TO_EMAIL_PRIVACY`
|
- `CONTACT_FORM_TO_EMAIL_PRIVACY`
|
||||||
- `CONTACT_FORM_TO_EMAIL_CAREERS`
|
- `CONTACT_FORM_TO_EMAIL_CAREERS`
|
||||||
@ -74,7 +72,7 @@ Variabili server (Cloudflare/local):
|
|||||||
Comportamento:
|
Comportamento:
|
||||||
|
|
||||||
- `dev`: accetta submit e scrive payload nei log (senza invio reale)
|
- `dev`: accetta submit e scrive payload nei log (senza invio reale)
|
||||||
- `live`: valida Turnstile e invia email via Resend
|
- `live`: valida Turnstile e invia email via binding `EMAIL` (Cloudflare Email Service)
|
||||||
|
|
||||||
Routing pubblico consigliato (portale vetrina):
|
Routing pubblico consigliato (portale vetrina):
|
||||||
|
|
||||||
@ -89,6 +87,45 @@ Indirizzi consigliati portale:
|
|||||||
- `careers@nexstudio.ai`
|
- `careers@nexstudio.ai`
|
||||||
- `no-reply@nexstudio.ai` (solo invio comunicazioni/newsletter, non destinazione inbound)
|
- `no-reply@nexstudio.ai` (solo invio comunicazioni/newsletter, non destinazione inbound)
|
||||||
|
|
||||||
|
## Configurazione newsletter
|
||||||
|
|
||||||
|
Sistema custom su Cloudflare D1 + Cloudflare Email Service (double opt-in).
|
||||||
|
|
||||||
|
Prerequisito: dominio (es. `nexstudio.ai`) onboardato in **Email Service** con SPF/DKIM, e binding `send_email` → `EMAIL` nel Worker.
|
||||||
|
|
||||||
|
Variabili server:
|
||||||
|
|
||||||
|
- `NEWSLETTER_MODE` (`dev` oppure `live`; se assente usa `CONTACT_FORM_MODE`)
|
||||||
|
- `NEWSLETTER_FROM_EMAIL` (fallback: `CONTACT_FORM_FROM_EMAIL`, consigliato `no-reply@nexstudio.ai`)
|
||||||
|
- `NEWSLETTER_NOTIFY_TO_EMAIL` (notifiche admin; fallback: `CONTACT_FORM_TO_EMAIL`)
|
||||||
|
- `NEWSLETTER_ADMIN_TOKEN` (Bearer token per endpoint admin)
|
||||||
|
- `NEWSLETTER_TURNSTILE_SECRET_KEY` (opzionale; fallback: `CONTACT_TURNSTILE_SECRET_KEY`)
|
||||||
|
- `PUBLIC_SITE_URL` (origine usata nei link di conferma/disiscrizione)
|
||||||
|
|
||||||
|
Endpoint pubblici:
|
||||||
|
|
||||||
|
- `POST /api/newsletter/subscribe` — iscrizione (form footer)
|
||||||
|
- `GET /api/newsletter/confirm?token=…` — conferma double opt-in
|
||||||
|
- `GET|POST /api/newsletter/unsubscribe?token=…` — disiscrizione
|
||||||
|
|
||||||
|
Endpoint admin (header `Authorization: Bearer <NEWSLETTER_ADMIN_TOKEN>`):
|
||||||
|
|
||||||
|
- `GET /api/newsletter/admin/subscribers?status=active|pending|unsubscribed`
|
||||||
|
- `POST /api/newsletter/admin/send` — body JSON `{ "subject", "text", "html?", "dryRun?" }`
|
||||||
|
|
||||||
|
Nota: Email Service è pensato per email **transazionali** (conferme, contatti, notifiche). Le campagne marketing di massa non sono ancora il target ufficiale del servizio.
|
||||||
|
|
||||||
|
Database:
|
||||||
|
|
||||||
|
- D1 `nexstudio-newsletter` (binding `NEWSLETTER_DB`)
|
||||||
|
- Migrazioni in `migrations/`
|
||||||
|
- `npm run db:migrate:local` / `npm run db:migrate:remote`
|
||||||
|
|
||||||
|
Comportamento:
|
||||||
|
|
||||||
|
- `dev`: accetta submit e logga (senza D1/Email obbligatori)
|
||||||
|
- `live`: scrive su D1, invia email di conferma via Cloudflare Email Service, richiede binding D1 + EMAIL
|
||||||
|
|
||||||
## Deploy Cloudflare
|
## Deploy Cloudflare
|
||||||
|
|
||||||
Impostazioni consigliate:
|
Impostazioni consigliate:
|
||||||
|
|||||||
49
migrations/0001_newsletter.sql
Normal file
49
migrations/0001_newsletter.sql
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
-- Newsletter subscribers and campaign send log
|
||||||
|
CREATE TABLE IF NOT EXISTS newsletter_subscribers (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
email TEXT NOT NULL COLLATE NOCASE,
|
||||||
|
status TEXT NOT NULL CHECK (status IN ('pending', 'active', 'unsubscribed')),
|
||||||
|
locale TEXT NOT NULL DEFAULT 'it',
|
||||||
|
confirm_token TEXT,
|
||||||
|
unsubscribe_token TEXT NOT NULL,
|
||||||
|
source TEXT NOT NULL DEFAULT 'footer',
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
confirmed_at TEXT,
|
||||||
|
unsubscribed_at TEXT,
|
||||||
|
UNIQUE (email)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_newsletter_subscribers_status
|
||||||
|
ON newsletter_subscribers (status);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_newsletter_subscribers_confirm_token
|
||||||
|
ON newsletter_subscribers (confirm_token);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_newsletter_subscribers_unsubscribe_token
|
||||||
|
ON newsletter_subscribers (unsubscribe_token);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS newsletter_campaigns (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
subject TEXT NOT NULL,
|
||||||
|
body_text TEXT NOT NULL,
|
||||||
|
body_html TEXT,
|
||||||
|
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'sending', 'sent', 'failed')),
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
sent_at TEXT,
|
||||||
|
created_by TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS newsletter_campaign_sends (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
campaign_id INTEGER NOT NULL,
|
||||||
|
subscriber_id INTEGER NOT NULL,
|
||||||
|
email TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL CHECK (status IN ('queued', 'sent', 'failed', 'skipped')),
|
||||||
|
error TEXT,
|
||||||
|
sent_at TEXT,
|
||||||
|
FOREIGN KEY (campaign_id) REFERENCES newsletter_campaigns (id),
|
||||||
|
FOREIGN KEY (subscriber_id) REFERENCES newsletter_subscribers (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_newsletter_campaign_sends_campaign
|
||||||
|
ON newsletter_campaign_sends (campaign_id);
|
||||||
@ -12,7 +12,9 @@
|
|||||||
"deploy:cf": "npm run build && wrangler deploy --config dist/server/wrangler.json",
|
"deploy:cf": "npm run build && wrangler deploy --config dist/server/wrangler.json",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"astro": "astro",
|
"astro": "astro",
|
||||||
"generate-types": "wrangler types"
|
"generate-types": "wrangler types",
|
||||||
|
"db:migrate:local": "wrangler d1 migrations apply nexstudio-newsletter --local",
|
||||||
|
"db:migrate:remote": "wrangler d1 migrations apply nexstudio-newsletter --remote"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/alpinejs": "^0.5.0",
|
"@astrojs/alpinejs": "^0.5.0",
|
||||||
|
|||||||
@ -1,27 +1,28 @@
|
|||||||
/**
|
/**
|
||||||
* Rete di particelle + linee (canvas #particleCanvas).
|
* Rete di particelle + linee (canvas #particleCanvas).
|
||||||
* Deve stare DENTRO il contenitore della pagina (es. .nx-grid-bg), non dietro body,
|
* Limitato al viewport (non all’altezza documento): evita canvas giganti che
|
||||||
* altrimenti lo sfondo opaco della pagina lo copre.
|
* bloccano lo scroll sulla home lunga.
|
||||||
*/
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
var canvas = document.getElementById('particleCanvas');
|
var canvas = document.getElementById('particleCanvas');
|
||||||
if (!canvas) return;
|
if (!canvas) return;
|
||||||
var ctx = canvas.getContext('2d');
|
var ctx = canvas.getContext('2d', { alpha: true });
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
|
|
||||||
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
|
||||||
var particles = [];
|
var particles = [];
|
||||||
var mouse = { x: null, y: null, radius: 150 };
|
var mouse = { x: null, y: null, radius: 150 };
|
||||||
var raf = 0;
|
var raf = 0;
|
||||||
|
var running = false;
|
||||||
|
var cachedW = 0;
|
||||||
|
var cachedH = 0;
|
||||||
|
var scrollPauseTimer = 0;
|
||||||
|
|
||||||
function canvasSize() {
|
function viewportSize() {
|
||||||
var p = canvas.parentElement;
|
var w = Math.max(1, Math.floor(window.innerWidth || document.documentElement.clientWidth || 1));
|
||||||
if (!p) return { w: 0, h: 0 };
|
var h = Math.max(1, Math.floor(window.innerHeight || document.documentElement.clientHeight || 1));
|
||||||
var w = Math.max(1, Math.floor(p.clientWidth));
|
|
||||||
var h = Math.max(1, Math.floor(p.clientHeight));
|
|
||||||
return { w: w, h: h };
|
return { w: w, h: h };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,8 +30,8 @@
|
|||||||
this.x = Math.random() * w;
|
this.x = Math.random() * w;
|
||||||
this.y = Math.random() * h;
|
this.y = Math.random() * h;
|
||||||
this.size = Math.random() * 2 + 1;
|
this.size = Math.random() * 2 + 1;
|
||||||
this.speedX = (Math.random() - 0.5) * 1.5;
|
this.speedX = (Math.random() - 0.5) * 1.2;
|
||||||
this.speedY = (Math.random() - 0.5) * 1.5;
|
this.speedY = (Math.random() - 0.5) * 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
Particle.prototype.draw = function () {
|
Particle.prototype.draw = function () {
|
||||||
@ -58,46 +59,55 @@
|
|||||||
var fx = dx / distance;
|
var fx = dx / distance;
|
||||||
var fy = dy / distance;
|
var fy = dy / distance;
|
||||||
var force = (mouse.radius - distance) / mouse.radius;
|
var force = (mouse.radius - distance) / mouse.radius;
|
||||||
this.x -= fx * force * 5;
|
this.x -= fx * force * 4;
|
||||||
this.y -= fy * force * 5;
|
this.y -= fy * force * 4;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
var sz = canvasSize();
|
var sz = viewportSize();
|
||||||
var w = sz.w;
|
var w = sz.w;
|
||||||
var h = sz.h;
|
var h = sz.h;
|
||||||
if (w < 8 || h < 8) return;
|
cachedW = w;
|
||||||
|
cachedH = h;
|
||||||
|
|
||||||
var dpr = Math.min(window.devicePixelRatio || 1, 2);
|
var dpr = Math.min(window.devicePixelRatio || 1, 1.5);
|
||||||
canvas.width = Math.floor(w * dpr);
|
canvas.width = Math.floor(w * dpr);
|
||||||
canvas.height = Math.floor(h * dpr);
|
canvas.height = Math.floor(h * dpr);
|
||||||
|
canvas.style.width = w + 'px';
|
||||||
|
canvas.style.height = h + 'px';
|
||||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||||
|
|
||||||
particles = [];
|
particles = [];
|
||||||
var n = Math.floor((w * h) / 9000);
|
var area = w * h;
|
||||||
if (n > 130) n = 130;
|
var n = Math.floor(area / 14000);
|
||||||
if (n < 45) n = 45;
|
if (n > 80) n = 80;
|
||||||
|
if (n < 28) n = 28;
|
||||||
|
if (w < 768) {
|
||||||
|
n = Math.min(n, 40);
|
||||||
|
}
|
||||||
for (var i = 0; i < n; i++) {
|
for (var i = 0; i < n; i++) {
|
||||||
particles.push(new Particle(w, h));
|
particles.push(new Particle(w, h));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function connect(w, h) {
|
function connect(w) {
|
||||||
|
var maxDist = w < 768 ? 110 : 140;
|
||||||
var a;
|
var a;
|
||||||
var b;
|
var b;
|
||||||
var dx;
|
var dx;
|
||||||
var dy;
|
var dy;
|
||||||
var distance;
|
var distance;
|
||||||
var opacity;
|
var opacity;
|
||||||
for (a = 0; a < particles.length; a++) {
|
var len = particles.length;
|
||||||
for (b = a + 1; b < particles.length; b++) {
|
for (a = 0; a < len; a++) {
|
||||||
|
for (b = a + 1; b < len; b++) {
|
||||||
dx = particles[a].x - particles[b].x;
|
dx = particles[a].x - particles[b].x;
|
||||||
dy = particles[a].y - particles[b].y;
|
dy = particles[a].y - particles[b].y;
|
||||||
distance = Math.sqrt(dx * dx + dy * dy);
|
distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
if (distance < 150) {
|
if (distance < maxDist) {
|
||||||
opacity = (1 - distance / 150) * 0.22;
|
opacity = (1 - distance / maxDist) * 0.2;
|
||||||
ctx.strokeStyle = 'rgba(59, 130, 246, ' + opacity + ')';
|
ctx.strokeStyle = 'rgba(59, 130, 246,' + opacity + ')';
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(particles[a].x, particles[a].y);
|
ctx.moveTo(particles[a].x, particles[a].y);
|
||||||
@ -109,9 +119,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function frame() {
|
function frame() {
|
||||||
var sz = canvasSize();
|
if (!running) return;
|
||||||
var w = sz.w;
|
var w = cachedW;
|
||||||
var h = sz.h;
|
var h = cachedH;
|
||||||
ctx.clearRect(0, 0, w, h);
|
ctx.clearRect(0, 0, w, h);
|
||||||
|
|
||||||
var i;
|
var i;
|
||||||
@ -119,29 +129,33 @@
|
|||||||
particles[i].update(w, h);
|
particles[i].update(w, h);
|
||||||
particles[i].draw();
|
particles[i].draw();
|
||||||
}
|
}
|
||||||
connect(w, h);
|
connect(w);
|
||||||
raf = requestAnimationFrame(frame);
|
raf = requestAnimationFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncCanvasCssSize() {
|
function start() {
|
||||||
var sz = canvasSize();
|
if (reduced || document.hidden || running || !particles.length) return;
|
||||||
canvas.style.width = sz.w + 'px';
|
running = true;
|
||||||
canvas.style.height = sz.h + 'px';
|
raf = requestAnimationFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onResize() {
|
function stop() {
|
||||||
cancelAnimationFrame(raf);
|
running = false;
|
||||||
syncCanvasCssSize();
|
if (raf) {
|
||||||
init();
|
cancelAnimationFrame(raf);
|
||||||
if (!reduced && particles.length) {
|
raf = 0;
|
||||||
raf = requestAnimationFrame(frame);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onResize() {
|
||||||
|
stop();
|
||||||
|
init();
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
function setMouseFromEvent(e) {
|
function setMouseFromEvent(e) {
|
||||||
var rect = canvas.getBoundingClientRect();
|
mouse.x = e.clientX;
|
||||||
mouse.x = e.clientX - rect.left;
|
mouse.y = e.clientY;
|
||||||
mouse.y = e.clientY - rect.top;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
@ -152,22 +166,26 @@
|
|||||||
{ passive: true },
|
{ passive: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
var parent = canvas.parentElement;
|
window.addEventListener(
|
||||||
if (parent) {
|
'scroll',
|
||||||
var ro = new ResizeObserver(onResize);
|
function () {
|
||||||
ro.observe(parent);
|
// Libera il main thread durante lo scroll; riprende subito dopo.
|
||||||
}
|
stop();
|
||||||
|
if (scrollPauseTimer) window.clearTimeout(scrollPauseTimer);
|
||||||
|
scrollPauseTimer = window.setTimeout(function () {
|
||||||
|
start();
|
||||||
|
}, 120);
|
||||||
|
},
|
||||||
|
{ passive: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', function () {
|
||||||
|
if (document.hidden) stop();
|
||||||
|
else start();
|
||||||
|
});
|
||||||
|
|
||||||
window.addEventListener('resize', onResize, { passive: true });
|
window.addEventListener('resize', onResize, { passive: true });
|
||||||
|
|
||||||
syncCanvasCssSize();
|
|
||||||
init();
|
init();
|
||||||
|
start();
|
||||||
if (reduced) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (particles.length) {
|
|
||||||
raf = requestAnimationFrame(frame);
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
---
|
---
|
||||||
import { chat } from '../data/home';
|
import { getChat } from '../data/home';
|
||||||
|
import type { SupportedLocale } from '../data/home/navigation';
|
||||||
|
|
||||||
|
const path = Astro.url.pathname;
|
||||||
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
|
const currentLocale = (localeMatch?.[1] ?? 'it') as SupportedLocale;
|
||||||
|
const chat = getChat(currentLocale);
|
||||||
const pc = chat.prechat;
|
const pc = chat.prechat;
|
||||||
const hasFormspree =
|
const hasFormspree =
|
||||||
typeof pc.actionUrl === 'string' && pc.actionUrl.length > 0;
|
typeof pc.actionUrl === 'string' && pc.actionUrl.length > 0;
|
||||||
|
|||||||
@ -19,6 +19,7 @@ const model = {
|
|||||||
fieldDepartment: cf.fieldDepartment,
|
fieldDepartment: cf.fieldDepartment,
|
||||||
fieldEmail: cf.fieldEmail,
|
fieldEmail: cf.fieldEmail,
|
||||||
fieldMessage: cf.fieldMessage,
|
fieldMessage: cf.fieldMessage,
|
||||||
|
successMessage: cf.successMessage,
|
||||||
turnstileError: cf.errors.turnstile,
|
turnstileError: cf.errors.turnstile,
|
||||||
configError: cf.errors.config,
|
configError: cf.errors.config,
|
||||||
mailtoFallbackSubject: cf.mailtoFallbackSubject,
|
mailtoFallbackSubject: cf.mailtoFallbackSubject,
|
||||||
@ -112,18 +113,6 @@ const fieldClass =
|
|||||||
<input type="hidden" name="_subject" value={notifySubject} />
|
<input type="hidden" name="_subject" value={notifySubject} />
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
{
|
|
||||||
hasFormspree ? (
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="_gotcha"
|
|
||||||
tabindex="-1"
|
|
||||||
autocomplete="off"
|
|
||||||
aria-hidden="true"
|
|
||||||
class="pointer-events-none absolute -left-[9999px] h-0 w-0 opacity-0"
|
|
||||||
/>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs font-medium text-nx-muted" for="nx-cf-name">
|
<label class="block text-xs font-medium text-nx-muted" for="nx-cf-name">
|
||||||
@ -203,6 +192,13 @@ const fieldClass =
|
|||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p
|
||||||
|
id="nx-contact-form-success"
|
||||||
|
class="hidden text-sm leading-relaxed text-emerald-400/90"
|
||||||
|
role="status"
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
|
||||||
<p
|
<p
|
||||||
id="nx-contact-form-error"
|
id="nx-contact-form-error"
|
||||||
class="hidden text-sm leading-relaxed text-red-400/90"
|
class="hidden text-sm leading-relaxed text-red-400/90"
|
||||||
@ -230,7 +226,7 @@ const fieldClass =
|
|||||||
<div class="pt-1">
|
<div class="pt-1">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="inline-flex h-10 w-full items-center justify-center rounded-lg bg-nx-fg px-5 text-sm font-semibold text-nx-bg hover:bg-white sm:w-auto sm:min-w-[10rem]"
|
class="inline-flex h-10 w-full cursor-pointer items-center justify-center rounded-lg bg-nx-fg px-5 text-sm font-semibold text-nx-bg hover:bg-white sm:w-auto sm:min-w-[10rem]"
|
||||||
>
|
>
|
||||||
{cf.submitLabel}
|
{cf.submitLabel}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -1,5 +1,18 @@
|
|||||||
---
|
---
|
||||||
import { cookieConsentMeta, cookieCategories } from '../data/cookie-consent';
|
import {
|
||||||
|
cookieConsentMeta,
|
||||||
|
getCookieCategories,
|
||||||
|
getCookieConsentUi,
|
||||||
|
} from '../data/cookie-consent';
|
||||||
|
import type { SupportedLocale } from '../data/home/navigation';
|
||||||
|
|
||||||
|
const path = Astro.url.pathname;
|
||||||
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
|
const currentLocale = (localeMatch?.[1] ?? 'it') as SupportedLocale;
|
||||||
|
const cookieCategories = getCookieCategories(currentLocale);
|
||||||
|
const ui = getCookieConsentUi(currentLocale);
|
||||||
|
const cookiesHref =
|
||||||
|
currentLocale === 'it' ? '/cookies' : `/${currentLocale}/cookies`;
|
||||||
|
|
||||||
const model = {
|
const model = {
|
||||||
storageKey: cookieConsentMeta.storageKey,
|
storageKey: cookieConsentMeta.storageKey,
|
||||||
@ -28,15 +41,14 @@ const model = {
|
|||||||
id="nx-cc-banner-title"
|
id="nx-cc-banner-title"
|
||||||
class="text-sm font-semibold tracking-tight text-nx-fg"
|
class="text-sm font-semibold tracking-tight text-nx-fg"
|
||||||
>
|
>
|
||||||
Utilizziamo cookie e memorizzazione locale
|
{ui.bannerTitle}
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
id="nx-cc-banner-desc"
|
id="nx-cc-banner-desc"
|
||||||
class="mt-1 text-xs leading-relaxed text-nx-muted sm:text-sm"
|
class="mt-1 text-xs leading-relaxed text-nx-muted sm:text-sm"
|
||||||
>
|
>
|
||||||
Seleziona le categorie che preferisci. I necessari restano attivi per
|
{ui.bannerDescBefore}{' '}
|
||||||
registrare la tua scelta. Dettagli nella{' '}
|
<a class="text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline" href={cookiesHref}>{ui.bannerPolicyLinkLabel}</a>.
|
||||||
<a class="text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline" href="/cookies">informativa cookie</a>.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -47,21 +59,21 @@ const model = {
|
|||||||
data-nx-cc-reject
|
data-nx-cc-reject
|
||||||
class="order-3 h-10 rounded-lg border border-nx-border px-4 text-sm font-medium text-nx-muted hover:border-sky-500/30 hover:text-nx-fg sm:order-1"
|
class="order-3 h-10 rounded-lg border border-nx-border px-4 text-sm font-medium text-nx-muted hover:border-sky-500/30 hover:text-nx-fg sm:order-1"
|
||||||
>
|
>
|
||||||
Rifiuta non essenziali
|
{ui.rejectLabel}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-nx-cc-customize
|
data-nx-cc-customize
|
||||||
class="order-2 h-10 rounded-lg border border-nx-border px-4 text-sm font-medium text-nx-fg hover:border-sky-500/40 sm:order-2"
|
class="order-2 h-10 rounded-lg border border-nx-border px-4 text-sm font-medium text-nx-fg hover:border-sky-500/40 sm:order-2"
|
||||||
>
|
>
|
||||||
Personalizza
|
{ui.customizeLabel}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-nx-cc-accept
|
data-nx-cc-accept
|
||||||
class="order-1 inline-flex h-10 items-center justify-center rounded-lg bg-gradient-to-r from-sky-500 to-violet-500 px-4 text-sm font-semibold text-white shadow-lg shadow-sky-500/20 hover:brightness-110 sm:order-3"
|
class="order-1 inline-flex h-10 items-center justify-center rounded-lg bg-gradient-to-r from-sky-500 to-violet-500 px-4 text-sm font-semibold text-white shadow-lg shadow-sky-500/20 hover:brightness-110 sm:order-3"
|
||||||
>
|
>
|
||||||
Accetta tutto
|
{ui.acceptLabel}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -86,11 +98,10 @@ const model = {
|
|||||||
>
|
>
|
||||||
<div class="border-b border-nx-border px-5 py-4">
|
<div class="border-b border-nx-border px-5 py-4">
|
||||||
<h2 id="nx-cc-dialog-title" class="text-base font-semibold tracking-tight">
|
<h2 id="nx-cc-dialog-title" class="text-base font-semibold tracking-tight">
|
||||||
Preferenze cookie
|
{ui.dialogTitle}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-xs leading-relaxed text-nx-muted">
|
<p class="mt-1 text-xs leading-relaxed text-nx-muted">
|
||||||
Versione informativa v{cookieConsentMeta.policyVersion}. Puoi modificare
|
{ui.dialogIntroBefore}{cookieConsentMeta.policyVersion}{ui.dialogIntroAfter}
|
||||||
le scelte in qualsiasi momento dal footer.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -108,7 +119,7 @@ const model = {
|
|||||||
</div>
|
</div>
|
||||||
{cat.required ? (
|
{cat.required ? (
|
||||||
<span class="shrink-0 rounded-md bg-nx-border/40 px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-nx-muted">
|
<span class="shrink-0 rounded-md bg-nx-border/40 px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-nx-muted">
|
||||||
Sempre attivi
|
{ui.alwaysOnLabel}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<label
|
<label
|
||||||
@ -138,14 +149,14 @@ const model = {
|
|||||||
data-nx-cc-cancel
|
data-nx-cc-cancel
|
||||||
class="h-10 rounded-lg border border-nx-border px-4 text-sm font-medium text-nx-muted hover:text-nx-fg"
|
class="h-10 rounded-lg border border-nx-border px-4 text-sm font-medium text-nx-muted hover:text-nx-fg"
|
||||||
>
|
>
|
||||||
Chiudi
|
{ui.cancelLabel}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-nx-cc-save
|
data-nx-cc-save
|
||||||
class="h-10 rounded-lg bg-nx-fg px-4 text-sm font-semibold text-nx-bg hover:bg-white"
|
class="h-10 rounded-lg bg-nx-fg px-4 text-sm font-semibold text-nx-bg hover:bg-white"
|
||||||
>
|
>
|
||||||
Salva preferenze
|
{ui.saveLabel}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,32 +3,20 @@
|
|||||||
* Continuazione dei flussi dati dell'hero: dalla destra verso sinistra,
|
* Continuazione dei flussi dati dell'hero: dalla destra verso sinistra,
|
||||||
* inclinazione verso il basso, fino alla fascia del titolo "Prodotti".
|
* inclinazione verso il basso, fino alla fascia del titolo "Prodotti".
|
||||||
* Dietro a testo e card (main ha stacking; questo blocco è z-0).
|
* Dietro a testo e card (main ha stacking; questo blocco è z-0).
|
||||||
|
* Senza feGaussianBlur: il filtro SVG su un layer alto quanto il main
|
||||||
|
* rendeva lo scroll a scatti.
|
||||||
*/
|
*/
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="nx-dataflow-spine" aria-hidden="true">
|
<div class="nx-dataflow-spine" aria-hidden="true">
|
||||||
<svg
|
<svg
|
||||||
class="nx-dataflow-spine-svg"
|
class="nx-dataflow-spine-svg"
|
||||||
viewBox="0 0 1480 2800"
|
viewBox="0 0 1480 1600"
|
||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<filter
|
|
||||||
id="nx-spine-glow"
|
|
||||||
x="-30%"
|
|
||||||
y="-30%"
|
|
||||||
width="160%"
|
|
||||||
height="160%"
|
|
||||||
color-interpolation-filters="sRGB"
|
|
||||||
>
|
|
||||||
<feGaussianBlur stdDeviation="2.2" result="blur" />
|
|
||||||
<feMerge>
|
|
||||||
<feMergeNode in="blur" />
|
|
||||||
<feMergeNode in="SourceGraphic" />
|
|
||||||
</feMerge>
|
|
||||||
</filter>
|
|
||||||
<linearGradient id="nx-spine-stroke" x1="100%" y1="0%" x2="0%" y2="60%">
|
<linearGradient id="nx-spine-stroke" x1="100%" y1="0%" x2="0%" y2="60%">
|
||||||
<stop offset="0%" stop-color="#a5f3fc" stop-opacity="0.75" />
|
<stop offset="0%" stop-color="#a5f3fc" stop-opacity="0.75" />
|
||||||
<stop offset="40%" stop-color="#38bdf8" stop-opacity="0.9" />
|
<stop offset="40%" stop-color="#38bdf8" stop-opacity="0.9" />
|
||||||
@ -36,8 +24,8 @@
|
|||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
<g filter="url(#nx-spine-glow)" opacity="0.72">
|
<g opacity="0.72">
|
||||||
<!-- Entrata da dx (fuori canvas) → curva verso sx con pendenza verso il basso; fascia ~y 980–1280 = titolo Prodotti su viewport tipiche -->
|
<!-- Entrata da dx → curva verso sx; fascia titolo Prodotti -->
|
||||||
<path
|
<path
|
||||||
class="nx-flow-line nx-flow-line--a"
|
class="nx-flow-line nx-flow-line--a"
|
||||||
vector-effect="non-scaling-stroke"
|
vector-effect="non-scaling-stroke"
|
||||||
|
|||||||
@ -166,6 +166,8 @@ const withLocale = (href: string) => {
|
|||||||
{notifySubject ? (
|
{notifySubject ? (
|
||||||
<input type="hidden" name="_subject" value={notifySubject} />
|
<input type="hidden" name="_subject" value={notifySubject} />
|
||||||
) : null}
|
) : null}
|
||||||
|
<input type="hidden" name="locale" value={currentLocale} />
|
||||||
|
<input type="hidden" name="source" value="footer" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="_gotcha"
|
name="_gotcha"
|
||||||
|
|||||||
208
src/data/about.ts
Normal file
208
src/data/about.ts
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
/**
|
||||||
|
* Pagina Chi siamo / About / เกี่ยวกับเรา — contenuti localizzati.
|
||||||
|
*/
|
||||||
|
import type { SupportedLocale } from './home/navigation';
|
||||||
|
|
||||||
|
export type AboutBullet = {
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AboutSection = {
|
||||||
|
title: string;
|
||||||
|
paragraphs?: readonly string[];
|
||||||
|
bullets?: readonly AboutBullet[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type AboutLocaleBundle = {
|
||||||
|
page: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
heading: string;
|
||||||
|
lead: string;
|
||||||
|
};
|
||||||
|
intro: string;
|
||||||
|
sections: readonly AboutSection[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const aboutByLocale: Record<SupportedLocale, AboutLocaleBundle> = {
|
||||||
|
it: {
|
||||||
|
page: {
|
||||||
|
title: 'Chi siamo — NexStudio',
|
||||||
|
description:
|
||||||
|
'Missione, valori e come lavoriamo con studi legali e strutture sanitarie: qualità, verticalità e impegno nel tempo.',
|
||||||
|
heading: 'Chi siamo',
|
||||||
|
lead:
|
||||||
|
'Siamo un team internazionale — tra Bangkok e l’Italia — che unisce ingegneria del software, intelligenza artificiale applicata e conoscenza dei domini regolamentati, per strumenti professionali usabili e sostenibili nel tempo.',
|
||||||
|
},
|
||||||
|
intro:
|
||||||
|
'Ci impegniamo a rendere la tecnologia invisibile nel lavoro quotidiano. Progettiamo software che assume su di sé processi strutturati, controlli e complessità computazionale, così che chi lavora su norme, pazienti o documentazione possa concentrarsi su giudizio, responsabilità e relazione — non su menu, formati o incertezze operative. Nascondere la complessità sotto interfaccia essenziale, tempi reattivi e tracciabilità è al centro di come disegniamo il prodotto, non un optional estetico.',
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: 'La nostra missione',
|
||||||
|
paragraphs: [
|
||||||
|
'Operiamo dove un errore pesa: Legal Tech e Health Tech. Qui servono precisione, tracciamento delle scelte e rispetto dei vincoli normativi, non sperimenti generici. La nostra missione è fornire piattaforme SaaS mature — come LexAura e MediAura — che integrino modelli d’intelligenza artificiale in flussi già vissuti dallo studio o dalla struttura, con continuità tra automazione, revisione umana e responsabilità riconoscibile.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Perché possiamo meritare la tua fiducia',
|
||||||
|
paragraphs: [
|
||||||
|
'La fiducia non si ottiene con un motto, ma con ingegneria, chiarezza e continuità. Significa architetture provate, aggiornamenti regolari dei modelli d’analisi, assistenza e formazione laddove l’adozione chiede metodo oltre al click. Significa anche onestà sui confini: l’IA nelle nostre offerte è pensata per estendere e accelerare il lavoro professionale, lasciando a avvocato, medico o organizzazione la decisione finale e la documentazione che la norma richiede.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Come lavoriamo, in sintesi',
|
||||||
|
bullets: [
|
||||||
|
{
|
||||||
|
title: 'Verticalità e dominio.',
|
||||||
|
text: 'Less genericismo, più requisiti operativi (normative, flussi, audit) incorporati fin dalla progettazione.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Qualità e sicurezza come priorità, non retorica.',
|
||||||
|
text: 'Ingegneria, test e infrastrutture coerenti con l’esposizione a dati sensibili.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Supporto e evoluzione.',
|
||||||
|
text: 'Il rapporto con il cliente non finisce all’abbonamento: manutenzione evolutiva, assistenza e procedure chiare in caso di urgenze operative, dove serve.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Comunicazione trasparente.',
|
||||||
|
text: 'Documentazione, informativa privacy (dove obbligo) e aspettative reciproche esplicite, perché l’onboarding non diventi il primo ostacolo.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Team e cultura',
|
||||||
|
paragraphs: [
|
||||||
|
'Siamo developer, product e specialisti di dominio in un modello distribuito: fusi orari diversi e linguaggi comuni, un solo obiettivo prodotto. Ci muoviamo con iterazioni rapide laddove serve innovazione, e con governance laddove un cambiamento impatta validazione, conformità o continuità del servizio. La diversità del gruppo (competenze, contesti) è per noi vantaggio competitivo: riduce l’assunto implicito e costringe a rendere esplicito ciò che il software promette, e a chi.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Impegno nel tempo',
|
||||||
|
paragraphs: [
|
||||||
|
'Il settore che ci sceglie non cerca un prototipo, ma un percorso sostenibile: allineamento normativo, adozione in team, affiancamento quando qualcosa non gira. Costruiamo e manteniamo il prodotto con quella lente, perché nelle vostre professioni la continuità non è comodità, è requisito.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
page: {
|
||||||
|
title: 'About us — NexStudio',
|
||||||
|
description:
|
||||||
|
'Mission, values, and how we work with law firms and healthcare organizations: quality, domain focus, and long-term commitment.',
|
||||||
|
heading: 'About us',
|
||||||
|
lead:
|
||||||
|
'We are an international team — between Bangkok and Italy — combining software engineering, applied artificial intelligence, and knowledge of regulated domains, for professional tools that are usable and sustainable over time.',
|
||||||
|
},
|
||||||
|
intro:
|
||||||
|
'We aim to make technology invisible in everyday work. We design software that takes on structured processes, controls, and computational complexity, so that people working with norms, patients, or documentation can focus on judgment, responsibility, and relationships — not on menus, formats, or operational uncertainty. Hiding complexity behind an essential interface, responsive performance, and traceability is central to how we design the product, not an aesthetic optional.',
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: 'Our mission',
|
||||||
|
paragraphs: [
|
||||||
|
'We operate where a mistake carries weight: Legal Tech and Health Tech. Here precision, choice tracking, and respect for regulatory constraints are required — not generic experiments. Our mission is to provide mature SaaS platforms — such as LexAura and MediAura — that integrate AI models into flows already lived by the firm or organization, with continuity between automation, human review, and recognizable responsibility.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Why we can earn your trust',
|
||||||
|
paragraphs: [
|
||||||
|
'Trust is not won with a slogan, but with engineering, clarity, and continuity. It means proven architectures, regular updates to analysis models, and support and training where adoption needs method beyond the click. It also means honesty about boundaries: AI in our offerings is designed to extend and accelerate professional work, leaving the final decision and the documentation the rules require to the lawyer, clinician, or organization.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'How we work, in brief',
|
||||||
|
bullets: [
|
||||||
|
{
|
||||||
|
title: 'Verticality and domain.',
|
||||||
|
text: 'Less genericism, more operational requirements (regulations, flows, audits) built in from design.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Quality and security as priorities, not rhetoric.',
|
||||||
|
text: 'Engineering, testing, and infrastructure aligned with exposure to sensitive data.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Support and evolution.',
|
||||||
|
text: 'The customer relationship does not end with the subscription: evolutionary maintenance, assistance, and clear procedures for operational emergencies where needed.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Transparent communication.',
|
||||||
|
text: 'Documentation, privacy notices (where required), and explicit mutual expectations, so onboarding is not the first obstacle.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Team and culture',
|
||||||
|
paragraphs: [
|
||||||
|
'We are developers, product people, and domain specialists in a distributed model: different time zones and shared languages, one product goal. We move with rapid iterations where innovation is needed, and with governance where a change affects validation, compliance, or service continuity. The diversity of the group (skills, contexts) is a competitive advantage for us: it reduces implicit assumptions and forces us to make explicit what the software promises, and to whom.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Long-term commitment',
|
||||||
|
paragraphs: [
|
||||||
|
'The sector that chooses us is not looking for a prototype, but a sustainable path: regulatory alignment, team adoption, and support when something does not work. We build and maintain the product with that lens, because in your professions continuity is not a convenience — it is a requirement.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
page: {
|
||||||
|
title: 'เกี่ยวกับเรา — NexStudio',
|
||||||
|
description:
|
||||||
|
'พันธกิจ คุณค่า และวิธีที่เราทำงานกับสำนักงานกฎหมายและองค์กรด้านสุขภาพ: คุณภาพ ความเชี่ยวชาญเชิงโดเมน และความมุ่งมั่นระยะยาว',
|
||||||
|
heading: 'เกี่ยวกับเรา',
|
||||||
|
lead:
|
||||||
|
'เราเป็นทีมนานาชาติ — ระหว่างกรุงเทพฯ และอิตาลี — ที่ผสานวิศวกรรมซอฟต์แวร์ ปัญญาประดิษฐ์เชิงประยุกต์ และความรู้ในโดเมนที่มีการกำกับดูแล เพื่อเครื่องมือระดับมืออาชีพที่ใช้งานได้และยั่งยืนในระยะยาว',
|
||||||
|
},
|
||||||
|
intro:
|
||||||
|
'เรามุ่งทำให้เทคโนโลยีมองไม่เห็นในงานประจำวัน เราออกแบบซอฟต์แวร์ที่รับภาระกระบวนการที่มีโครงสร้าง การควบคุม และความซับซ้อนเชิงคำนวณ เพื่อให้ผู้ที่ทำงานกับกฎเกณฑ์ ผู้ป่วย หรือเอกสาร มุ่งเน้นที่การตัดสินใจ ความรับผิดชอบ และความสัมพันธ์ — ไม่ใช่เมนู รูปแบบ หรือความไม่แน่นอนในการปฏิบัติงาน การซ่อนความซับซ้อนไว้ใต้ส่วนติดต่อที่จำเป็น ประสิทธิภาพที่ตอบสนอง และการตรวจสอบย้อนกลับได้ เป็นหัวใจของการออกแบบผลิตภัณฑ์ ไม่ใช่ทางเลือกด้านสุนทรียะ',
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: 'พันธกิจของเรา',
|
||||||
|
paragraphs: [
|
||||||
|
'เราดำเนินงานในที่ที่ความผิดพลาดมีน้ำหนัก: Legal Tech และ Health Tech ที่นี่ต้องการความแม่นยำ การติดตามการตัดสินใจ และการเคารพข้อจำกัดด้านกฎระเบียบ — ไม่ใช่การทดลองทั่วไป พันธกิจของเราคือการมอบแพลตฟอร์ม SaaS ที่เติบโตแล้ว — เช่น LexAura และ MediAura — ที่ผสานโมเดลปัญญาประดิษฐ์เข้ากับเวิร์กโฟลว์ที่สำนักงานหรือองค์กรใช้อยู่แล้ว โดยมีความต่อเนื่องระหว่างระบบอัตโนมัติ การตรวจสอบโดยมนุษย์ และความรับผิดชอบที่ระบุได้',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'เหตุใดเราจึงสมควรได้รับความไว้วางใจ',
|
||||||
|
paragraphs: [
|
||||||
|
'ความไว้วางใจไม่ได้มาจากคำขวัญ แต่มาจากวิศวกรรม ความชัดเจน และความต่อเนื่อง หมายถึงสถาปัตยกรรมที่พิสูจน์แล้ว การอัปเดตโมเดลการวิเคราะห์อย่างสม่ำเสมอ รวมถึงการสนับสนุนและการอบรมเมื่อการนำมาใช้ต้องการวิธีการมากกว่าการคลิก และยังหมายถึงความซื่อสัตย์เรื่องขอบเขต: AI ในข้อเสนอของเราออกแบบมาเพื่อขยายและเร่งงานระดับมืออาชีพ โดยปล่อยให้ทนายความ แพทย์ หรือองค์กรเป็นผู้ตัดสินใจขั้นสุดท้ายและจัดทำเอกสารตามที่กฎกำหนด',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'วิธีที่เราทำงาน โดยสรุป',
|
||||||
|
bullets: [
|
||||||
|
{
|
||||||
|
title: 'ความเชี่ยวชาญเชิงแนวตั้งและโดเมน',
|
||||||
|
text: 'น้อยลงในความเป็นทั่วไป มากขึ้นในข้อกำหนดเชิงปฏิบัติการ (กฎระเบียบ เวิร์กโฟลว์ การตรวจสอบ) ที่ฝังตั้งแต่การออกแบบ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'คุณภาพและความปลอดภัยเป็นลำดับแรก ไม่ใช่คำพูดสวยหรู',
|
||||||
|
text: 'วิศวกรรม การทดสอบ และโครงสร้างพื้นฐานที่สอดคล้องกับการสัมผัสข้อมูลที่ละเอียดอ่อน',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'การสนับสนุนและการพัฒนาต่อเนื่อง',
|
||||||
|
text: 'ความสัมพันธ์กับลูกค้าไม่จบที่การสมัครสมาชิก: การบำรุงรักษาเชิงวิวัฒนาการ ความช่วยเหลือ และขั้นตอนที่ชัดเจนเมื่อมีเหตุฉุกเฉินในการดำเนินงานตามที่จำเป็น',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'การสื่อสารที่โปร่งใส',
|
||||||
|
text: 'เอกสาร ประกาศความเป็นส่วนตัว (เมื่อกฎหมายกำหนด) และความคาดหวังร่วมกันที่ชัดเจน เพื่อไม่ให้ออนบอร์ดดิ้งเป็นอุปสรรคแรก',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ทีมและวัฒนธรรม',
|
||||||
|
paragraphs: [
|
||||||
|
'เราเป็นนักพัฒนา ผู้ดูแลผลิตภัณฑ์ และผู้เชี่ยวชาญโดเมนในโมเดลแบบกระจาย: เขตเวลาต่างกันและภาษาที่ร่วมกัน เป้าหมายผลิตภัณฑ์เดียว เราเคลื่อนไหวด้วยการทำซ้ำอย่างรวดเร็วเมื่อต้องการนวัตกรรม และด้วยการกำกับดูแลเมื่อการเปลี่ยนแปลงกระทบการตรวจสอบ ความสอดคล้อง หรือความต่อเนื่องของบริการ ความหลากหลายของกลุ่ม (ทักษะ บริบท) เป็นข้อได้เปรียบในการแข่งขันสำหรับเรา: ลดสมมติฐานโดยนัยและบังคับให้ทำให้ชัดเจนว่าซอฟต์แวร์สัญญาอะไร และกับใคร',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ความมุ่งมั่นในระยะยาว',
|
||||||
|
paragraphs: [
|
||||||
|
'ภาคส่วนที่เลือกเราไม่ได้มองหาต้นแบบ แต่เป็นเส้นทางที่ยั่งยืน: การปรับให้สอดคล้องกับกฎระเบียบ การนำไปใช้ในทีม และการสนับสนุนเมื่อมีสิ่งที่ไม่ทำงาน เราสร้างและดูแลผลิตภัณฑ์ด้วยมุมมองนั้น เพราะในวิชาชีพของท่าน ความต่อเนื่องไม่ใช่ความสะดวก — เป็นข้อกำหนด',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAbout = (locale: SupportedLocale) => aboutByLocale[locale];
|
||||||
2475
src/data/allegati-codice-etico.ts
Normal file
2475
src/data/allegati-codice-etico.ts
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,8 @@
|
|||||||
* Consenso cookie — NexStudio CMP (dimostrativo / produzione light).
|
* Consenso cookie — NexStudio CMP (dimostrativo / produzione light).
|
||||||
* Allineare `policyVersion` quando cambiano categorie o testi legali rilevanti.
|
* Allineare `policyVersion` quando cambiano categorie o testi legali rilevanti.
|
||||||
*/
|
*/
|
||||||
|
import type { SupportedLocale } from './home/navigation';
|
||||||
|
|
||||||
export const cookieConsentMeta = {
|
export const cookieConsentMeta = {
|
||||||
storageKey: 'nexstudio.cookieConsent',
|
storageKey: 'nexstudio.cookieConsent',
|
||||||
policyVersion: '1.0',
|
policyVersion: '1.0',
|
||||||
@ -13,33 +15,153 @@ export type CookieCategoryId =
|
|||||||
| 'analytics'
|
| 'analytics'
|
||||||
| 'marketing';
|
| 'marketing';
|
||||||
|
|
||||||
export const cookieCategories = [
|
const cookieCategoriesByLocale = {
|
||||||
{
|
it: [
|
||||||
id: 'necessary' as const,
|
{
|
||||||
label: 'Strettamente necessari',
|
id: 'necessary' as const,
|
||||||
description:
|
label: 'Strettamente necessari',
|
||||||
'Memorizzazione delle preferenze di consenso, sicurezza e funzioni indispensabili al sito. Non possono essere disattivati dal pannello.',
|
description:
|
||||||
required: true,
|
'Memorizzazione delle preferenze di consenso, sicurezza e funzioni indispensabili al sito. Non possono essere disattivati dal pannello.',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'preferences' as const,
|
||||||
|
label: 'Preferenze e funzionalità',
|
||||||
|
description:
|
||||||
|
'Ricordare impostazioni opzionali (es. lingua, tema) se introdotte in futuro.',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'analytics' as const,
|
||||||
|
label: 'Misurazione e statistica',
|
||||||
|
description:
|
||||||
|
'Cookie o SDK per capire come viene usato il sito (es. analytics), solo se attivati dal codice dopo consenso.',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'marketing' as const,
|
||||||
|
label: 'Marketing e profilazione leggera',
|
||||||
|
description:
|
||||||
|
'Strumenti pubblicitari, remarketing o pixel social, solo se collegati e dopo consenso esplicito.',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
en: [
|
||||||
|
{
|
||||||
|
id: 'necessary' as const,
|
||||||
|
label: 'Strictly necessary',
|
||||||
|
description:
|
||||||
|
'Storage of consent preferences, security, and essential site functions. These cannot be disabled from the panel.',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'preferences' as const,
|
||||||
|
label: 'Preferences and functionality',
|
||||||
|
description:
|
||||||
|
'Remember optional settings (e.g. language, theme) if introduced in the future.',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'analytics' as const,
|
||||||
|
label: 'Measurement and statistics',
|
||||||
|
description:
|
||||||
|
'Cookies or SDKs to understand how the site is used (e.g. analytics), only if activated by code after consent.',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'marketing' as const,
|
||||||
|
label: 'Marketing and light profiling',
|
||||||
|
description:
|
||||||
|
'Advertising tools, remarketing, or social pixels, only if connected and after explicit consent.',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
th: [
|
||||||
|
{
|
||||||
|
id: 'necessary' as const,
|
||||||
|
label: 'จำเป็นอย่างยิ่ง',
|
||||||
|
description:
|
||||||
|
'จัดเก็บการตั้งค่าความยินยอม ความปลอดภัย และฟังก์ชันที่จำเป็นต่อเว็บไซต์ ไม่สามารถปิดได้จากแผงควบคุม',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'preferences' as const,
|
||||||
|
label: 'การตั้งค่าและฟังก์ชัน',
|
||||||
|
description:
|
||||||
|
'จดจำการตั้งค่าเพิ่มเติม (เช่น ภาษา ธีม) หากมีการเพิ่มในอนาคต',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'analytics' as const,
|
||||||
|
label: 'การวัดผลและสถิติ',
|
||||||
|
description:
|
||||||
|
'คุกกี้หรือ SDK เพื่อทำความเข้าใจการใช้งานเว็บไซต์ (เช่น analytics) จะเปิดใช้หลังได้รับความยินยอมเท่านั้น',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'marketing' as const,
|
||||||
|
label: 'การตลาดและการทำโปรไฟล์แบบเบา',
|
||||||
|
description:
|
||||||
|
'เครื่องมือโฆษณา remarketing หรือพิกเซลโซเชียล เฉพาะเมื่อเชื่อมต่อแล้วและหลังได้รับความยินยอมอย่างชัดเจน',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const cookieConsentUiByLocale = {
|
||||||
|
it: {
|
||||||
|
bannerTitle: 'Utilizziamo cookie e memorizzazione locale',
|
||||||
|
bannerDescBefore:
|
||||||
|
'Seleziona le categorie che preferisci. I necessari restano attivi per registrare la tua scelta. Dettagli nella',
|
||||||
|
bannerPolicyLinkLabel: 'informativa cookie',
|
||||||
|
rejectLabel: 'Rifiuta non essenziali',
|
||||||
|
customizeLabel: 'Personalizza',
|
||||||
|
acceptLabel: 'Accetta tutto',
|
||||||
|
dialogTitle: 'Preferenze cookie',
|
||||||
|
dialogIntroBefore: 'Versione informativa v',
|
||||||
|
dialogIntroAfter:
|
||||||
|
'. Puoi modificare le scelte in qualsiasi momento dal footer.',
|
||||||
|
alwaysOnLabel: 'Sempre attivi',
|
||||||
|
cancelLabel: 'Chiudi',
|
||||||
|
saveLabel: 'Salva preferenze',
|
||||||
},
|
},
|
||||||
{
|
en: {
|
||||||
id: 'preferences' as const,
|
bannerTitle: 'We use cookies and local storage',
|
||||||
label: 'Preferenze e funzionalità',
|
bannerDescBefore:
|
||||||
description:
|
'Choose the categories you prefer. Necessary cookies stay active to record your choice. Details in the',
|
||||||
'Ricordare impostazioni opzionali (es. lingua, tema) se introdotte in futuro.',
|
bannerPolicyLinkLabel: 'cookie policy',
|
||||||
required: false,
|
rejectLabel: 'Reject non-essential',
|
||||||
|
customizeLabel: 'Customize',
|
||||||
|
acceptLabel: 'Accept all',
|
||||||
|
dialogTitle: 'Cookie preferences',
|
||||||
|
dialogIntroBefore: 'Policy version v',
|
||||||
|
dialogIntroAfter: '. You can change your choices anytime from the footer.',
|
||||||
|
alwaysOnLabel: 'Always on',
|
||||||
|
cancelLabel: 'Close',
|
||||||
|
saveLabel: 'Save preferences',
|
||||||
},
|
},
|
||||||
{
|
th: {
|
||||||
id: 'analytics' as const,
|
bannerTitle: 'เราใช้คุกกี้และการจัดเก็บในเครื่อง',
|
||||||
label: 'Misurazione e statistica',
|
bannerDescBefore:
|
||||||
description:
|
'เลือกหมวดหมู่ที่คุณต้องการ คุกกี้ที่จำเป็นยังคงเปิดอยู่เพื่อบันทึกทางเลือกของคุณ รายละเอียดอยู่ใน',
|
||||||
'Cookie o SDK per capire come viene usato il sito (es. analytics), solo se attivati dal codice dopo consenso.',
|
bannerPolicyLinkLabel: 'นโยบายคุกกี้',
|
||||||
required: false,
|
rejectLabel: 'ปฏิเสธที่ไม่จำเป็น',
|
||||||
|
customizeLabel: 'ปรับแต่ง',
|
||||||
|
acceptLabel: 'ยอมรับทั้งหมด',
|
||||||
|
dialogTitle: 'การตั้งค่าคุกกี้',
|
||||||
|
dialogIntroBefore: 'เวอร์ชันนโยบาย v',
|
||||||
|
dialogIntroAfter: ' คุณสามารถเปลี่ยนตัวเลือกได้ตลอดเวลาจากส่วนท้ายเว็บไซต์',
|
||||||
|
alwaysOnLabel: 'เปิดเสมอ',
|
||||||
|
cancelLabel: 'ปิด',
|
||||||
|
saveLabel: 'บันทึกการตั้งค่า',
|
||||||
},
|
},
|
||||||
{
|
} as const;
|
||||||
id: 'marketing' as const,
|
|
||||||
label: 'Marketing e profilazione leggera',
|
export const getCookieCategories = (locale: SupportedLocale) =>
|
||||||
description:
|
cookieCategoriesByLocale[locale];
|
||||||
'Strumenti pubblicitari, remarketing o pixel social, solo se collegati e dopo consenso esplicito.',
|
|
||||||
required: false,
|
export const getCookieConsentUi = (locale: SupportedLocale) =>
|
||||||
},
|
cookieConsentUiByLocale[locale];
|
||||||
] as const;
|
|
||||||
|
/** Retro-compatibilità: fallback italiano. */
|
||||||
|
export const cookieCategories = cookieCategoriesByLocale.it;
|
||||||
|
|||||||
829
src/data/cookies.ts
Normal file
829
src/data/cookies.ts
Normal file
@ -0,0 +1,829 @@
|
|||||||
|
/**
|
||||||
|
* Cookie Policy del portale NexStudio.
|
||||||
|
* Centralizza i contenuti di /cookies per revisioni legali e localizzazione IT/EN/TH.
|
||||||
|
*/
|
||||||
|
import type { SupportedLocale } from './home/navigation';
|
||||||
|
|
||||||
|
export type CookiesRichPart =
|
||||||
|
| { t: 'text'; v: string }
|
||||||
|
| { t: 'link'; href: string; label: string; external?: boolean }
|
||||||
|
| { t: 'em'; v: string }
|
||||||
|
| { t: 'strong'; v: string };
|
||||||
|
|
||||||
|
export type CookiesDefinition = {
|
||||||
|
term: string;
|
||||||
|
definition: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CookiesSubsection = {
|
||||||
|
title: string;
|
||||||
|
purposeLabel?: string;
|
||||||
|
purpose?: string;
|
||||||
|
legalBasisLabel?: string;
|
||||||
|
legalBasis?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CookiesBullet =
|
||||||
|
| string
|
||||||
|
| { strong: string; after?: string }
|
||||||
|
| { before: string; strong: string; after?: string };
|
||||||
|
|
||||||
|
export type CookiesSection = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
paragraphs?: readonly string[];
|
||||||
|
richParagraphs?: readonly (readonly CookiesRichPart[])[];
|
||||||
|
definitions?: readonly CookiesDefinition[];
|
||||||
|
subsections?: readonly CookiesSubsection[];
|
||||||
|
bullets?: readonly CookiesBullet[];
|
||||||
|
paragraphsAfterBullets?: readonly (string | readonly CookiesRichPart[])[];
|
||||||
|
table?: {
|
||||||
|
headers: readonly string[];
|
||||||
|
emptyNote: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type CookiesLocaleBundle = {
|
||||||
|
document: { version: string; inVigoreDal: string };
|
||||||
|
riferimenti: readonly { label: string; href: string }[];
|
||||||
|
sections: readonly CookiesSection[];
|
||||||
|
page: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
heading: string;
|
||||||
|
lead: string;
|
||||||
|
versionLabel: string;
|
||||||
|
inForceFromLabel: string;
|
||||||
|
consentVersionLabel: string;
|
||||||
|
relatedDocsLabel: string;
|
||||||
|
indexLabel: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const cookiesByLocale: Record<SupportedLocale, CookiesLocaleBundle> = {
|
||||||
|
it: {
|
||||||
|
document: {
|
||||||
|
version: '1.1',
|
||||||
|
inVigoreDal: '[DATA]',
|
||||||
|
},
|
||||||
|
riferimenti: [
|
||||||
|
{ label: 'Informativa privacy', href: '/privacy' },
|
||||||
|
{ label: "Condizioni d'uso", href: '/terms' },
|
||||||
|
{ label: 'Diritti GDPR', href: '/gdpr' },
|
||||||
|
],
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
id: 'premessa',
|
||||||
|
title: '1. Premessa',
|
||||||
|
richParagraphs: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
t: 'text',
|
||||||
|
v: 'Questa Cookie Policy descrive le tipologie di cookie e tecnologie simili utilizzate sul sito web di NexStudio S.r.l. (di seguito «NexStudio», «noi»), con sede legale in Italia, [INDIRIZZO_COMPLETO], e spiega come gestire le preferenze. Per il trattamento dei dati personali e per i diritti degli interessati si rimanda all’',
|
||||||
|
},
|
||||||
|
{ t: 'link', href: '/privacy', label: 'Informativa privacy' },
|
||||||
|
{ t: 'text', v: '.' },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
t: 'text',
|
||||||
|
v: 'Per le cookie policy dei prodotti SaaS LexAura e MediAura, consultare le rispettive pagine sui portali dedicati.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'definizioni',
|
||||||
|
title: '2. Definizioni',
|
||||||
|
definitions: [
|
||||||
|
{
|
||||||
|
term: 'Cookie',
|
||||||
|
definition:
|
||||||
|
'piccolo file di testo inviato dal sito al browser e memorizzato sul dispositivo, re-inviato al sito alle visite successive.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
term: 'Cookie di prima parte',
|
||||||
|
definition: 'impostati dal dominio del sito che l’utente visita.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
term: 'Cookie di terze parti',
|
||||||
|
definition:
|
||||||
|
'impostati da domini diversi (fornitori esterni, embed, CDN, strumenti di misura).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
term: 'Tecnologie simili',
|
||||||
|
definition:
|
||||||
|
'web beacon, pixel, local storage, e in generale identificatori lato client.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'finalita',
|
||||||
|
title: '3. Finalità dei cookie e categorie',
|
||||||
|
subsections: [
|
||||||
|
{
|
||||||
|
title: '3.1 Cookie strettamente necessari (essenziali)',
|
||||||
|
purposeLabel: 'Finalità',
|
||||||
|
purpose:
|
||||||
|
'navigazione, sicurezza, gestione sessione, memorizzazione delle preferenze di consenso.',
|
||||||
|
legalBasisLabel: 'Base giuridica',
|
||||||
|
legalBasis:
|
||||||
|
'legittimo interesse del titolare (Art. 6.1.f GDPR) a garantire il funzionamento del servizio richiesto dall’utente.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '3.2 Cookie di preferenza / funzionali',
|
||||||
|
purposeLabel: 'Finalità',
|
||||||
|
purpose: 'lingua, regione, tema o altre impostazioni di interfaccia.',
|
||||||
|
legalBasisLabel: 'Base giuridica',
|
||||||
|
legalBasis: 'consenso (Art. 6.1.a GDPR).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '3.3 Cookie di performance / analytics',
|
||||||
|
purposeLabel: 'Finalità',
|
||||||
|
purpose: 'statistiche aggregate su visite e percorsi per migliorare il sito.',
|
||||||
|
legalBasisLabel: 'Base giuridica',
|
||||||
|
legalBasis: 'consenso (Art. 6.1.a GDPR).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '3.4 Cookie per advertising e targeting',
|
||||||
|
purposeLabel: 'Finalità',
|
||||||
|
purpose: 'pubblicità mirata, remarketing, misura delle campagne.',
|
||||||
|
legalBasisLabel: 'Base giuridica',
|
||||||
|
legalBasis: 'consenso esplicito.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '3.5 Cookie di terze parti e integrazioni social',
|
||||||
|
purposeLabel: 'Finalità',
|
||||||
|
purpose:
|
||||||
|
'video incorporati, pulsanti social, mappe, chat di terze parti. Il trattamento da parte di terzi è regolato dalle rispettive policy.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'elenco',
|
||||||
|
title: '4. Elenco dei cookie utilizzati',
|
||||||
|
paragraphs: [
|
||||||
|
'Elenco in appendice tecnica (da mantenere aggiornato con scansione periodica).',
|
||||||
|
],
|
||||||
|
table: {
|
||||||
|
headers: ['Nome', 'Provider', 'Categoria', 'Durata', 'Tipo', 'Base giuridica'],
|
||||||
|
emptyNote: 'Da compilare dopo scansione del sito in staging e produzione.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'base-giuridica',
|
||||||
|
title: '5. Base giuridica',
|
||||||
|
bullets: [
|
||||||
|
'Cookie tecnici essenziali: legittimo interesse del titolare (Art. 6.1.f GDPR).',
|
||||||
|
'Cookie analitici e di marketing: consenso libero, informato e revocabile (Art. 6.1.a GDPR).',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'consenso',
|
||||||
|
title: '6. Modalità di raccolta del consenso e registro',
|
||||||
|
paragraphs: [
|
||||||
|
'Il sito utilizza una CMP first-party. Al primo accesso viene mostrato un banner con:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
{ strong: 'Rifiuta non essenziali' },
|
||||||
|
{ strong: 'Personalizza', after: ' (apertura pannello categorie)' },
|
||||||
|
{ strong: 'Accetta tutto' },
|
||||||
|
],
|
||||||
|
paragraphsAfterBullets: [
|
||||||
|
[
|
||||||
|
{ t: 'text', v: 'La prova della scelta è memorizzata nel browser in ' },
|
||||||
|
{ t: 'em', v: 'local storage' },
|
||||||
|
{ t: 'text', v: '.' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'revoca',
|
||||||
|
title: '7. Gestione e revoca del consenso',
|
||||||
|
bullets: [
|
||||||
|
{
|
||||||
|
before: 'Tramite il banner o il pannello ',
|
||||||
|
strong: 'Personalizza',
|
||||||
|
after: ' al primo accesso.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
before: 'In qualsiasi momento tramite ',
|
||||||
|
strong: 'Impostazioni cookie',
|
||||||
|
after: ' nel footer del sito.',
|
||||||
|
},
|
||||||
|
'Cancellando i dati locali del sito dalle impostazioni del browser.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'browser',
|
||||||
|
title: '8. Disabilitazione via browser e impatto',
|
||||||
|
paragraphs: [
|
||||||
|
'È possibile bloccare o cancellare i cookie dalle impostazioni del browser (Chrome, Firefox, Safari, Edge e altri).',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'terze-parti',
|
||||||
|
title: '9. Cookie di terze parti e responsabilità',
|
||||||
|
paragraphs: [
|
||||||
|
'Il sito può integrare strumenti di terze parti (analytics, CDN, social). Le terze parti possono impostare cookie propri e trattare dati secondo le loro policy.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'retention',
|
||||||
|
title: '10. Conservazione e criteri di retention',
|
||||||
|
paragraphs: [
|
||||||
|
'Le durate dei singoli cookie seguono le tabelle pubblicate e le policy dei fornitori. Cookie di consenso: 365 giorni, rinnovabili. Log di sicurezza: massimo 30 giorni.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trasferimenti',
|
||||||
|
title: '11. Trasferimenti internazionali',
|
||||||
|
paragraphs: [
|
||||||
|
'Alcuni fornitori possono trattare dati in Paesi terzi (es. Stati Uniti). Ove applicabile si adottano garanzie come clausole contrattuali standard, decisioni di adeguatezza o altre basi previste dalla legge.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'minori',
|
||||||
|
title: '12. Minori età',
|
||||||
|
paragraphs: ['Il sito non è rivolto alla raccolta intenzionale di dati di minori.'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sicurezza',
|
||||||
|
title: '13. Misure di sicurezza',
|
||||||
|
paragraphs: [
|
||||||
|
'Si applicano misure tecniche e organizzative appropriate: trasmissione su TLS, accesso limitato ai log, principio di minimizzazione.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'advertising',
|
||||||
|
title: '14. Consenso per scopi pubblicitari e profiling',
|
||||||
|
paragraphs: [
|
||||||
|
'Profilazione e pubblicità comportano consenso esplicito e tracciabilità della scelta.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'effetti',
|
||||||
|
title: '15. Effetti di rifiuto o accettazione',
|
||||||
|
paragraphs: [
|
||||||
|
'Accettando le categorie opzionali si abilitano preferenze avanzate, analytics e funzioni di marketing. Rifiutando i non essenziali il sito resta navigabile.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'modifiche',
|
||||||
|
title: '16. Modifiche alla Cookie Policy',
|
||||||
|
paragraphs: [
|
||||||
|
'La Policy può essere aggiornata quando cambiano cookie, fornitori o norme. Sarà indicata la data di revisione.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'diritti',
|
||||||
|
title: '17. Diritti degli interessati e contatti',
|
||||||
|
richParagraphs: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
t: 'text',
|
||||||
|
v: 'Diritti: accesso, rettifica, cancellazione, limitazione, portabilità, opposizione e revoca del consenso. Per esercitarli: ',
|
||||||
|
},
|
||||||
|
{ t: 'link', href: 'mailto:privacy@nexstudio.com', label: 'privacy@nexstudio.com' },
|
||||||
|
{
|
||||||
|
t: 'text',
|
||||||
|
v: ' e tramite il form su nexstudio.ai. Autorità di controllo: Garante per la protezione dei dati personali (',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
t: 'link',
|
||||||
|
href: 'https://www.garanteprivacy.it',
|
||||||
|
label: 'www.garanteprivacy.it',
|
||||||
|
external: true,
|
||||||
|
},
|
||||||
|
{ t: 'text', v: ').' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
page: {
|
||||||
|
title: 'Cookie Policy — NexStudio',
|
||||||
|
description:
|
||||||
|
'Cookie Policy NexStudio S.r.l.: categorie, base giuridica, consenso, terze parti, diritti e contatti. Sede legale in Italia.',
|
||||||
|
heading: 'Cookie Policy',
|
||||||
|
lead:
|
||||||
|
'Titolare del trattamento: NexStudio S.r.l., con sede legale in Italia, [INDIRIZZO_COMPLETO].',
|
||||||
|
versionLabel: 'Versione',
|
||||||
|
inForceFromLabel: 'In vigore dal',
|
||||||
|
consentVersionLabel: 'Versione tecnica consenso',
|
||||||
|
relatedDocsLabel: 'Documenti connessi',
|
||||||
|
indexLabel: 'Indice',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
document: {
|
||||||
|
version: '1.1',
|
||||||
|
inVigoreDal: '[DATA]',
|
||||||
|
},
|
||||||
|
riferimenti: [
|
||||||
|
{ label: 'Privacy notice', href: '/privacy' },
|
||||||
|
{ label: 'Terms of use', href: '/terms' },
|
||||||
|
{ label: 'GDPR rights', href: '/gdpr' },
|
||||||
|
],
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
id: 'premessa',
|
||||||
|
title: '1. Introduction',
|
||||||
|
richParagraphs: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
t: 'text',
|
||||||
|
v: 'This Cookie Policy describes the types of cookies and similar technologies used on the website of NexStudio S.r.l. (hereinafter “NexStudio”, “we”), with registered office in Italy, [INDIRIZZO_COMPLETO], and explains how to manage preferences. For personal data processing and data subject rights, please refer to the ',
|
||||||
|
},
|
||||||
|
{ t: 'link', href: '/privacy', label: 'Privacy notice' },
|
||||||
|
{ t: 'text', v: '.' },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
t: 'text',
|
||||||
|
v: 'For the cookie policies of the LexAura and MediAura SaaS products, please consult the dedicated pages on the respective portals.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'definizioni',
|
||||||
|
title: '2. Definitions',
|
||||||
|
definitions: [
|
||||||
|
{
|
||||||
|
term: 'Cookie',
|
||||||
|
definition:
|
||||||
|
'a small text file sent by the site to the browser and stored on the device, then sent back to the site on subsequent visits.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
term: 'First-party cookie',
|
||||||
|
definition: 'set by the domain of the site the user is visiting.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
term: 'Third-party cookie',
|
||||||
|
definition:
|
||||||
|
'set by different domains (external providers, embeds, CDNs, measurement tools).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
term: 'Similar technologies',
|
||||||
|
definition:
|
||||||
|
'web beacons, pixels, local storage, and client-side identifiers in general.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'finalita',
|
||||||
|
title: '3. Cookie purposes and categories',
|
||||||
|
subsections: [
|
||||||
|
{
|
||||||
|
title: '3.1 Strictly necessary (essential) cookies',
|
||||||
|
purposeLabel: 'Purpose',
|
||||||
|
purpose:
|
||||||
|
'navigation, security, session management, storage of consent preferences.',
|
||||||
|
legalBasisLabel: 'Legal basis',
|
||||||
|
legalBasis:
|
||||||
|
'legitimate interest of the controller (Art. 6.1.f GDPR) in ensuring the operation of the service requested by the user.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '3.2 Preference / functional cookies',
|
||||||
|
purposeLabel: 'Purpose',
|
||||||
|
purpose: 'language, region, theme, or other interface settings.',
|
||||||
|
legalBasisLabel: 'Legal basis',
|
||||||
|
legalBasis: 'consent (Art. 6.1.a GDPR).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '3.3 Performance / analytics cookies',
|
||||||
|
purposeLabel: 'Purpose',
|
||||||
|
purpose: 'aggregate statistics on visits and journeys to improve the site.',
|
||||||
|
legalBasisLabel: 'Legal basis',
|
||||||
|
legalBasis: 'consent (Art. 6.1.a GDPR).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '3.4 Advertising and targeting cookies',
|
||||||
|
purposeLabel: 'Purpose',
|
||||||
|
purpose: 'targeted advertising, remarketing, campaign measurement.',
|
||||||
|
legalBasisLabel: 'Legal basis',
|
||||||
|
legalBasis: 'explicit consent.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '3.5 Third-party cookies and social integrations',
|
||||||
|
purposeLabel: 'Purpose',
|
||||||
|
purpose:
|
||||||
|
'embedded videos, social buttons, maps, third-party chat. Processing by third parties is governed by their respective policies.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'elenco',
|
||||||
|
title: '4. List of cookies used',
|
||||||
|
paragraphs: [
|
||||||
|
'Technical appendix list (to be kept up to date with periodic scanning).',
|
||||||
|
],
|
||||||
|
table: {
|
||||||
|
headers: ['Name', 'Provider', 'Category', 'Duration', 'Type', 'Legal basis'],
|
||||||
|
emptyNote: 'To be completed after scanning the site in staging and production.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'base-giuridica',
|
||||||
|
title: '5. Legal basis',
|
||||||
|
bullets: [
|
||||||
|
'Essential technical cookies: legitimate interest of the controller (Art. 6.1.f GDPR).',
|
||||||
|
'Analytics and marketing cookies: free, informed, and revocable consent (Art. 6.1.a GDPR).',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'consenso',
|
||||||
|
title: '6. Consent collection method and record',
|
||||||
|
paragraphs: [
|
||||||
|
'The site uses a first-party CMP. On first visit a banner is shown with:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
{ strong: 'Reject non-essential' },
|
||||||
|
{ strong: 'Customize', after: ' (opens category panel)' },
|
||||||
|
{ strong: 'Accept all' },
|
||||||
|
],
|
||||||
|
paragraphsAfterBullets: [
|
||||||
|
[
|
||||||
|
{ t: 'text', v: 'Proof of the choice is stored in the browser in ' },
|
||||||
|
{ t: 'em', v: 'local storage' },
|
||||||
|
{ t: 'text', v: '.' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'revoca',
|
||||||
|
title: '7. Managing and withdrawing consent',
|
||||||
|
bullets: [
|
||||||
|
{
|
||||||
|
before: 'Via the banner or the ',
|
||||||
|
strong: 'Customize',
|
||||||
|
after: ' panel on first visit.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
before: 'At any time via ',
|
||||||
|
strong: 'Cookie settings',
|
||||||
|
after: ' in the site footer.',
|
||||||
|
},
|
||||||
|
'By clearing the site’s local data from browser settings.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'browser',
|
||||||
|
title: '8. Browser disabling and impact',
|
||||||
|
paragraphs: [
|
||||||
|
'You can block or delete cookies from browser settings (Chrome, Firefox, Safari, Edge, and others).',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'terze-parti',
|
||||||
|
title: '9. Third-party cookies and responsibility',
|
||||||
|
paragraphs: [
|
||||||
|
'The site may integrate third-party tools (analytics, CDN, social). Third parties may set their own cookies and process data under their policies.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'retention',
|
||||||
|
title: '10. Retention and retention criteria',
|
||||||
|
paragraphs: [
|
||||||
|
'Individual cookie durations follow the published tables and provider policies. Consent cookies: 365 days, renewable. Security logs: maximum 30 days.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trasferimenti',
|
||||||
|
title: '11. International transfers',
|
||||||
|
paragraphs: [
|
||||||
|
'Some providers may process data in third countries (e.g. the United States). Where applicable, safeguards such as standard contractual clauses, adequacy decisions, or other bases provided by law are adopted.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'minori',
|
||||||
|
title: '12. Minors',
|
||||||
|
paragraphs: [
|
||||||
|
'The site is not directed at the intentional collection of minors’ data.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sicurezza',
|
||||||
|
title: '13. Security measures',
|
||||||
|
paragraphs: [
|
||||||
|
'Appropriate technical and organisational measures apply: TLS transmission, limited log access, minimisation principle.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'advertising',
|
||||||
|
title: '14. Consent for advertising and profiling',
|
||||||
|
paragraphs: [
|
||||||
|
'Profiling and advertising require explicit consent and traceability of the choice.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'effetti',
|
||||||
|
title: '15. Effects of refusal or acceptance',
|
||||||
|
paragraphs: [
|
||||||
|
'Accepting optional categories enables advanced preferences, analytics, and marketing features. Rejecting non-essential cookies keeps the site navigable.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'modifiche',
|
||||||
|
title: '16. Changes to the Cookie Policy',
|
||||||
|
paragraphs: [
|
||||||
|
'The Policy may be updated when cookies, providers, or rules change. The revision date will be indicated.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'diritti',
|
||||||
|
title: '17. Data subject rights and contacts',
|
||||||
|
richParagraphs: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
t: 'text',
|
||||||
|
v: 'Rights: access, rectification, erasure, restriction, portability, objection, and withdrawal of consent. To exercise them: ',
|
||||||
|
},
|
||||||
|
{ t: 'link', href: 'mailto:privacy@nexstudio.com', label: 'privacy@nexstudio.com' },
|
||||||
|
{
|
||||||
|
t: 'text',
|
||||||
|
v: ' and via the form on nexstudio.ai. Supervisory authority: Garante per la protezione dei dati personali (',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
t: 'link',
|
||||||
|
href: 'https://www.garanteprivacy.it',
|
||||||
|
label: 'www.garanteprivacy.it',
|
||||||
|
external: true,
|
||||||
|
},
|
||||||
|
{ t: 'text', v: ').' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
page: {
|
||||||
|
title: 'Cookie Policy — NexStudio',
|
||||||
|
description:
|
||||||
|
'NexStudio S.r.l. Cookie Policy: categories, legal basis, consent, third parties, rights, and contacts. Registered office in Italy.',
|
||||||
|
heading: 'Cookie Policy',
|
||||||
|
lead:
|
||||||
|
'Data controller: NexStudio S.r.l., with registered office in Italy, [INDIRIZZO_COMPLETO].',
|
||||||
|
versionLabel: 'Version',
|
||||||
|
inForceFromLabel: 'In force from',
|
||||||
|
consentVersionLabel: 'Technical consent version',
|
||||||
|
relatedDocsLabel: 'Related documents',
|
||||||
|
indexLabel: 'Index',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
document: {
|
||||||
|
version: '1.1',
|
||||||
|
inVigoreDal: '[DATA]',
|
||||||
|
},
|
||||||
|
riferimenti: [
|
||||||
|
{ label: 'ประกาศความเป็นส่วนตัว', href: '/privacy' },
|
||||||
|
{ label: 'เงื่อนไขการใช้งาน', href: '/terms' },
|
||||||
|
{ label: 'สิทธิ GDPR', href: '/gdpr' },
|
||||||
|
],
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
id: 'premessa',
|
||||||
|
title: '1. บทนำ',
|
||||||
|
richParagraphs: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
t: 'text',
|
||||||
|
v: 'นโยบายคุกกี้นี้อธิบายประเภทของคุกกี้และเทคโนโลยีที่คล้ายกันซึ่งใช้บนเว็บไซต์ของ NexStudio S.r.l. (ต่อไปนี้เรียกว่า «NexStudio» หรือ «เรา») สำนักงานจดทะเบียนในอิตาลี [INDIRIZZO_COMPLETO] และอธิบายวิธีจัดการการตั้งค่า สำหรับการประมวลผลข้อมูลส่วนบุคคลและสิทธิของเจ้าของข้อมูล โปรดดูที่ ',
|
||||||
|
},
|
||||||
|
{ t: 'link', href: '/privacy', label: 'ประกาศความเป็นส่วนตัว' },
|
||||||
|
{ t: 'text', v: '.' },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
t: 'text',
|
||||||
|
v: 'สำหรับนโยบายคุกกี้ของผลิตภัณฑ์ SaaS LexAura และ MediAura โปรดดูหน้าที่เกี่ยวข้องบนพอร์ทัลเฉพาะ',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'definizioni',
|
||||||
|
title: '2. คำจำกัดความ',
|
||||||
|
definitions: [
|
||||||
|
{
|
||||||
|
term: 'คุกกี้',
|
||||||
|
definition:
|
||||||
|
'ไฟล์ข้อความขนาดเล็กที่เว็บไซต์ส่งไปยังเบราว์เซอร์และเก็บไว้บนอุปกรณ์ แล้วส่งกลับไปยังเว็บไซต์ในการเข้าชมครั้งถัดไป',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
term: 'คุกกี้ฝั่งเว็บไซต์ (first-party)',
|
||||||
|
definition: 'ตั้งโดยโดเมนของเว็บไซต์ที่ผู้ใช้เข้าชม',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
term: 'คุกกี้ของบุคคลที่สาม',
|
||||||
|
definition:
|
||||||
|
'ตั้งโดยโดเมนอื่น (ผู้ให้บริการภายนอก สื่อฝัง CDN เครื่องมือวัดผล)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
term: 'เทคโนโลยีที่คล้ายกัน',
|
||||||
|
definition:
|
||||||
|
'web beacon, พิกเซล, local storage และตัวระบุฝั่งไคลเอนต์โดยทั่วไป',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'finalita',
|
||||||
|
title: '3. วัตถุประสงค์และหมวดหมู่ของคุกกี้',
|
||||||
|
subsections: [
|
||||||
|
{
|
||||||
|
title: '3.1 คุกกี้ที่จำเป็นอย่างเคร่งครัด (สำคัญ)',
|
||||||
|
purposeLabel: 'วัตถุประสงค์',
|
||||||
|
purpose:
|
||||||
|
'การนำทาง ความปลอดภัย การจัดการเซสชัน และการจัดเก็บการตั้งค่าความยินยอม',
|
||||||
|
legalBasisLabel: 'ฐานทางกฎหมาย',
|
||||||
|
legalBasis:
|
||||||
|
'ประโยชน์โดยชอบด้วยกฎหมายของผู้ควบคุม (มาตรา 6.1.f GDPR) เพื่อให้บริการที่ผู้ใช้ร้องขอทำงานได้',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '3.2 คุกกี้การตั้งค่า / ฟังก์ชัน',
|
||||||
|
purposeLabel: 'วัตถุประสงค์',
|
||||||
|
purpose: 'ภาษา ภูมิภาค ธีม หรือการตั้งค่าอินเทอร์เฟซอื่น ๆ',
|
||||||
|
legalBasisLabel: 'ฐานทางกฎหมาย',
|
||||||
|
legalBasis: 'ความยินยอม (มาตรา 6.1.a GDPR)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '3.3 คุกกี้ประสิทธิภาพ / การวิเคราะห์',
|
||||||
|
purposeLabel: 'วัตถุประสงค์',
|
||||||
|
purpose: 'สถิติรวมเกี่ยวกับการเข้าชมและเส้นทางเพื่อปรับปรุงเว็บไซต์',
|
||||||
|
legalBasisLabel: 'ฐานทางกฎหมาย',
|
||||||
|
legalBasis: 'ความยินยอม (มาตรา 6.1.a GDPR)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '3.4 คุกกี้โฆษณาและการกำหนดเป้าหมาย',
|
||||||
|
purposeLabel: 'วัตถุประสงค์',
|
||||||
|
purpose: 'โฆษณาที่ตรงเป้าหมาย การ remarketing และการวัดผลแคมเปญ',
|
||||||
|
legalBasisLabel: 'ฐานทางกฎหมาย',
|
||||||
|
legalBasis: 'ความยินยอมอย่างชัดแจ้ง',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '3.5 คุกกี้ของบุคคลที่สามและการผสานโซเชียล',
|
||||||
|
purposeLabel: 'วัตถุประสงค์',
|
||||||
|
purpose:
|
||||||
|
'วิดีโอฝัง ปุ่มโซเชียล แผนที่ แชทของบุคคลที่สาม การประมวลผลโดยบุคคลที่สามอยู่ภายใต้นโยบายของตน',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'elenco',
|
||||||
|
title: '4. รายการคุกกี้ที่ใช้',
|
||||||
|
paragraphs: [
|
||||||
|
'รายการในภาคผนวกทางเทคนิค (ต้องอัปเดตด้วยการสแกนเป็นระยะ)',
|
||||||
|
],
|
||||||
|
table: {
|
||||||
|
headers: ['ชื่อ', 'ผู้ให้บริการ', 'หมวดหมู่', 'ระยะเวลา', 'ประเภท', 'ฐานทางกฎหมาย'],
|
||||||
|
emptyNote: 'ต้องกรอกหลังสแกนเว็บไซต์ใน staging และ production',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'base-giuridica',
|
||||||
|
title: '5. ฐานทางกฎหมาย',
|
||||||
|
bullets: [
|
||||||
|
'คุกกี้ทางเทคนิคที่จำเป็น: ประโยชน์โดยชอบด้วยกฎหมายของผู้ควบคุม (มาตรา 6.1.f GDPR)',
|
||||||
|
'คุกกี้วิเคราะห์และการตลาด: ความยินยอมที่อิสระ มีข้อมูล และเพิกถอนได้ (มาตรา 6.1.a GDPR)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'consenso',
|
||||||
|
title: '6. วิธีการเก็บความยินยอมและการบันทึก',
|
||||||
|
paragraphs: [
|
||||||
|
'เว็บไซต์ใช้ CMP ของฝั่งเว็บไซต์เอง ในการเข้าชมครั้งแรกจะแสดงแบนเนอร์พร้อม:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
{ strong: 'ปฏิเสธที่ไม่จำเป็น' },
|
||||||
|
{ strong: 'ปรับแต่ง', after: ' (เปิดแผงหมวดหมู่)' },
|
||||||
|
{ strong: 'ยอมรับทั้งหมด' },
|
||||||
|
],
|
||||||
|
paragraphsAfterBullets: [
|
||||||
|
[
|
||||||
|
{ t: 'text', v: 'หลักฐานการเลือกถูกเก็บไว้ในเบราว์เซอร์ใน ' },
|
||||||
|
{ t: 'em', v: 'local storage' },
|
||||||
|
{ t: 'text', v: '.' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'revoca',
|
||||||
|
title: '7. การจัดการและการเพิกถอนความยินยอม',
|
||||||
|
bullets: [
|
||||||
|
{
|
||||||
|
before: 'ผ่านแบนเนอร์หรือแผง ',
|
||||||
|
strong: 'ปรับแต่ง',
|
||||||
|
after: ' ในการเข้าชมครั้งแรก',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
before: 'ได้ตลอดเวลาผ่าน ',
|
||||||
|
strong: 'การตั้งค่าคุกกี้',
|
||||||
|
after: ' ในส่วนท้ายของเว็บไซต์',
|
||||||
|
},
|
||||||
|
'โดยลบข้อมูลในเครื่องของเว็บไซต์จากการตั้งค่าเบราว์เซอร์',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'browser',
|
||||||
|
title: '8. การปิดใช้งานผ่านเบราว์เซอร์และผลกระทบ',
|
||||||
|
paragraphs: [
|
||||||
|
'สามารถบล็อกหรือลบคุกกี้จากการตั้งค่าเบราว์เซอร์ได้ (Chrome, Firefox, Safari, Edge และอื่น ๆ)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'terze-parti',
|
||||||
|
title: '9. คุกกี้ของบุคคลที่สามและความรับผิดชอบ',
|
||||||
|
paragraphs: [
|
||||||
|
'เว็บไซต์อาจผสานเครื่องมือของบุคคลที่สาม (analytics, CDN, โซเชียล) บุคคลที่สามอาจตั้งคุกกี้ของตนเองและประมวลผลข้อมูลตามนโยบายของตน',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'retention',
|
||||||
|
title: '10. การเก็บรักษาและเกณฑ์ retention',
|
||||||
|
paragraphs: [
|
||||||
|
'ระยะเวลาของคุกกี้แต่ละตัวเป็นไปตามตารางที่เผยแพร่และนโยบายของผู้ให้บริการ คุกกี้ความยินยอม: 365 วัน ต่ออายุได้ ล็อกความปลอดภัย: สูงสุด 30 วัน',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trasferimenti',
|
||||||
|
title: '11. การโอนข้อมูลระหว่างประเทศ',
|
||||||
|
paragraphs: [
|
||||||
|
'ผู้ให้บริการบางรายอาจประมวลผลข้อมูลในประเทศที่สาม (เช่น สหรัฐอเมริกา) เมื่อใช้ได้ จะใช้การรับประกัน เช่น ข้อสัญญามาตรฐาน การตัดสินใจว่าเพียงพอ หรือฐานอื่นตามกฎหมาย',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'minori',
|
||||||
|
title: '12. ผู้เยาว์',
|
||||||
|
paragraphs: [
|
||||||
|
'เว็บไซต์ไม่ได้มุ่งเป้าไปที่การเก็บข้อมูลของผู้เยาว์โดยเจตนา',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sicurezza',
|
||||||
|
title: '13. มาตรการความปลอดภัย',
|
||||||
|
paragraphs: [
|
||||||
|
'ใช้มาตรการทางเทคนิคและองค์กรที่เหมาะสม: การส่งผ่าน TLS การเข้าถึงล็อกอย่างจำกัด หลักการลดข้อมูลให้เหลือน้อยที่สุด',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'advertising',
|
||||||
|
title: '14. ความยินยอมเพื่อโฆษณาและการทำโปรไฟล์',
|
||||||
|
paragraphs: [
|
||||||
|
'การทำโปรไฟล์และโฆษณาต้องมีความยินยอมอย่างชัดแจ้งและความสามารถในการติดตามการเลือก',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'effetti',
|
||||||
|
title: '15. ผลของการปฏิเสธหรือการยอมรับ',
|
||||||
|
paragraphs: [
|
||||||
|
'การยอมรับหมวดหมู่ที่ไม่บังคับจะเปิดใช้การตั้งค่าขั้นสูง analytics และฟังก์ชันการตลาด การปฏิเสธคุกกี้ที่ไม่จำเป็นยังคงทำให้เว็บไซต์ใช้งานได้',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'modifiche',
|
||||||
|
title: '16. การเปลี่ยนแปลงนโยบายคุกกี้',
|
||||||
|
paragraphs: [
|
||||||
|
'นโยบายอาจได้รับการอัปเดตเมื่อคุกกี้ ผู้ให้บริการ หรือกฎระเบียบเปลี่ยน จะระบุวันที่แก้ไข',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'diritti',
|
||||||
|
title: '17. สิทธิของเจ้าของข้อมูลและการติดต่อ',
|
||||||
|
richParagraphs: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
t: 'text',
|
||||||
|
v: 'สิทธิ: เข้าถึง แก้ไข ลบ จำกัด โอนย้าย คัดค้าน และเพิกถอนความยินยอม เพื่อใช้สิทธิ: ',
|
||||||
|
},
|
||||||
|
{ t: 'link', href: 'mailto:privacy@nexstudio.com', label: 'privacy@nexstudio.com' },
|
||||||
|
{
|
||||||
|
t: 'text',
|
||||||
|
v: ' และผ่านแบบฟอร์มบน nexstudio.ai หน่วยงานกำกับดูแล: Garante per la protezione dei dati personali (',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
t: 'link',
|
||||||
|
href: 'https://www.garanteprivacy.it',
|
||||||
|
label: 'www.garanteprivacy.it',
|
||||||
|
external: true,
|
||||||
|
},
|
||||||
|
{ t: 'text', v: ')' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
page: {
|
||||||
|
title: 'นโยบายคุกกี้ — NexStudio',
|
||||||
|
description:
|
||||||
|
'นโยบายคุกกี้ของ NexStudio S.r.l.: หมวดหมู่ ฐานทางกฎหมาย ความยินยอม บุคคลที่สาม สิทธิ และการติดต่อ สำนักงานจดทะเบียนในอิตาลี',
|
||||||
|
heading: 'นโยบายคุกกี้',
|
||||||
|
lead:
|
||||||
|
'ผู้ควบคุมข้อมูล: NexStudio S.r.l. สำนักงานจดทะเบียนในอิตาลี [INDIRIZZO_COMPLETO]',
|
||||||
|
versionLabel: 'เวอร์ชัน',
|
||||||
|
inForceFromLabel: 'มีผลตั้งแต่',
|
||||||
|
consentVersionLabel: 'เวอร์ชันทางเทคนิคของความยินยอม',
|
||||||
|
relatedDocsLabel: 'เอกสารที่เกี่ยวข้อง',
|
||||||
|
indexLabel: 'สารบัญ',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCookies = (locale: SupportedLocale) => cookiesByLocale[locale];
|
||||||
95
src/data/dove-siamo.ts
Normal file
95
src/data/dove-siamo.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* Pagina Dove siamo / Where we are / ที่ตั้ง — contenuti localizzati.
|
||||||
|
*/
|
||||||
|
import type { SupportedLocale } from './home/navigation';
|
||||||
|
|
||||||
|
export type DoveSiamoSection = {
|
||||||
|
title: string;
|
||||||
|
paragraphs: readonly string[];
|
||||||
|
/** When true, render "A — B" with both sides bold. */
|
||||||
|
locationLine?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type DoveSiamoLocaleBundle = {
|
||||||
|
page: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
heading: string;
|
||||||
|
lead: string;
|
||||||
|
};
|
||||||
|
sections: readonly DoveSiamoSection[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const doveSiamoByLocale: Record<SupportedLocale, DoveSiamoLocaleBundle> = {
|
||||||
|
it: {
|
||||||
|
page: {
|
||||||
|
title: 'Dove siamo — NexStudio',
|
||||||
|
description:
|
||||||
|
'Sede operativa a Bangkok, Thailandia; fusi orari e lingue per lavorare con clienti in Europa e altre regioni.',
|
||||||
|
heading: 'Dove siamo',
|
||||||
|
lead:
|
||||||
|
'NexStudio ha base a Bangkok, in Thailandia. Sotto, come organizziamo fusi orari e lingue rispetto a clienti in Europa e oltre.',
|
||||||
|
},
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: 'Sede',
|
||||||
|
paragraphs: ['Thailandia — Bangkok'],
|
||||||
|
locationLine: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Fusi orari e lingue',
|
||||||
|
paragraphs: [
|
||||||
|
'Il sito e il supporto via web possono essere gestiti in italiano, inglese, tedesco, francese, spagnolo e tailandese, con orari da concordare in base al team e al fuso rispetto ai clienti in Europa e altre regioni.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
page: {
|
||||||
|
title: 'Where we are — NexStudio',
|
||||||
|
description:
|
||||||
|
'Operational base in Bangkok, Thailand; time zones and languages for working with clients in Europe and other regions.',
|
||||||
|
heading: 'Where we are',
|
||||||
|
lead:
|
||||||
|
'NexStudio is based in Bangkok, Thailand. Below, how we organize time zones and languages relative to clients in Europe and beyond.',
|
||||||
|
},
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: 'Location',
|
||||||
|
paragraphs: ['Thailand — Bangkok'],
|
||||||
|
locationLine: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Time zones and languages',
|
||||||
|
paragraphs: [
|
||||||
|
'The site and web support can be handled in Italian, English, German, French, Spanish, and Thai, with hours to be agreed based on the team and time zone relative to clients in Europe and other regions.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
page: {
|
||||||
|
title: 'ที่ตั้ง — NexStudio',
|
||||||
|
description:
|
||||||
|
'ฐานปฏิบัติการที่กรุงเทพฯ ประเทศไทย เขตเวลาและภาษาสำหรับการทำงานกับลูกค้าในยุโรปและภูมิภาคอื่น',
|
||||||
|
heading: 'ที่ตั้ง',
|
||||||
|
lead:
|
||||||
|
'NexStudio มีฐานที่กรุงเทพฯ ประเทศไทย ด้านล่างคือวิธีที่เราจัดเขตเวลาและภาษาเมื่อเทียบกับลูกค้าในยุโรปและที่อื่น',
|
||||||
|
},
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: 'สำนักงาน',
|
||||||
|
paragraphs: ['ประเทศไทย — กรุงเทพฯ'],
|
||||||
|
locationLine: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'เขตเวลาและภาษา',
|
||||||
|
paragraphs: [
|
||||||
|
'เว็บไซต์และการสนับสนุนผ่านเว็บสามารถจัดการเป็นภาษาอิตาลี อังกฤษ เยอรมัน ฝรั่งเศส สเปน และไทย โดยตกลงเวลาตามทีมและเขตเวลาเทียบกับลูกค้าในยุโรปและภูมิภาคอื่น',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDoveSiamo = (locale: SupportedLocale) => doveSiamoByLocale[locale];
|
||||||
396
src/data/gdpr.ts
396
src/data/gdpr.ts
@ -2,22 +2,7 @@
|
|||||||
* Pagina GDPR: diritti degli interessati e modalità operative.
|
* Pagina GDPR: diritti degli interessati e modalità operative.
|
||||||
* Documento di supporto alla privacy policy quadro del portale NexStudio.
|
* Documento di supporto alla privacy policy quadro del portale NexStudio.
|
||||||
*/
|
*/
|
||||||
export const gdprDocument = {
|
import type { SupportedLocale } from './home/navigation';
|
||||||
version: '1.1',
|
|
||||||
inVigoreDal: '[DATA]',
|
|
||||||
ultimoAggiornamento: '[DATA]',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const gdprCrossReferences = {
|
|
||||||
lead:
|
|
||||||
"Per LexAura e MediAura, i dettagli operativi del singolo prodotto sono indicati nelle rispettive informative privacy pubblicate sui portali SaaS dedicati.",
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const gdprRiferimenti: readonly { label: string; href: string }[] = [
|
|
||||||
{ label: 'Informativa privacy', href: '/privacy' },
|
|
||||||
{ label: 'Cookie policy', href: '/cookies' },
|
|
||||||
{ label: "Condizioni d'uso", href: '/terms' },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export type GdprSection = {
|
export type GdprSection = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -28,75 +13,316 @@ export type GdprSection = {
|
|||||||
paragraphsAfterBullets?: readonly string[];
|
paragraphsAfterBullets?: readonly string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const gdprSections: readonly GdprSection[] = [
|
const gdprByLocale = {
|
||||||
{
|
it: {
|
||||||
id: 'quadro',
|
document: {
|
||||||
title: '1. Quadro normativo e ambito',
|
version: '1.1',
|
||||||
paragraphs: [
|
inVigoreDal: '[DATA]',
|
||||||
"Il Regolamento (UE) 2016/679 (GDPR) tutela i diritti degli interessati e disciplina il trattamento dei dati personali. Questa pagina spiega come esercitare tali diritti verso NexStudio nel contesto del portale corporate e dei servizi collegati.",
|
ultimoAggiornamento: '[DATA]',
|
||||||
],
|
},
|
||||||
|
crossReferences: {
|
||||||
|
lead:
|
||||||
|
'Per LexAura e MediAura, i dettagli operativi del singolo prodotto sono indicati nelle rispettive informative privacy pubblicate sui portali SaaS dedicati.',
|
||||||
|
},
|
||||||
|
riferimenti: [
|
||||||
|
{ label: 'Informativa privacy', href: '/privacy' },
|
||||||
|
{ label: 'Cookie policy', href: '/cookies' },
|
||||||
|
{ label: "Condizioni d'uso", href: '/terms' },
|
||||||
|
] as const,
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
id: 'quadro',
|
||||||
|
title: '1. Quadro normativo e ambito',
|
||||||
|
paragraphs: [
|
||||||
|
'Il Regolamento (UE) 2016/679 (GDPR) tutela i diritti degli interessati e disciplina il trattamento dei dati personali. Questa pagina spiega come esercitare tali diritti verso NexStudio nel contesto del portale corporate e dei servizi collegati.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'diritti',
|
||||||
|
title: '2. Diritti riconosciuti agli interessati',
|
||||||
|
paragraphs: [
|
||||||
|
"Nei limiti previsti dalla normativa applicabile, l'interessato può esercitare i seguenti diritti:",
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'Accesso ai dati personali e ottenimento di copia in formato intelligibile;',
|
||||||
|
'Rettifica dei dati inesatti e integrazione dei dati incompleti;',
|
||||||
|
"Cancellazione dei dati (diritto all'oblio), quando ne ricorrono i presupposti;",
|
||||||
|
'Limitazione del trattamento in presenza delle condizioni previste dal GDPR;',
|
||||||
|
'Portabilità dei dati trattati con mezzi automatizzati sulla base di consenso o contratto;',
|
||||||
|
"Opposizione al trattamento fondato su legittimo interesse, inclusa l'opposizione a comunicazioni marketing;",
|
||||||
|
'Revoca del consenso in qualsiasi momento, senza pregiudicare la liceità del trattamento precedente;',
|
||||||
|
"Reclamo all'autorità di controllo competente.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'chi-contattare',
|
||||||
|
title: '3. Chi contattare per esercitare i diritti',
|
||||||
|
paragraphs: [],
|
||||||
|
bullets: [
|
||||||
|
'Email: privacy@nexstudio.com',
|
||||||
|
'Form: nexstudio.ai/contatti',
|
||||||
|
'Per trattamenti nei tenant SaaS (LexAura/MediAura): rivolgersi prima al cliente titolare (studio legale, struttura sanitaria). NexStudio supporta il cliente titolare secondo DPA e accordi contrattuali.',
|
||||||
|
],
|
||||||
|
paragraphsAfterBullets: [
|
||||||
|
'Autorità di controllo competente: Garante per la protezione dei dati personali (www.garanteprivacy.it) per i trattamenti soggetti al GDPR.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'identificazione',
|
||||||
|
title: "4. Verifica dell'identità e gestione della richiesta",
|
||||||
|
paragraphs: [
|
||||||
|
"Per tutelare riservatezza e sicurezza, NexStudio può richiedere informazioni minime aggiuntive necessarie a verificare l'identità del richiedente. Le richieste sono gestite con procedure tracciate e prese in carico nei tempi previsti dal GDPR; in caso di richieste complesse o multiple, i termini possono essere prorogati nei limiti consentiti dalla legge, con comunicazione motivata.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'limitazioni',
|
||||||
|
title: '5. Casi di limitazione o rigetto',
|
||||||
|
paragraphs: [
|
||||||
|
"L'esercizio dei diritti può essere limitato o non accolto quando sussistono obblighi legali prevalenti, esigenze di difesa in giudizio, tutela dei diritti di terzi o altre eccezioni previste dalla normativa. In tali casi NexStudio fornisce un riscontro motivato e indica i rimedi disponibili, incluso il diritto di proporre reclamo all'autorità competente.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trasferimenti',
|
||||||
|
title: '6. Trasferimenti internazionali e garanzie',
|
||||||
|
paragraphs: [
|
||||||
|
'Qualora il trattamento comporti trasferimenti extra SEE/UE, NexStudio adotta le misure richieste dalla normativa (es. clausole contrattuali standard e misure supplementari). I dati risiedono primariamente in UE via infrastruttura Cloudflare.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'documenti-prodotto',
|
||||||
|
title: '7. Coordinamento con le privacy dei SaaS',
|
||||||
|
paragraphs: [
|
||||||
|
'Le privacy policy dei portali SaaS LexAura e MediAura dettagliano, per ciascun prodotto, flussi dati, tempi di conservazione, ruoli privacy e canali dedicati. In caso di differenze tra documenti, prevalgono i documenti specifici del servizio e gli accordi contrattuali applicabili.',
|
||||||
|
'Per consultare le privacy dei prodotti:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'LexAura: vedere il portale dedicato',
|
||||||
|
'MediAura: mediaura.doctor/privacy',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const satisfies readonly GdprSection[],
|
||||||
|
page: {
|
||||||
|
title: 'GDPR — NexStudio',
|
||||||
|
description:
|
||||||
|
'Pagina GDPR NexStudio S.r.l.: diritti degli interessati, modalità operative, ruoli tra portale corporate e SaaS LexAura/MediAura, coordinamento con privacy dedicate.',
|
||||||
|
heading: 'GDPR e diritti degli interessati',
|
||||||
|
lead:
|
||||||
|
"Diritti degli interessati e modalità di esercizio ai sensi del Regolamento (UE) 2016/679, in coerenza con l'informativa privacy quadro di NexStudio S.r.l. (Italia, [INDIRIZZO_COMPLETO]). Per LexAura e MediAura, i dettagli operativi del singolo prodotto sono indicati nelle rispettive informative privacy pubblicate sui portali SaaS dedicati.",
|
||||||
|
versionLabel: 'Versione',
|
||||||
|
inForceFromLabel: 'In vigore dal',
|
||||||
|
lastUpdateLabel: 'Ultimo aggiornamento',
|
||||||
|
relatedDocsLabel: 'Documenti connessi',
|
||||||
|
indexLabel: 'Indice',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
en: {
|
||||||
id: 'diritti',
|
document: {
|
||||||
title: '2. Diritti riconosciuti agli interessati',
|
version: '1.1',
|
||||||
paragraphs: [
|
inVigoreDal: '[DATA]',
|
||||||
"Nei limiti previsti dalla normativa applicabile, l'interessato può esercitare i seguenti diritti:",
|
ultimoAggiornamento: '[DATA]',
|
||||||
],
|
},
|
||||||
bullets: [
|
crossReferences: {
|
||||||
'Accesso ai dati personali e ottenimento di copia in formato intelligibile;',
|
lead:
|
||||||
'Rettifica dei dati inesatti e integrazione dei dati incompleti;',
|
'For LexAura and MediAura, product-specific operational details are set out in the privacy notices published on the dedicated SaaS portals.',
|
||||||
'Cancellazione dei dati (diritto all\'oblio), quando ne ricorrono i presupposti;',
|
},
|
||||||
'Limitazione del trattamento in presenza delle condizioni previste dal GDPR;',
|
riferimenti: [
|
||||||
'Portabilità dei dati trattati con mezzi automatizzati sulla base di consenso o contratto;',
|
{ label: 'Privacy notice', href: '/privacy' },
|
||||||
"Opposizione al trattamento fondato su legittimo interesse, inclusa l'opposizione a comunicazioni marketing;",
|
{ label: 'Cookie policy', href: '/cookies' },
|
||||||
'Revoca del consenso in qualsiasi momento, senza pregiudicare la liceità del trattamento precedente;',
|
{ label: 'Terms of use', href: '/terms' },
|
||||||
"Reclamo all'autorità di controllo competente.",
|
] as const,
|
||||||
],
|
sections: [
|
||||||
|
{
|
||||||
|
id: 'quadro',
|
||||||
|
title: '1. Regulatory framework and scope',
|
||||||
|
paragraphs: [
|
||||||
|
'Regulation (EU) 2016/679 (GDPR) protects data subjects’ rights and governs the processing of personal data. This page explains how to exercise those rights vis-à-vis NexStudio in the context of the corporate portal and related services.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'diritti',
|
||||||
|
title: '2. Rights granted to data subjects',
|
||||||
|
paragraphs: [
|
||||||
|
'Within the limits provided by applicable law, the data subject may exercise the following rights:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'Access to personal data and obtaining a copy in an intelligible format;',
|
||||||
|
'Rectification of inaccurate data and completion of incomplete data;',
|
||||||
|
'Erasure of data (right to be forgotten), where the relevant conditions are met;',
|
||||||
|
'Restriction of processing where the conditions set out in the GDPR apply;',
|
||||||
|
'Data portability for data processed by automated means on the basis of consent or contract;',
|
||||||
|
'Objection to processing based on legitimate interest, including objection to marketing communications;',
|
||||||
|
'Withdrawal of consent at any time, without affecting the lawfulness of processing carried out before withdrawal;',
|
||||||
|
'Complaint to the competent supervisory authority.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'chi-contattare',
|
||||||
|
title: '3. Who to contact to exercise rights',
|
||||||
|
paragraphs: [],
|
||||||
|
bullets: [
|
||||||
|
'Email: privacy@nexstudio.com',
|
||||||
|
'Form: nexstudio.ai/contatti',
|
||||||
|
'For processing in SaaS tenants (LexAura/MediAura): contact the customer controller first (law firm, healthcare organization). NexStudio supports the customer controller under the DPA and contractual agreements.',
|
||||||
|
],
|
||||||
|
paragraphsAfterBullets: [
|
||||||
|
'Competent supervisory authority: Italian Data Protection Authority — Garante per la protezione dei dati personali (www.garanteprivacy.it) — for processing subject to the GDPR.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'identificazione',
|
||||||
|
title: '4. Identity verification and request handling',
|
||||||
|
paragraphs: [
|
||||||
|
'To protect confidentiality and security, NexStudio may request the minimum additional information needed to verify the requester’s identity. Requests are handled under tracked procedures and acknowledged within the time limits set by the GDPR; for complex or multiple requests, the deadlines may be extended within the limits permitted by law, with a reasoned notice.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'limitazioni',
|
||||||
|
title: '5. Cases of limitation or refusal',
|
||||||
|
paragraphs: [
|
||||||
|
'The exercise of rights may be limited or refused where overriding legal obligations, needs of defense in legal proceedings, protection of third-party rights, or other exceptions provided by law apply. In such cases NexStudio provides a reasoned response and indicates the available remedies, including the right to lodge a complaint with the competent authority.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trasferimenti',
|
||||||
|
title: '6. International transfers and safeguards',
|
||||||
|
paragraphs: [
|
||||||
|
'Where processing involves transfers outside the EEA/EU, NexStudio adopts the measures required by law (e.g. standard contractual clauses and supplementary measures). Data primarily resides in the EU via Cloudflare infrastructure.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'documenti-prodotto',
|
||||||
|
title: '7. Coordination with SaaS privacy notices',
|
||||||
|
paragraphs: [
|
||||||
|
'The privacy policies of the LexAura and MediAura SaaS portals detail, for each product, data flows, retention periods, privacy roles, and dedicated channels. In case of differences between documents, the service-specific documents and applicable contractual agreements prevail.',
|
||||||
|
'To consult product privacy notices:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'LexAura: see the dedicated portal',
|
||||||
|
'MediAura: mediaura.doctor/privacy',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const satisfies readonly GdprSection[],
|
||||||
|
page: {
|
||||||
|
title: 'GDPR — NexStudio',
|
||||||
|
description:
|
||||||
|
'NexStudio S.r.l. GDPR page: data subject rights, operational procedures, roles between the corporate portal and LexAura/MediAura SaaS, coordination with dedicated privacy notices.',
|
||||||
|
heading: 'GDPR and data subject rights',
|
||||||
|
lead:
|
||||||
|
'Data subject rights and how to exercise them under Regulation (EU) 2016/679, consistent with the framework privacy notice of NexStudio S.r.l. (Italy, [INDIRIZZO_COMPLETO]). For LexAura and MediAura, product-specific operational details are set out in the privacy notices published on the dedicated SaaS portals.',
|
||||||
|
versionLabel: 'Version',
|
||||||
|
inForceFromLabel: 'In force from',
|
||||||
|
lastUpdateLabel: 'Last updated',
|
||||||
|
relatedDocsLabel: 'Related documents',
|
||||||
|
indexLabel: 'Contents',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
th: {
|
||||||
id: 'chi-contattare',
|
document: {
|
||||||
title: '3. Chi contattare per esercitare i diritti',
|
version: '1.1',
|
||||||
paragraphs: [],
|
inVigoreDal: '[DATA]',
|
||||||
bullets: [
|
ultimoAggiornamento: '[DATA]',
|
||||||
'Email: privacy@nexstudio.com',
|
},
|
||||||
'Form: nexstudio.ai/contatti',
|
crossReferences: {
|
||||||
'Per trattamenti nei tenant SaaS (LexAura/MediAura): rivolgersi prima al cliente titolare (studio legale, struttura sanitaria). NexStudio supporta il cliente titolare secondo DPA e accordi contrattuali.',
|
lead:
|
||||||
],
|
'สำหรับ LexAura และ MediAura รายละเอียดการดำเนินงานเฉพาะผลิตภัณฑ์ระบุไว้ในประกาศความเป็นส่วนตัวที่เผยแพร่บนพอร์ทัล SaaS ที่เกี่ยวข้อง',
|
||||||
paragraphsAfterBullets: [
|
},
|
||||||
"Autorità di controllo competente: Garante per la protezione dei dati personali (www.garanteprivacy.it) per i trattamenti soggetti al GDPR.",
|
riferimenti: [
|
||||||
],
|
{ label: 'ประกาศความเป็นส่วนตัว', href: '/privacy' },
|
||||||
|
{ label: 'นโยบายคุกกี้', href: '/cookies' },
|
||||||
|
{ label: 'เงื่อนไขการใช้งาน', href: '/terms' },
|
||||||
|
] as const,
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
id: 'quadro',
|
||||||
|
title: '1. กรอบกฎหมายและขอบเขต',
|
||||||
|
paragraphs: [
|
||||||
|
'ระเบียบ (EU) 2016/679 (GDPR) คุ้มครองสิทธิของเจ้าของข้อมูลและกำกับดูแลการประมวลผลข้อมูลส่วนบุคคล หน้านี้ชี้แจงวิธีการใช้สิทธิดังกล่าวกับ NexStudio ในบริบทของพอร์ทัลองค์กรและบริการที่เกี่ยวข้อง',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'diritti',
|
||||||
|
title: '2. สิทธิที่เจ้าของข้อมูลได้รับ',
|
||||||
|
paragraphs: [
|
||||||
|
'ภายในขอบเขตที่กฎหมายที่ใช้บังคับกำหนด เจ้าของข้อมูลอาจใช้สิทธิต่อไปนี้:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'เข้าถึงข้อมูลส่วนบุคคลและขอสำเนาในรูปแบบที่เข้าใจได้;',
|
||||||
|
'แก้ไขข้อมูลที่ไม่ถูกต้องและเติมเต็มข้อมูลที่ไม่ครบถ้วน;',
|
||||||
|
'ลบข้อมูล (สิทธิที่จะถูกลืม) เมื่อเข้าเงื่อนไข;',
|
||||||
|
'จำกัดการประมวลผลเมื่อเข้าเงื่อนไขตาม GDPR;',
|
||||||
|
'โอนย้ายข้อมูลที่ประมวลผลด้วยวิธีอัตโนมัติบนพื้นฐานของความยินยอมหรือสัญญา;',
|
||||||
|
'คัดค้านการประมวลผลบนพื้นฐานของประโยชน์โดยชอบด้วยกฎหมาย รวมถึงคัดค้านการสื่อสารการตลาด;',
|
||||||
|
'ถอนความยินยอมได้ทุกเมื่อ โดยไม่กระทบความชอบด้วยกฎหมายของการประมวลผลก่อนการถอน;',
|
||||||
|
'ร้องเรียนต่อหน่วยงานกำกับดูแลที่มีอำนาจ.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'chi-contattare',
|
||||||
|
title: '3. ติดต่อใครเพื่อใช้สิทธิ',
|
||||||
|
paragraphs: [],
|
||||||
|
bullets: [
|
||||||
|
'อีเมล: privacy@nexstudio.com',
|
||||||
|
'แบบฟอร์ม: nexstudio.ai/contatti',
|
||||||
|
'สำหรับการประมวลผลในเทแนนท์ SaaS (LexAura/MediAura): ติดต่อลูกค้าซึ่งเป็นผู้ควบคุมข้อมูลก่อน (สำนักงานกฎหมาย องค์กรด้านสุขภาพ) NexStudio สนับสนุนลูกค้าผู้ควบคุมตาม DPA และข้อตกลงตามสัญญา',
|
||||||
|
],
|
||||||
|
paragraphsAfterBullets: [
|
||||||
|
'หน่วยงานกำกับดูแลที่มีอำนาจ: Garante per la protezione dei dati personali (www.garanteprivacy.it) สำหรับการประมวลผลที่อยู่ภายใต้ GDPR',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'identificazione',
|
||||||
|
title: '4. การยืนยันตัวตนและการจัดการคำขอ',
|
||||||
|
paragraphs: [
|
||||||
|
'เพื่อคุ้มครองความลับและความปลอดภัย NexStudio อาจขอข้อมูลเพิ่มเติมเท่าที่จำเป็นขั้นต่ำเพื่อยืนยันตัวตนของผู้ร้องขอ คำขอจะได้รับการจัดการด้วยกระบวนการที่ติดตามได้และรับเรื่องภายในระยะเวลาตาม GDPR หากคำขอซับซ้อนหรือมีหลายคำขอ อาจขยายระยะเวลาได้ภายในขอบเขตที่กฎหมายอนุญาต พร้อมแจ้งเหตุผล',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'limitazioni',
|
||||||
|
title: '5. กรณีจำกัดหรือปฏิเสธ',
|
||||||
|
paragraphs: [
|
||||||
|
'การใช้สิทธิอาจถูกจำกัดหรือไม่ได้รับการตอบรับ เมื่อมีข้อผูกพันทางกฎหมายที่เหนือกว่า ความจำเป็นในการป้องกันคดี ความคุ้มครองสิทธิของบุคคลที่สาม หรือข้อยกเว้นอื่นตามกฎหมาย ในกรณีดังกล่าว NexStudio จะให้คำตอบพร้อมเหตุผลและระบุการเยียวยาที่มี รวมถึงสิทธิร้องเรียนต่อหน่วยงานที่มีอำนาจ',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trasferimenti',
|
||||||
|
title: '6. การโอนข้อมูลระหว่างประเทศและการคุ้มครอง',
|
||||||
|
paragraphs: [
|
||||||
|
'หากการประมวลผลเกี่ยวข้องกับการโอนนอก EEA/EU NexStudio จะใช้มาตรการที่กฎหมายกำหนด (เช่น ข้อสัญญามาตรฐานและมาตรการเสริม) ข้อมูลส่วนใหญ่อยู่ในสหภาพยุโรปผ่านโครงสร้างพื้นฐาน Cloudflare',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'documenti-prodotto',
|
||||||
|
title: '7. การประสานกับประกาศความเป็นส่วนตัวของ SaaS',
|
||||||
|
paragraphs: [
|
||||||
|
'นโยบายความเป็นส่วนตัวของพอร์ทัล SaaS LexAura และ MediAura ระบุรายละเอียดสำหรับแต่ละผลิตภัณฑ์เกี่ยวกับกระแสข้อมูล ระยะเวลาเก็บรักษา บทบาทความเป็นส่วนตัว และช่องทางเฉพาะ หากเอกสารต่างกัน ให้ยึดเอกสารเฉพาะบริการและข้อตกลงตามสัญญาที่ใช้บังคับ',
|
||||||
|
'เพื่อดูประกาศความเป็นส่วนตัวของผลิตภัณฑ์:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'LexAura: ดูที่พอร์ทัลเฉพาะ',
|
||||||
|
'MediAura: mediaura.doctor/privacy',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const satisfies readonly GdprSection[],
|
||||||
|
page: {
|
||||||
|
title: 'GDPR — NexStudio',
|
||||||
|
description:
|
||||||
|
'หน้า GDPR ของ NexStudio S.r.l.: สิทธิของเจ้าของข้อมูล วิธีการดำเนินงาน บทบาทระหว่างพอร์ทัลองค์กรและ SaaS LexAura/MediAura และการประสานกับประกาศความเป็นส่วนตัวเฉพาะ',
|
||||||
|
heading: 'GDPR และสิทธิของเจ้าของข้อมูล',
|
||||||
|
lead:
|
||||||
|
'สิทธิของเจ้าของข้อมูลและวิธีการใช้สิทธิตามระเบียบ (EU) 2016/679 ให้สอดคล้องกับประกาศความเป็นส่วนตัวกรอบของ NexStudio S.r.l. (อิตาลี, [INDIRIZZO_COMPLETO]) สำหรับ LexAura และ MediAura รายละเอียดการดำเนินงานเฉพาะผลิตภัณฑ์ระบุไว้ในประกาศความเป็นส่วนตัวที่เผยแพร่บนพอร์ทัล SaaS ที่เกี่ยวข้อง',
|
||||||
|
versionLabel: 'เวอร์ชัน',
|
||||||
|
inForceFromLabel: 'มีผลตั้งแต่',
|
||||||
|
lastUpdateLabel: 'อัปเดตล่าสุด',
|
||||||
|
relatedDocsLabel: 'เอกสารที่เกี่ยวข้อง',
|
||||||
|
indexLabel: 'สารบัญ',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
} as const;
|
||||||
id: 'identificazione',
|
|
||||||
title: "4. Verifica dell'identità e gestione della richiesta",
|
export const getGdpr = (locale: SupportedLocale) => gdprByLocale[locale];
|
||||||
paragraphs: [
|
|
||||||
"Per tutelare riservatezza e sicurezza, NexStudio può richiedere informazioni minime aggiuntive necessarie a verificare l'identità del richiedente. Le richieste sono gestite con procedure tracciate e prese in carico nei tempi previsti dal GDPR; in caso di richieste complesse o multiple, i termini possono essere prorogati nei limiti consentiti dalla legge, con comunicazione motivata.",
|
/** Retro-compatibilità: fallback italiano. */
|
||||||
],
|
export const gdprDocument = gdprByLocale.it.document;
|
||||||
},
|
export const gdprCrossReferences = gdprByLocale.it.crossReferences;
|
||||||
{
|
export const gdprRiferimenti = gdprByLocale.it.riferimenti;
|
||||||
id: 'limitazioni',
|
export const gdprSections = gdprByLocale.it.sections;
|
||||||
title: '5. Casi di limitazione o rigetto',
|
|
||||||
paragraphs: [
|
|
||||||
"L'esercizio dei diritti può essere limitato o non accolto quando sussistono obblighi legali prevalenti, esigenze di difesa in giudizio, tutela dei diritti di terzi o altre eccezioni previste dalla normativa. In tali casi NexStudio fornisce un riscontro motivato e indica i rimedi disponibili, incluso il diritto di proporre reclamo all'autorità competente.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'trasferimenti',
|
|
||||||
title: '6. Trasferimenti internazionali e garanzie',
|
|
||||||
paragraphs: [
|
|
||||||
"Qualora il trattamento comporti trasferimenti extra SEE/UE, NexStudio adotta le misure richieste dalla normativa (es. clausole contrattuali standard e misure supplementari). I dati risiedono primariamente in UE via infrastruttura Cloudflare.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'documenti-prodotto',
|
|
||||||
title: '7. Coordinamento con le privacy dei SaaS',
|
|
||||||
paragraphs: [
|
|
||||||
"Le privacy policy dei portali SaaS LexAura e MediAura dettagliano, per ciascun prodotto, flussi dati, tempi di conservazione, ruoli privacy e canali dedicati. In caso di differenze tra documenti, prevalgono i documenti specifici del servizio e gli accordi contrattuali applicabili.",
|
|
||||||
'Per consultare le privacy dei prodotti:',
|
|
||||||
],
|
|
||||||
bullets: [
|
|
||||||
'LexAura: vedere il portale dedicato',
|
|
||||||
'MediAura: mediaura.doctor/privacy',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
] as const;
|
|
||||||
|
|||||||
@ -7,44 +7,20 @@
|
|||||||
* con flusso token via email lasciare `false` e usare `successMessage`.
|
* con flusso token via email lasciare `false` e usare `successMessage`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** Destinazione della chat dopo identificazione / click su «Entra» */
|
import type { SupportedLocale } from './navigation';
|
||||||
const entryUrl = '/#contatti';
|
|
||||||
|
/** Destinazione della chat dopo identificazione / click su «Entra» (path IT). */
|
||||||
|
const entryPath = '/#contatti';
|
||||||
// Esempio WhatsApp: `https://wa.me/TUO_NUMERO?text=` + encodeURIComponent('Ciao NexStudio...');
|
// Esempio WhatsApp: `https://wa.me/TUO_NUMERO?text=` + encodeURIComponent('Ciao NexStudio...');
|
||||||
|
|
||||||
export const chat = {
|
const chatShared = {
|
||||||
entryUrl,
|
|
||||||
/** Nuova scheda solo per link esterni (wa.me, widget, ecc.) */
|
/** Nuova scheda solo per link esterni (wa.me, widget, ecc.) */
|
||||||
openInNewTab: entryUrl.startsWith('http'),
|
openInNewTab: entryPath.startsWith('http'),
|
||||||
linkTitle:
|
|
||||||
'Accesso alla chat NexStudio: stato canale, email di verifica e assistenza in più lingue.',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cosa vede l’utente **prima** di inserire email: chi risponde (umano / bot / entrambi).
|
|
||||||
* Aggiorna i testi in base agli orari effettivi del team.
|
|
||||||
*/
|
|
||||||
availability: {
|
|
||||||
badge: 'Operatore umano in orario · chatbot sempre attivo',
|
|
||||||
body:
|
|
||||||
'In orario lavorativo un operatore NexStudio può intervenire in chat; all’ingresso il chatbot può raccogliere il contesto iniziale e smistare la richiesta. Fuori orario la conversazione può proseguire con il solo chatbot fino al rientro del team. Lo stato effettivo è indicato qui sopra prima di procedere.',
|
|
||||||
},
|
|
||||||
|
|
||||||
prechat: {
|
prechat: {
|
||||||
title: 'Prima della chat',
|
|
||||||
intro:
|
|
||||||
'Per evitare attese inutili chiediamo l’indirizzo email a cui inviarti un link o un codice monouso per aprire la sessione in modo sicuro. Dopo la verifica potrai usare la chat dal canale che ti indicheremo.',
|
|
||||||
nameLabel: 'Nome (facoltativo)',
|
|
||||||
emailLabel: 'Email',
|
|
||||||
submitLabel: 'Richiedi accesso alla chat',
|
|
||||||
/** Visibile dopo l’invio riuscito: utile se il link nella mail punta allo stesso `entryUrl` */
|
|
||||||
enterChatLabel: 'Ho ricevuto il messaggio — apri la chat',
|
|
||||||
closeLabel: 'Chiudi',
|
|
||||||
successMessage:
|
|
||||||
'Controlla la posta in arrivo: ti abbiamo inviato le istruzioni (link o codice) per entrare in chat. Se non vedi nulla, controlla anche lo spam.',
|
|
||||||
/** Formspree dedicato alla richiesta accesso chat — vuoto = prova `mailto` */
|
/** Formspree dedicato alla richiesta accesso chat — vuoto = prova `mailto` */
|
||||||
actionUrl: '',
|
actionUrl: '',
|
||||||
/** Se `actionUrl` è vuoto */
|
/** Se `actionUrl` è vuoto */
|
||||||
mailto: '',
|
mailto: '',
|
||||||
notifySubject: 'Richiesta accesso chat — sito NexStudio',
|
|
||||||
successRedirect: '',
|
successRedirect: '',
|
||||||
fieldName: 'name',
|
fieldName: 'name',
|
||||||
fieldEmail: 'email',
|
fieldEmail: 'email',
|
||||||
@ -55,3 +31,96 @@ export const chat = {
|
|||||||
openChatAfterSubmit: false,
|
openChatAfterSubmit: false,
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
const entryUrlFor = (locale: SupportedLocale) => {
|
||||||
|
if (entryPath.startsWith('http') || locale === 'it') return entryPath;
|
||||||
|
if (entryPath.startsWith('/#')) return `/${locale}${entryPath}`;
|
||||||
|
if (entryPath.startsWith('/')) return `/${locale}${entryPath}`;
|
||||||
|
return entryPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
const chatCopyByLocale = {
|
||||||
|
it: {
|
||||||
|
linkTitle:
|
||||||
|
'Accesso alla chat NexStudio: stato canale, email di verifica e assistenza in più lingue.',
|
||||||
|
availability: {
|
||||||
|
badge: 'Operatore umano in orario · chatbot sempre attivo',
|
||||||
|
body:
|
||||||
|
'In orario lavorativo un operatore NexStudio può intervenire in chat; all’ingresso il chatbot può raccogliere il contesto iniziale e smistare la richiesta. Fuori orario la conversazione può proseguire con il solo chatbot fino al rientro del team. Lo stato effettivo è indicato qui sopra prima di procedere.',
|
||||||
|
},
|
||||||
|
prechat: {
|
||||||
|
title: 'Prima della chat',
|
||||||
|
intro:
|
||||||
|
'Per evitare attese inutili chiediamo l’indirizzo email a cui inviarti un link o un codice monouso per aprire la sessione in modo sicuro. Dopo la verifica potrai usare la chat dal canale che ti indicheremo.',
|
||||||
|
nameLabel: 'Nome (facoltativo)',
|
||||||
|
emailLabel: 'Email',
|
||||||
|
submitLabel: 'Richiedi accesso alla chat',
|
||||||
|
enterChatLabel: 'Ho ricevuto il messaggio — apri la chat',
|
||||||
|
closeLabel: 'Chiudi',
|
||||||
|
successMessage:
|
||||||
|
'Controlla la posta in arrivo: ti abbiamo inviato le istruzioni (link o codice) per entrare in chat. Se non vedi nulla, controlla anche lo spam.',
|
||||||
|
notifySubject: 'Richiesta accesso chat — sito NexStudio',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
linkTitle:
|
||||||
|
'NexStudio chat access: channel status, verification email, and multilingual support.',
|
||||||
|
availability: {
|
||||||
|
badge: 'Human operator during hours · chatbot always on',
|
||||||
|
body:
|
||||||
|
'During business hours a NexStudio operator can join the chat; at entry the chatbot can gather initial context and route the request. Outside hours the conversation may continue with the chatbot alone until the team is back. The live status is shown above before you proceed.',
|
||||||
|
},
|
||||||
|
prechat: {
|
||||||
|
title: 'Before chat',
|
||||||
|
intro:
|
||||||
|
'To avoid unnecessary waiting we ask for the email address where we can send a one-time link or code to open the session securely. After verification you can use chat on the channel we indicate.',
|
||||||
|
nameLabel: 'Name (optional)',
|
||||||
|
emailLabel: 'Email',
|
||||||
|
submitLabel: 'Request chat access',
|
||||||
|
enterChatLabel: 'I received the message — open chat',
|
||||||
|
closeLabel: 'Close',
|
||||||
|
successMessage:
|
||||||
|
'Check your inbox: we sent instructions (link or code) to enter the chat. If you see nothing, also check spam.',
|
||||||
|
notifySubject: 'Chat access request — NexStudio website',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
linkTitle:
|
||||||
|
'เข้าใช้งานแชท NexStudio: สถานะช่องทาง อีเมลยืนยัน และการช่วยเหลือหลายภาษา',
|
||||||
|
availability: {
|
||||||
|
badge: 'มีเจ้าหน้าที่ในเวลาทำการ · แชทบอตพร้อมตลอดเวลา',
|
||||||
|
body:
|
||||||
|
'ในเวลาทำการ เจ้าหน้าที่ NexStudio สามารถเข้าร่วมแชทได้ และตอนเริ่มต้นแชทบอตอาจเก็บบริบทเบื้องต้นแล้วส่งต่อคำขอ นอกเวลาทำการ การสนทนาอาจดำเนินต่อด้วยแชทบอตจนกว่าทีมจะกลับมา สถานะจริงจะแสดงด้านบนก่อนดำเนินการต่อ',
|
||||||
|
},
|
||||||
|
prechat: {
|
||||||
|
title: 'ก่อนเริ่มแชท',
|
||||||
|
intro:
|
||||||
|
'เพื่อลดการรอที่ไม่จำเป็น เราขอที่อยู่อีเมลสำหรับส่งลิงก์หรือรหัสแบบใช้ครั้งเดียวเพื่อเปิดเซสชันอย่างปลอดภัย หลังยืนยันแล้ว คุณจะใช้แชทผ่านช่องทางที่เราระบุได้',
|
||||||
|
nameLabel: 'ชื่อ (ไม่บังคับ)',
|
||||||
|
emailLabel: 'อีเมล',
|
||||||
|
submitLabel: 'ขอเข้าใช้งานแชท',
|
||||||
|
enterChatLabel: 'ได้รับข้อความแล้ว — เปิดแชท',
|
||||||
|
closeLabel: 'ปิด',
|
||||||
|
successMessage:
|
||||||
|
'โปรดตรวจสอบกล่องจดหมาย: เราได้ส่งคำแนะนำ (ลิงก์หรือรหัส) สำหรับเข้าแชทแล้ว หากไม่พบ ให้ตรวจสแปมด้วย',
|
||||||
|
notifySubject: 'คำขอเข้าใช้งานแชท — เว็บไซต์ NexStudio',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const getChat = (locale: SupportedLocale) => {
|
||||||
|
const copy = chatCopyByLocale[locale];
|
||||||
|
return {
|
||||||
|
...chatShared,
|
||||||
|
entryUrl: entryUrlFor(locale),
|
||||||
|
linkTitle: copy.linkTitle,
|
||||||
|
availability: copy.availability,
|
||||||
|
prechat: {
|
||||||
|
...chatShared.prechat,
|
||||||
|
...copy.prechat,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Retro-compatibilità: fallback italiano. */
|
||||||
|
export const chat = getChat('it');
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
/** Call-to-action (blocco contatti / chiusura pagina) */
|
/** Call-to-action (blocco contatti / chiusura pagina) */
|
||||||
const env = import.meta.env;
|
const env = import.meta.env;
|
||||||
const contactActionUrl = (env.PUBLIC_CONTACT_FORM_ACTION_URL ?? '/api/contact').trim();
|
const contactActionUrl = (env.PUBLIC_CONTACT_FORM_ACTION_URL ?? '/api/contact').trim();
|
||||||
const contactSuccessRedirect = (env.PUBLIC_CONTACT_FORM_SUCCESS_REDIRECT ?? '').trim();
|
const contactSuccessRedirect = (
|
||||||
|
env.PUBLIC_CONTACT_FORM_SUCCESS_REDIRECT ?? '/#contatti'
|
||||||
|
).trim();
|
||||||
const contactMailto = (env.PUBLIC_CONTACT_FORM_MAILTO ?? '').trim();
|
const contactMailto = (env.PUBLIC_CONTACT_FORM_MAILTO ?? '').trim();
|
||||||
const contactTurnstileSiteKey = (env.PUBLIC_CONTACT_TURNSTILE_SITE_KEY ?? '').trim();
|
const contactTurnstileSiteKey = (env.PUBLIC_CONTACT_TURNSTILE_SITE_KEY ?? '').trim();
|
||||||
|
|
||||||
@ -25,6 +27,7 @@ const ctaByLocale = {
|
|||||||
emailLabel: 'Email',
|
emailLabel: 'Email',
|
||||||
messageLabel: 'Scrivi qui il tuo messaggio',
|
messageLabel: 'Scrivi qui il tuo messaggio',
|
||||||
submitLabel: 'Invia messaggio',
|
submitLabel: 'Invia messaggio',
|
||||||
|
successMessage: 'Messaggio inviato. Ti risponderemo al più presto.',
|
||||||
actionUrl: contactActionUrl,
|
actionUrl: contactActionUrl,
|
||||||
successRedirect: contactSuccessRedirect,
|
successRedirect: contactSuccessRedirect,
|
||||||
turnstileSiteKey: contactTurnstileSiteKey,
|
turnstileSiteKey: contactTurnstileSiteKey,
|
||||||
@ -76,6 +79,7 @@ const ctaByLocale = {
|
|||||||
emailLabel: 'Email',
|
emailLabel: 'Email',
|
||||||
messageLabel: 'Write your message',
|
messageLabel: 'Write your message',
|
||||||
submitLabel: 'Send message',
|
submitLabel: 'Send message',
|
||||||
|
successMessage: 'Message sent. We will get back to you soon.',
|
||||||
actionUrl: contactActionUrl,
|
actionUrl: contactActionUrl,
|
||||||
successRedirect: contactSuccessRedirect,
|
successRedirect: contactSuccessRedirect,
|
||||||
turnstileSiteKey: contactTurnstileSiteKey,
|
turnstileSiteKey: contactTurnstileSiteKey,
|
||||||
@ -127,6 +131,7 @@ const ctaByLocale = {
|
|||||||
emailLabel: 'อีเมล',
|
emailLabel: 'อีเมล',
|
||||||
messageLabel: 'พิมพ์ข้อความของคุณ',
|
messageLabel: 'พิมพ์ข้อความของคุณ',
|
||||||
submitLabel: 'ส่งข้อความ',
|
submitLabel: 'ส่งข้อความ',
|
||||||
|
successMessage: 'ส่งข้อความแล้ว เราจะติดต่อกลับโดยเร็ว',
|
||||||
actionUrl: contactActionUrl,
|
actionUrl: contactActionUrl,
|
||||||
successRedirect: contactSuccessRedirect,
|
successRedirect: contactSuccessRedirect,
|
||||||
turnstileSiteKey: contactTurnstileSiteKey,
|
turnstileSiteKey: contactTurnstileSiteKey,
|
||||||
|
|||||||
@ -20,7 +20,7 @@ const footerByLocale = {
|
|||||||
] as const,
|
] as const,
|
||||||
cookiePreferences: {
|
cookiePreferences: {
|
||||||
label: 'Impostazioni cookie',
|
label: 'Impostazioni cookie',
|
||||||
hint: 'Preferenze di consenso sul tuo browser (non è un testo legale).',
|
hint: 'Preferenze di consenso sul tuo browser.',
|
||||||
} as const,
|
} as const,
|
||||||
legalColumnNav: [
|
legalColumnNav: [
|
||||||
{ label: 'Condizioni d’uso', href: '/terms' },
|
{ label: 'Condizioni d’uso', href: '/terms' },
|
||||||
@ -45,9 +45,9 @@ const footerByLocale = {
|
|||||||
description: 'Aggiornamenti su prodotti e roadmap.',
|
description: 'Aggiornamenti su prodotti e roadmap.',
|
||||||
placeholder: 'La tua email',
|
placeholder: 'La tua email',
|
||||||
buttonLabel: 'Iscriviti',
|
buttonLabel: 'Iscriviti',
|
||||||
actionUrl: '',
|
actionUrl: '/api/newsletter/subscribe',
|
||||||
fieldName: 'email',
|
fieldName: 'email',
|
||||||
successRedirect: '',
|
successRedirect: '/newsletter/grazie',
|
||||||
notifySubject: 'Nuova iscrizione newsletter — NexStudio',
|
notifySubject: 'Nuova iscrizione newsletter — NexStudio',
|
||||||
},
|
},
|
||||||
labels: {
|
labels: {
|
||||||
@ -76,7 +76,7 @@ const footerByLocale = {
|
|||||||
] as const,
|
] as const,
|
||||||
cookiePreferences: {
|
cookiePreferences: {
|
||||||
label: 'Cookie settings',
|
label: 'Cookie settings',
|
||||||
hint: 'Consent preferences in your browser (this is not a legal document).',
|
hint: 'Consent preferences in your browser.',
|
||||||
} as const,
|
} as const,
|
||||||
legalColumnNav: [
|
legalColumnNav: [
|
||||||
{ label: 'Terms of use', href: '/terms' },
|
{ label: 'Terms of use', href: '/terms' },
|
||||||
@ -101,9 +101,9 @@ const footerByLocale = {
|
|||||||
description: 'Updates on products and roadmap.',
|
description: 'Updates on products and roadmap.',
|
||||||
placeholder: 'Your email',
|
placeholder: 'Your email',
|
||||||
buttonLabel: 'Subscribe',
|
buttonLabel: 'Subscribe',
|
||||||
actionUrl: '',
|
actionUrl: '/api/newsletter/subscribe',
|
||||||
fieldName: 'email',
|
fieldName: 'email',
|
||||||
successRedirect: '',
|
successRedirect: '/en/newsletter/grazie',
|
||||||
notifySubject: 'New newsletter signup — NexStudio',
|
notifySubject: 'New newsletter signup — NexStudio',
|
||||||
},
|
},
|
||||||
labels: {
|
labels: {
|
||||||
@ -132,7 +132,7 @@ const footerByLocale = {
|
|||||||
] as const,
|
] as const,
|
||||||
cookiePreferences: {
|
cookiePreferences: {
|
||||||
label: 'ตั้งค่าคุกกี้',
|
label: 'ตั้งค่าคุกกี้',
|
||||||
hint: 'การตั้งค่าความยินยอมในเบราว์เซอร์ของคุณ (ไม่ใช่เอกสารทางกฎหมาย)',
|
hint: 'การตั้งค่าความยินยอมในเบราว์เซอร์ของคุณ',
|
||||||
} as const,
|
} as const,
|
||||||
legalColumnNav: [
|
legalColumnNav: [
|
||||||
{ label: 'เงื่อนไขการใช้งาน', href: '/terms' },
|
{ label: 'เงื่อนไขการใช้งาน', href: '/terms' },
|
||||||
@ -157,9 +157,9 @@ const footerByLocale = {
|
|||||||
description: 'อัปเดตเกี่ยวกับผลิตภัณฑ์และโรดแมป',
|
description: 'อัปเดตเกี่ยวกับผลิตภัณฑ์และโรดแมป',
|
||||||
placeholder: 'อีเมลของคุณ',
|
placeholder: 'อีเมลของคุณ',
|
||||||
buttonLabel: 'สมัครรับข่าวสาร',
|
buttonLabel: 'สมัครรับข่าวสาร',
|
||||||
actionUrl: '',
|
actionUrl: '/api/newsletter/subscribe',
|
||||||
fieldName: 'email',
|
fieldName: 'email',
|
||||||
successRedirect: '',
|
successRedirect: '/th/newsletter/grazie',
|
||||||
notifySubject: 'มีผู้สมัครจดหมายข่าวใหม่ — NexStudio',
|
notifySubject: 'มีผู้สมัครจดหมายข่าวใหม่ — NexStudio',
|
||||||
},
|
},
|
||||||
labels: {
|
labels: {
|
||||||
|
|||||||
@ -5,16 +5,19 @@
|
|||||||
export { siteMeta } from './site-meta';
|
export { siteMeta } from './site-meta';
|
||||||
export { getSiteMeta } from './site-meta';
|
export { getSiteMeta } from './site-meta';
|
||||||
export { chat } from './chat';
|
export { chat } from './chat';
|
||||||
|
export { getChat } from './chat';
|
||||||
export { navigation } from './navigation';
|
export { navigation } from './navigation';
|
||||||
export { getNavigation } from './navigation';
|
export { getNavigation } from './navigation';
|
||||||
export { hero } from './hero';
|
export { hero } from './hero';
|
||||||
export { getHero } from './hero';
|
export { getHero } from './hero';
|
||||||
export { stats } from './stats';
|
export { stats } from './stats';
|
||||||
|
export { getStats } from './stats';
|
||||||
export { services } from './services';
|
export { services } from './services';
|
||||||
export { getServices } from './services';
|
export { getServices } from './services';
|
||||||
export { stackSection } from './stack';
|
export { stackSection } from './stack';
|
||||||
export { getStackSection } from './stack';
|
export { getStackSection } from './stack';
|
||||||
export { productsSection } from './products';
|
export { productsSection } from './products';
|
||||||
|
export { getProductsSection } from './products';
|
||||||
export { faq } from './faq';
|
export { faq } from './faq';
|
||||||
export { getFaq } from './faq';
|
export { getFaq } from './faq';
|
||||||
export { cta } from './cta';
|
export { cta } from './cta';
|
||||||
|
|||||||
@ -1,55 +1,173 @@
|
|||||||
|
import type { SupportedLocale } from './navigation';
|
||||||
|
|
||||||
/** Sezione prodotti — LexAura (Legal Tech), MediAura (Health Tech) */
|
/** Sezione prodotti — LexAura (Legal Tech), MediAura (Health Tech) */
|
||||||
export const productsSection = {
|
const productsByLocale = {
|
||||||
eyebrow: 'Prodotti',
|
it: {
|
||||||
title: 'LexAura e MediAura',
|
eyebrow: 'Prodotti',
|
||||||
lead:
|
title: 'LexAura e MediAura',
|
||||||
'Due piattaforme SaaS verticali per professionisti del diritto e della sanità: AI integrata dove precisione e conformità sono fondamentali.',
|
lead:
|
||||||
items: [
|
'Due piattaforme SaaS verticali per professionisti del diritto e della sanità: AI integrata dove precisione e conformità sono fondamentali.',
|
||||||
{
|
items: [
|
||||||
id: 'lexaura',
|
{
|
||||||
name: 'LexAura',
|
id: 'lexaura',
|
||||||
badge: 'Legal Tech',
|
name: 'LexAura',
|
||||||
tagline: 'Il supporto analitico per l’eccellenza legale.',
|
badge: 'Legal Tech',
|
||||||
focus: 'Gestione dati, memorie e contrattualistica.',
|
tagline: 'Il supporto analitico per l’eccellenza legale.',
|
||||||
body:
|
focus: 'Gestione dati, memorie e contrattualistica.',
|
||||||
'LexAura è la piattaforma SaaS di NexStudio dedicata ai professionisti del diritto. Sviluppata per ottimizzare la gestione di carichi informativi elevati, LexAura utilizza l’Intelligenza Artificiale per l’incrocio dinamico di leggi, sentenze e normative, garantendo una base analitica solida e tempestiva.',
|
body:
|
||||||
highlights: [
|
'LexAura è la piattaforma SaaS di NexStudio dedicata ai professionisti del diritto. Sviluppata per ottimizzare la gestione di carichi informativi elevati, LexAura utilizza l’Intelligenza Artificiale per l’incrocio dinamico di leggi, sentenze e normative, garantendo una base analitica solida e tempestiva.',
|
||||||
{
|
highlights: [
|
||||||
title: 'Sintesi normativa',
|
{
|
||||||
text: 'Analisi accelerata dei precedenti, delle fonti e dei contesti.',
|
title: 'Sintesi normativa',
|
||||||
},
|
text: 'Analisi accelerata dei precedenti, delle fonti e dei contesti.',
|
||||||
{
|
},
|
||||||
title: 'Drafting assistito',
|
{
|
||||||
text: 'Supporto alla stesura di memorie difensive e contratti complessi.',
|
title: 'Drafting assistito',
|
||||||
},
|
text: 'Supporto alla stesura di memorie difensive e contratti complessi.',
|
||||||
{
|
},
|
||||||
title: 'Workflow integration',
|
{
|
||||||
text: 'Automazione dei processi documentali per lo studio legale moderno.',
|
title: 'Workflow integration',
|
||||||
},
|
text: 'Automazione dei processi documentali per lo studio legale moderno.',
|
||||||
] as const,
|
},
|
||||||
},
|
] as const,
|
||||||
{
|
},
|
||||||
id: 'mediaura',
|
{
|
||||||
name: 'MediAura',
|
id: 'mediaura',
|
||||||
badge: 'Health Tech',
|
name: 'MediAura',
|
||||||
tagline: 'L’automazione intelligente al servizio della medicina.',
|
badge: 'Health Tech',
|
||||||
focus: 'Flussi di lavoro, comandi vocali, scalabilità.',
|
tagline: 'L’automazione intelligente al servizio della medicina.',
|
||||||
body:
|
focus: 'Flussi di lavoro, comandi vocali, scalabilità.',
|
||||||
'MediAura è il sistema SaaS progettato da NexStudio per abbattere il carico burocratico in ambito sanitario. Dallo studio professionale alla struttura ospedaliera, il software coordina i flussi operativi attraverso un alto grado di automazione e interazione naturale.',
|
body:
|
||||||
highlights: [
|
'MediAura è il sistema SaaS progettato da NexStudio per abbattere il carico burocratico in ambito sanitario. Dallo studio professionale alla struttura ospedaliera, il software coordina i flussi operativi attraverso un alto grado di automazione e interazione naturale.',
|
||||||
{
|
highlights: [
|
||||||
title: 'Voice-first interface',
|
{
|
||||||
text: 'Gestione dei flussi e della refertazione tramite comandi vocali avanzati.',
|
title: 'Voice-first interface',
|
||||||
},
|
text: 'Gestione dei flussi e della refertazione tramite comandi vocali avanzati.',
|
||||||
{
|
},
|
||||||
title: 'Scalabilità operativa',
|
{
|
||||||
text: 'Un’architettura che si adatta alle dimensioni della struttura, dal singolo medico alla gestione di reparti complessi.',
|
title: 'Scalabilità operativa',
|
||||||
},
|
text: 'Un’architettura che si adatta alle dimensioni della struttura, dal singolo medico alla gestione di reparti complessi.',
|
||||||
{
|
},
|
||||||
title: 'Ottimizzazione del flusso',
|
{
|
||||||
text: 'Automazione delle attività di routine per restituire centralità al rapporto medico-paziente.',
|
title: 'Ottimizzazione del flusso',
|
||||||
},
|
text: 'Automazione delle attività di routine per restituire centralità al rapporto medico-paziente.',
|
||||||
] as const,
|
},
|
||||||
},
|
] as const,
|
||||||
] as const,
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
eyebrow: 'Products',
|
||||||
|
title: 'LexAura and MediAura',
|
||||||
|
lead:
|
||||||
|
'Two vertical SaaS platforms for legal and healthcare professionals: AI integrated where precision and compliance are essential.',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'lexaura',
|
||||||
|
name: 'LexAura',
|
||||||
|
badge: 'Legal Tech',
|
||||||
|
tagline: 'Analytical support for legal excellence.',
|
||||||
|
focus: 'Data management, briefs, and contracts.',
|
||||||
|
body:
|
||||||
|
'LexAura is NexStudio’s SaaS platform for legal professionals. Built to optimize high information workloads, LexAura uses Artificial Intelligence for dynamic cross-referencing of laws, judgments, and regulations, delivering a solid and timely analytical foundation.',
|
||||||
|
highlights: [
|
||||||
|
{
|
||||||
|
title: 'Regulatory synthesis',
|
||||||
|
text: 'Accelerated analysis of precedents, sources, and contexts.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Assisted drafting',
|
||||||
|
text: 'Support for drafting defense briefs and complex contracts.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Workflow integration',
|
||||||
|
text: 'Document process automation for the modern law firm.',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mediaura',
|
||||||
|
name: 'MediAura',
|
||||||
|
badge: 'Health Tech',
|
||||||
|
tagline: 'Intelligent automation in service of medicine.',
|
||||||
|
focus: 'Workflows, voice commands, scalability.',
|
||||||
|
body:
|
||||||
|
'MediAura is the SaaS system designed by NexStudio to reduce bureaucratic load in healthcare. From private practice to hospital settings, the software coordinates operational flows through high automation and natural interaction.',
|
||||||
|
highlights: [
|
||||||
|
{
|
||||||
|
title: 'Voice-first interface',
|
||||||
|
text: 'Manage workflows and reporting through advanced voice commands.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Operational scalability',
|
||||||
|
text: 'An architecture that adapts from a single physician to complex multi-department organizations.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Flow optimization',
|
||||||
|
text: 'Automation of routine tasks to put the doctor–patient relationship back at the center.',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
eyebrow: 'ผลิตภัณฑ์',
|
||||||
|
title: 'LexAura และ MediAura',
|
||||||
|
lead:
|
||||||
|
'แพลตฟอร์ม SaaS เฉพาะทางสองระบบสำหรับมืออาชีพด้านกฎหมายและสุขภาพ: ผสาน AI ในจุดที่ต้องการความแม่นยำและการปฏิบัติตามข้อกำกับ',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'lexaura',
|
||||||
|
name: 'LexAura',
|
||||||
|
badge: 'Legal Tech',
|
||||||
|
tagline: 'เครื่องมือวิเคราะห์เพื่อความเป็นเลิศทางกฎหมาย',
|
||||||
|
focus: 'การจัดการข้อมูล คำแถลง และสัญญา',
|
||||||
|
body:
|
||||||
|
'LexAura คือแพลตฟอร์ม SaaS ของ NexStudio สำหรับมืออาชีพด้านกฎหมาย พัฒนาเพื่อจัดการปริมาณข้อมูลจำนวนมาก โดยใช้ปัญญาประดิษฐ์ในการเชื่อมโยงกฎหมาย คำพิพากษา และข้อกำกับอย่างไดนามิก เพื่อฐานวิเคราะห์ที่แข็งแรงและทันเวลา',
|
||||||
|
highlights: [
|
||||||
|
{
|
||||||
|
title: 'การสรุปเชิงกฎหมาย',
|
||||||
|
text: 'วิเคราะห์บรรทัดฐาน แหล่งข้อมูล และบริบทได้รวดเร็วขึ้น',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'การร่างเอกสารแบบมีผู้ช่วย',
|
||||||
|
text: 'สนับสนุนการร่างคำแถลงและสัญญาที่ซับซ้อน',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'การผสานเวิร์กโฟลว์',
|
||||||
|
text: 'ระบบงานเอกสารอัตโนมัติสำหรับสำนักงานกฎหมายสมัยใหม่',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mediaura',
|
||||||
|
name: 'MediAura',
|
||||||
|
badge: 'Health Tech',
|
||||||
|
tagline: 'ระบบอัตโนมัติอัจฉริยะเพื่อการแพทย์',
|
||||||
|
focus: 'เวิร์กโฟลว์ คำสั่งเสียง ความสามารถในการขยาย',
|
||||||
|
body:
|
||||||
|
'MediAura คือระบบ SaaS ที่ NexStudio ออกแบบเพื่อลดภาระงานธุรการในภาคสุขภาพ ตั้งแต่คลินิกส่วนตัวถึงโรงพยาบาล ซอฟต์แวร์ช่วยประสานงานปฏิบัติการด้วยระบบอัตโนมัติสูงและการโต้ตอบที่เป็นธรรมชาติ',
|
||||||
|
highlights: [
|
||||||
|
{
|
||||||
|
title: 'อินเทอร์เฟซแบบ Voice-first',
|
||||||
|
text: 'จัดการเวิร์กโฟลว์และรายงานผลผ่านคำสั่งเสียงขั้นสูง',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ขยายได้ตามการดำเนินงาน',
|
||||||
|
text: 'สถาปัตยกรรมที่ปรับได้ตั้งแต่วิทยาแพทย์คนเดียวถึงแผนกที่ซับซ้อน',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'เพิ่มประสิทธิภาพเวิร์กโฟลว์',
|
||||||
|
text: 'ทำให้งานประจำอัตโนมัติ เพื่อคืนความสำคัญให้ความสัมพันธ์แพทย์–ผู้ป่วย',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const getProductsSection = (locale: SupportedLocale) =>
|
||||||
|
productsByLocale[locale];
|
||||||
|
|
||||||
|
/** Retro-compatibilità: fallback italiano. */
|
||||||
|
export const productsSection = productsByLocale.it;
|
||||||
|
|||||||
@ -1,20 +1,67 @@
|
|||||||
|
import type { SupportedLocale } from './navigation';
|
||||||
|
|
||||||
/** Fascia metriche — merge performance Edge + AI + compliance settoriale (Legal/Health) */
|
/** Fascia metriche — merge performance Edge + AI + compliance settoriale (Legal/Health) */
|
||||||
export const stats = {
|
const statsByLocale = {
|
||||||
items: [
|
it: {
|
||||||
{
|
items: [
|
||||||
value: '< 30ms',
|
{
|
||||||
caption:
|
value: '< 30ms',
|
||||||
'Latenza globale: elaborazione istantanea dei dati sull’architettura Cloudflare Edge, per risposte in tempo reale dove servono clinica e forense.',
|
caption:
|
||||||
},
|
'Latenza globale: elaborazione istantanea dei dati sull’architettura Cloudflare Edge, per risposte in tempo reale dove servono clinica e forense.',
|
||||||
{
|
},
|
||||||
value: '99.9%',
|
{
|
||||||
caption:
|
value: '99.9%',
|
||||||
'Affidabilità dell’analisi predittiva e diagnostica con modelli LLM di ultima generazione; automazione intelligente dei workflow complessi e integrazione con un ecosistema curato di SaaS verticali certificati per professionisti.',
|
caption:
|
||||||
},
|
'Affidabilità dell’analisi predittiva e diagnostica con modelli LLM di ultima generazione; automazione intelligente dei workflow complessi e integrazione con un ecosistema curato di SaaS verticali certificati per professionisti.',
|
||||||
{
|
},
|
||||||
value: '100%',
|
{
|
||||||
caption:
|
value: '100%',
|
||||||
'GDPR e HIPAA compliant: gestione di documenti medici e legali a norma; protezione Zero-Trust e crittografia end-to-end su dati sensibili.',
|
caption:
|
||||||
},
|
'GDPR e HIPAA compliant: gestione di documenti medici e legali a norma; protezione Zero-Trust e crittografia end-to-end su dati sensibili.',
|
||||||
] as const,
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
value: '< 30ms',
|
||||||
|
caption:
|
||||||
|
'Global latency: instant data processing on Cloudflare Edge architecture, delivering real-time responses for clinical and forensic use cases.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '99.9%',
|
||||||
|
caption:
|
||||||
|
'Predictive and diagnostic analysis reliability with state-of-the-art LLM models; intelligent automation of complex workflows and integration with a curated ecosystem of certified vertical SaaS for professionals.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '100%',
|
||||||
|
caption:
|
||||||
|
'GDPR and HIPAA compliant: handling of medical and legal documents in compliance; Zero-Trust protection and end-to-end encryption for sensitive data.',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
value: '< 30ms',
|
||||||
|
caption:
|
||||||
|
'ความหน่วงระดับโลก: การประมวลผลข้อมูลทันใจบนสถาปัตยกรรม Cloudflare Edge เพื่อการตอบสนองเรียลไทม์ในงานคลินิกและนิติเวช',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '99.9%',
|
||||||
|
caption:
|
||||||
|
'ความน่าเชื่อถือในการวิเคราะห์เชิงพยากรณ์และวินิจฉัยด้วยโมเดล LLM ล่าสุด; ระบบอัตโนมัติอัจฉริยะสำหรับเวิร์กโฟลว์ซับซ้อนพร้อมระบบนิเวศ SaaS เฉพาะทางที่ผ่านการรับรองสำหรับมืออาชีพ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '100%',
|
||||||
|
caption:
|
||||||
|
'สอดคล้องกับ GDPR และ HIPAA: การจัดการเอกสารทางการแพทย์และกฎหมายตามข้อกำหนด; การป้องกันแบบ Zero-Trust และการเข้ารหัส end-to-end สำหรับข้อมูลอ่อนไหว',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const getStats = (locale: SupportedLocale) => statsByLocale[locale];
|
||||||
|
|
||||||
|
/** Retro-compatibilità: fallback italiano. */
|
||||||
|
export const stats = statsByLocale.it;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
208
src/data/news.ts
Normal file
208
src/data/news.ts
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
/**
|
||||||
|
* Pagina Novità / News / ข่าว — contenuti localizzati.
|
||||||
|
*/
|
||||||
|
import type { SupportedLocale } from './home/navigation';
|
||||||
|
|
||||||
|
export type NewsBullet = {
|
||||||
|
title?: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NewsSection = {
|
||||||
|
title: string;
|
||||||
|
paragraphs?: readonly string[];
|
||||||
|
bullets?: readonly NewsBullet[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type NewsLocaleBundle = {
|
||||||
|
page: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
heading: string;
|
||||||
|
lead: string;
|
||||||
|
};
|
||||||
|
statusBanner: string;
|
||||||
|
sections: readonly NewsSection[];
|
||||||
|
contact: {
|
||||||
|
before: string;
|
||||||
|
linkLabel: string;
|
||||||
|
href: string;
|
||||||
|
after: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const newsByLocale: Record<SupportedLocale, NewsLocaleBundle> = {
|
||||||
|
it: {
|
||||||
|
page: {
|
||||||
|
title: 'Novità — NexStudio',
|
||||||
|
description: 'Novità, release e articoli da NexStudio.',
|
||||||
|
heading: 'Novità',
|
||||||
|
lead:
|
||||||
|
'Aggiornamenti ufficiali su piattaforma, compliance, infrastruttura e contenuti editoriali.',
|
||||||
|
},
|
||||||
|
statusBanner:
|
||||||
|
'Stato editoriale: canale attivo. Le prossime pubblicazioni verranno aggiornate su questa pagina con frequenza periodica.',
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: 'Programma editoriale',
|
||||||
|
bullets: [
|
||||||
|
{
|
||||||
|
title: 'Release tecniche:',
|
||||||
|
text: 'aggiornamenti su funzionalità, integrazioni e miglioramenti di stabilità.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Compliance:',
|
||||||
|
text: 'novità su policy, sicurezza applicativa e adempimenti normativi.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Case e risultati:',
|
||||||
|
text: 'note operative su adozione, scalabilità e impatti di business.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Canali di distribuzione',
|
||||||
|
bullets: [
|
||||||
|
{
|
||||||
|
text: 'Questa pagina resta il punto di riferimento ufficiale per annunci e changelog sintetici.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'La newsletter è attiva dal footer del sito (iscrizione con conferma email).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'I contenuti long-form potranno essere pubblicati anche su canali esterni, con rimando qui.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Archivio',
|
||||||
|
paragraphs: [
|
||||||
|
'In questa fase il sito è in pre-lancio operativo. Il primo blocco di news verrà pubblicato con il go-live ufficiale.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
contact: {
|
||||||
|
before:
|
||||||
|
'Per richieste stampa, partnership o approfondimenti tecnici è possibile usare il form nella sezione ',
|
||||||
|
linkLabel: 'Contatti',
|
||||||
|
href: '/#contatti',
|
||||||
|
after: '.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
page: {
|
||||||
|
title: 'News — NexStudio',
|
||||||
|
description: 'News, releases, and articles from NexStudio.',
|
||||||
|
heading: 'News',
|
||||||
|
lead:
|
||||||
|
'Official updates on platform, compliance, infrastructure, and editorial content.',
|
||||||
|
},
|
||||||
|
statusBanner:
|
||||||
|
'Editorial status: channel active. Upcoming publications will be updated on this page on a periodic basis.',
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: 'Editorial programme',
|
||||||
|
bullets: [
|
||||||
|
{
|
||||||
|
title: 'Technical releases:',
|
||||||
|
text: 'updates on features, integrations, and stability improvements.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Compliance:',
|
||||||
|
text: 'news on policies, application security, and regulatory requirements.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Cases and results:',
|
||||||
|
text: 'operational notes on adoption, scalability, and business impact.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Distribution channels',
|
||||||
|
bullets: [
|
||||||
|
{
|
||||||
|
text: 'This page remains the official reference point for announcements and concise changelogs.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'The newsletter is available from the site footer (subscription with email confirmation).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Long-form content may also be published on external channels, with a link back here.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Archive',
|
||||||
|
paragraphs: [
|
||||||
|
'At this stage the site is in pre-launch operations. The first block of news will be published with the official go-live.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
contact: {
|
||||||
|
before:
|
||||||
|
'For press requests, partnerships, or technical deep-dives, you can use the form in the ',
|
||||||
|
linkLabel: 'Contacts',
|
||||||
|
href: '/#contatti',
|
||||||
|
after: ' section.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
th: {
|
||||||
|
page: {
|
||||||
|
title: 'ข่าว — NexStudio',
|
||||||
|
description: 'ข่าว รีลีส และบทความจาก NexStudio',
|
||||||
|
heading: 'ข่าว',
|
||||||
|
lead:
|
||||||
|
'อัปเดตอย่างเป็นทางการเกี่ยวกับแพลตฟอร์ม การปฏิบัติตามกฎ โครงสร้างพื้นฐาน และเนื้อหาบรรณาธิการ',
|
||||||
|
},
|
||||||
|
statusBanner:
|
||||||
|
'สถานะบรรณาธิการ: ช่องทางใช้งานอยู่ สิ่งที่จะเผยแพร่ต่อไปจะอัปเดตบนหน้านี้เป็นระยะ',
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: 'แผนบรรณาธิการ',
|
||||||
|
bullets: [
|
||||||
|
{
|
||||||
|
title: 'รีลีสทางเทคนิค:',
|
||||||
|
text: 'อัปเดตฟีเจอร์ การผสานระบบ และการปรับปรุงความเสถียร',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'การปฏิบัติตามกฎ:',
|
||||||
|
text: 'ข่าวเกี่ยวกับนโยบาย ความปลอดภัยของแอปพลิเคชัน และข้อกำหนดด้านกฎระเบียบ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'กรณีศึกษาและผลลัพธ์:',
|
||||||
|
text: 'บันทึกเชิงปฏิบัติเกี่ยวกับการนำไปใช้ ความสามารถในการขยายผล และผลกระทบทางธุรกิจ',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ช่องทางการเผยแพร่',
|
||||||
|
bullets: [
|
||||||
|
{
|
||||||
|
text: 'หน้านี้ยังคงเป็นจุดอ้างอิงอย่างเป็นทางการสำหรับประกาศและ changelog แบบสรุป',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'จดหมายข่าวเปิดใช้งานจากส่วนท้ายของเว็บไซต์ (สมัครพร้อมยืนยันอีเมล)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'เนื้อหายาวอาจเผยแพร่บนช่องทางภายนอกด้วย โดยมีลิงก์กลับมายังที่นี่',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'คลังข่าว',
|
||||||
|
paragraphs: [
|
||||||
|
'ในระยะนี้เว็บไซต์อยู่ในช่วงก่อนเปิดใช้งานอย่างเป็นทางการ ชุดข่าวชุดแรกจะเผยแพร่พร้อมการเปิดตัวอย่างเป็นทางการ',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
contact: {
|
||||||
|
before:
|
||||||
|
'สำหรับคำขอสื่อ ความร่วมมือ หรือข้อมูลเชิงลึกทางเทคนิค สามารถใช้แบบฟอร์มในส่วน ',
|
||||||
|
linkLabel: 'ติดต่อ',
|
||||||
|
href: '/#contatti',
|
||||||
|
after: ' ได้',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getNews = (locale: SupportedLocale) => newsByLocale[locale];
|
||||||
@ -2,22 +2,7 @@
|
|||||||
* Informativa privacy del portale NexStudio e dei prodotti SaaS LexAura / MediAura.
|
* Informativa privacy del portale NexStudio e dei prodotti SaaS LexAura / MediAura.
|
||||||
* Questo file centralizza i contenuti della pagina /privacy per facilitare revisioni legali.
|
* Questo file centralizza i contenuti della pagina /privacy per facilitare revisioni legali.
|
||||||
*/
|
*/
|
||||||
export const privacyDocument = {
|
import type { SupportedLocale } from './home/navigation';
|
||||||
version: '1.1',
|
|
||||||
inVigoreDal: '[DATA]',
|
|
||||||
ultimoAggiornamento: '[DATA]',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const privacyCrossReferences = {
|
|
||||||
lead:
|
|
||||||
"L'informativa distingue il portale istituzionale NexStudio dai servizi SaaS LexAura e MediAura; le informative privacy specifiche dei singoli SaaS, pubblicate nei rispettivi portali applicativi, restano il riferimento di dettaglio per l'uso del prodotto.",
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const privacyRiferimenti: readonly { label: string; href: string }[] = [
|
|
||||||
{ label: "Condizioni d'uso", href: '/terms' },
|
|
||||||
{ label: 'Cookie policy', href: '/cookies' },
|
|
||||||
{ label: 'Diritti GDPR', href: '/gdpr' },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export type PrivacySection = {
|
export type PrivacySection = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -28,138 +13,505 @@ export type PrivacySection = {
|
|||||||
paragraphsAfterBullets?: readonly string[];
|
paragraphsAfterBullets?: readonly string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const privacySections: readonly PrivacySection[] = [
|
const privacyByLocale = {
|
||||||
{
|
it: {
|
||||||
id: 'ambito',
|
document: {
|
||||||
title: '1. Ambito della presente informativa',
|
version: '1.1',
|
||||||
paragraphs: [
|
inVigoreDal: '[DATA]',
|
||||||
"La presente informativa descrive il quadro generale con cui NexStudio S.r.l. tratta i dati personali raccolti tramite il sito istituzionale pubblico e i canali corporate (collegati anche ai prodotti LexAura e MediAura).",
|
ultimoAggiornamento: '[DATA]',
|
||||||
"Per l'uso operativo dei SaaS, il livello di dettaglio e il ruolo privacy possono variare: NexStudio può operare come titolare, contitolare o responsabile del trattamento a seconda del servizio e degli accordi contrattuali.",
|
},
|
||||||
"In caso di conflitto tra questa pagina e un Data Processing Agreement (DPA), un contratto quadro o condizioni speciali firmate, prevalgono i documenti contrattuali specifici per il servizio acquistato.",
|
crossReferences: {
|
||||||
],
|
lead:
|
||||||
|
"L'informativa distingue il portale istituzionale NexStudio dai servizi SaaS LexAura e MediAura; le informative privacy specifiche dei singoli SaaS, pubblicate nei rispettivi portali applicativi, restano il riferimento di dettaglio per l'uso del prodotto.",
|
||||||
|
},
|
||||||
|
riferimenti: [
|
||||||
|
{ label: "Condizioni d'uso", href: '/terms' },
|
||||||
|
{ label: 'Cookie policy', href: '/cookies' },
|
||||||
|
{ label: 'Diritti GDPR', href: '/gdpr' },
|
||||||
|
] as const,
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
id: 'ambito',
|
||||||
|
title: '1. Ambito della presente informativa',
|
||||||
|
paragraphs: [
|
||||||
|
'La presente informativa descrive il quadro generale con cui NexStudio S.r.l. tratta i dati personali raccolti tramite il sito istituzionale pubblico e i canali corporate (collegati anche ai prodotti LexAura e MediAura).',
|
||||||
|
"Per l'uso operativo dei SaaS, il livello di dettaglio e il ruolo privacy possono variare: NexStudio può operare come titolare, contitolare o responsabile del trattamento a seconda del servizio e degli accordi contrattuali.",
|
||||||
|
'In caso di conflitto tra questa pagina e un Data Processing Agreement (DPA), un contratto quadro o condizioni speciali firmate, prevalgono i documenti contrattuali specifici per il servizio acquistato.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'titolare',
|
||||||
|
title: '2. Identità del titolare e punti di contatto',
|
||||||
|
paragraphs: [
|
||||||
|
'Titolare del trattamento: NexStudio S.r.l., con sede legale in Italia, [INDIRIZZO_COMPLETO], P.IVA [DA_INSERIRE].',
|
||||||
|
'Canali per richieste privacy:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'Email: privacy@nexstudio.com',
|
||||||
|
'Form di contatto su nexstudio.ai',
|
||||||
|
'Per i clienti SaaS: canali autenticati/ticket previsti nei rispettivi portali (LexAura, MediAura)',
|
||||||
|
],
|
||||||
|
paragraphsAfterBullets: [
|
||||||
|
'Il Responsabile della Protezione dei Dati (DPO) è contattabile agli stessi recapiti.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'categorie-dati',
|
||||||
|
title: '3. Categorie di dati trattati',
|
||||||
|
paragraphs: [
|
||||||
|
'Le categorie di dati variano in funzione del canale utilizzato e dei moduli/feature attivi.',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'Portale NexStudio: dati di navigazione (log tecnici, IP, user agent), dati di contatto inviati volontariamente (nome, email, azienda, contenuto del messaggio), preferenze cookie e metriche aggregate.',
|
||||||
|
'LexAura (Legal Tech): dati di account e profilo utente, metadati di workspace, documenti giuridici caricati o generati, richieste testuali ai motori di analisi/sintesi, log di audit su accessi e operazioni.',
|
||||||
|
'MediAura (Health Tech): dati di account e ruoli, metadati organizzativi, documentazione sanitaria o amministrativa, input vocali e relative trascrizioni, log tecnici e di sicurezza.',
|
||||||
|
'Categorie particolari ex art. 9 GDPR: possono emergere in MediAura (e, in specifici casi, in LexAura) quando i clienti caricano contenuti che includono dati sanitari o altri dati sensibili; il trattamento avviene solo nei limiti del servizio e delle istruzioni ricevute dal cliente titolare.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'finalita-basi',
|
||||||
|
title: '4. Finalità e basi giuridiche del trattamento',
|
||||||
|
paragraphs: [],
|
||||||
|
bullets: [
|
||||||
|
'Gestione del sito istituzionale, sicurezza e continuità operativa (base: legittimo interesse alla sicurezza, prevenzione abusi e mantenimento del servizio).',
|
||||||
|
"Gestione richieste pre-contrattuali, demo, contatti commerciali e supporto (base: misure precontrattuali su richiesta dell'interessato; legittimo interesse nella gestione B2B).",
|
||||||
|
'Erogazione dei SaaS LexAura e MediAura, autenticazione utenti, gestione tenant e assistenza tecnica (base: esecuzione del contratto con il cliente business).',
|
||||||
|
'Conformità normativa, adempimenti amministrativi/fiscali, gestione contenziosi e difesa di diritti (base: obbligo legale e legittimo interesse alla tutela in giudizio).',
|
||||||
|
'Comunicazioni marketing/newsletter, ove attive (base: consenso, salvo eccezioni di soft spam consentite dalla normativa applicabile).',
|
||||||
|
'Miglioramento del servizio e sviluppo funzionalità IA: solo se previsto dal contratto o da istruzioni documentate; in assenza di tale base i dati non sono utilizzati per training generalista.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ruoli-saas',
|
||||||
|
title: '5. Ruoli privacy specifici per LexAura e MediAura',
|
||||||
|
paragraphs: [
|
||||||
|
'Per i servizi SaaS B2B, il cliente determina normalmente finalità e mezzi del trattamento relativi ai dati caricati nel proprio ambiente applicativo, agendo come titolare del trattamento. NexStudio agisce di regola come responsabile del trattamento per conto del cliente, con nomina contrattuale e istruzioni documentate.',
|
||||||
|
'Qualora NexStudio determini autonomamente finalità specifiche (es. prevenzione frodi, sicurezza infrastrutturale, metriche aggregate non riconducibili al singolo interessato), agisce come titolare autonomo per tali trattamenti.',
|
||||||
|
'Le informative privacy dedicate di LexAura e MediAura, pubblicate nei rispettivi siti o ambienti applicativi, approfondiscono categorie dati, flussi, tempi di retention e basi giuridiche specifiche del singolo prodotto.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'conservazione',
|
||||||
|
title: '6. Tempi di conservazione',
|
||||||
|
paragraphs: [],
|
||||||
|
bullets: [
|
||||||
|
"Dati di contatto dal sito: 24 mesi dall'ultima interazione.",
|
||||||
|
'Dati di account SaaS: per tutta la durata del rapporto contrattuale + 30 giorni dalla cancellazione.',
|
||||||
|
'Log tecnici e di sicurezza: massimo 30 giorni.',
|
||||||
|
'Dati di fatturazione: 10 anni come richiesto dalla normativa fiscale italiana.',
|
||||||
|
'Dati sanitari: secondo le istruzioni del titolare (cliente) e gli obblighi di legge.',
|
||||||
|
'Dati analitici aggregati: indefinitamente in forma anonima.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'destinatari',
|
||||||
|
title: '7. Destinatari e categorie di soggetti autorizzati',
|
||||||
|
paragraphs: [
|
||||||
|
'I dati possono essere trattati da personale NexStudio autorizzato e istruito, nonché da fornitori qualificati che operano come responsabili/sub-responsabili (es. hosting cloud, servizi infrastrutturali, monitoraggio sicurezza, strumenti di comunicazione).',
|
||||||
|
"L'elenco aggiornato dei responsabili e sub-responsabili rilevanti per i clienti SaaS è messo a disposizione nei canali contrattuali o su richiesta.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trasferimenti',
|
||||||
|
title: '8. Trasferimenti internazionali di dati',
|
||||||
|
paragraphs: [
|
||||||
|
"In ragione dell'uso di fornitori cloud globali, alcuni trattamenti possono comportare trasferimenti verso Paesi extra SEE/UE. Quando richiesto dalla normativa, NexStudio adotta garanzie adeguate (es. clausole contrattuali standard, misure supplementari tecniche/organizzative, valutazioni di impatto sul trasferimento) e limita il trasferimento ai soli dati necessari.",
|
||||||
|
'I dati sono preferibilmente trattati nello Spazio Economico Europeo. Cloudflare Inc. aderisce al Data Privacy Framework UE-USA e adotta Clausole Contrattuali Standard (SCC).',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sicurezza',
|
||||||
|
title: '9. Misure di sicurezza e governance',
|
||||||
|
paragraphs: [
|
||||||
|
'NexStudio adotta misure tecniche e organizzative proporzionate al rischio:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'Controlli di accesso basati su ruoli, autenticazione, segregazione logica degli ambienti cliente e principio del minimo privilegio.',
|
||||||
|
'Cifratura dei dati in transito (TLS 1.3) e a riposo; gestione sicura delle chiavi.',
|
||||||
|
'Logging, monitoraggio, audit trail e procedure di gestione incidenti.',
|
||||||
|
'Procedure di backup, continuità operativa e disaster recovery.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'diritti',
|
||||||
|
title: '10. Diritti degli interessati e modalità di esercizio',
|
||||||
|
paragraphs: [
|
||||||
|
"Gli interessati possono esercitare i diritti previsti dalla normativa applicabile (accesso, rettifica, cancellazione, limitazione, opposizione, portabilità, revoca del consenso, reclamo all'autorità competente).",
|
||||||
|
'Per dati trattati nel contesto dei SaaS, la richiesta va di norma indirizzata prima al cliente titolare (es. studio legale o struttura sanitaria). NexStudio fornisce supporto al cliente titolare secondo contratto.',
|
||||||
|
'Per dati trattati da NexStudio come titolare (es. contatti dal sito, log di sicurezza propri), le richieste possono essere inviate a privacy@nexstudio.com o tramite il form su nexstudio.ai.',
|
||||||
|
'Autorità di controllo competente: Garante per la protezione dei dati personali (www.garanteprivacy.it).',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cookie-tracking',
|
||||||
|
title: '11. Cookie, analytics e tracciamenti',
|
||||||
|
paragraphs: [
|
||||||
|
'Le informazioni su cookie tecnici, eventuali cookie analitici e strumenti di tracciamento sono descritte nella Cookie Policy, da leggere congiuntamente alla presente informativa.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'minori',
|
||||||
|
title: '12. Trattamento dati di minori',
|
||||||
|
paragraphs: [
|
||||||
|
"I servizi NexStudio sono progettati per un'utenza professionale e business. Non sono rivolti intenzionalmente a minori.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'aggiornamenti',
|
||||||
|
title: '13. Aggiornamenti della policy',
|
||||||
|
paragraphs: [
|
||||||
|
'NexStudio può aggiornare questa informativa per adeguamenti normativi, evoluzione dei servizi, modifiche organizzative o introduzione di nuove funzionalità. Le modifiche rilevanti saranno pubblicate su questa pagina con aggiornamento di versione e data.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const satisfies readonly PrivacySection[],
|
||||||
|
page: {
|
||||||
|
title: 'Privacy — NexStudio',
|
||||||
|
description:
|
||||||
|
'Informativa privacy quadro NexStudio S.r.l.: sito istituzionale, contatti corporate, rimandi ai dettagli privacy dei SaaS LexAura (Legal Tech) e MediAura (Health Tech).',
|
||||||
|
heading: 'Informativa sulla privacy',
|
||||||
|
lead:
|
||||||
|
"Informativa quadro sul trattamento dei dati personali per il portale istituzionale NexStudio S.r.l., con sede legale in Italia, [INDIRIZZO_COMPLETO], e per i servizi SaaS LexAura e MediAura. L'informativa distingue il portale istituzionale NexStudio dai servizi SaaS LexAura e MediAura; le informative privacy specifiche dei singoli SaaS, pubblicate nei rispettivi portali applicativi, restano il riferimento di dettaglio per l'uso del prodotto.",
|
||||||
|
versionLabel: 'Versione',
|
||||||
|
inForceFromLabel: 'In vigore dal',
|
||||||
|
lastUpdateLabel: 'Ultimo aggiornamento',
|
||||||
|
relatedDocsLabel: 'Documenti connessi',
|
||||||
|
indexLabel: 'Indice',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
en: {
|
||||||
id: 'titolare',
|
document: {
|
||||||
title: "2. Identità del titolare e punti di contatto",
|
version: '1.1',
|
||||||
paragraphs: [
|
inVigoreDal: '[DATA]',
|
||||||
"Titolare del trattamento: NexStudio S.r.l., con sede legale in Italia, [INDIRIZZO_COMPLETO], P.IVA [DA_INSERIRE].",
|
ultimoAggiornamento: '[DATA]',
|
||||||
"Canali per richieste privacy:",
|
},
|
||||||
],
|
crossReferences: {
|
||||||
bullets: [
|
lead:
|
||||||
'Email: privacy@nexstudio.com',
|
'This notice distinguishes the NexStudio institutional portal from the LexAura and MediAura SaaS services; the product-specific privacy notices published on the respective application portals remain the detailed reference for product use.',
|
||||||
'Form di contatto su nexstudio.ai',
|
},
|
||||||
'Per i clienti SaaS: canali autenticati/ticket previsti nei rispettivi portali (LexAura, MediAura)',
|
riferimenti: [
|
||||||
],
|
{ label: 'Terms of use', href: '/terms' },
|
||||||
paragraphsAfterBullets: [
|
{ label: 'Cookie policy', href: '/cookies' },
|
||||||
'Il Responsabile della Protezione dei Dati (DPO) è contattabile agli stessi recapiti.',
|
{ label: 'GDPR rights', href: '/gdpr' },
|
||||||
],
|
] as const,
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
id: 'ambito',
|
||||||
|
title: '1. Scope of this notice',
|
||||||
|
paragraphs: [
|
||||||
|
'This notice describes the general framework under which NexStudio S.r.l. processes personal data collected through the public institutional website and corporate channels (also linked to the LexAura and MediAura products).',
|
||||||
|
'For operational use of the SaaS products, the level of detail and the privacy role may vary: NexStudio may act as controller, joint controller, or processor depending on the service and contractual agreements.',
|
||||||
|
'In case of conflict between this page and a Data Processing Agreement (DPA), a framework agreement, or signed special terms, the contractual documents specific to the purchased service prevail.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'titolare',
|
||||||
|
title: '2. Controller identity and contact points',
|
||||||
|
paragraphs: [
|
||||||
|
'Data controller: NexStudio S.r.l., registered office in Italy, [INDIRIZZO_COMPLETO], VAT [DA_INSERIRE].',
|
||||||
|
'Channels for privacy requests:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'Email: privacy@nexstudio.com',
|
||||||
|
'Contact form on nexstudio.ai',
|
||||||
|
'For SaaS customers: authenticated channels/tickets provided in the respective portals (LexAura, MediAura)',
|
||||||
|
],
|
||||||
|
paragraphsAfterBullets: [
|
||||||
|
'The Data Protection Officer (DPO) can be contacted at the same addresses.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'categorie-dati',
|
||||||
|
title: '3. Categories of data processed',
|
||||||
|
paragraphs: [
|
||||||
|
'Data categories vary depending on the channel used and the active modules/features.',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'NexStudio portal: browsing data (technical logs, IP, user agent), contact data submitted voluntarily (name, email, company, message content), cookie preferences, and aggregate metrics.',
|
||||||
|
'LexAura (Legal Tech): account and user profile data, workspace metadata, legal documents uploaded or generated, text requests to analysis/summarization engines, audit logs of access and operations.',
|
||||||
|
'MediAura (Health Tech): account and role data, organizational metadata, health or administrative documentation, voice inputs and related transcripts, technical and security logs.',
|
||||||
|
'Special categories under Art. 9 GDPR: may arise in MediAura (and, in specific cases, in LexAura) when customers upload content including health data or other sensitive data; processing occurs only within the limits of the service and the instructions received from the customer controller.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'finalita-basi',
|
||||||
|
title: '4. Purposes and legal bases of processing',
|
||||||
|
paragraphs: [],
|
||||||
|
bullets: [
|
||||||
|
'Operation of the institutional website, security, and operational continuity (basis: legitimate interest in security, abuse prevention, and service maintenance).',
|
||||||
|
'Handling of pre-contractual requests, demos, commercial contacts, and support (basis: pre-contractual measures at the data subject’s request; legitimate interest in B2B management).',
|
||||||
|
'Provision of LexAura and MediAura SaaS, user authentication, tenant management, and technical support (basis: performance of the contract with the business customer).',
|
||||||
|
'Regulatory compliance, administrative/tax obligations, dispute management, and defense of rights (basis: legal obligation and legitimate interest in legal protection).',
|
||||||
|
'Marketing/newsletter communications, where active (basis: consent, subject to soft-spam exceptions permitted by applicable law).',
|
||||||
|
'Service improvement and AI feature development: only if provided for by contract or documented instructions; absent such a basis, data are not used for general-purpose training.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ruoli-saas',
|
||||||
|
title: '5. Privacy roles specific to LexAura and MediAura',
|
||||||
|
paragraphs: [
|
||||||
|
'For B2B SaaS services, the customer normally determines the purposes and means of processing relating to data uploaded into its application environment, acting as controller. NexStudio generally acts as processor on behalf of the customer, under contractual appointment and documented instructions.',
|
||||||
|
'Where NexStudio independently determines specific purposes (e.g. fraud prevention, infrastructure security, aggregate metrics not attributable to an individual data subject), it acts as an independent controller for such processing.',
|
||||||
|
'The dedicated LexAura and MediAura privacy notices, published on the respective sites or application environments, provide further detail on data categories, flows, retention periods, and legal bases specific to each product.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'conservazione',
|
||||||
|
title: '6. Retention periods',
|
||||||
|
paragraphs: [],
|
||||||
|
bullets: [
|
||||||
|
'Website contact data: 24 months from the last interaction.',
|
||||||
|
'SaaS account data: for the entire duration of the contractual relationship + 30 days from deletion.',
|
||||||
|
'Technical and security logs: maximum 30 days.',
|
||||||
|
'Billing data: 10 years as required by Italian tax law.',
|
||||||
|
'Health data: according to the controller (customer) instructions and legal obligations.',
|
||||||
|
'Aggregate analytics data: indefinitely in anonymous form.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'destinatari',
|
||||||
|
title: '7. Recipients and categories of authorized parties',
|
||||||
|
paragraphs: [
|
||||||
|
'Data may be processed by authorized and instructed NexStudio personnel, as well as by qualified suppliers acting as processors/sub-processors (e.g. cloud hosting, infrastructure services, security monitoring, communication tools).',
|
||||||
|
'The up-to-date list of processors and sub-processors relevant to SaaS customers is made available through contractual channels or upon request.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trasferimenti',
|
||||||
|
title: '8. International data transfers',
|
||||||
|
paragraphs: [
|
||||||
|
'Due to the use of global cloud providers, some processing may involve transfers to countries outside the EEA/EU. Where required by law, NexStudio adopts appropriate safeguards (e.g. standard contractual clauses, supplementary technical/organizational measures, transfer impact assessments) and limits transfers to necessary data only.',
|
||||||
|
'Data are preferably processed within the European Economic Area. Cloudflare Inc. adheres to the EU-US Data Privacy Framework and adopts Standard Contractual Clauses (SCCs).',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sicurezza',
|
||||||
|
title: '9. Security measures and governance',
|
||||||
|
paragraphs: [
|
||||||
|
'NexStudio adopts technical and organizational measures proportionate to the risk:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'Role-based access controls, authentication, logical segregation of customer environments, and the principle of least privilege.',
|
||||||
|
'Encryption of data in transit (TLS 1.3) and at rest; secure key management.',
|
||||||
|
'Logging, monitoring, audit trails, and incident management procedures.',
|
||||||
|
'Backup, operational continuity, and disaster recovery procedures.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'diritti',
|
||||||
|
title: '10. Data subject rights and how to exercise them',
|
||||||
|
paragraphs: [
|
||||||
|
'Data subjects may exercise the rights provided by applicable law (access, rectification, erasure, restriction, objection, portability, withdrawal of consent, complaint to the competent authority).',
|
||||||
|
'For data processed in the SaaS context, the request should normally be addressed first to the customer controller (e.g. law firm or healthcare organization). NexStudio supports the customer controller under contract.',
|
||||||
|
'For data processed by NexStudio as controller (e.g. website contacts, its own security logs), requests may be sent to privacy@nexstudio.com or via the form on nexstudio.ai.',
|
||||||
|
'Competent supervisory authority: Garante per la protezione dei dati personali (www.garanteprivacy.it).',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cookie-tracking',
|
||||||
|
title: '11. Cookies, analytics, and tracking',
|
||||||
|
paragraphs: [
|
||||||
|
'Information on technical cookies, any analytics cookies, and tracking tools is set out in the Cookie Policy, which should be read together with this notice.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'minori',
|
||||||
|
title: '12. Processing of children’s data',
|
||||||
|
paragraphs: [
|
||||||
|
'NexStudio services are designed for professional and business users. They are not intentionally directed at children.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'aggiornamenti',
|
||||||
|
title: '13. Policy updates',
|
||||||
|
paragraphs: [
|
||||||
|
'NexStudio may update this notice for regulatory adjustments, service evolution, organizational changes, or introduction of new features. Material changes will be published on this page with an updated version and date.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const satisfies readonly PrivacySection[],
|
||||||
|
page: {
|
||||||
|
title: 'Privacy — NexStudio',
|
||||||
|
description:
|
||||||
|
'NexStudio S.r.l. framework privacy notice: institutional website, corporate contacts, and references to LexAura (Legal Tech) and MediAura (Health Tech) SaaS privacy details.',
|
||||||
|
heading: 'Privacy notice',
|
||||||
|
lead:
|
||||||
|
'Framework notice on the processing of personal data for the institutional portal of NexStudio S.r.l., registered office in Italy, [INDIRIZZO_COMPLETO], and for the LexAura and MediAura SaaS services. This notice distinguishes the NexStudio institutional portal from the LexAura and MediAura SaaS services; the product-specific privacy notices published on the respective application portals remain the detailed reference for product use.',
|
||||||
|
versionLabel: 'Version',
|
||||||
|
inForceFromLabel: 'In force from',
|
||||||
|
lastUpdateLabel: 'Last updated',
|
||||||
|
relatedDocsLabel: 'Related documents',
|
||||||
|
indexLabel: 'Contents',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
th: {
|
||||||
id: 'categorie-dati',
|
document: {
|
||||||
title: '3. Categorie di dati trattati',
|
version: '1.1',
|
||||||
paragraphs: [
|
inVigoreDal: '[DATA]',
|
||||||
"Le categorie di dati variano in funzione del canale utilizzato e dei moduli/feature attivi.",
|
ultimoAggiornamento: '[DATA]',
|
||||||
],
|
},
|
||||||
bullets: [
|
crossReferences: {
|
||||||
"Portale NexStudio: dati di navigazione (log tecnici, IP, user agent), dati di contatto inviati volontariamente (nome, email, azienda, contenuto del messaggio), preferenze cookie e metriche aggregate.",
|
lead:
|
||||||
"LexAura (Legal Tech): dati di account e profilo utente, metadati di workspace, documenti giuridici caricati o generati, richieste testuali ai motori di analisi/sintesi, log di audit su accessi e operazioni.",
|
'ประกาศนี้แยกพอร์ทัลสถาบัน NexStudio จากบริการ SaaS LexAura และ MediAura ประกาศความเป็นส่วนตัวเฉพาะผลิตภัณฑ์ที่เผยแพร่บนพอร์ทัลแอปพลิเคชันที่เกี่ยวข้องยังคงเป็นเอกสารอ้างอิงรายละเอียดสำหรับการใช้ผลิตภัณฑ์',
|
||||||
"MediAura (Health Tech): dati di account e ruoli, metadati organizzativi, documentazione sanitaria o amministrativa, input vocali e relative trascrizioni, log tecnici e di sicurezza.",
|
},
|
||||||
"Categorie particolari ex art. 9 GDPR: possono emergere in MediAura (e, in specifici casi, in LexAura) quando i clienti caricano contenuti che includono dati sanitari o altri dati sensibili; il trattamento avviene solo nei limiti del servizio e delle istruzioni ricevute dal cliente titolare.",
|
riferimenti: [
|
||||||
],
|
{ label: 'เงื่อนไขการใช้งาน', href: '/terms' },
|
||||||
|
{ label: 'นโยบายคุกกี้', href: '/cookies' },
|
||||||
|
{ label: 'สิทธิ GDPR', href: '/gdpr' },
|
||||||
|
] as const,
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
id: 'ambito',
|
||||||
|
title: '1. ขอบเขตของประกาศฉบับนี้',
|
||||||
|
paragraphs: [
|
||||||
|
'ประกาศฉบับนี้อธิบายกรอบทั่วไปที่ NexStudio S.r.l. ประมวลผลข้อมูลส่วนบุคคลที่เก็บรวบรวมผ่านเว็บไซต์สถาบันสาธารณะและช่องทางองค์กร (ซึ่งเชื่อมโยงกับผลิตภัณฑ์ LexAura และ MediAura ด้วย)',
|
||||||
|
'สำหรับการใช้งาน SaaS ในทางปฏิบัติ ระดับรายละเอียดและบทบาทความเป็นส่วนตัวอาจแตกต่างกัน: NexStudio อาจทำหน้าที่เป็นผู้ควบคุม ผู้ควบคุมร่วม หรือผู้ประมวลผล ขึ้นอยู่กับบริการและข้อตกลงตามสัญญา',
|
||||||
|
'หากเกิดความขัดแย้งระหว่างหน้านี้กับ Data Processing Agreement (DPA) สัญญากรอบ หรือเงื่อนไขพิเศษที่ลงนามแล้ว ให้ยึดเอกสารสัญญาเฉพาะของบริการที่ซื้อ',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'titolare',
|
||||||
|
title: '2. ตัวตนของผู้ควบคุมและจุดติดต่อ',
|
||||||
|
paragraphs: [
|
||||||
|
'ผู้ควบคุมข้อมูล: NexStudio S.r.l. สำนักงานจดทะเบียนในอิตาลี [INDIRIZZO_COMPLETO] หมายเลขภาษีมูลค่าเพิ่ม [DA_INSERIRE]',
|
||||||
|
'ช่องทางสำหรับคำขอความเป็นส่วนตัว:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'อีเมล: privacy@nexstudio.com',
|
||||||
|
'แบบฟอร์มติดต่อบน nexstudio.ai',
|
||||||
|
'สำหรับลูกค้า SaaS: ช่องทางที่ผ่านการยืนยันตัวตน/ตั๋วในพอร์ทัลที่เกี่ยวข้อง (LexAura, MediAura)',
|
||||||
|
],
|
||||||
|
paragraphsAfterBullets: [
|
||||||
|
'เจ้าหน้าที่คุ้มครองข้อมูล (DPO) สามารถติดต่อได้ที่ที่อยู่เดียวกัน',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'categorie-dati',
|
||||||
|
title: '3. ประเภทข้อมูลที่ประมวลผล',
|
||||||
|
paragraphs: [
|
||||||
|
'ประเภทข้อมูลแตกต่างกันตามช่องทางที่ใช้และโมดูล/ฟีเจอร์ที่เปิดใช้งาน',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'พอร์ทัล NexStudio: ข้อมูลการท่องเว็บ (ล็อกทางเทคนิค IP user agent) ข้อมูลติดต่อที่ส่งโดยสมัครใจ (ชื่อ อีเมล บริษัท เนื้อหาข้อความ) การตั้งค่าคุกกี้ และเมตริกรวม',
|
||||||
|
'LexAura (Legal Tech): ข้อมูลบัญชีและโปรไฟล์ผู้ใช้ เมทาดาทาของ workspace เอกสารกฎหมายที่อัปโหลดหรือสร้าง คำขอข้อความไปยังเอนจินวิเคราะห์/สรุป ล็อกการตรวจสอบการเข้าถึงและการดำเนินการ',
|
||||||
|
'MediAura (Health Tech): ข้อมูลบัญชีและบทบาท เมทาดาทาองค์กร เอกสารด้านสุขภาพหรือธุรการ อินพุตเสียงและใบถอดเสียงที่เกี่ยวข้อง ล็อกทางเทคนิคและความปลอดภัย',
|
||||||
|
'ประเภทพิเศษตามมาตรา 9 GDPR: อาจเกิดขึ้นใน MediAura (และในบางกรณีใน LexAura) เมื่อลูกค้าอัปโหลดเนื้อหาที่มีข้อมูลสุขภาพหรือข้อมูลอ่อนไหวอื่น การประมวลผลเกิดขึ้นเฉพาะในขอบเขตของบริการและตามคำสั่งที่ได้รับจากลูกค้าผู้ควบคุม',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'finalita-basi',
|
||||||
|
title: '4. วัตถุประสงค์และฐานทางกฎหมายของการประมวลผล',
|
||||||
|
paragraphs: [],
|
||||||
|
bullets: [
|
||||||
|
'การจัดการเว็บไซต์สถาบัน ความปลอดภัย และความต่อเนื่องในการดำเนินงาน (ฐาน: ประโยชน์โดยชอบด้วยกฎหมายด้านความปลอดภัย การป้องกันการใช้ในทางที่ผิด และการรักษบริการ)',
|
||||||
|
'การจัดการคำขอก่อนสัญญา การสาธิต การติดต่อเชิงพาณิชย์ และการสนับสนุน (ฐาน: มาตรการก่อนสัญญาตามคำขอของเจ้าของข้อมูล; ประโยชน์โดยชอบด้วยกฎหมายในการจัดการ B2B)',
|
||||||
|
'การให้บริการ SaaS LexAura และ MediAura การยืนยันตัวตนผู้ใช้ การจัดการเทแนนท์ และการสนับสนุนทางเทคนิค (ฐาน: การปฏิบัติตามสัญญากับลูกค้าธุรกิจ)',
|
||||||
|
'การปฏิบัติตามข้อกำกับ ภาระผูกพันด้านการบริหาร/ภาษี การจัดการข้อพิพาท และการป้องกันสิทธิ (ฐาน: ข้อผูกพันทางกฎหมายและประโยชน์โดยชอบด้วยกฎหมายในการคุ้มครองทางคดี)',
|
||||||
|
'การสื่อสารการตลาด/จดหมายข่าว หากเปิดใช้งาน (ฐาน: ความยินยอม เว้นแต่ข้อยกเว้น soft spam ที่กฎหมายอนุญาต)',
|
||||||
|
'การปรับปรุงบริการและการพัฒนาฟีเจอร์ AI: เฉพาะเมื่อสัญญาหรือคำสั่งที่เป็นลายลักษณ์อักษรกำหนดไว้ หากไม่มีฐานดังกล่าว จะไม่ใช้ข้อมูลเพื่อการฝึกโมเดลทั่วไป',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ruoli-saas',
|
||||||
|
title: '5. บทบาทความเป็นส่วนตัวเฉพาะสำหรับ LexAura และ MediAura',
|
||||||
|
paragraphs: [
|
||||||
|
'สำหรับบริการ SaaS แบบ B2B โดยปกติลูกค้าเป็นผู้กำหนดวัตถุประสงค์และวิธีการประมวลผลที่เกี่ยวข้องกับข้อมูลที่อัปโหลดในสภาพแวดล้อมแอปพลิเคชันของตน โดยทำหน้าที่เป็นผู้ควบคุม NexStudio โดยทั่วไปทำหน้าที่เป็นผู้ประมวลผลในนามของลูกค้า ตามการแต่งตั้งตามสัญญาและคำสั่งที่เป็นลายลักษณ์อักษร',
|
||||||
|
'หาก NexStudio กำหนดวัตถุประสงค์เฉพาะด้วยตนเอง (เช่น การป้องกันการฉ้อโกง ความปลอดภัยโครงสร้างพื้นฐาน เมตริกรวมที่ไม่สามารถโยงไปยังเจ้าของข้อมูลรายบุคคล) จะทำหน้าที่เป็นผู้ควบคุมอิสระสำหรับการประมวลผลดังกล่าว',
|
||||||
|
'ประกาศความเป็นส่วนตัวเฉพาะของ LexAura และ MediAura ที่เผยแพร่บนไซต์หรือสภาพแวดล้อมแอปพลิเคชันที่เกี่ยวข้อง จะให้รายละเอียดเพิ่มเติมเกี่ยวกับประเภทข้อมูล กระแสข้อมูล ระยะเวลาเก็บรักษา และฐานทางกฎหมายเฉพาะของแต่ละผลิตภัณฑ์',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'conservazione',
|
||||||
|
title: '6. ระยะเวลาเก็บรักษา',
|
||||||
|
paragraphs: [],
|
||||||
|
bullets: [
|
||||||
|
'ข้อมูลติดต่อจากเว็บไซต์: 24 เดือนนับจากการโต้ตอบครั้งล่าสุด',
|
||||||
|
'ข้อมูลบัญชี SaaS: ตลอดระยะเวลาความสัมพันธ์ตามสัญญา + 30 วันนับจากการลบ',
|
||||||
|
'ล็อกทางเทคนิคและความปลอดภัย: สูงสุด 30 วัน',
|
||||||
|
'ข้อมูลการเรียกเก็บเงิน: 10 ปีตามที่กฎหมายภาษีอิตาลีกำหนด',
|
||||||
|
'ข้อมูลสุขภาพ: ตามคำสั่งของผู้ควบคุม (ลูกค้า) และข้อผูกพันทางกฎหมาย',
|
||||||
|
'ข้อมูลวิเคราะห์แบบรวม: ไม่มีกำหนดในรูปแบบนิรนาม',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'destinatari',
|
||||||
|
title: '7. ผู้รับและประเภทของผู้ที่ได้รับอนุญาต',
|
||||||
|
paragraphs: [
|
||||||
|
'ข้อมูลอาจถูกประมวลผลโดยบุคลากร NexStudio ที่ได้รับอนุญาตและคำแนะนำ รวมถึงผู้ให้บริการที่มีคุณสมบัติซึ่งทำหน้าที่เป็นผู้ประมวลผล/ผู้ประมวลผลช่วง (เช่น การโฮสต์คลาวด์ บริการโครงสร้างพื้นฐาน การตรวจสอบความปลอดภัย เครื่องมือสื่อสาร)',
|
||||||
|
'รายชื่อผู้ประมวลผลและผู้ประมวลผลช่วงที่เกี่ยวข้องกับลูกค้า SaaS ที่เป็นปัจจุบัน จะจัดให้ผ่านช่องทางตามสัญญาหรือตามคำขอ',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trasferimenti',
|
||||||
|
title: '8. การโอนข้อมูลระหว่างประเทศ',
|
||||||
|
paragraphs: [
|
||||||
|
'เนื่องจากการใช้ผู้ให้บริการคลาวด์ระดับโลก การประมวลผลบางส่วนอาจเกี่ยวข้องกับการโอนไปยังประเทศนอก EEA/EU เมื่อกฎหมายกำหนด NexStudio จะใช้การคุ้มครองที่เหมาะสม (เช่น ข้อสัญญามาตรฐาน มาตรการทางเทคนิค/องค์กรเสริม การประเมินผลกระทบการโอน) และจำกัดการโอนเฉพาะข้อมูลที่จำเป็น',
|
||||||
|
'ข้อมูลจะถูกประมวลผลในเขตเศรษฐกิจยุโรปเป็นหลัก Cloudflare Inc. ปฏิบัติตาม EU-US Data Privacy Framework และใช้ Standard Contractual Clauses (SCC)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sicurezza',
|
||||||
|
title: '9. มาตรการความปลอดภัยและการกำกับดูแล',
|
||||||
|
paragraphs: [
|
||||||
|
'NexStudio ใช้มาตรการทางเทคนิคและองค์กรที่สัดส่วนกับความเสี่ยง:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'การควบคุมการเข้าถึงตามบทบาท การยืนยันตัวตน การแยกสภาพแวดล้อมลูกค้าเชิงตรรกะ และหลักการสิทธิ์น้อยที่สุด',
|
||||||
|
'การเข้ารหัสข้อมูลระหว่างส่ง (TLS 1.3) และขณะเก็บรักษา การจัดการคีย์อย่างปลอดภัย',
|
||||||
|
'การบันทึกล็อก การตรวจสอบ เส้นทางการตรวจสอบ และกระบวนการจัดการเหตุการณ์',
|
||||||
|
'กระบวนการสำรองข้อมูล ความต่อเนื่องในการดำเนินงาน และการกู้คืนจากภัยพิบัติ',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'diritti',
|
||||||
|
title: '10. สิทธิของเจ้าของข้อมูลและวิธีการใช้สิทธิ',
|
||||||
|
paragraphs: [
|
||||||
|
'เจ้าของข้อมูลอาจใช้สิทธิตามกฎหมายที่ใช้บังคับ (เข้าถึง แก้ไข ลบ จำกัด คัดค้าน โอนย้าย ถอนความยินยอม ร้องเรียนต่อหน่วยงานที่มีอำนาจ)',
|
||||||
|
'สำหรับข้อมูลที่ประมวลผลในบริบท SaaS โดยปกติควรร้องขอไปยังลูกค้าผู้ควบคุมก่อน (เช่น สำนักงานกฎหมายหรือองค์กรด้านสุขภาพ) NexStudio สนับสนุนลูกค้าผู้ควบคุมตามสัญญา',
|
||||||
|
'สำหรับข้อมูลที่ NexStudio ประมวลผลในฐานะผู้ควบคุม (เช่น ข้อมูลติดต่อจากเว็บไซต์ ล็อกความปลอดภัยของตนเอง) สามารถส่งคำขอไปที่ privacy@nexstudio.com หรือผ่านแบบฟอร์มบน nexstudio.ai',
|
||||||
|
'หน่วยงานกำกับดูแลที่มีอำนาจ: Garante per la protezione dei dati personali (www.garanteprivacy.it)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cookie-tracking',
|
||||||
|
title: '11. คุกกี้ การวิเคราะห์ และการติดตาม',
|
||||||
|
paragraphs: [
|
||||||
|
'ข้อมูลเกี่ยวกับคุกกี้ทางเทคนิค คุกกี้เชิงวิเคราะห์ (หากมี) และเครื่องมือติดตาม ระบุไว้ในนโยบายคุกกี้ ซึ่งควรอ่านร่วมกับประกาศฉบับนี้',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'minori',
|
||||||
|
title: '12. การประมวลผลข้อมูลของผู้เยาว์',
|
||||||
|
paragraphs: [
|
||||||
|
'บริการของ NexStudio ออกแบบมาสำหรับผู้ใช้ระดับมืออาชีพและธุรกิจ ไม่ได้มุ่งเป้าไปที่ผู้เยาว์โดยเจตนา',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'aggiornamenti',
|
||||||
|
title: '13. การอัปเดตนโยบาย',
|
||||||
|
paragraphs: [
|
||||||
|
'NexStudio อาจอัปเดตประกาศนี้เพื่อปรับตามข้อกำกับ การพัฒนาบริการ การเปลี่ยนแปลงองค์กร หรือการนำฟีเจอร์ใหม่มาใช้ การเปลี่ยนแปลงที่มีนัยสำคัญจะเผยแพร่บนหน้านี้พร้อมอัปเดตเวอร์ชันและวันที่',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const satisfies readonly PrivacySection[],
|
||||||
|
page: {
|
||||||
|
title: 'Privacy — NexStudio',
|
||||||
|
description:
|
||||||
|
'ประกาศความเป็นส่วนตัวกรอบของ NexStudio S.r.l.: เว็บไซต์สถาบัน การติดต่อองค์กร และการอ้างอิงรายละเอียดความเป็นส่วนตัวของ SaaS LexAura (Legal Tech) และ MediAura (Health Tech)',
|
||||||
|
heading: 'ประกาศความเป็นส่วนตัว',
|
||||||
|
lead:
|
||||||
|
'ประกาศกรอบเกี่ยวกับการประมวลผลข้อมูลส่วนบุคคลสำหรับพอร์ทัลสถาบันของ NexStudio S.r.l. สำนักงานจดทะเบียนในอิตาลี [INDIRIZZO_COMPLETO] และสำหรับบริการ SaaS LexAura และ MediAura ประกาศนี้แยกพอร์ทัลสถาบัน NexStudio จากบริการ SaaS LexAura และ MediAura ประกาศความเป็นส่วนตัวเฉพาะผลิตภัณฑ์ที่เผยแพร่บนพอร์ทัลแอปพลิเคชันที่เกี่ยวข้องยังคงเป็นเอกสารอ้างอิงรายละเอียดสำหรับการใช้ผลิตภัณฑ์',
|
||||||
|
versionLabel: 'เวอร์ชัน',
|
||||||
|
inForceFromLabel: 'มีผลตั้งแต่',
|
||||||
|
lastUpdateLabel: 'อัปเดตล่าสุด',
|
||||||
|
relatedDocsLabel: 'เอกสารที่เกี่ยวข้อง',
|
||||||
|
indexLabel: 'สารบัญ',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
} as const;
|
||||||
id: 'finalita-basi',
|
|
||||||
title: '4. Finalità e basi giuridiche del trattamento',
|
export const getPrivacy = (locale: SupportedLocale) => privacyByLocale[locale];
|
||||||
paragraphs: [],
|
|
||||||
bullets: [
|
/** Retro-compatibilità: fallback italiano. */
|
||||||
"Gestione del sito istituzionale, sicurezza e continuità operativa (base: legittimo interesse alla sicurezza, prevenzione abusi e mantenimento del servizio).",
|
export const privacyDocument = privacyByLocale.it.document;
|
||||||
"Gestione richieste pre-contrattuali, demo, contatti commerciali e supporto (base: misure precontrattuali su richiesta dell'interessato; legittimo interesse nella gestione B2B).",
|
export const privacyCrossReferences = privacyByLocale.it.crossReferences;
|
||||||
"Erogazione dei SaaS LexAura e MediAura, autenticazione utenti, gestione tenant e assistenza tecnica (base: esecuzione del contratto con il cliente business).",
|
export const privacyRiferimenti = privacyByLocale.it.riferimenti;
|
||||||
"Conformità normativa, adempimenti amministrativi/fiscali, gestione contenziosi e difesa di diritti (base: obbligo legale e legittimo interesse alla tutela in giudizio).",
|
export const privacySections = privacyByLocale.it.sections;
|
||||||
"Comunicazioni marketing/newsletter, ove attive (base: consenso, salvo eccezioni di soft spam consentite dalla normativa applicabile).",
|
|
||||||
"Miglioramento del servizio e sviluppo funzionalità IA: solo se previsto dal contratto o da istruzioni documentate; in assenza di tale base i dati non sono utilizzati per training generalista.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'ruoli-saas',
|
|
||||||
title: '5. Ruoli privacy specifici per LexAura e MediAura',
|
|
||||||
paragraphs: [
|
|
||||||
"Per i servizi SaaS B2B, il cliente determina normalmente finalità e mezzi del trattamento relativi ai dati caricati nel proprio ambiente applicativo, agendo come titolare del trattamento. NexStudio agisce di regola come responsabile del trattamento per conto del cliente, con nomina contrattuale e istruzioni documentate.",
|
|
||||||
"Qualora NexStudio determini autonomamente finalità specifiche (es. prevenzione frodi, sicurezza infrastrutturale, metriche aggregate non riconducibili al singolo interessato), agisce come titolare autonomo per tali trattamenti.",
|
|
||||||
"Le informative privacy dedicate di LexAura e MediAura, pubblicate nei rispettivi siti o ambienti applicativi, approfondiscono categorie dati, flussi, tempi di retention e basi giuridiche specifiche del singolo prodotto.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'conservazione',
|
|
||||||
title: '6. Tempi di conservazione',
|
|
||||||
paragraphs: [],
|
|
||||||
bullets: [
|
|
||||||
"Dati di contatto dal sito: 24 mesi dall'ultima interazione.",
|
|
||||||
"Dati di account SaaS: per tutta la durata del rapporto contrattuale + 30 giorni dalla cancellazione.",
|
|
||||||
"Log tecnici e di sicurezza: massimo 30 giorni.",
|
|
||||||
"Dati di fatturazione: 10 anni come richiesto dalla normativa fiscale italiana.",
|
|
||||||
"Dati sanitari: secondo le istruzioni del titolare (cliente) e gli obblighi di legge.",
|
|
||||||
"Dati analitici aggregati: indefinitamente in forma anonima.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'destinatari',
|
|
||||||
title: '7. Destinatari e categorie di soggetti autorizzati',
|
|
||||||
paragraphs: [
|
|
||||||
"I dati possono essere trattati da personale NexStudio autorizzato e istruito, nonché da fornitori qualificati che operano come responsabili/sub-responsabili (es. hosting cloud, servizi infrastrutturali, monitoraggio sicurezza, strumenti di comunicazione).",
|
|
||||||
"L'elenco aggiornato dei responsabili e sub-responsabili rilevanti per i clienti SaaS è messo a disposizione nei canali contrattuali o su richiesta.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'trasferimenti',
|
|
||||||
title: '8. Trasferimenti internazionali di dati',
|
|
||||||
paragraphs: [
|
|
||||||
"In ragione dell'uso di fornitori cloud globali, alcuni trattamenti possono comportare trasferimenti verso Paesi extra SEE/UE. Quando richiesto dalla normativa, NexStudio adotta garanzie adeguate (es. clausole contrattuali standard, misure supplementari tecniche/organizzative, valutazioni di impatto sul trasferimento) e limita il trasferimento ai soli dati necessari.",
|
|
||||||
"I dati sono preferibilmente trattati nello Spazio Economico Europeo. Cloudflare Inc. aderisce al Data Privacy Framework UE-USA e adotta Clausole Contrattuali Standard (SCC).",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sicurezza',
|
|
||||||
title: '9. Misure di sicurezza e governance',
|
|
||||||
paragraphs: [
|
|
||||||
"NexStudio adotta misure tecniche e organizzative proporzionate al rischio:",
|
|
||||||
],
|
|
||||||
bullets: [
|
|
||||||
"Controlli di accesso basati su ruoli, autenticazione, segregazione logica degli ambienti cliente e principio del minimo privilegio.",
|
|
||||||
"Cifratura dei dati in transito (TLS 1.3) e a riposo; gestione sicura delle chiavi.",
|
|
||||||
"Logging, monitoraggio, audit trail e procedure di gestione incidenti.",
|
|
||||||
"Procedure di backup, continuità operativa e disaster recovery.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'diritti',
|
|
||||||
title: "10. Diritti degli interessati e modalità di esercizio",
|
|
||||||
paragraphs: [
|
|
||||||
"Gli interessati possono esercitare i diritti previsti dalla normativa applicabile (accesso, rettifica, cancellazione, limitazione, opposizione, portabilità, revoca del consenso, reclamo all'autorità competente).",
|
|
||||||
"Per dati trattati nel contesto dei SaaS, la richiesta va di norma indirizzata prima al cliente titolare (es. studio legale o struttura sanitaria). NexStudio fornisce supporto al cliente titolare secondo contratto.",
|
|
||||||
"Per dati trattati da NexStudio come titolare (es. contatti dal sito, log di sicurezza propri), le richieste possono essere inviate a privacy@nexstudio.com o tramite il form su nexstudio.ai.",
|
|
||||||
"Autorità di controllo competente: Garante per la protezione dei dati personali (www.garanteprivacy.it).",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'cookie-tracking',
|
|
||||||
title: '11. Cookie, analytics e tracciamenti',
|
|
||||||
paragraphs: [
|
|
||||||
"Le informazioni su cookie tecnici, eventuali cookie analitici e strumenti di tracciamento sono descritte nella Cookie Policy, da leggere congiuntamente alla presente informativa.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'minori',
|
|
||||||
title: '12. Trattamento dati di minori',
|
|
||||||
paragraphs: [
|
|
||||||
"I servizi NexStudio sono progettati per un'utenza professionale e business. Non sono rivolti intenzionalmente a minori.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'aggiornamenti',
|
|
||||||
title: '13. Aggiornamenti della policy',
|
|
||||||
paragraphs: [
|
|
||||||
"NexStudio può aggiornare questa informativa per adeguamenti normativi, evoluzione dei servizi, modifiche organizzative o introduzione di nuove funzionalità. Le modifiche rilevanti saranno pubblicate su questa pagina con aggiornamento di versione e data.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
] as const;
|
|
||||||
|
|||||||
@ -3,10 +3,7 @@
|
|||||||
* I contratti B2B su LexAura, MediAura o servizi professionali restano strumenti separati.
|
* I contratti B2B su LexAura, MediAura o servizi professionali restano strumenti separati.
|
||||||
* Aggiornare versione e data in sync con la pagina (box in testa).
|
* Aggiornare versione e data in sync con la pagina (box in testa).
|
||||||
*/
|
*/
|
||||||
export const termsDocument = {
|
import type { SupportedLocale } from './home/navigation';
|
||||||
version: '1.1',
|
|
||||||
inVigoreDal: '[DATA]',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type TermsSection = {
|
export type TermsSection = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -15,96 +12,343 @@ export type TermsSection = {
|
|||||||
bullets?: readonly string[];
|
bullets?: readonly string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const termsSections: readonly TermsSection[] = [
|
const termsByLocale = {
|
||||||
{
|
it: {
|
||||||
id: 'premessa',
|
document: {
|
||||||
title: '1. Premessa e ambito',
|
version: '1.1',
|
||||||
paragraphs: [
|
inVigoreDal: '[DATA]',
|
||||||
"Le presenti condizioni d’uso regolano l’accesso e la consultazione del sito web di NexStudio S.r.l. (di seguito «NexStudio», «noi»), con sede legale in Italia, [INDIRIZZO_COMPLETO]. I servizi software commerciali (inclusi i prodotti LexAura e MediAura) sono regolati da condizioni generali, ordini, offerte o accordi specifici sottoscritti con i clienti: in caso di conflitto tra il presente testo e un contratto B2B sottoscritto, prevalgono i documenti specifici del servizio e gli accordi contrattuali applicabili.",
|
},
|
||||||
"Per il trattamento dei dati personali e i cookie si rimanda rispettivamente all’informativa privacy e alla cookie policy, citate più sotto.",
|
crossReferences: {
|
||||||
],
|
leadNote:
|
||||||
|
'Per LexAura e MediAura, le informative privacy dedicate pubblicate sui rispettivi portali SaaS restano il riferimento di dettaglio del singolo prodotto.',
|
||||||
|
},
|
||||||
|
riferimenti: [
|
||||||
|
{ label: 'Informativa privacy', href: '/privacy' },
|
||||||
|
{ label: 'Cookie policy', href: '/cookies' },
|
||||||
|
{ label: 'Diritti GDPR', href: '/gdpr' },
|
||||||
|
{ label: 'Codice etico', href: '/codice-etico' },
|
||||||
|
] as const,
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
id: 'premessa',
|
||||||
|
title: '1. Premessa e ambito',
|
||||||
|
paragraphs: [
|
||||||
|
"Le presenti condizioni d’uso regolano l’accesso e la consultazione del sito web di NexStudio S.r.l. (di seguito «NexStudio», «noi»), con sede legale in Italia, [INDIRIZZO_COMPLETO]. I servizi software commerciali (inclusi i prodotti LexAura e MediAura) sono regolati da condizioni generali, ordini, offerte o accordi specifici sottoscritti con i clienti: in caso di conflitto tra il presente testo e un contratto B2B sottoscritto, prevalgono i documenti specifici del servizio e gli accordi contrattuali applicabili.",
|
||||||
|
'Per il trattamento dei dati personali e i cookie si rimanda rispettivamente all’informativa privacy e alla cookie policy, citate più sotto.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'accettazione',
|
||||||
|
title: '2. Accettazione e requisiti',
|
||||||
|
paragraphs: [
|
||||||
|
'L’utilizzo del sito (navigazione oltre la mera apertura della home, invio di moduli ove presenti, uso di strumenti di contatto) implica la conoscenza e l’accettazione delle presenti condizioni e della versione in vigore della privacy policy e, ove applicabile, della cookie policy.',
|
||||||
|
'L’utente dichiara di avere l’età e la capacità legale per vincolarsi, o di agire sotto la responsabilità di un rappresentante legale idoneo.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'servizi',
|
||||||
|
title: '3. Contenuti e servizi informativi',
|
||||||
|
paragraphs: [
|
||||||
|
'Il sito ha finalità promozionali e informative su NexStudio, sui prodotti e sull’organizzazione. Le descrizioni, schermate, esempi e roadmap non costituiscono offerta vincolante né promessa di feature: restano subordinati a contrattualizzazione, disponibilità tecnica e condizioni di mercato.',
|
||||||
|
'NexStudio si impegna a mantenere le informazioni ragionevolmente aggiornate, senza obbligo di esaustività o di aggiornamento in tempo reale. Può modificare, sospendere o limitare l’accesso a sezioni del sito per manutenzione, sicurezza o evoluzione del prodotto.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'uso-lecito',
|
||||||
|
title: '4. Uso lecito del sito',
|
||||||
|
paragraphs: [
|
||||||
|
'L’utente si impegna a usare il sito in conformità a legge, buona fede e condotta professionale, in particolare a:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'non introdurre malware, né tentare di eludere misure di sicurezza, né effettuare attività che sovraccaricano in modo abnorme o automatizzato non autorizzato l’infrastruttura;',
|
||||||
|
'non copiare o sfruttare illecitamente i contenuti (cfr. sotto proprietà intellettuale) né impersonare NexStudio o terzi;',
|
||||||
|
'non usare canali di contatto per contenuti illeciti, diffamatori, spam o al di fuori delle finalità richieste (commerciale, supporto, segnalazioni, ecc.).',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'proprieta',
|
||||||
|
title: '5. Proprietà intellettuale',
|
||||||
|
paragraphs: [
|
||||||
|
'Testi, logotipi, elementi grafici, layout, nomi di prodotto (inclusi LexAura e MediAura) e, ove presenti, software dimostrativo o materiali scaricabili, sono protetti dalle norme sul diritto d’autore e dalle norme sui marchi applicabili. È vietata la riproduzione, distribuzione o modifica non espressamente autorizzata, salvo uso consentito per legge (citazione, breve estratto con attribuzione) o espressa deroga scritta.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'terze-parti',
|
||||||
|
title: '6. Link, servizi di terzi e strumenti incorporati',
|
||||||
|
paragraphs: [
|
||||||
|
'Il sito può contenere collegamenti a siti o servizi di terzi. NexStudio non controlla tali risorse e non risponde dei loro contenuti o politiche; l’accesso avviene sotto la responsabilità dell’utente, che dovrà leggere le condizioni e l’informativa privacy del terzo.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'responsabilita',
|
||||||
|
title: '7. Limitazione di responsabilità',
|
||||||
|
paragraphs: [
|
||||||
|
'Nella misura massima consentita dalla legge applicabile, il sito e i suoi contenuti sono forniti «così come sono» e «secondo disponibilità», senza garanzie implicite di adeguatezza a un fine particolare, non interruzione o assenza di errori. Il materiale informativo non sostituisce consulenza legale, sanitaria o professionale: decisioni e responsabilità restano a carico del visitatore o del professionista e dei propri clienti.',
|
||||||
|
'NexStudio non risponde per danni indiretti, consequenziali, mancati guadagni, perdita di dati, interruzioni, azioni di terzi o fatti oltre il controllo ragionevole, salvo quanto non possa essere escluso o limitato per legge imperativa.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sospensione',
|
||||||
|
title: '8. Sospensione, violazione e forza maggiore',
|
||||||
|
paragraphs: [
|
||||||
|
'NexStudio può sospendere, limitare o interrompere l’accesso in caso di violazione delle presenti condizioni, rischio per la sicurezza, ordini di autorità, o forza maggiore. La mancata esecuzione in tali casi non costituisce inadempimento a titolo di danno.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'legge',
|
||||||
|
title: '9. Legge applicabile e foro competente',
|
||||||
|
paragraphs: [
|
||||||
|
'Salvo disposizioni inderogabili a tutela del consumatore, le presenti condizioni e ogni contestazione inerente al sito informativo sono regolate dalla legge italiana. Il foro competente è Padova.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'contatti-termini',
|
||||||
|
title: '10. Contatti in merito a queste condizioni',
|
||||||
|
paragraphs: [
|
||||||
|
'Per domande sulle presenti condizioni d’uso del sito, utilizzare i canali indicati nella sezione contatti del sito. Per trattamento dei dati personali e diritti: privacy@nexstudio.com o il form su nexstudio.ai.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const satisfies readonly TermsSection[],
|
||||||
|
page: {
|
||||||
|
title: 'Condizioni d’uso — NexStudio',
|
||||||
|
description:
|
||||||
|
'Condizioni d’uso del sito web NexStudio S.r.l.: accettazione, utilizzo lecito, responsabilità, legge italiana e foro di Padova. LexAura e MediAura, servizi B2B per contratto a parte.',
|
||||||
|
heading: 'Condizioni d’uso',
|
||||||
|
lead:
|
||||||
|
"Si applicano alla navigazione e all'uso del sito informativo pubblico di NexStudio S.r.l., con sede legale in Italia, [INDIRIZZO_COMPLETO]. I contratti d'abbonamento o d'acquisto sui prodotti software restano retti da offerte, ordini o accordi separati. Per LexAura e MediAura, le informative privacy dedicate pubblicate sui rispettivi portali SaaS restano il riferimento di dettaglio del singolo prodotto.",
|
||||||
|
versionLabel: 'Versione',
|
||||||
|
inForceFromLabel: 'In vigore dal',
|
||||||
|
lastUpdateLabel: 'Ultimo aggiornamento',
|
||||||
|
relatedDocsLabel: 'Documenti connessi',
|
||||||
|
indexLabel: 'Indice',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
en: {
|
||||||
id: 'accettazione',
|
document: {
|
||||||
title: '2. Accettazione e requisiti',
|
version: '1.1',
|
||||||
paragraphs: [
|
inVigoreDal: '[DATA]',
|
||||||
"L’utilizzo del sito (navigazione oltre la mera apertura della home, invio di moduli ove presenti, uso di strumenti di contatto) implica la conoscenza e l’accettazione delle presenti condizioni e della versione in vigore della privacy policy e, ove applicabile, della cookie policy.",
|
},
|
||||||
"L’utente dichiara di avere l’età e la capacità legale per vincolarsi, o di agire sotto la responsabilità di un rappresentante legale idoneo.",
|
crossReferences: {
|
||||||
],
|
leadNote:
|
||||||
|
'For LexAura and MediAura, the dedicated privacy notices published on the respective SaaS portals remain the detailed reference for each product.',
|
||||||
|
},
|
||||||
|
riferimenti: [
|
||||||
|
{ label: 'Privacy notice', href: '/privacy' },
|
||||||
|
{ label: 'Cookie policy', href: '/cookies' },
|
||||||
|
{ label: 'GDPR rights', href: '/gdpr' },
|
||||||
|
{ label: 'Code of ethics', href: '/codice-etico' },
|
||||||
|
] as const,
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
id: 'premessa',
|
||||||
|
title: '1. Preamble and scope',
|
||||||
|
paragraphs: [
|
||||||
|
'These terms of use govern access to and consultation of the website of NexStudio S.r.l. (hereinafter “NexStudio”, “we”), with registered office in Italy, [INDIRIZZO_COMPLETO]. Commercial software services (including the LexAura and MediAura products) are governed by general terms, orders, offers, or specific agreements entered into with customers: in case of conflict between this text and a signed B2B contract, the service-specific documents and applicable contractual agreements prevail.',
|
||||||
|
'For the processing of personal data and cookies, reference is made respectively to the privacy notice and the cookie policy cited below.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'accettazione',
|
||||||
|
title: '2. Acceptance and requirements',
|
||||||
|
paragraphs: [
|
||||||
|
'Use of the site (browsing beyond merely opening the home page, submitting forms where present, using contact tools) implies knowledge and acceptance of these terms and of the privacy policy in force and, where applicable, the cookie policy.',
|
||||||
|
'The user declares that they have the age and legal capacity to be bound, or that they act under the responsibility of a suitable legal representative.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'servizi',
|
||||||
|
title: '3. Informational content and services',
|
||||||
|
paragraphs: [
|
||||||
|
'The site has promotional and informational purposes regarding NexStudio, its products, and its organization. Descriptions, screenshots, examples, and roadmaps do not constitute a binding offer or a promise of features: they remain subject to contracting, technical availability, and market conditions.',
|
||||||
|
'NexStudio undertakes to keep information reasonably up to date, without any obligation of exhaustiveness or real-time updates. It may modify, suspend, or restrict access to sections of the site for maintenance, security, or product evolution.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'uso-lecito',
|
||||||
|
title: '4. Lawful use of the site',
|
||||||
|
paragraphs: [
|
||||||
|
'The user undertakes to use the site in accordance with law, good faith, and professional conduct, in particular to:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'not introduce malware, attempt to circumvent security measures, or carry out activities that abnormally overload or involve unauthorized automation of the infrastructure;',
|
||||||
|
'not copy or unlawfully exploit content (see intellectual property below) or impersonate NexStudio or third parties;',
|
||||||
|
'not use contact channels for unlawful, defamatory, or spam content, or outside the requested purposes (commercial, support, reports, etc.).',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'proprieta',
|
||||||
|
title: '5. Intellectual property',
|
||||||
|
paragraphs: [
|
||||||
|
'Texts, logos, graphic elements, layout, product names (including LexAura and MediAura) and, where present, demo software or downloadable materials are protected by applicable copyright and trademark laws. Reproduction, distribution, or modification not expressly authorized is prohibited, except for use permitted by law (citation, short excerpt with attribution) or an express written waiver.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'terze-parti',
|
||||||
|
title: '6. Links, third-party services, and embedded tools',
|
||||||
|
paragraphs: [
|
||||||
|
'The site may contain links to third-party sites or services. NexStudio does not control such resources and is not responsible for their content or policies; access is at the user’s responsibility, who must read the third party’s terms and privacy notice.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'responsabilita',
|
||||||
|
title: '7. Limitation of liability',
|
||||||
|
paragraphs: [
|
||||||
|
'To the maximum extent permitted by applicable law, the site and its contents are provided “as is” and “as available”, without implied warranties of fitness for a particular purpose, uninterrupted availability, or absence of errors. Informational material does not replace legal, healthcare, or professional advice: decisions and responsibility remain with the visitor or the professional and their own clients.',
|
||||||
|
'NexStudio is not liable for indirect, consequential damages, lost profits, data loss, interruptions, third-party actions, or events beyond reasonable control, except where exclusion or limitation is not permitted by mandatory law.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sospensione',
|
||||||
|
title: '8. Suspension, breach, and force majeure',
|
||||||
|
paragraphs: [
|
||||||
|
'NexStudio may suspend, restrict, or interrupt access in case of breach of these terms, security risk, orders of authorities, or force majeure. Non-performance in such cases does not constitute a breach giving rise to damages.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'legge',
|
||||||
|
title: '9. Governing law and competent court',
|
||||||
|
paragraphs: [
|
||||||
|
'Save for mandatory provisions protecting consumers, these terms and any dispute relating to the informational site are governed by Italian law. The competent court is Padua.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'contatti-termini',
|
||||||
|
title: '10. Contacts regarding these terms',
|
||||||
|
paragraphs: [
|
||||||
|
'For questions about these website terms of use, use the channels indicated in the contacts section of the site. For personal data processing and rights: privacy@nexstudio.com or the form on nexstudio.ai.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const satisfies readonly TermsSection[],
|
||||||
|
page: {
|
||||||
|
title: 'Terms of use — NexStudio',
|
||||||
|
description:
|
||||||
|
'Terms of use of the NexStudio S.r.l. website: acceptance, lawful use, liability, Italian law and Padua court. LexAura and MediAura B2B services under separate contract.',
|
||||||
|
heading: 'Terms of use',
|
||||||
|
lead:
|
||||||
|
'These terms apply to browsing and use of the public informational website of NexStudio S.r.l., registered office in Italy, [INDIRIZZO_COMPLETO]. Subscription or purchase contracts for software products remain governed by separate offers, orders, or agreements. For LexAura and MediAura, the dedicated privacy notices published on the respective SaaS portals remain the detailed reference for each product.',
|
||||||
|
versionLabel: 'Version',
|
||||||
|
inForceFromLabel: 'In force from',
|
||||||
|
lastUpdateLabel: 'Last updated',
|
||||||
|
relatedDocsLabel: 'Related documents',
|
||||||
|
indexLabel: 'Contents',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
th: {
|
||||||
id: 'servizi',
|
document: {
|
||||||
title: '3. Contenuti e servizi informativi',
|
version: '1.1',
|
||||||
paragraphs: [
|
inVigoreDal: '[DATA]',
|
||||||
"Il sito ha finalità promozionali e informative su NexStudio, sui prodotti e sull’organizzazione. Le descrizioni, schermate, esempi e roadmap non costituiscono offerta vincolante né promessa di feature: restano subordinati a contrattualizzazione, disponibilità tecnica e condizioni di mercato.",
|
},
|
||||||
"NexStudio si impegna a mantenere le informazioni ragionevolmente aggiornate, senza obbligo di esaustività o di aggiornamento in tempo reale. Può modificare, sospendere o limitare l’accesso a sezioni del sito per manutenzione, sicurezza o evoluzione del prodotto.",
|
crossReferences: {
|
||||||
],
|
leadNote:
|
||||||
|
'สำหรับ LexAura และ MediAura ประกาศความเป็นส่วนตัวเฉพาะที่เผยแพร่บนพอร์ทัล SaaS ที่เกี่ยวข้องยังคงเป็นเอกสารอ้างอิงรายละเอียดของแต่ละผลิตภัณฑ์',
|
||||||
|
},
|
||||||
|
riferimenti: [
|
||||||
|
{ label: 'ประกาศความเป็นส่วนตัว', href: '/privacy' },
|
||||||
|
{ label: 'นโยบายคุกกี้', href: '/cookies' },
|
||||||
|
{ label: 'สิทธิ GDPR', href: '/gdpr' },
|
||||||
|
{ label: 'จรรยาบรรณ', href: '/codice-etico' },
|
||||||
|
] as const,
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
id: 'premessa',
|
||||||
|
title: '1. คำนำและขอบเขต',
|
||||||
|
paragraphs: [
|
||||||
|
'เงื่อนไขการใช้งานฉบับนี้กำกับดูแลการเข้าถึงและการดูเว็บไซต์ของ NexStudio S.r.l. (ต่อไปนี้เรียก «NexStudio» «เรา») สำนักงานจดทะเบียนในอิตาลี [INDIRIZZO_COMPLETO] บริการซอฟต์แวร์เชิงพาณิชย์ (รวมถึงผลิตภัณฑ์ LexAura และ MediAura) อยู่ภายใต้เงื่อนไขทั่วไป คำสั่งซื้อ ข้อเสนอ หรือข้อตกลงเฉพาะที่ทำกับลูกค้า หากเกิดความขัดแย้งระหว่างข้อความนี้กับสัญญา B2B ที่ลงนามแล้ว ให้ยึดเอกสารเฉพาะของบริการและข้อตกลงตามสัญญาที่ใช้บังคับ',
|
||||||
|
'สำหรับการประมวลผลข้อมูลส่วนบุคคลและคุกกี้ ให้อ้างอิงประกาศความเป็นส่วนตัวและนโยบายคุกกี้ที่ระบุไว้ด้านล่างตามลำดับ',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'accettazione',
|
||||||
|
title: '2. การยอมรับและคุณสมบัติ',
|
||||||
|
paragraphs: [
|
||||||
|
'การใช้เว็บไซต์ (การท่องเว็บนอกเหนือจากการเปิดหน้าแรก การส่งแบบฟอร์มหากมี การใช้เครื่องมือติดต่อ) ถือว่าทราบและยอมรับเงื่อนไขฉบับนี้ รวมถึงนโยบายความเป็นส่วนตัวที่ใช้บังคับ และนโยบายคุกกี้หากเกี่ยวข้อง',
|
||||||
|
'ผู้ใช้รับรองว่ามีอายุและความสามารถทางกฎหมายที่จะผูกพัน หรือดำเนินการภายใต้ความรับผิดชอบของตัวแทนทางกฎหมายที่เหมาะสม',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'servizi',
|
||||||
|
title: '3. เนื้อหาและบริการข้อมูล',
|
||||||
|
paragraphs: [
|
||||||
|
'เว็บไซต์มีวัตถุประสงค์เพื่อส่งเสริมและให้ข้อมูลเกี่ยวกับ NexStudio ผลิตภัณฑ์ และองค์กร คำอธิบาย ภาพหน้าจอ ตัวอย่าง และ roadmap ไม่ถือเป็นข้อเสนอที่มีผลผูกพันหรือคำมั่นสัญญาฟีเจอร์: ยังขึ้นอยู่กับการทำสัญญา ความพร้อมทางเทคนิค และเงื่อนไขตลาด',
|
||||||
|
'NexStudio มุ่งมั่นรักษาข้อมูลให้เป็นปัจจุบันอย่างสมเหตุสมผล โดยไม่มีข้อผูกพันว่าต้องครบถ้วนหรืออัปเดตแบบเรียลไทม์ อาจแก้ไข ระงับ หรือจำกัดการเข้าถึงส่วนต่างๆ ของเว็บไซต์เพื่อบำรุงรักษา ความปลอดภัย หรือการพัฒนาผลิตภัณฑ์',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'uso-lecito',
|
||||||
|
title: '4. การใช้เว็บไซต์โดยชอบด้วยกฎหมาย',
|
||||||
|
paragraphs: [
|
||||||
|
'ผู้ใช้ตกลงใช้เว็บไซต์ให้สอดคล้องกับกฎหมาย เจตนาดี และพฤติกรรมระดับมืออาชีพ โดยเฉพาะอย่างยิ่ง:',
|
||||||
|
],
|
||||||
|
bullets: [
|
||||||
|
'ไม่นำมัลแวร์เข้ามา ไม่พยายามหลีกเลี่ยงมาตรการความปลอดภัย และไม่ดำเนินกิจกรรมที่ทำให้โครงสร้างพื้นฐานรับภาระเกินปกติหรือใช้ระบบอัตโนมัติโดยไม่ได้รับอนุญาต;',
|
||||||
|
'ไม่คัดลอกหรือใช้ประโยชน์เนื้อหาโดยมิชอบ (ดูทรัพย์สินทางปัญญาด้านล่าง) และไม่แอบอ้างเป็น NexStudio หรือบุคคลที่สาม;',
|
||||||
|
'ไม่ใช้ช่องทางติดต่อเพื่อเนื้อหาที่ผิดกฎหมาย หมิ่นประมาท สแปม หรือนอกเหนือวัตถุประสงค์ที่ร้องขอ (เชิงพาณิชย์ การสนับสนุน การรายงาน ฯลฯ)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'proprieta',
|
||||||
|
title: '5. ทรัพย์สินทางปัญญา',
|
||||||
|
paragraphs: [
|
||||||
|
'ข้อความ โลโก้ องค์ประกอบกราฟิก เลย์เอาต์ ชื่อผลิตภัณฑ์ (รวมถึง LexAura และ MediAura) และซอฟต์แวร์สาธิตหรือวัสดุที่ดาวน์โหลดได้ (หากมี) ได้รับการคุ้มครองตามกฎหมายลิขสิทธิ์และเครื่องหมายการค้าที่ใช้บังคับ ห้ามทำซ้ำ แจกจ่าย หรือแก้ไขโดยไม่ได้รับอนุญาตอย่างชัดเจน เว้นแต่การใช้ที่กฎหมายอนุญาต (การอ้างอิง ข้อความสั้นพร้อมระบุแหล่งที่มา) หรือการผ่อนผันเป็นลายลักษณ์อักษร',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'terze-parti',
|
||||||
|
title: '6. ลิงก์ บริการของบุคคลที่สาม และเครื่องมือที่ฝังไว้',
|
||||||
|
paragraphs: [
|
||||||
|
'เว็บไซต์อาจมีลิงก์ไปยังไซต์หรือบริการของบุคคลที่สาม NexStudio ไม่ได้ควบคุมทรัพยากรดังกล่าวและไม่รับผิดชอบต่อเนื้อหาหรือนโยบายของบุคคลนั้น การเข้าถึงเป็นความรับผิดชอบของผู้ใช้ ซึ่งต้องอ่านเงื่อนไขและประกาศความเป็นส่วนตัวของบุคคลที่สาม',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'responsabilita',
|
||||||
|
title: '7. ข้อจำกัดความรับผิด',
|
||||||
|
paragraphs: [
|
||||||
|
'ในขอบเขตสูงสุดที่กฎหมายที่ใช้บังคับอนุญาต เว็บไซต์และเนื้อหาถูกจัดให้ «ตามสภาพ» และ «ตามความพร้อมใช้งาน» โดยไม่มีการรับประกันโดยนัยว่าเหมาะสมกับวัตถุประสงค์เฉพาะ ไม่มีการรับประกันว่าจะไม่ขัดข้อง หรือไม่มีข้อผิดพลาด เอกสารข้อมูลไม่ทดแทนคำปรึกษาด้านกฎหมาย สุขภาพ หรือวิชาชีพ: การตัดสินใจและความรับผิดชอบยังคงอยู่กับผู้เยี่ยมชมหรือผู้ประกอบวิชาชีพและลูกค้าของตน',
|
||||||
|
'NexStudio ไม่รับผิดต่อความเสียหายทางอ้อม ความเสียหายที่เป็นผลสืบเนื่อง กำไรที่สูญเสีย การสูญเสียข้อมูล การขัดข้อง การกระทำของบุคคลที่สาม หรือเหตุการณ์ที่อยู่นอกการควบคุมอย่างสมเหตุสมผล เว้นแต่สิ่งที่กฎหมายบังคับไม่อนุญาตให้ยกเว้นหรือจำกัด',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sospensione',
|
||||||
|
title: '8. การระงับ การละเมิด และเหตุสุดวิสัย',
|
||||||
|
paragraphs: [
|
||||||
|
'NexStudio อาจระงับ จำกัด หรือยุติการเข้าถึงในกรณีที่มีการละเมิดเงื่อนไขฉบับนี้ ความเสี่ยงด้านความปลอดภัย คำสั่งของหน่วยงาน หรือเหตุสุดวิสัย การไม่ปฏิบัติตามในกรณีดังกล่าวไม่ถือเป็นการผิดสัญญาที่ก่อให้เกิดค่าเสียหาย',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'legge',
|
||||||
|
title: '9. กฎหมายที่ใช้บังคับและศาลที่มีอำนาจ',
|
||||||
|
paragraphs: [
|
||||||
|
'เว้นแต่บทบัญญัติที่ไม่อาจยกเว้นได้เพื่อคุ้มครองผู้บริโภค เงื่อนไขฉบับนี้และข้อพิพาทใดๆ ที่เกี่ยวกับเว็บไซต์ข้อมูลนี้อยู่ภายใต้กฎหมายอิตาลี ศาลที่มีอำนาจคือ Padova',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'contatti-termini',
|
||||||
|
title: '10. การติดต่อเกี่ยวกับเงื่อนไขเหล่านี้',
|
||||||
|
paragraphs: [
|
||||||
|
'สำหรับคำถามเกี่ยวกับเงื่อนไขการใช้งานเว็บไซต์ฉบับนี้ ให้ใช้ช่องทางที่ระบุในส่วนติดต่อของเว็บไซต์ สำหรับการประมวลผลข้อมูลส่วนบุคคลและสิทธิ: privacy@nexstudio.com หรือแบบฟอร์มบน nexstudio.ai',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const satisfies readonly TermsSection[],
|
||||||
|
page: {
|
||||||
|
title: 'เงื่อนไขการใช้งาน — NexStudio',
|
||||||
|
description:
|
||||||
|
'เงื่อนไขการใช้งานเว็บไซต์ NexStudio S.r.l.: การยอมรับ การใช้โดยชอบ ความรับผิด กฎหมายอิตาลี และศาล Padova บริการ B2B LexAura และ MediAura อยู่ภายใต้สัญญาแยกต่างหาก',
|
||||||
|
heading: 'เงื่อนไขการใช้งาน',
|
||||||
|
lead:
|
||||||
|
'เงื่อนไขเหล่านี้ใช้กับการท่องเว็บและการใช้เว็บไซต์ข้อมูลสาธารณะของ NexStudio S.r.l. สำนักงานจดทะเบียนในอิตาลี [INDIRIZZO_COMPLETO] สัญญาสมัครสมาชิกหรือซื้อผลิตภัณฑ์ซอฟต์แวร์ยังคงอยู่ภายใต้ข้อเสนอ คำสั่งซื้อ หรือข้อตกลงแยกต่างหาก สำหรับ LexAura และ MediAura ประกาศความเป็นส่วนตัวเฉพาะที่เผยแพร่บนพอร์ทัล SaaS ที่เกี่ยวข้องยังคงเป็นเอกสารอ้างอิงรายละเอียดของแต่ละผลิตภัณฑ์',
|
||||||
|
versionLabel: 'เวอร์ชัน',
|
||||||
|
inForceFromLabel: 'มีผลตั้งแต่',
|
||||||
|
lastUpdateLabel: 'อัปเดตล่าสุด',
|
||||||
|
relatedDocsLabel: 'เอกสารที่เกี่ยวข้อง',
|
||||||
|
indexLabel: 'สารบัญ',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'uso-lecito',
|
|
||||||
title: '4. Uso lecito del sito',
|
|
||||||
paragraphs: [
|
|
||||||
"L’utente si impegna a usare il sito in conformità a legge, buona fede e condotta professionale, in particolare a:",
|
|
||||||
],
|
|
||||||
bullets: [
|
|
||||||
'non introdurre malware, né tentare di eludere misure di sicurezza, né effettuare attività che sovraccaricano in modo abnorme o automatizzato non autorizzato l’infrastruttura;',
|
|
||||||
'non copiare o sfruttare illecitamente i contenuti (cfr. sotto proprietà intellettuale) né impersonare NexStudio o terzi;',
|
|
||||||
"non usare canali di contatto per contenuti illeciti, diffamatori, spam o al di fuori delle finalità richieste (commerciale, supporto, segnalazioni, ecc.).",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'proprieta',
|
|
||||||
title: '5. Proprietà intellettuale',
|
|
||||||
paragraphs: [
|
|
||||||
"Testi, logotipi, elementi grafici, layout, nomi di prodotto (inclusi LexAura e MediAura) e, ove presenti, software dimostrativo o materiali scaricabili, sono protetti dalle norme sul diritto d’autore e dalle norme sui marchi applicabili. È vietata la riproduzione, distribuzione o modifica non espressamente autorizzata, salvo uso consentito per legge (citazione, breve estratto con attribuzione) o espressa deroga scritta.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'terze-parti',
|
|
||||||
title: '6. Link, servizi di terzi e strumenti incorporati',
|
|
||||||
paragraphs: [
|
|
||||||
"Il sito può contenere collegamenti a siti o servizi di terzi. NexStudio non controlla tali risorse e non risponde dei loro contenuti o politiche; l’accesso avviene sotto la responsabilità dell’utente, che dovrà leggere le condizioni e l’informativa privacy del terzo.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'responsabilita',
|
|
||||||
title: '7. Limitazione di responsabilità',
|
|
||||||
paragraphs: [
|
|
||||||
"Nella misura massima consentita dalla legge applicabile, il sito e i suoi contenuti sono forniti «così come sono» e «secondo disponibilità», senza garanzie implicite di adeguatezza a un fine particolare, non interruzione o assenza di errori. Il materiale informativo non sostituisce consulenza legale, sanitaria o professionale: decisioni e responsabilità restano a carico del visitatore o del professionista e dei propri clienti.",
|
|
||||||
"NexStudio non risponde per danni indiretti, consequenziali, mancati guadagni, perdita di dati, interruzioni, azioni di terzi o fatti oltre il controllo ragionevole, salvo quanto non possa essere escluso o limitato per legge imperativa.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sospensione',
|
|
||||||
title: '8. Sospensione, violazione e forza maggiore',
|
|
||||||
paragraphs: [
|
|
||||||
"NexStudio può sospendere, limitare o interrompere l’accesso in caso di violazione delle presenti condizioni, rischio per la sicurezza, ordini di autorità, o forza maggiore. La mancata esecuzione in tali casi non costituisce inadempimento a titolo di danno.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'legge',
|
|
||||||
title: '9. Legge applicabile e foro competente',
|
|
||||||
paragraphs: [
|
|
||||||
"Salvo disposizioni inderogabili a tutela del consumatore, le presenti condizioni e ogni contestazione inerente al sito informativo sono regolate dalla legge italiana. Il foro competente è Padova.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'contatti-termini',
|
|
||||||
title: '10. Contatti in merito a queste condizioni',
|
|
||||||
paragraphs: [
|
|
||||||
"Per domande sulle presenti condizioni d’uso del sito, utilizzare i canali indicati nella sezione contatti del sito. Per trattamento dei dati personali e diritti: privacy@nexstudio.com o il form su nexstudio.ai.",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const termsRiferimenti: readonly { label: string; href: string }[] = [
|
|
||||||
{ label: 'Informativa privacy', href: '/privacy' },
|
|
||||||
{ label: 'Cookie policy', href: '/cookies' },
|
|
||||||
{ label: 'Diritti GDPR', href: '/gdpr' },
|
|
||||||
{ label: 'Codice etico', href: '/codice-etico' },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const termsCrossReferences = {
|
|
||||||
leadNote:
|
|
||||||
"Per LexAura e MediAura, le informative privacy dedicate pubblicate sui rispettivi portali SaaS restano il riferimento di dettaglio del singolo prodotto.",
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const getTerms = (locale: SupportedLocale) => termsByLocale[locale];
|
||||||
|
|
||||||
|
/** Retro-compatibilità: fallback italiano. */
|
||||||
|
export const termsDocument = termsByLocale.it.document;
|
||||||
|
export const termsCrossReferences = termsByLocale.it.crossReferences;
|
||||||
|
export const termsRiferimenti = termsByLocale.it.riferimenti;
|
||||||
|
export const termsSections = termsByLocale.it.sections;
|
||||||
|
|||||||
@ -20,11 +20,12 @@ const canonicalUrl = Astro.url.toString();
|
|||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang={htmlLang} class="bg-nx-bg">
|
<html lang={htmlLang} class="bg-nx-bg" translate="no">
|
||||||
<head>
|
<head>
|
||||||
<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} />
|
||||||
|
<meta name="google" content="notranslate" />
|
||||||
<link rel="canonical" href={canonicalUrl} />
|
<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:type" content="website" />
|
||||||
@ -46,7 +47,7 @@ const canonicalUrl = Astro.url.toString();
|
|||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen">
|
<body class="notranslate min-h-screen">
|
||||||
<slot />
|
<slot />
|
||||||
<CookieConsent />
|
<CookieConsent />
|
||||||
<ChatGateModal />
|
<ChatGateModal />
|
||||||
|
|||||||
47
src/lib/email.ts
Normal file
47
src/lib/email.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Outbound email via Cloudflare Email Service (`send_email` binding).
|
||||||
|
*/
|
||||||
|
import { env as workerEnv } from 'cloudflare:workers';
|
||||||
|
|
||||||
|
export type RuntimeEnv = Record<string, unknown>;
|
||||||
|
|
||||||
|
type EmailBinding = {
|
||||||
|
send: (message: {
|
||||||
|
to: string;
|
||||||
|
from: string;
|
||||||
|
subject: string;
|
||||||
|
text?: string;
|
||||||
|
html?: string;
|
||||||
|
replyTo?: string;
|
||||||
|
}) => Promise<{ messageId: string }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getEmailBinding(runtimeEnv?: RuntimeEnv): EmailBinding | null {
|
||||||
|
const env = (runtimeEnv ?? (workerEnv as unknown as RuntimeEnv)) as RuntimeEnv;
|
||||||
|
const binding = env.EMAIL;
|
||||||
|
if (binding && typeof binding === 'object' && 'send' in binding) {
|
||||||
|
return binding as EmailBinding;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendCloudflareEmail(
|
||||||
|
email: EmailBinding,
|
||||||
|
options: {
|
||||||
|
fromEmail: string;
|
||||||
|
toEmail: string;
|
||||||
|
subject: string;
|
||||||
|
text: string;
|
||||||
|
html?: string;
|
||||||
|
replyTo?: string;
|
||||||
|
},
|
||||||
|
): Promise<void> {
|
||||||
|
await email.send({
|
||||||
|
from: options.fromEmail,
|
||||||
|
to: options.toEmail,
|
||||||
|
subject: options.subject,
|
||||||
|
text: options.text,
|
||||||
|
...(options.html ? { html: options.html } : {}),
|
||||||
|
...(options.replyTo ? { replyTo: options.replyTo } : {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
109
src/lib/newsletter.ts
Normal file
109
src/lib/newsletter.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/**
|
||||||
|
* Shared helpers for newsletter API routes (Cloudflare Worker / Astro).
|
||||||
|
*/
|
||||||
|
import { env as workerEnv } from 'cloudflare:workers';
|
||||||
|
|
||||||
|
export type RuntimeEnv = Record<string, unknown>;
|
||||||
|
|
||||||
|
export type SubscriberStatus = 'pending' | 'active' | 'unsubscribed';
|
||||||
|
|
||||||
|
export type NewsletterSubscriber = {
|
||||||
|
id: number;
|
||||||
|
email: string;
|
||||||
|
status: SubscriberStatus;
|
||||||
|
locale: string;
|
||||||
|
confirm_token: string | null;
|
||||||
|
unsubscribe_token: string;
|
||||||
|
source: string;
|
||||||
|
created_at: string;
|
||||||
|
confirmed_at: string | null;
|
||||||
|
unsubscribed_at: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Astro v6+: use `cloudflare:workers` env (locals.runtime.env was removed). */
|
||||||
|
export function getRuntimeEnv(_locals?: unknown): RuntimeEnv {
|
||||||
|
return workerEnv as unknown as RuntimeEnv;
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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() : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNewsletterDb(runtimeEnv: RuntimeEnv): D1Database | null {
|
||||||
|
const db = runtimeEnv.NEWSLETTER_DB;
|
||||||
|
if (db && typeof db === 'object' && 'prepare' in db) {
|
||||||
|
return db as D1Database;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sanitizeRedirect(candidate: string, requestUrl: URL): string {
|
||||||
|
if (!candidate) return '';
|
||||||
|
try {
|
||||||
|
const u = new URL(candidate, requestUrl);
|
||||||
|
if (u.origin !== requestUrl.origin) return '';
|
||||||
|
return u.pathname + u.search + u.hash;
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function required(value: FormDataEntryValue | null): string {
|
||||||
|
return typeof value === 'string' ? value.trim() : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidEmail(email: string): boolean {
|
||||||
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) && email.length <= 254;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeEmail(email: string): string {
|
||||||
|
return email.trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createToken(): string {
|
||||||
|
const bytes = new Uint8Array(32);
|
||||||
|
crypto.getRandomValues(bytes);
|
||||||
|
return [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getEmailBinding, sendCloudflareEmail } from './email';
|
||||||
|
|
||||||
|
export function siteOrigin(requestUrl: URL, runtimeEnv: RuntimeEnv): string {
|
||||||
|
const configured = readEnv('PUBLIC_SITE_URL', runtimeEnv) || readEnv('SITE', runtimeEnv);
|
||||||
|
if (configured) {
|
||||||
|
try {
|
||||||
|
return new URL(configured).origin;
|
||||||
|
} catch {
|
||||||
|
/* fall through */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requestUrl.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function absoluteUrl(origin: string, path: string): string {
|
||||||
|
return new URL(path, origin).toString();
|
||||||
|
}
|
||||||
@ -1,83 +1,40 @@
|
|||||||
---
|
---
|
||||||
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
|
import type { SupportedLocale } from '../data/home/navigation';
|
||||||
|
import { getAbout } from '../data/about';
|
||||||
|
|
||||||
|
const path = Astro.url.pathname;
|
||||||
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
|
const currentLocale = (localeMatch?.[1] ?? 'it') as SupportedLocale;
|
||||||
|
const about = getAbout(currentLocale);
|
||||||
|
const { page, intro, sections } = about;
|
||||||
---
|
---
|
||||||
|
|
||||||
<SubpageLayout
|
<SubpageLayout
|
||||||
title="Chi siamo — NexStudio"
|
title={page.title}
|
||||||
description="Missione, valori e come lavoriamo con studi legali e strutture sanitarie: qualità, verticalità e impegno nel tempo."
|
description={page.description}
|
||||||
heading="Chi siamo"
|
heading={page.heading}
|
||||||
lead="Siamo un team internazionale — tra Bangkok e l’Italia — che unisce ingegneria del software, intelligenza artificiale applicata e conoscenza dei domini regolamentati, per strumenti professionali usabili e sostenibili nel tempo."
|
lead={page.lead}
|
||||||
>
|
>
|
||||||
<p>
|
<p>{intro}</p>
|
||||||
Ci impegniamo a rendere la tecnologia invisibile nel lavoro quotidiano. Progettiamo
|
|
||||||
<strong>software che assume su di sé</strong> processi strutturati, controlli e
|
|
||||||
complessità computazionale, così che chi lavora su norme, pazienti o documentazione
|
|
||||||
possa concentrarsi su <strong>giudizio, responsabilità e relazione</strong> — non su
|
|
||||||
menu, formati o incertezze operative. Nascondere la complessità sotto
|
|
||||||
<strong>interfaccia essenziale, tempi reattivi e tracciabilità</strong> è al centro di
|
|
||||||
come disegniamo il prodotto, non un optional estetico.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>La nostra missione</h2>
|
{
|
||||||
<p>
|
sections.map((s) => (
|
||||||
Operiamo dove un errore pesa: <strong>Legal Tech e Health Tech</strong>. Qui servono
|
<>
|
||||||
precisione, tracciamento delle scelte e rispetto dei vincoli normativi, non
|
<h2>{s.title}</h2>
|
||||||
sperimenti generici. La nostra missione è fornire <strong>piattaforme SaaS mature</strong>
|
{s.paragraphs?.map((p) => (
|
||||||
— come <strong>LexAura</strong> e <strong>MediAura</strong> — che integrino modelli
|
<p>{p}</p>
|
||||||
d’intelligenza artificiale in flussi già vissuti dallo studio o dalla struttura, con
|
))}
|
||||||
continuità tra automazione, revisione umana e responsabilità riconoscibile.
|
{s.bullets && s.bullets.length > 0 ? (
|
||||||
</p>
|
<ul>
|
||||||
|
{s.bullets.map((b) => (
|
||||||
<h2>Perché possiamo meritare la tua fiducia</h2>
|
<li>
|
||||||
<p>
|
<strong>{b.title}</strong> {b.text}
|
||||||
La fiducia non si ottiene con un motto, ma con <strong>ingegneria, chiarezza e
|
</li>
|
||||||
continuità</strong>. Significa architetture provate, aggiornamenti regolari dei
|
))}
|
||||||
modelli d’analisi, assistenza e formazione laddove l’adozione chiede metodo oltre
|
</ul>
|
||||||
al click. Significa anche onestà sui confini: l’IA nelle nostre offerte è pensata
|
) : null}
|
||||||
per <strong>estendere e accelerare</strong> il lavoro professionale, lasciando a
|
</>
|
||||||
avvocato, medico o organizzazione <strong>la decisione finale e la
|
))
|
||||||
documentazione</strong> che la norma richiede.
|
}
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Come lavoriamo, in sintesi</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<strong>Verticalità e dominio.</strong> Less genericismo, più requisiti operativi
|
|
||||||
(normative, flussi, audit) incorporati fin dalla progettazione.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Qualità e sicurezza come priorità, non retorica.</strong> Ingegneria, test
|
|
||||||
e infrastrutture coerenti con l’esposizione a dati sensibili.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Supporto e evoluzione.</strong> Il rapporto con il cliente non finisce
|
|
||||||
all’abbonamento: manutenzione evolutiva, assistenza e procedure chiare in caso di
|
|
||||||
urgenze operative, dove serve.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Comunicazione trasparente.</strong> Documentazione, informativa privacy
|
|
||||||
(dove obbligo) e <strong>aspettative reciproche</strong> esplicite, perché
|
|
||||||
l’onboarding non diventi il primo ostacolo.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>Team e cultura</h2>
|
|
||||||
<p>
|
|
||||||
Siamo developer, product e specialisti di dominio in un <strong>modello
|
|
||||||
distribuito</strong>: fusi orari diversi e linguaggi comuni, un solo obiettivo
|
|
||||||
prodotto. Ci muoviamo con <strong>iterazioni rapide</strong> laddove serve
|
|
||||||
innovazione, e con <strong>governance</strong> laddove un cambiamento impatta
|
|
||||||
validazione, conformità o continuità del servizio. La diversità del gruppo
|
|
||||||
(competenze, contesti) è per noi vantaggio competitivo: riduce l’assunto implicito
|
|
||||||
e costringe a rendere esplicito ciò che il software promette, e a chi.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Impegno nel tempo</h2>
|
|
||||||
<p>
|
|
||||||
Il settore che ci sceglie non cerca un prototipo, ma <strong>un percorso
|
|
||||||
sostenibile</strong>: allineamento normativo, adozione in team, affiancamento quando
|
|
||||||
qualcosa non gira. Costruiamo e manteniamo il prodotto con quella lente, perché
|
|
||||||
nelle vostre professioni la <strong>continuità</strong> non è comodità, è
|
|
||||||
requisito.
|
|
||||||
</p>
|
|
||||||
</SubpageLayout>
|
</SubpageLayout>
|
||||||
|
|||||||
109
src/pages/allegati.astro
Normal file
109
src/pages/allegati.astro
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
---
|
||||||
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
|
import { getAllegatiCodiceEtico } from '../data/allegati-codice-etico';
|
||||||
|
|
||||||
|
const path = Astro.url.pathname;
|
||||||
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
|
const currentLocale = (localeMatch?.[1] ?? 'it') as 'it' | 'en' | 'th';
|
||||||
|
const a = getAllegatiCodiceEtico(currentLocale);
|
||||||
|
const { page, allegati } = a;
|
||||||
|
|
||||||
|
const link = 'text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline';
|
||||||
|
|
||||||
|
const isLabeledFields = (item: unknown): item is { label: string; fields: string[] } =>
|
||||||
|
typeof item === 'object' && item !== null && 'label' in item && 'fields' in item;
|
||||||
|
|
||||||
|
const isLabeledText = (item: unknown): item is { label: string; text: string } =>
|
||||||
|
typeof item === 'object' && item !== null && 'label' in item && 'text' in item;
|
||||||
|
---
|
||||||
|
|
||||||
|
<SubpageLayout
|
||||||
|
title={page.title}
|
||||||
|
description={page.description}
|
||||||
|
heading={page.heading}
|
||||||
|
lead={page.lead}
|
||||||
|
>
|
||||||
|
<p class="mb-8 text-sm">
|
||||||
|
<a class={link} href={`/${currentLocale === 'it' ? '' : currentLocale + '/'}codice-etico`}>
|
||||||
|
{page.backLabel}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="space-y-16">
|
||||||
|
{
|
||||||
|
allegati.map((allegato) => (
|
||||||
|
<section id={allegato.id} class="scroll-mt-8">
|
||||||
|
<h2 class="border-b border-nx-border pb-2 text-xl font-bold text-nx-fg">
|
||||||
|
{allegato.heading}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-1 text-sm text-nx-muted">{allegato.description}</p>
|
||||||
|
|
||||||
|
{allegato.intro ? (
|
||||||
|
<p class="mt-4 text-sm italic text-nx-muted/90">{allegato.intro}</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div class="mt-6 space-y-6">
|
||||||
|
{
|
||||||
|
allegato.sections.map((section) => (
|
||||||
|
<div>
|
||||||
|
{section.title ? (
|
||||||
|
<h3 class="mb-3 text-base font-semibold text-nx-fg">{section.title}</h3>
|
||||||
|
) : null}
|
||||||
|
{section.intro ? (
|
||||||
|
<p class="mb-3 text-sm text-nx-muted">{section.intro}</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{Array.isArray(section.content) ? (
|
||||||
|
section.content.length > 0 && typeof section.content[0] === 'string' ? (
|
||||||
|
<ul class="list-disc space-y-1.5 pl-5 text-sm leading-relaxed sm:text-base">
|
||||||
|
{(section.content as string[]).map((item) => (
|
||||||
|
<li>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : section.content.length > 0 && isLabeledFields(section.content[0]) ? (
|
||||||
|
<dl class="space-y-3">
|
||||||
|
{(section.content as { label: string; fields: string[] }[]).map((item) => (
|
||||||
|
<div class="border-l-2 border-nx-border pl-4">
|
||||||
|
<dt class="text-sm font-medium text-nx-fg">{item.label}</dt>
|
||||||
|
<dd class="mt-0.5 font-mono text-sm text-nx-muted">
|
||||||
|
{item.fields.join(' | ')}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
) : section.content.length > 0 && isLabeledText(section.content[0]) ? (
|
||||||
|
<dl class="space-y-3">
|
||||||
|
{(section.content as { label: string; text: string }[]).map((item) => (
|
||||||
|
<div class="border-l-2 border-nx-border pl-4">
|
||||||
|
<dt class="text-sm font-semibold text-nx-fg">{item.label}</dt>
|
||||||
|
<dd class="mt-0.5 text-sm leading-relaxed text-nx-muted">{item.text}</dd>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
) : (
|
||||||
|
<p class="text-sm text-nx-muted">{String(section.content)}</p>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<p class="text-sm leading-relaxed sm:text-base">{String(section.content)}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{section.note ? (
|
||||||
|
<p class="mt-3 border-l-2 border-sky-500/30 pl-4 text-sm leading-relaxed text-nx-muted">
|
||||||
|
{section.note}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{allegato.footer ? (
|
||||||
|
<p class="mt-8 border-t border-nx-border pt-4 text-sm text-nx-fg">
|
||||||
|
{allegato.footer}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</section>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</SubpageLayout>
|
||||||
@ -1,32 +1,28 @@
|
|||||||
import type { APIRoute } from 'astro';
|
import type { APIRoute } from 'astro';
|
||||||
|
import { env as workerEnv } from 'cloudflare:workers';
|
||||||
|
import { getEmailBinding, sendCloudflareEmail } from '../../lib/email';
|
||||||
|
|
||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
|
|
||||||
type RuntimeEnv = Record<string, unknown>;
|
type RuntimeEnv = Record<string, unknown>;
|
||||||
|
|
||||||
function getRuntimeEnv(locals: unknown): RuntimeEnv {
|
function getRuntimeEnv(): RuntimeEnv {
|
||||||
if (
|
return workerEnv as unknown as RuntimeEnv;
|
||||||
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 {
|
function readEnv(name: string, runtimeEnv: RuntimeEnv): string {
|
||||||
const runtimeValue = runtimeEnv[name];
|
const runtimeValue = runtimeEnv[name];
|
||||||
if (typeof runtimeValue === 'string' && runtimeValue.trim().length > 0) {
|
if (typeof runtimeValue === 'string' && runtimeValue.trim().length > 0) {
|
||||||
return runtimeValue.trim();
|
return runtimeValue.trim().replace(/^['"]+|['"]+$/g, '').trim();
|
||||||
}
|
}
|
||||||
const staticValue = import.meta.env[name];
|
const staticValue = import.meta.env[name];
|
||||||
return typeof staticValue === 'string' ? staticValue.trim() : '';
|
return typeof staticValue === 'string'
|
||||||
|
? staticValue.trim().replace(/^['"]+|['"]+$/g, '').trim()
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function looksLikeEmail(value: string): boolean {
|
||||||
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeRedirect(candidate: string, requestUrl: URL): string {
|
function sanitizeRedirect(candidate: string, requestUrl: URL): string {
|
||||||
@ -43,6 +39,45 @@ function required(value: FormDataEntryValue | null): string {
|
|||||||
return typeof value === 'string' ? value.trim() : '';
|
return typeof value === 'string' ? value.trim() : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wantsJson(request: Request): boolean {
|
||||||
|
return (request.headers.get('Accept') || '').includes('application/json');
|
||||||
|
}
|
||||||
|
|
||||||
|
const USER_SEND_ERROR =
|
||||||
|
'Impossibile inviare il messaggio in questo momento. Riprova più tardi.';
|
||||||
|
|
||||||
|
function fail(request: Request, message: string, status: number): Response {
|
||||||
|
if (wantsJson(request)) {
|
||||||
|
return Response.json({ ok: false, error: message }, { status });
|
||||||
|
}
|
||||||
|
return new Response(message, { status });
|
||||||
|
}
|
||||||
|
|
||||||
|
function succeed(request: Request, redirectTarget: string): Response {
|
||||||
|
if (wantsJson(request)) {
|
||||||
|
return Response.json({ ok: true, redirect: redirectTarget });
|
||||||
|
}
|
||||||
|
return Response.redirect(redirectTarget, 303);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emailErrorCode(error: unknown): string {
|
||||||
|
if (error && typeof error === 'object' && 'code' in error) {
|
||||||
|
return String((error as { code?: unknown }).code || '');
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function userEmailErrorMessage(error: unknown): string {
|
||||||
|
const code = emailErrorCode(error);
|
||||||
|
switch (code) {
|
||||||
|
case 'E_RATE_LIMIT_EXCEEDED':
|
||||||
|
case 'E_DAILY_LIMIT_EXCEEDED':
|
||||||
|
return 'Troppe richieste al momento. Riprova tra poco.';
|
||||||
|
default:
|
||||||
|
return USER_SEND_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function verifyTurnstile(token: string, secret: string, ip?: string | null): Promise<boolean> {
|
async function verifyTurnstile(token: string, secret: string, ip?: string | null): Promise<boolean> {
|
||||||
const body = new URLSearchParams();
|
const body = new URLSearchParams();
|
||||||
body.set('secret', secret);
|
body.set('secret', secret);
|
||||||
@ -58,106 +93,166 @@ async function verifyTurnstile(token: string, secret: string, ip?: string | null
|
|||||||
return payload.success === true;
|
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', {
|
* Destinatario per area. Override solo se l'indirizzo è valido;
|
||||||
method: 'POST',
|
* altrimenti usa sempre CONTACT_FORM_TO_EMAIL (evita secret INFO/PRIVACY malformati).
|
||||||
headers: {
|
*/
|
||||||
Authorization: `Bearer ${apiKey}`,
|
function resolveDepartmentRecipient(
|
||||||
'Content-Type': 'application/json',
|
runtimeEnv: RuntimeEnv,
|
||||||
},
|
defaultRecipient: string,
|
||||||
body: JSON.stringify({
|
department: string,
|
||||||
from: fromEmail,
|
): string {
|
||||||
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 normalized = department.trim().toLowerCase();
|
||||||
const map: Record<string, string> = {
|
if (!normalized || normalized === 'info') {
|
||||||
info: readEnv('CONTACT_FORM_TO_EMAIL_INFO', runtimeEnv) || defaultRecipient,
|
return defaultRecipient;
|
||||||
privacy: readEnv('CONTACT_FORM_TO_EMAIL_PRIVACY', runtimeEnv),
|
}
|
||||||
careers: readEnv('CONTACT_FORM_TO_EMAIL_CAREERS', runtimeEnv),
|
const override =
|
||||||
};
|
normalized === 'privacy'
|
||||||
return map[normalized] || defaultRecipient;
|
? readEnv('CONTACT_FORM_TO_EMAIL_PRIVACY', runtimeEnv)
|
||||||
|
: normalized === 'careers'
|
||||||
|
? readEnv('CONTACT_FORM_TO_EMAIL_CAREERS', runtimeEnv)
|
||||||
|
: '';
|
||||||
|
return looksLikeEmail(override) ? override : defaultRecipient;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const POST: APIRoute = async ({ request, locals, url }) => {
|
export const POST: APIRoute = async ({ request, url }) => {
|
||||||
const runtimeEnv = getRuntimeEnv(locals);
|
try {
|
||||||
const mode = readEnv('CONTACT_FORM_MODE', runtimeEnv).toLowerCase() || 'dev';
|
const runtimeEnv = getRuntimeEnv();
|
||||||
const resendApiKey = readEnv('CONTACT_FORM_RESEND_API_KEY', runtimeEnv);
|
const mode = readEnv('CONTACT_FORM_MODE', runtimeEnv).toLowerCase() || 'dev';
|
||||||
const fromEmail = readEnv('CONTACT_FORM_FROM_EMAIL', runtimeEnv);
|
// Preferisci il mittente newsletter (secret già usato con successo) sul Variable del form.
|
||||||
const toEmail = readEnv('CONTACT_FORM_TO_EMAIL', runtimeEnv);
|
const fromEmail =
|
||||||
const turnstileSecret = readEnv('CONTACT_TURNSTILE_SECRET_KEY', runtimeEnv);
|
readEnv('NEWSLETTER_FROM_EMAIL', runtimeEnv) ||
|
||||||
|
readEnv('CONTACT_FORM_FROM_EMAIL', runtimeEnv);
|
||||||
|
const toEmail =
|
||||||
|
readEnv('CONTACT_FORM_TO_EMAIL', runtimeEnv) ||
|
||||||
|
readEnv('NEWSLETTER_NOTIFY_TO_EMAIL', runtimeEnv);
|
||||||
|
const turnstileSecret = readEnv('CONTACT_TURNSTILE_SECRET_KEY', runtimeEnv);
|
||||||
|
|
||||||
const form = await request.formData();
|
const form = await request.formData();
|
||||||
const redirectTarget =
|
// Honeypot rimosso dal form (Turnstile basta). Se un client legacy lo manda, ignoralo.
|
||||||
sanitizeRedirect(required(form.get('_next')), url) ||
|
const redirectTarget =
|
||||||
sanitizeRedirect(readEnv('PUBLIC_CONTACT_FORM_SUCCESS_REDIRECT', runtimeEnv), url) ||
|
sanitizeRedirect(required(form.get('_next')), url) ||
|
||||||
sanitizeRedirect(request.headers.get('referer') ?? '/', url) ||
|
sanitizeRedirect(readEnv('PUBLIC_CONTACT_FORM_SUCCESS_REDIRECT', runtimeEnv), url) ||
|
||||||
'/';
|
sanitizeRedirect(request.headers.get('referer') ?? '/', url) ||
|
||||||
|
new URL('/#contatti', url).toString();
|
||||||
|
|
||||||
const name = required(form.get('name'));
|
const name = required(form.get('name'));
|
||||||
const company = required(form.get('company'));
|
const company = required(form.get('company'));
|
||||||
const email = required(form.get('email'));
|
const email = required(form.get('email'));
|
||||||
const country = required(form.get('country'));
|
const country = required(form.get('country'));
|
||||||
const department = required(form.get('department')) || 'info';
|
const department = required(form.get('department')) || 'info';
|
||||||
const message = required(form.get('message'));
|
const message = required(form.get('message'));
|
||||||
|
|
||||||
if (!name || !email || !country || !message) {
|
if (!name || !email || !country || !message) {
|
||||||
return new Response('Missing required contact fields.', { status: 400 });
|
return fail(request, 'Compila tutti i campi obbligatori.', 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 turnstileToken = required(form.get('cf-turnstile-response'));
|
||||||
const text = [
|
if (turnstileSecret) {
|
||||||
`Nome: ${name}`,
|
const isHuman = await verifyTurnstile(
|
||||||
company ? `Azienda: ${company}` : '',
|
turnstileToken,
|
||||||
`Email: ${email}`,
|
turnstileSecret,
|
||||||
`Paese: ${country}`,
|
request.headers.get('CF-Connecting-IP'),
|
||||||
`Reparto: ${department}`,
|
);
|
||||||
'',
|
if (!isHuman) {
|
||||||
message,
|
return fail(
|
||||||
]
|
request,
|
||||||
.filter(Boolean)
|
'Completa il controllo anti-spam e riprova.',
|
||||||
.join('\n');
|
400,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mode === 'live') {
|
const subject = required(form.get('_subject')) || 'Richiesta contatto - sito NexStudio';
|
||||||
if (!resendApiKey || !fromEmail || !toEmail) {
|
const text = [
|
||||||
return new Response('Live mode requires CONTACT_FORM_RESEND_API_KEY, CONTACT_FORM_FROM_EMAIL and CONTACT_FORM_TO_EMAIL.', {
|
`Nome: ${name}`,
|
||||||
status: 500,
|
company ? `Azienda: ${company}` : '',
|
||||||
|
`Email: ${email}`,
|
||||||
|
`Paese: ${country}`,
|
||||||
|
`Reparto: ${department}`,
|
||||||
|
'',
|
||||||
|
message,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
if (mode === 'live') {
|
||||||
|
const emailBinding = getEmailBinding(runtimeEnv);
|
||||||
|
if (!emailBinding || !fromEmail || !toEmail) {
|
||||||
|
const missing = [
|
||||||
|
!emailBinding ? 'EMAIL binding' : '',
|
||||||
|
!fromEmail ? 'CONTACT_FORM_FROM_EMAIL|NEWSLETTER_FROM_EMAIL' : '',
|
||||||
|
!toEmail ? 'CONTACT_FORM_TO_EMAIL|NEWSLETTER_NOTIFY_TO_EMAIL' : '',
|
||||||
|
].filter(Boolean);
|
||||||
|
console.error('[contact] incomplete email config', { missing });
|
||||||
|
return fail(request, USER_SEND_ERROR, 500);
|
||||||
|
}
|
||||||
|
const destination = resolveDepartmentRecipient(runtimeEnv, toEmail, department);
|
||||||
|
if (!looksLikeEmail(fromEmail) || !looksLikeEmail(destination)) {
|
||||||
|
console.error('[contact] invalid from/to email shape', {
|
||||||
|
fromOk: looksLikeEmail(fromEmail),
|
||||||
|
toOk: looksLikeEmail(destination),
|
||||||
|
department,
|
||||||
|
});
|
||||||
|
return fail(request, USER_SEND_ERROR, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendCloudflareEmail(emailBinding, {
|
||||||
|
fromEmail,
|
||||||
|
toEmail: destination,
|
||||||
|
subject,
|
||||||
|
text,
|
||||||
|
replyTo: email,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[contact] email failed', {
|
||||||
|
code: emailErrorCode(error),
|
||||||
|
fromDomain: fromEmail.split('@')[1] || '',
|
||||||
|
toDomain: destination.split('@')[1] || '',
|
||||||
|
department,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
// Retry without replyTo: some payloads reject Reply-To even when From/To are valid.
|
||||||
|
try {
|
||||||
|
await sendCloudflareEmail(emailBinding, {
|
||||||
|
fromEmail,
|
||||||
|
toEmail: destination,
|
||||||
|
subject,
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
} catch (retryError) {
|
||||||
|
console.error('[contact] email retry failed', {
|
||||||
|
code: emailErrorCode(retryError),
|
||||||
|
fromDomain: fromEmail.split('@')[1] || '',
|
||||||
|
toDomain: destination.split('@')[1] || '',
|
||||||
|
error: retryError,
|
||||||
|
});
|
||||||
|
return fail(request, userEmailErrorMessage(retryError), 502);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const isProdHost =
|
||||||
|
url.hostname === 'nexstudio.ai' ||
|
||||||
|
url.hostname.endsWith('.nexstudio.ai') ||
|
||||||
|
url.hostname.endsWith('.workers.dev');
|
||||||
|
console.warn('[contact] CONTACT_FORM_MODE is not live', { mode, host: url.hostname });
|
||||||
|
if (isProdHost) {
|
||||||
|
return fail(request, USER_SEND_ERROR, 503);
|
||||||
|
}
|
||||||
|
console.info('[contact:dev] Contact payload received', {
|
||||||
|
name,
|
||||||
|
company,
|
||||||
|
email,
|
||||||
|
country,
|
||||||
|
department,
|
||||||
|
subject,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
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);
|
return succeed(request, redirectTarget);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[contact] unexpected error', error);
|
||||||
|
return fail(request, USER_SEND_ERROR, 500);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
176
src/pages/api/newsletter/admin/send.ts
Normal file
176
src/pages/api/newsletter/admin/send.ts
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
import type { APIRoute } from 'astro';
|
||||||
|
import {
|
||||||
|
absoluteUrl,
|
||||||
|
getEmailBinding,
|
||||||
|
getNewsletterDb,
|
||||||
|
getRuntimeEnv,
|
||||||
|
readEnv,
|
||||||
|
sendCloudflareEmail,
|
||||||
|
siteOrigin,
|
||||||
|
type NewsletterSubscriber,
|
||||||
|
} from '../../../../lib/newsletter';
|
||||||
|
|
||||||
|
export const prerender = false;
|
||||||
|
|
||||||
|
type SendBody = {
|
||||||
|
subject?: string;
|
||||||
|
text?: string;
|
||||||
|
html?: string;
|
||||||
|
dryRun?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function unauthorized(): Response {
|
||||||
|
return new Response('Unauthorized', { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
function authorize(request: Request, runtimeEnv: ReturnType<typeof getRuntimeEnv>): boolean {
|
||||||
|
const expected = readEnv('NEWSLETTER_ADMIN_TOKEN', runtimeEnv);
|
||||||
|
if (!expected) return false;
|
||||||
|
const header = request.headers.get('authorization') || '';
|
||||||
|
const bearer = header.toLowerCase().startsWith('bearer ')
|
||||||
|
? header.slice(7).trim()
|
||||||
|
: '';
|
||||||
|
return bearer === expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const POST: APIRoute = async ({ request, locals, url }) => {
|
||||||
|
const runtimeEnv = getRuntimeEnv(locals);
|
||||||
|
if (!authorize(request, runtimeEnv)) return unauthorized();
|
||||||
|
|
||||||
|
const mode = (readEnv('NEWSLETTER_MODE', runtimeEnv) || readEnv('CONTACT_FORM_MODE', runtimeEnv) || 'dev').toLowerCase();
|
||||||
|
let body: SendBody;
|
||||||
|
try {
|
||||||
|
body = (await request.json()) as SendBody;
|
||||||
|
} catch {
|
||||||
|
return new Response('Invalid JSON body.', { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const subject = (body.subject || '').trim();
|
||||||
|
const text = (body.text || '').trim();
|
||||||
|
const html = (body.html || '').trim();
|
||||||
|
if (!subject || !text) {
|
||||||
|
return new Response('Fields subject and text are required.', { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode !== 'live') {
|
||||||
|
console.info('[newsletter:dev] Campaign send requested', {
|
||||||
|
subject,
|
||||||
|
dryRun: Boolean(body.dryRun),
|
||||||
|
textPreview: text.slice(0, 120),
|
||||||
|
});
|
||||||
|
return Response.json({
|
||||||
|
mode,
|
||||||
|
accepted: true,
|
||||||
|
sent: 0,
|
||||||
|
failed: 0,
|
||||||
|
note: 'Dev mode: campaign not sent.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = getNewsletterDb(runtimeEnv);
|
||||||
|
if (!db) {
|
||||||
|
return new Response('Newsletter database binding NEWSLETTER_DB is not configured.', {
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailBinding = getEmailBinding(runtimeEnv);
|
||||||
|
const fromEmail =
|
||||||
|
readEnv('NEWSLETTER_FROM_EMAIL', runtimeEnv) ||
|
||||||
|
readEnv('CONTACT_FORM_FROM_EMAIL', runtimeEnv);
|
||||||
|
|
||||||
|
if (!emailBinding || !fromEmail) {
|
||||||
|
return new Response('Missing EMAIL binding or from email for newsletter send.', {
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscribersResult = await db
|
||||||
|
.prepare(
|
||||||
|
`SELECT * FROM newsletter_subscribers
|
||||||
|
WHERE status = 'active'
|
||||||
|
ORDER BY id ASC`,
|
||||||
|
)
|
||||||
|
.all<NewsletterSubscriber>();
|
||||||
|
const subscribers = subscribersResult.results ?? [];
|
||||||
|
|
||||||
|
const campaignInsert = await db
|
||||||
|
.prepare(
|
||||||
|
`INSERT INTO newsletter_campaigns (subject, body_text, body_html, status, created_by)
|
||||||
|
VALUES (?, ?, ?, ?, ?)`,
|
||||||
|
)
|
||||||
|
.bind(subject, text, html || null, body.dryRun ? 'draft' : 'sending', 'admin-api')
|
||||||
|
.run();
|
||||||
|
|
||||||
|
const campaignId = Number(campaignInsert.meta.last_row_id);
|
||||||
|
const origin = siteOrigin(url, runtimeEnv);
|
||||||
|
|
||||||
|
if (body.dryRun) {
|
||||||
|
return Response.json({
|
||||||
|
dryRun: true,
|
||||||
|
campaignId,
|
||||||
|
recipients: subscribers.length,
|
||||||
|
sample: subscribers.slice(0, 5).map((s) => s.email),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let sent = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
for (const subscriber of subscribers) {
|
||||||
|
const unsubUrl = absoluteUrl(
|
||||||
|
origin,
|
||||||
|
`/api/newsletter/unsubscribe?token=${subscriber.unsubscribe_token}`,
|
||||||
|
);
|
||||||
|
const personalizedText = `${text}\n\n---\nDisiscrizione: ${unsubUrl}\n`;
|
||||||
|
const personalizedHtml = html
|
||||||
|
? `${html}<hr /><p style="font-size:12px;color:#666">Disiscrizione: <a href="${unsubUrl}">${unsubUrl}</a></p>`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendCloudflareEmail(emailBinding, {
|
||||||
|
fromEmail,
|
||||||
|
toEmail: subscriber.email,
|
||||||
|
subject,
|
||||||
|
text: personalizedText,
|
||||||
|
html: personalizedHtml,
|
||||||
|
});
|
||||||
|
await db
|
||||||
|
.prepare(
|
||||||
|
`INSERT INTO newsletter_campaign_sends
|
||||||
|
(campaign_id, subscriber_id, email, status, sent_at)
|
||||||
|
VALUES (?, ?, ?, 'sent', datetime('now'))`,
|
||||||
|
)
|
||||||
|
.bind(campaignId, subscriber.id, subscriber.email)
|
||||||
|
.run();
|
||||||
|
sent += 1;
|
||||||
|
} catch (error) {
|
||||||
|
failed += 1;
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
await db
|
||||||
|
.prepare(
|
||||||
|
`INSERT INTO newsletter_campaign_sends
|
||||||
|
(campaign_id, subscriber_id, email, status, error, sent_at)
|
||||||
|
VALUES (?, ?, ?, 'failed', ?, datetime('now'))`,
|
||||||
|
)
|
||||||
|
.bind(campaignId, subscriber.id, subscriber.email, message.slice(0, 500))
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await db
|
||||||
|
.prepare(
|
||||||
|
`UPDATE newsletter_campaigns
|
||||||
|
SET status = ?, sent_at = datetime('now')
|
||||||
|
WHERE id = ?`,
|
||||||
|
)
|
||||||
|
.bind(failed > 0 && sent === 0 ? 'failed' : 'sent', campaignId)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
campaignId,
|
||||||
|
recipients: subscribers.length,
|
||||||
|
sent,
|
||||||
|
failed,
|
||||||
|
});
|
||||||
|
};
|
||||||
74
src/pages/api/newsletter/admin/subscribers.ts
Normal file
74
src/pages/api/newsletter/admin/subscribers.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import type { APIRoute } from 'astro';
|
||||||
|
import {
|
||||||
|
getNewsletterDb,
|
||||||
|
getRuntimeEnv,
|
||||||
|
readEnv,
|
||||||
|
type NewsletterSubscriber,
|
||||||
|
} from '../../../../lib/newsletter';
|
||||||
|
|
||||||
|
export const prerender = false;
|
||||||
|
|
||||||
|
function unauthorized(): Response {
|
||||||
|
return new Response('Unauthorized', { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
function authorize(request: Request, runtimeEnv: ReturnType<typeof getRuntimeEnv>): boolean {
|
||||||
|
const expected = readEnv('NEWSLETTER_ADMIN_TOKEN', runtimeEnv);
|
||||||
|
if (!expected) return false;
|
||||||
|
const header = request.headers.get('authorization') || '';
|
||||||
|
const bearer = header.toLowerCase().startsWith('bearer ')
|
||||||
|
? header.slice(7).trim()
|
||||||
|
: '';
|
||||||
|
const queryToken = new URL(request.url).searchParams.get('token') || '';
|
||||||
|
return bearer === expected || queryToken === expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GET: APIRoute = async ({ request, locals }) => {
|
||||||
|
const runtimeEnv = getRuntimeEnv(locals);
|
||||||
|
if (!authorize(request, runtimeEnv)) return unauthorized();
|
||||||
|
|
||||||
|
const mode = (readEnv('NEWSLETTER_MODE', runtimeEnv) || readEnv('CONTACT_FORM_MODE', runtimeEnv) || 'dev').toLowerCase();
|
||||||
|
if (mode !== 'live') {
|
||||||
|
return Response.json({
|
||||||
|
mode,
|
||||||
|
subscribers: [],
|
||||||
|
note: 'Dev mode: no database query performed.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = getNewsletterDb(runtimeEnv);
|
||||||
|
if (!db) {
|
||||||
|
return new Response('Newsletter database binding NEWSLETTER_DB is not configured.', {
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = new URL(request.url).searchParams.get('status');
|
||||||
|
let rows: NewsletterSubscriber[];
|
||||||
|
if (status === 'pending' || status === 'active' || status === 'unsubscribed') {
|
||||||
|
const result = await db
|
||||||
|
.prepare(
|
||||||
|
`SELECT id, email, status, locale, source, created_at, confirmed_at, unsubscribed_at
|
||||||
|
FROM newsletter_subscribers
|
||||||
|
WHERE status = ?
|
||||||
|
ORDER BY created_at DESC`,
|
||||||
|
)
|
||||||
|
.bind(status)
|
||||||
|
.all<NewsletterSubscriber>();
|
||||||
|
rows = result.results ?? [];
|
||||||
|
} else {
|
||||||
|
const result = await db
|
||||||
|
.prepare(
|
||||||
|
`SELECT id, email, status, locale, source, created_at, confirmed_at, unsubscribed_at
|
||||||
|
FROM newsletter_subscribers
|
||||||
|
ORDER BY created_at DESC`,
|
||||||
|
)
|
||||||
|
.all<NewsletterSubscriber>();
|
||||||
|
rows = result.results ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
count: rows.length,
|
||||||
|
subscribers: rows,
|
||||||
|
});
|
||||||
|
};
|
||||||
86
src/pages/api/newsletter/confirm.ts
Normal file
86
src/pages/api/newsletter/confirm.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import type { APIRoute } from 'astro';
|
||||||
|
import {
|
||||||
|
getEmailBinding,
|
||||||
|
getNewsletterDb,
|
||||||
|
getRuntimeEnv,
|
||||||
|
readEnv,
|
||||||
|
required,
|
||||||
|
sendCloudflareEmail,
|
||||||
|
type NewsletterSubscriber,
|
||||||
|
} from '../../../lib/newsletter';
|
||||||
|
|
||||||
|
export const prerender = false;
|
||||||
|
|
||||||
|
export const GET: APIRoute = async ({ request, locals, url }) => {
|
||||||
|
const runtimeEnv = getRuntimeEnv(locals);
|
||||||
|
const mode = (readEnv('NEWSLETTER_MODE', runtimeEnv) || readEnv('CONTACT_FORM_MODE', runtimeEnv) || 'dev').toLowerCase();
|
||||||
|
const token = required(url.searchParams.get('token'));
|
||||||
|
const okPath = '/newsletter/confermata';
|
||||||
|
const failPath = '/newsletter/conferma-errore';
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return Response.redirect(new URL(failPath, url).toString(), 303);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode !== 'live') {
|
||||||
|
console.info('[newsletter:dev] Confirm token received', { token: token.slice(0, 8) + '…' });
|
||||||
|
return Response.redirect(new URL(okPath, url).toString(), 303);
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = getNewsletterDb(runtimeEnv);
|
||||||
|
if (!db) {
|
||||||
|
return new Response('Newsletter database binding NEWSLETTER_DB is not configured.', {
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = await db
|
||||||
|
.prepare(
|
||||||
|
`SELECT * FROM newsletter_subscribers
|
||||||
|
WHERE confirm_token = ? AND status = 'pending'
|
||||||
|
LIMIT 1`,
|
||||||
|
)
|
||||||
|
.bind(token)
|
||||||
|
.first<NewsletterSubscriber>();
|
||||||
|
|
||||||
|
if (!row) {
|
||||||
|
return Response.redirect(new URL(failPath, url).toString(), 303);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db
|
||||||
|
.prepare(
|
||||||
|
`UPDATE newsletter_subscribers
|
||||||
|
SET status = 'active',
|
||||||
|
confirmed_at = datetime('now'),
|
||||||
|
confirm_token = NULL
|
||||||
|
WHERE id = ?`,
|
||||||
|
)
|
||||||
|
.bind(row.id)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
const emailBinding = getEmailBinding(runtimeEnv);
|
||||||
|
const fromEmail =
|
||||||
|
readEnv('NEWSLETTER_FROM_EMAIL', runtimeEnv) ||
|
||||||
|
readEnv('CONTACT_FORM_FROM_EMAIL', runtimeEnv);
|
||||||
|
const notifyTo =
|
||||||
|
readEnv('NEWSLETTER_NOTIFY_TO_EMAIL', runtimeEnv) ||
|
||||||
|
readEnv('CONTACT_FORM_TO_EMAIL', runtimeEnv);
|
||||||
|
|
||||||
|
if (emailBinding && fromEmail && notifyTo) {
|
||||||
|
try {
|
||||||
|
await sendCloudflareEmail(emailBinding, {
|
||||||
|
fromEmail,
|
||||||
|
toEmail: notifyTo,
|
||||||
|
subject: 'Iscrizione newsletter confermata — NexStudio',
|
||||||
|
text: `L’indirizzo ${row.email} ha confermato l’iscrizione alla newsletter.`,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[newsletter] confirm notify failed', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.redirect(
|
||||||
|
new URL(`${okPath}?unsub=${encodeURIComponent(row.unsubscribe_token)}`, request.url).toString(),
|
||||||
|
303,
|
||||||
|
);
|
||||||
|
};
|
||||||
222
src/pages/api/newsletter/subscribe.ts
Normal file
222
src/pages/api/newsletter/subscribe.ts
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
import type { APIRoute } from 'astro';
|
||||||
|
import {
|
||||||
|
absoluteUrl,
|
||||||
|
createToken,
|
||||||
|
getEmailBinding,
|
||||||
|
getNewsletterDb,
|
||||||
|
getRuntimeEnv,
|
||||||
|
isValidEmail,
|
||||||
|
normalizeEmail,
|
||||||
|
readEnv,
|
||||||
|
required,
|
||||||
|
sanitizeRedirect,
|
||||||
|
sendCloudflareEmail,
|
||||||
|
siteOrigin,
|
||||||
|
verifyTurnstile,
|
||||||
|
type NewsletterSubscriber,
|
||||||
|
} from '../../../lib/newsletter';
|
||||||
|
|
||||||
|
export const prerender = false;
|
||||||
|
|
||||||
|
function confirmEmailCopy(
|
||||||
|
locale: string,
|
||||||
|
confirmUrl: string,
|
||||||
|
unsubscribeUrl: string,
|
||||||
|
): { subject: string; text: string } {
|
||||||
|
if (locale === 'en') {
|
||||||
|
return {
|
||||||
|
subject: 'Confirm your NexStudio newsletter subscription',
|
||||||
|
text: [
|
||||||
|
'Thanks for subscribing to the NexStudio newsletter.',
|
||||||
|
'',
|
||||||
|
'Confirm your email by opening this link:',
|
||||||
|
confirmUrl,
|
||||||
|
'',
|
||||||
|
'If you did not request this, you can ignore this message.',
|
||||||
|
'',
|
||||||
|
'Unsubscribe:',
|
||||||
|
unsubscribeUrl,
|
||||||
|
].join('\n'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (locale === 'th') {
|
||||||
|
return {
|
||||||
|
subject: 'ยืนยันการสมัครรับจดหมายข่าว NexStudio',
|
||||||
|
text: [
|
||||||
|
'ขอบคุณที่สมัครรับจดหมายข่าว NexStudio',
|
||||||
|
'',
|
||||||
|
'ยืนยันอีเมลของคุณโดยเปิดลิงก์นี้:',
|
||||||
|
confirmUrl,
|
||||||
|
'',
|
||||||
|
'หากคุณไม่ได้ขอสมัคร สามารถเพิกเฉยต่อข้อความนี้ได้',
|
||||||
|
'',
|
||||||
|
'ยกเลิกการสมัคร:',
|
||||||
|
unsubscribeUrl,
|
||||||
|
].join('\n'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
subject: 'Conferma iscrizione newsletter NexStudio',
|
||||||
|
text: [
|
||||||
|
'Grazie per esserti iscritto alla newsletter di NexStudio.',
|
||||||
|
'',
|
||||||
|
'Conferma l’indirizzo email aprendo questo link:',
|
||||||
|
confirmUrl,
|
||||||
|
'',
|
||||||
|
'Se non hai richiesto l’iscrizione, puoi ignorare questo messaggio.',
|
||||||
|
'',
|
||||||
|
'Per disiscriverti:',
|
||||||
|
unsubscribeUrl,
|
||||||
|
].join('\n'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const POST: APIRoute = async ({ request, locals, url }) => {
|
||||||
|
const runtimeEnv = getRuntimeEnv(locals);
|
||||||
|
const mode = (readEnv('NEWSLETTER_MODE', runtimeEnv) || readEnv('CONTACT_FORM_MODE', runtimeEnv) || 'dev').toLowerCase();
|
||||||
|
const db = getNewsletterDb(runtimeEnv);
|
||||||
|
const form = await request.formData();
|
||||||
|
|
||||||
|
const redirectOk =
|
||||||
|
sanitizeRedirect(required(form.get('_next')), url) ||
|
||||||
|
'/newsletter/grazie';
|
||||||
|
const redirectErr = sanitizeRedirect(required(form.get('_error')), url) || redirectOk;
|
||||||
|
|
||||||
|
// Honeypot
|
||||||
|
if (required(form.get('_gotcha'))) {
|
||||||
|
return Response.redirect(new URL(redirectOk, url).toString(), 303);
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailRaw = required(form.get('email'));
|
||||||
|
const locale = required(form.get('locale')) || 'it';
|
||||||
|
const source = required(form.get('source')) || 'footer';
|
||||||
|
|
||||||
|
if (!emailRaw || !isValidEmail(emailRaw)) {
|
||||||
|
return new Response('Invalid email.', { status: 400 });
|
||||||
|
}
|
||||||
|
const email = normalizeEmail(emailRaw);
|
||||||
|
|
||||||
|
// Turnstile solo se configurato esplicitamente per la newsletter.
|
||||||
|
// Non riusare CONTACT_TURNSTILE_SECRET_KEY: il form footer non ha il widget.
|
||||||
|
const turnstileSecret = readEnv('NEWSLETTER_TURNSTILE_SECRET_KEY', runtimeEnv);
|
||||||
|
if (turnstileSecret) {
|
||||||
|
const turnstileToken = required(form.get('cf-turnstile-response'));
|
||||||
|
const isHuman = await verifyTurnstile(
|
||||||
|
turnstileToken,
|
||||||
|
turnstileSecret,
|
||||||
|
request.headers.get('CF-Connecting-IP'),
|
||||||
|
);
|
||||||
|
if (!isHuman) {
|
||||||
|
return new Response('Turnstile verification failed.', { status: 400 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmToken = createToken();
|
||||||
|
const unsubscribeToken = createToken();
|
||||||
|
const origin = siteOrigin(url, runtimeEnv);
|
||||||
|
const confirmUrl = absoluteUrl(origin, `/api/newsletter/confirm?token=${confirmToken}`);
|
||||||
|
const unsubscribeUrl = absoluteUrl(
|
||||||
|
origin,
|
||||||
|
`/api/newsletter/unsubscribe?token=${unsubscribeToken}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mode !== 'live') {
|
||||||
|
console.info('[newsletter:dev] Subscription request', {
|
||||||
|
email,
|
||||||
|
locale,
|
||||||
|
source,
|
||||||
|
confirmUrl,
|
||||||
|
});
|
||||||
|
return Response.redirect(new URL(redirectOk, url).toString(), 303);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!db) {
|
||||||
|
return new Response('Newsletter database binding NEWSLETTER_DB is not configured.', {
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailBinding = getEmailBinding(runtimeEnv);
|
||||||
|
const fromEmail =
|
||||||
|
readEnv('NEWSLETTER_FROM_EMAIL', runtimeEnv) ||
|
||||||
|
readEnv('CONTACT_FORM_FROM_EMAIL', runtimeEnv);
|
||||||
|
const notifyTo =
|
||||||
|
readEnv('NEWSLETTER_NOTIFY_TO_EMAIL', runtimeEnv) ||
|
||||||
|
readEnv('CONTACT_FORM_TO_EMAIL', runtimeEnv);
|
||||||
|
|
||||||
|
if (!emailBinding || !fromEmail) {
|
||||||
|
return new Response(
|
||||||
|
'Live mode requires EMAIL binding (Cloudflare Email Service) and NEWSLETTER_FROM_EMAIL (or CONTACT_FORM_FROM_EMAIL).',
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = await db
|
||||||
|
.prepare('SELECT * FROM newsletter_subscribers WHERE email = ? LIMIT 1')
|
||||||
|
.bind(email)
|
||||||
|
.first<NewsletterSubscriber>();
|
||||||
|
|
||||||
|
if (existing?.status === 'active') {
|
||||||
|
// Already subscribed: redirect without revealing status too loudly.
|
||||||
|
return Response.redirect(new URL(redirectOk, url).toString(), 303);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing?.status === 'unsubscribed' || existing?.status === 'pending') {
|
||||||
|
await db
|
||||||
|
.prepare(
|
||||||
|
`UPDATE newsletter_subscribers
|
||||||
|
SET status = 'pending',
|
||||||
|
locale = ?,
|
||||||
|
source = ?,
|
||||||
|
confirm_token = ?,
|
||||||
|
unsubscribe_token = ?,
|
||||||
|
confirmed_at = NULL,
|
||||||
|
unsubscribed_at = NULL
|
||||||
|
WHERE id = ?`,
|
||||||
|
)
|
||||||
|
.bind(locale, source, confirmToken, unsubscribeToken, existing.id)
|
||||||
|
.run();
|
||||||
|
} else {
|
||||||
|
await db
|
||||||
|
.prepare(
|
||||||
|
`INSERT INTO newsletter_subscribers
|
||||||
|
(email, status, locale, confirm_token, unsubscribe_token, source)
|
||||||
|
VALUES (?, 'pending', ?, ?, ?, ?)`,
|
||||||
|
)
|
||||||
|
.bind(email, locale, confirmToken, unsubscribeToken, source)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
const copy = confirmEmailCopy(locale, confirmUrl, unsubscribeUrl);
|
||||||
|
try {
|
||||||
|
await sendCloudflareEmail(emailBinding, {
|
||||||
|
fromEmail,
|
||||||
|
toEmail: email,
|
||||||
|
subject: copy.subject,
|
||||||
|
text: copy.text,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[newsletter] confirm email failed', error);
|
||||||
|
return Response.redirect(new URL(redirectErr, url).toString(), 303);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifyTo) {
|
||||||
|
try {
|
||||||
|
await sendCloudflareEmail(emailBinding, {
|
||||||
|
fromEmail,
|
||||||
|
toEmail: notifyTo,
|
||||||
|
subject: required(form.get('_subject')) || 'Nuova richiesta newsletter — NexStudio',
|
||||||
|
text: [
|
||||||
|
`Nuova richiesta di iscrizione (pending conferma).`,
|
||||||
|
`Email: ${email}`,
|
||||||
|
`Locale: ${locale}`,
|
||||||
|
`Source: ${source}`,
|
||||||
|
].join('\n'),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[newsletter] admin notify failed', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.redirect(new URL(redirectOk, url).toString(), 303);
|
||||||
|
};
|
||||||
85
src/pages/api/newsletter/unsubscribe.ts
Normal file
85
src/pages/api/newsletter/unsubscribe.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import type { APIRoute } from 'astro';
|
||||||
|
import {
|
||||||
|
getNewsletterDb,
|
||||||
|
getRuntimeEnv,
|
||||||
|
readEnv,
|
||||||
|
required,
|
||||||
|
type NewsletterSubscriber,
|
||||||
|
} from '../../../lib/newsletter';
|
||||||
|
|
||||||
|
export const prerender = false;
|
||||||
|
|
||||||
|
async function unsubscribeByToken(
|
||||||
|
locals: unknown,
|
||||||
|
token: string,
|
||||||
|
): Promise<'ok' | 'missing' | 'not_found' | 'no_db'> {
|
||||||
|
if (!token) return 'missing';
|
||||||
|
|
||||||
|
const runtimeEnv = getRuntimeEnv(locals);
|
||||||
|
const mode = (readEnv('NEWSLETTER_MODE', runtimeEnv) || readEnv('CONTACT_FORM_MODE', runtimeEnv) || 'dev').toLowerCase();
|
||||||
|
|
||||||
|
if (mode !== 'live') {
|
||||||
|
console.info('[newsletter:dev] Unsubscribe token received', { token: token.slice(0, 8) + '…' });
|
||||||
|
return 'ok';
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = getNewsletterDb(runtimeEnv);
|
||||||
|
if (!db) return 'no_db';
|
||||||
|
|
||||||
|
const row = await db
|
||||||
|
.prepare(
|
||||||
|
`SELECT * FROM newsletter_subscribers
|
||||||
|
WHERE unsubscribe_token = ?
|
||||||
|
LIMIT 1`,
|
||||||
|
)
|
||||||
|
.bind(token)
|
||||||
|
.first<NewsletterSubscriber>();
|
||||||
|
|
||||||
|
if (!row) return 'not_found';
|
||||||
|
|
||||||
|
if (row.status !== 'unsubscribed') {
|
||||||
|
await db
|
||||||
|
.prepare(
|
||||||
|
`UPDATE newsletter_subscribers
|
||||||
|
SET status = 'unsubscribed',
|
||||||
|
unsubscribed_at = datetime('now'),
|
||||||
|
confirm_token = NULL
|
||||||
|
WHERE id = ?`,
|
||||||
|
)
|
||||||
|
.bind(row.id)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'ok';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GET: APIRoute = async ({ locals, url }) => {
|
||||||
|
const token = required(url.searchParams.get('token'));
|
||||||
|
const result = await unsubscribeByToken(locals, token);
|
||||||
|
|
||||||
|
if (result === 'no_db') {
|
||||||
|
return new Response('Newsletter database binding NEWSLETTER_DB is not configured.', {
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const path =
|
||||||
|
result === 'ok' ? '/newsletter/disiscrizione' : '/newsletter/disiscrizione-errore';
|
||||||
|
return Response.redirect(new URL(path, url).toString(), 303);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const POST: APIRoute = async ({ request, locals, url }) => {
|
||||||
|
const form = await request.formData();
|
||||||
|
const token = required(form.get('token')) || required(url.searchParams.get('token'));
|
||||||
|
const result = await unsubscribeByToken(locals, token);
|
||||||
|
|
||||||
|
if (result === 'no_db') {
|
||||||
|
return new Response('Newsletter database binding NEWSLETTER_DB is not configured.', {
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const path =
|
||||||
|
result === 'ok' ? '/newsletter/disiscrizione' : '/newsletter/disiscrizione-errore';
|
||||||
|
return Response.redirect(new URL(path, url).toString(), 303);
|
||||||
|
};
|
||||||
@ -1,143 +1,77 @@
|
|||||||
---
|
---
|
||||||
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
import {
|
import { getCodiceEtico } from '../data/codice-etico';
|
||||||
getCodiceEticoPremessa,
|
|
||||||
codiceEticoConformityBlocks,
|
const path = Astro.url.pathname;
|
||||||
qualitaValidazioneProdotto,
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
getCodiceEticoProductListPhrase,
|
const currentLocale = (localeMatch?.[1] ?? 'it') as 'it' | 'en' | 'th';
|
||||||
notePraticheDPIA,
|
const c = getCodiceEtico(currentLocale);
|
||||||
codiceEticoTutelaDatiRuoli,
|
const { page } = c;
|
||||||
} from '../data/codice-etico';
|
|
||||||
|
|
||||||
const link = 'text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline';
|
const link = 'text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline';
|
||||||
|
|
||||||
|
const isLabeled = (item: string | { label: string; text: string }): item is { label: string; text: string } =>
|
||||||
|
typeof item !== 'string';
|
||||||
---
|
---
|
||||||
|
|
||||||
<SubpageLayout
|
<SubpageLayout
|
||||||
title="Codice etico — NexStudio"
|
title={page.title}
|
||||||
description="Codice etico definitivo NexStudio: valori, conformità legale e sanitaria, privacy, sicurezza, AI, governance e segnalazioni."
|
description={page.description}
|
||||||
heading="Codice etico definitivo"
|
heading={page.heading}
|
||||||
lead="Per NexStudio — sede: Bangkok, Thailandia. Versione 1.0, in vigore dal 21 aprile 2026. Documento interno ed esterno: principi, obblighi e procedure operative per chi collabora con la Società."
|
lead={page.lead}
|
||||||
>
|
>
|
||||||
<div class="[&_h3]:mt-6 [&_h3]:text-base [&_h3]:font-semibold [&_h3]:text-nx-fg">
|
<div class="[&_h3]:mt-6 [&_h3]:text-base [&_h3]:font-semibold [&_h3]:text-nx-fg">
|
||||||
<p class="border-l-2 border-sky-500/35 pl-4 text-sm leading-relaxed text-nx-muted">
|
<p class="border-l-2 border-sky-500/35 pl-4 text-sm leading-relaxed text-nx-muted">
|
||||||
<span class="font-medium text-nx-fg">Versione</span> 1.0
|
<span class="font-medium text-nx-fg">{page.versionLabel}</span> 1.0
|
||||||
<span class="text-nx-muted/80"> · </span>
|
<span class="text-nx-muted/80"> · </span>
|
||||||
<span class="font-medium text-nx-fg">Entrata in vigore</span>: 21 aprile 2026
|
<span class="font-medium text-nx-fg">{page.inForceLabel}</span>: {page.inForceDate}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 id="indice" class="!mt-8">Indice sintetico</h2>
|
<h2 id="indice" class="!mt-8">{page.indexTitle}</h2>
|
||||||
<ol class="!mt-3 list-decimal space-y-1.5 pl-5 text-sm leading-relaxed sm:text-base">
|
<ol class="!mt-3 list-decimal space-y-1.5 pl-5 text-sm leading-relaxed sm:text-base">
|
||||||
<li><a class={link} href="#premessa">Premessa</a></li>
|
{
|
||||||
<li><a class={link} href="#ambito">Ambito di applicazione</a></li>
|
c.index.map((item) => (
|
||||||
<li><a class={link} href="#valori">Valori e principi fondamentali</a></li>
|
|
||||||
<li>
|
|
||||||
<a class={link} href="#conformita">Conformità normativa (per perimetro: piattaforma, legale, sanitario)</a>
|
|
||||||
<ol class="mt-2 list-decimal space-y-1 pl-5 text-nx-muted/95">
|
|
||||||
<li>
|
<li>
|
||||||
<a class={link} href="#conformita-piattaforma">4.1 Piattaforma e SaaS (orizzontale)</a>
|
<a class={link} href={item.href}>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
{item.children ? (
|
||||||
|
<ol class="mt-2 list-decimal space-y-1 pl-5 text-nx-muted/95">
|
||||||
|
{item.children.map((child) => (
|
||||||
|
<li>
|
||||||
|
<a class={link} href={child.href}>
|
||||||
|
{child.label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
) : null}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
))
|
||||||
<a class={link} href="#conformita-legal">4.2 Ambito legale (LexAura)</a>
|
}
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class={link} href="#conformita-sanitario">4.3 Ambito sanitario (MediAura)</a>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class={link} href="#tutela-dati">Tutela dei dati personali e dati sensibili</a>
|
|
||||||
<ol class="mt-2 list-decimal space-y-1 pl-5 text-nx-muted/95">
|
|
||||||
<li>
|
|
||||||
<a class={link} href="#tutela-dati-ruoli"
|
|
||||||
>5.1 Ruoli: pazienti, clienti, abbonati, Società</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class={link} href="#sicurezza">Sicurezza dell’informazione e resilienza operativa</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class={link} href="#qualita"
|
|
||||||
>Qualità del software, validazione clinica e rischio per gli utenti</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class={link} href="#proprieta-intellettuale"
|
|
||||||
>Proprietà intellettuale e uso di componenti esterni</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li><a class={link} href="#ai-ml">Uso responsabile di AI/ML e automazione</a></li>
|
|
||||||
<li>
|
|
||||||
<a class={link} href="#conflitti">Conflitti di interesse e integrità commerciale</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class={link} href="#linee-operative"
|
|
||||||
>Linee operative: sviluppo, rilascio e gestione vulnerabilità</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li><a class={link} href="#governance">Governance, ruoli e responsabilità</a></li>
|
|
||||||
<li>
|
|
||||||
<a class={link} href="#onboarding">Onboarding, formazione e certificazione</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class={link} href="#segnalazioni"
|
|
||||||
>Segnalazioni, protezione del segnalante e investigazioni</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li><a class={link} href="#violazioni">Gestione delle violazioni e sanzioni</a></li>
|
|
||||||
<li>
|
|
||||||
<a class={link} href="#monitoraggio">Monitoraggio, audit e miglioramento continuo</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class={link} href="#documentazione">Documentazione obbligatoria e registri</a>
|
|
||||||
</li>
|
|
||||||
<li><a class={link} href="#allegati">Allegati e moduli</a></li>
|
|
||||||
<li><a class={link} href="#approvazione">Approvazione e contatti utili</a></li>
|
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<h2 id="premessa">1. Premessa</h2>
|
<h2 id="premessa">{c.premessaTitle}</h2>
|
||||||
<p>
|
<p>{c.premessa}</p>
|
||||||
{getCodiceEticoPremessa()}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2 id="ambito">2. Ambito di applicazione</h2>
|
<h2 id="ambito">{c.ambito.title}</h2>
|
||||||
<p>
|
<p>{c.ambito.text}</p>
|
||||||
Si applica a: fondatori, dirigenti, dipendenti (full-time, part-time), collaboratori esterni,
|
|
||||||
consulenti, appaltatori e fornitori. Copre tutte le attività: analisi, progettazione,
|
|
||||||
sviluppo, test, rilascio, manutenzione, supporto, marketing, vendite e ricerca.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2 id="valori">3. Valori e principi fondamentali</h2>
|
<h2 id="valori">{c.valori.title}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Integrità, onestà e trasparenza.</li>
|
{c.valori.items.map((item) => <li>{item}</li>)}
|
||||||
<li>Rispetto della dignità umana, inclusione e non discriminazione.</li>
|
|
||||||
<li>Responsabilità tecnico-professionale e qualità del servizio.</li>
|
|
||||||
<li>Riservatezza e protezione dei dati personali e sensibili.</li>
|
|
||||||
<li>Sicurezza e resilienza operativa.</li>
|
|
||||||
<li>Conformità legale e regolamentare.</li>
|
|
||||||
<li>Miglioramento continuo e accountability.</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="conformita">4. Conformità normativa specifica</h2>
|
<h2 id="conformita">{c.conformita.title}</h2>
|
||||||
<p>
|
<p>
|
||||||
I riferimenti sotto sono raggruppati: obblighi orizzontali a tutte le offerte
|
{c.conformita.introBefore}
|
||||||
<strong>SaaS</strong>, riferimenti distinti per il perimetro
|
<span class="text-nx-fg">{c.productListPhrase}</span>
|
||||||
<strong>Legal Tech / LexAura</strong> e per <strong>Health Tech / MediAura</strong> (ciascuna
|
{c.conformita.introAfter}
|
||||||
linea può estendersi o diversificare secondo prodotto e mercato, senza commistione indebita
|
|
||||||
ad altri perimetri). Riepilogo riga prodotto:
|
|
||||||
<span class="text-nx-fg">{getCodiceEticoProductListPhrase()}</span>.
|
|
||||||
</p>
|
</p>
|
||||||
{
|
{
|
||||||
codiceEticoConformityBlocks.map((block, i) => (
|
c.conformityBlocks.map((block, i) => (
|
||||||
<section
|
<section class:list={['', i > 0 && 'mt-5']} data-conformity-block={block.id}>
|
||||||
class:list={['', i > 0 && 'mt-5']}
|
<h3 id={block.anchorId} class="scroll-mt-8 text-base font-semibold text-nx-fg">
|
||||||
data-conformity-block={block.id}
|
|
||||||
>
|
|
||||||
<h3
|
|
||||||
id={block.anchorId}
|
|
||||||
class="scroll-mt-8 text-base font-semibold text-nx-fg"
|
|
||||||
>
|
|
||||||
{block.title}
|
{block.title}
|
||||||
</h3>
|
</h3>
|
||||||
{block.lead ? <p class="text-sm text-nx-muted sm:text-base">{block.lead}</p> : null}
|
{block.lead ? <p class="text-sm text-nx-muted sm:text-base">{block.lead}</p> : null}
|
||||||
@ -149,392 +83,271 @@ const link = 'text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underl
|
|||||||
</section>
|
</section>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
<p class="mt-6 text-sm sm:text-base">
|
<p class="mt-6 text-sm sm:text-base">{c.conformita.afterBlocks}</p>
|
||||||
La Società effettua DPIA (Data Protection Impact Assessment) per trattamenti ad alto rischio e
|
|
||||||
consulta autorità o <em>regulatory counsel</em> quando necessario, con riferimenti
|
|
||||||
<strong>per ciascun trattamento</strong> al perimetro (piattaforma, legale, sanitario) ove
|
|
||||||
interessa.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2 id="tutela-dati">5. Tutela dei dati personali e dati sensibili</h2>
|
<h2 id="tutela-dati">{c.tutelaDati.title}</h2>
|
||||||
<p class="text-nx-muted sm:text-base">{codiceEticoTutelaDatiRuoli.intro}</p>
|
<p class="text-nx-muted sm:text-base">{c.tutelaDatiRuoli.intro}</p>
|
||||||
<h3
|
<h3
|
||||||
id={codiceEticoTutelaDatiRuoli.anchorId}
|
id={c.tutelaDatiRuoli.anchorId}
|
||||||
class="!mt-6 text-base font-semibold text-nx-fg"
|
class="!mt-6 text-base font-semibold text-nx-fg"
|
||||||
>
|
>
|
||||||
{codiceEticoTutelaDatiRuoli.title}
|
{c.tutelaDatiRuoli.title}
|
||||||
</h3>
|
</h3>
|
||||||
{
|
{
|
||||||
codiceEticoTutelaDatiRuoli.rows.map((r) => (
|
c.tutelaDatiRuoli.rows.map((r) => (
|
||||||
<p class="mt-3 sm:text-base">
|
<p class="mt-3 sm:text-base">
|
||||||
<span class="font-semibold text-nx-fg">{r.label}.</span>{' '}
|
<span class="font-semibold text-nx-fg">{r.label}.</span> {r.text}
|
||||||
{r.text}
|
|
||||||
</p>
|
</p>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
<p
|
<p class="mt-4 border-l-2 border-sky-500/30 pl-4 text-sm leading-relaxed text-nx-muted">
|
||||||
class="mt-4 border-l-2 border-sky-500/30 pl-4 text-sm leading-relaxed text-nx-muted"
|
{c.tutelaDatiRuoli.note}
|
||||||
>
|
|
||||||
{codiceEticoTutelaDatiRuoli.note}
|
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-8 text-sm font-medium text-nx-fg sm:text-base">
|
<p class="mt-8 text-sm font-medium text-nx-fg sm:text-base">
|
||||||
Nella concreta operatività, la Società e i collaboratori si attengono ai principi operativi
|
{c.tutelaDati.operationalIntro}
|
||||||
seguenti (complementari ai ruoli sopra):
|
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Minimizzazione:</strong> raccogliere solo i dati necessari e pertinenti.</li>
|
|
||||||
<li>
|
|
||||||
<strong>Base giuridica:</strong> documentare la base legale per ogni trattamento (consenso,
|
|
||||||
esecuzione del contratto, obbligo legale, interesse legittimo valutato).
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Informativa e consenso:</strong> fornire informative chiare e ottenere consensi
|
|
||||||
quando richiesto; gestire il consenso in modo verificabile.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Classificazione:</strong> definire categorie (PII, dati sanitari, informazioni legali
|
|
||||||
privilegiate) e applicare misure differenziate.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Conservazione e cancellazione:</strong> politiche di retention documentate;
|
|
||||||
anonimizzazione o cancellazione al termine delle finalità.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Trasferimenti internazionali:</strong> valutare basi legali (clausole contrattuali
|
|
||||||
standard, decisioni di adeguatezza, misure tecniche) per trasferimenti fuori dalla
|
|
||||||
Thailandia.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Diritti degli interessati:</strong> procedure per accesso, rettifica, cancellazione,
|
|
||||||
limitazione, portabilità e opposizione, con tempi e log documentati.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Sub-processori:</strong> contratti che impongono misure di sicurezza equivalenti,
|
|
||||||
obblighi di notifica in caso di violazione (<em>breach</em>) e divieti di ulteriori
|
|
||||||
sub-affidamenti non autorizzati.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="sicurezza">6. Sicurezza dell’informazione e resilienza operativa</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<strong>Security by design</strong> e <strong>privacy by design:</strong> integrazione di
|
|
||||||
sicurezza e privacy nel ciclo di vita del prodotto.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Controlli di accesso:</strong> principio del privilegio minimo, MFA obbligatorio per
|
|
||||||
accesso a risorse sensibili, gestione centralizzata delle identità.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Crittografia:</strong> cifratura a riposo e in transito con algoritmi aggiornati;
|
|
||||||
gestione sicura delle chiavi.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Logging e monitoraggio:</strong> log immutabili per accessi e operazioni su dati
|
|
||||||
sensibili e ambienti di produzione.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Vulnerability management:</strong> scansioni automatiche, penetration test
|
|
||||||
periodici, processo di patching e programma di responsible disclosure o bug bounty.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Continuità operativa e disaster recovery:</strong> piani testati regolarmente con
|
|
||||||
RTO/RPO definiti.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Gestione incidenti:</strong> playbook documentato (identificazione, containment,
|
|
||||||
eradicazione, recovery, post-mortem), ruoli e tempi di notifica interni ed esterni.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="qualita">7. Qualità del software, validazione clinica e rischio per gli utenti</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<strong>Standard di sviluppo:</strong> codice leggibile, test automatici, code review
|
|
||||||
obbligatorie, CI/CD sicuro.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Test e copertura:</strong> definire copertura minima per componenti critici; test di
|
|
||||||
integrazione, performance e sicurezza.
|
|
||||||
</li>
|
|
||||||
{
|
{
|
||||||
qualitaValidazioneProdotto.map((v) => (
|
c.tutelaDati.principles.map((p) => (
|
||||||
<li>
|
<li>
|
||||||
<strong>Validazione (per {v.id === 'legal' ? 'perimetro legale' : 'perimetro sanitario'}):</strong>{' '}
|
<strong>{p.label}:</strong> {p.text}
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="sicurezza">{c.sicurezza.title}</h2>
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
c.sicurezza.items.map((item) => (
|
||||||
|
<li>
|
||||||
|
<strong>{item.label}:</strong> {item.text}
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="qualita">{c.qualita.title}</h2>
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
c.qualita.itemsBefore.map((item) => (
|
||||||
|
<li>
|
||||||
|
<strong>{item.label}:</strong> {item.text}
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
c.qualitaValidazioneProdotto.map((v) => (
|
||||||
|
<li>
|
||||||
|
<strong>
|
||||||
|
{v.id === 'legal'
|
||||||
|
? c.qualita.validazioneLabelLegal
|
||||||
|
: c.qualita.validazioneLabelHealth}
|
||||||
|
:
|
||||||
|
</strong>{' '}
|
||||||
{v.text}
|
{v.text}
|
||||||
</li>
|
</li>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
c.qualita.itemsAfter.map((item) => (
|
||||||
|
<li>
|
||||||
|
<strong>{item.label}:</strong> {item.text}
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="proprieta-intellettuale">{c.proprieta.title}</h2>
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
c.proprieta.items.map((item) =>
|
||||||
|
item.kind === 'labeled' ? (
|
||||||
|
<li>
|
||||||
|
<strong>{item.label}:</strong> {item.text}
|
||||||
|
</li>
|
||||||
|
) : (
|
||||||
|
<li>{item.text}</li>
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="ai-ml">{c.ai.title}</h2>
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
c.ai.items.map((item) =>
|
||||||
|
isLabeled(item) ? (
|
||||||
|
<li>
|
||||||
|
<strong>{item.label}:</strong> {item.text}
|
||||||
|
</li>
|
||||||
|
) : (
|
||||||
|
<li>{item}</li>
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="conflitti">{c.conflitti.title}</h2>
|
||||||
|
<ul>
|
||||||
|
{c.conflitti.items.map((item) => <li>{item}</li>)}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="linee-operative">{c.lineeOperative.title}</h2>
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
c.lineeOperative.items.map((item) => (
|
||||||
|
<li>
|
||||||
|
<strong>{item.label}:</strong> {item.text}
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
<p class="mt-3 text-sm">
|
||||||
|
<a
|
||||||
|
class={link}
|
||||||
|
href={`/${currentLocale === 'it' ? '' : currentLocale + '/'}allegati#security-disclosure`}
|
||||||
|
>
|
||||||
|
{c.lineeOperative.disclosureLinkLabel}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="governance">{c.governance.title}</h2>
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
c.governance.items.map((item) => (
|
||||||
|
<li>
|
||||||
|
<strong>{item.label}:</strong> {item.text}
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="onboarding">{c.onboarding.title}</h2>
|
||||||
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<strong>Classificazione del rischio:</strong> valutare impatto su salute o diritti e
|
{c.onboarding.item1Before}
|
||||||
applicare misure di mitigazione proporzionate.
|
{c.productListPhrase}
|
||||||
|
{c.onboarding.item1After}
|
||||||
|
</li>
|
||||||
|
{c.onboarding.itemsRest.map((item) => <li>{item}</li>)}
|
||||||
|
</ul>
|
||||||
|
<p class="mt-3 text-sm">
|
||||||
|
<a
|
||||||
|
class={link}
|
||||||
|
href={`/${currentLocale === 'it' ? '' : currentLocale + '/'}allegati#piano-formazione`}
|
||||||
|
>
|
||||||
|
{c.onboarding.planLinkLabel}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="segnalazioni">{c.segnalazioni.title}</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>{c.segnalazioni.channelsLabel}:</strong>{' '}
|
||||||
|
{c.segnalazioni.channelsBefore}{' '}
|
||||||
|
<a class={link} href="mailto:ethics@nexstudio.com">ethics@nexstudio.com</a>
|
||||||
|
{c.segnalazioni.channelsAfter}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Non sostituzione del professionista:</strong> la piattaforma fornisce supporto e
|
<strong>{c.segnalazioni.protection.label}:</strong>{' '}
|
||||||
strumenti informativi; non si presenta come sostitutiva di pareri professionali senza
|
{c.segnalazioni.protection.text}
|
||||||
esplicita supervisione e avallo professionale.
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Registri ed evidenze:</strong> mantenere documentazione di test clinici, valutazioni
|
<strong>{c.segnalazioni.investigations.label}:</strong>{' '}
|
||||||
di rischio e approvazioni regolatorie.
|
{c.segnalazioni.investigations.text}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="proprieta-intellettuale">8. Proprietà intellettuale e uso di componenti esterni</h2>
|
<h2 id="violazioni">{c.violazioni.title}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
{c.violazioni.items.map((item) => <li>{item}</li>)}
|
||||||
I contributi di dipendenti e consulenti inerenti a software, documentazione e know-how sono
|
|
||||||
di proprietà della Società, salvo diverso accordo scritto.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Open source:</strong> mantenere SBOM (Software Bill of Materials), verificare la
|
|
||||||
compatibilità delle licenze, rispettare obblighi di disclosure e aggiornamento delle patch.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Divieto di uso non autorizzato di codice o proprietà intellettuale di terzi; gestione dei
|
|
||||||
rischi di contaminazione IP.
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="ai-ml">9. Uso responsabile di AI/ML e automazione</h2>
|
<h2 id="monitoraggio">{c.monitoraggio.title}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Documentare dataset di training, provenienza, pre-elaborazione e limiti noti.</li>
|
{
|
||||||
<li>
|
c.monitoraggio.items.map((item) =>
|
||||||
<strong>Bias assessment:</strong> condurre analisi per identificare e mitigare pregiudizi
|
isLabeled(item) ? (
|
||||||
che possano discriminare o causare danni.
|
<li>
|
||||||
</li>
|
<strong>{item.label}:</strong> {item.text}
|
||||||
<li>
|
</li>
|
||||||
<strong>Supervisione umana:</strong> per decisioni che influenzano salute, procedimenti
|
) : (
|
||||||
legali o diritti fondamentali, prevedere supervisione umana e avvisi chiari sui limiti del
|
<li>{item}</li>
|
||||||
sistema.
|
),
|
||||||
</li>
|
)
|
||||||
<li>
|
}
|
||||||
<strong>Explainability e responsabilità:</strong> fornire informazioni comprensibili su
|
|
||||||
come i modelli supportano le decisioni e su prestazioni e affidabilità.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Monitoraggio post-release:</strong> misurare le prestazioni in produzione e
|
|
||||||
correggere drift o comportamenti inattesi.
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="conflitti">10. Conflitti di interesse e integrità commerciale</h2>
|
<h2 id="documentazione">{c.documentazione.title}</h2>
|
||||||
|
<p>{c.documentazione.intro}</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
{c.documentazione.items.map((item) => <li>{item}</li>)}
|
||||||
Dichiarazione obbligatoria di conflitti reali o potenziali (investimenti, relazioni con
|
</ul>
|
||||||
fornitori o clienti).
|
<p class="mt-3 text-sm">
|
||||||
</li>
|
<a
|
||||||
<li>Politiche anticorruzione e anti-tangenti conformi a leggi nazionali e internazionali.</li>
|
class={link}
|
||||||
<li>
|
href={`/${currentLocale === 'it' ? '' : currentLocale + '/'}allegati#registri-operativi`}
|
||||||
Comunicazioni commerciali veritiere; non ingannare su capacità, approvazioni regolatorie o
|
>
|
||||||
risultati.
|
{c.documentazione.registersLinkLabel}
|
||||||
</li>
|
</a>
|
||||||
<li>
|
</p>
|
||||||
Trasparenza contrattuale: SLA, limitazioni di responsabilità e condizioni d’uso chiare e
|
|
||||||
accessibili.
|
<h2 id="allegati">{c.allegati.title}</h2>
|
||||||
</li>
|
<p class="mb-4 text-sm">
|
||||||
|
<a class={link} href={`/${currentLocale === 'it' ? '' : currentLocale + '/'}allegati`}>
|
||||||
|
{c.allegati.linkLabel}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{c.allegati.items.map((item) => <li>{item}</li>)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="linee-operative">11. Linee operative: sviluppo, rilascio e gestione vulnerabilità</h2>
|
<h2 id="approvazione">{c.approvazione.title}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
{
|
||||||
<strong>Sviluppo:</strong> strategia di branch, code review obbligatoria, merge solo con CI
|
c.approvazione.items.map((item) => (
|
||||||
positiva, checklist pre-release (sicurezza, privacy, compliance).
|
<li>
|
||||||
</li>
|
<strong>{item.label}:</strong> {item.text}
|
||||||
<li>
|
</li>
|
||||||
<strong>Ambiente di test:</strong> uso di dati sintetici o anonimizzati; accesso a dati
|
))
|
||||||
reali solo su base minima e autorizzata, in ambienti isolati.
|
}
|
||||||
</li>
|
</ul>
|
||||||
<li>
|
<p class="font-medium text-nx-fg">{page.contactsTitle}</p>
|
||||||
<strong>Deployment:</strong> CI/CD standardizzato con piano di rollback e rilasci graduali
|
<ul>
|
||||||
per funzionalità critiche.
|
{
|
||||||
</li>
|
c.approvazione.contacts.map((contact) => (
|
||||||
<li>
|
<li>
|
||||||
<strong>Gestione vulnerabilità:</strong> canale pubblico o privato per segnalazioni (bug
|
{contact.label}:{' '}
|
||||||
bounty o indirizzo dedicato <em>security@</em>), conferma di ricezione entro 72 ore, piano di
|
<a class={link} href={`mailto:${contact.email}`}>
|
||||||
remediation con tempi stimati.
|
{contact.email}
|
||||||
</li>
|
</a>
|
||||||
<li>
|
{contact.note === 'anonymous' ? (
|
||||||
<strong>Patch management:</strong> tempi di rilascio delle patch definiti in base alla
|
<> {page.anonymousPlatformNote}</>
|
||||||
severità.
|
) : null}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
))
|
||||||
<strong>Documentazione di rilascio:</strong> changelog, impatti noti e raccomandazioni
|
}
|
||||||
operative.
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="governance">12. Governance, ruoli e responsabilità</h2>
|
<h2 id="clausola-adesione">{c.adesione.title}</h2>
|
||||||
<ul>
|
|
||||||
<li><strong>Board / fondatori:</strong> approvano le politiche, definiscono strategia e risorse.</li>
|
|
||||||
<li><strong>CEO:</strong> responsabilità complessiva di conformità e governance.</li>
|
|
||||||
<li><strong>CTO:</strong> qualità tecnica, architettura e pratiche di sviluppo.</li>
|
|
||||||
<li>
|
|
||||||
<strong>CISO / responsabile sicurezza:</strong> sicurezza operativa, risposta agli incidenti,
|
|
||||||
gestione delle vulnerabilità.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>DPO / referente privacy:</strong> conformità PDPA e GDPR, DPIA, gestione richieste
|
|
||||||
degli interessati.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Legal & compliance:</strong> supervisione legale, contratti e valutazioni
|
|
||||||
regolatorie.
|
|
||||||
</li>
|
|
||||||
<li><strong>HR:</strong> formazione, codice di condotta e gestione disciplinare.</li>
|
|
||||||
<li>
|
|
||||||
<strong>Team lead / PM:</strong> applicano le policy operative e garantiscono conformità
|
|
||||||
quotidiana.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Comitato etico (consigliato):</strong> gruppo multidisciplinare per valutare casi
|
|
||||||
complessi (impatti clinici o legali, conflitti), con riunioni periodiche e su richiesta.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="onboarding">13. Onboarding, formazione e certificazione</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Onboarding obbligatorio su: sicurezza informatica, privacy (PDPA e GDPR), gestione dati
|
|
||||||
sensibili, requisiti e documentazione riferibili a ciascuna linea prodotto
|
|
||||||
(oggi: {getCodiceEticoProductListPhrase()}; estendere il curriculum quando l’elenco
|
|
||||||
cresce), uso responsabile dell’AI, pratiche di coding sicuro.
|
|
||||||
</li>
|
|
||||||
<li>Formazione annuale obbligatoria e formazione aggiuntiva per ruoli critici.</li>
|
|
||||||
<li>Registrazione e conservazione dei record di formazione.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="segnalazioni">14. Segnalazioni, protezione del segnalante e investigazioni</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<strong>Canali:</strong> almeno un canale confidenziale interno (es. <a
|
|
||||||
class={link}
|
|
||||||
href="mailto:ethics@nexstudio.com">ethics@nexstudio.com</a
|
|
||||||
>), piattaforma anonima esterna o servizio terzo; contatto per escalation indipendente.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Protezione:</strong> divieto di ritorsioni; misure disciplinari per ritorsioni
|
|
||||||
comprovate.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Indagini:</strong> condotte in modo imparziale, documentato e con tempi definiti;
|
|
||||||
comunicazione dei risultati alla persona segnalante e alle parti interessate, nei limiti
|
|
||||||
della riservatezza.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="violazioni">15. Gestione delle violazioni e sanzioni</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Azioni proporzionate alla gravità: formazione correttiva, richiamo, riassegnazione,
|
|
||||||
sospensione, risoluzione del rapporto contrattuale, azioni legali se necessario.
|
|
||||||
</li>
|
|
||||||
<li>Registro delle infrazioni e delle azioni intraprese; diritto di difesa dell’interessato.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="monitoraggio">16. Monitoraggio, audit e miglioramento continuo</h2>
|
|
||||||
<ul>
|
|
||||||
<li>Audit interni ed esterni regolari su sicurezza, privacy, compliance e controllo qualità.</li>
|
|
||||||
<li>
|
|
||||||
<strong>KPI (esempi):</strong> numero di incidenti, tempo medio di remediation, percentuale
|
|
||||||
di copertura dei test, percentuale di formazione completata, tempo medio di risposta alle
|
|
||||||
segnalazioni.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Revisione del Codice: almeno annuale o in caso di cambiamenti normativi, tecnologici o di
|
|
||||||
mercato.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="documentazione">17. Documentazione obbligatoria e registri</h2>
|
|
||||||
<p>La Società mantiene e aggiorna, tra l’altro:</p>
|
|
||||||
<ul>
|
|
||||||
<li>registro dei trattamenti e DPIA;</li>
|
|
||||||
<li>SBOM e inventario dei componenti (open source);</li>
|
|
||||||
<li>log di accesso e audit trail;</li>
|
|
||||||
<li>registro incidenti e violazioni dei dati;</li>
|
|
||||||
<li>contratti con fornitori e sub-processori;</li>
|
|
||||||
<li>record di formazione e dichiarazioni di adesione al Codice.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="allegati">18. Allegati e moduli (inclusi)</h2>
|
|
||||||
<ul>
|
|
||||||
<li>Dichiarazione personale di adesione al Codice (da firmare in onboarding).</li>
|
|
||||||
<li>Template NDA e clausole minime per fornitori e sub-processori.</li>
|
|
||||||
<li>Checklist pre-release (security e privacy).</li>
|
|
||||||
<li>Flowchart gestione incidenti e template di notifica.</li>
|
|
||||||
<li>Modello DPIA semplificato ed esempio compilato.</li>
|
|
||||||
<li>Modello informativa privacy e modulo consenso per utenti o pazienti.</li>
|
|
||||||
<li>Template SBOM.</li>
|
|
||||||
<li>Policy di retention dei dati (tempi e giustificazioni).</li>
|
|
||||||
<li>Template per valutazione d’impatto AI/ML.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="approvazione">19. Approvazione e contatti utili</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<strong>Approvato da:</strong> organo competente / amministratore delegato (nome e cognome
|
|
||||||
alla firma).
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Responsabile aggiornamenti:</strong> Legal & compliance, con supporto di DPO
|
|
||||||
e CISO.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Revisione:</strong> minimo annuale o a fronte di mutamenti rilevanti.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p class="font-medium text-nx-fg">Contatti</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
DPO / referente privacy: <a class={link} href="mailto:privacy@nexstudio.com"
|
|
||||||
>privacy@nexstudio.com</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Security / incident response: <a class={link} href="mailto:security@nexstudio.com"
|
|
||||||
>security@nexstudio.com</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Segnalazioni confidenziali: <a class={link} href="mailto:ethics@nexstudio.com"
|
|
||||||
>ethics@nexstudio.com</a
|
|
||||||
>
|
|
||||||
(o link a piattaforma anonima dedicata)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Legal: <a class={link} href="mailto:legal@nexstudio.com">legal@nexstudio.com</a>
|
|
||||||
</li>
|
|
||||||
<li>HR: <a class={link} href="mailto:hr@nexstudio.com">hr@nexstudio.com</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="clausola-adesione">Clausola finale di adesione (da firmare in onboarding)</h2>
|
|
||||||
<p>
|
<p>
|
||||||
Dichiaro di aver letto e compreso il Codice Etico di <strong>NexStudio</strong> e mi impegno a
|
{c.adesione.textBefore}
|
||||||
rispettarne i principi e le procedure. Mi impegno inoltre a segnalare, in buona fede, eventuali
|
<strong>NexStudio</strong>
|
||||||
violazioni di cui dovessi venire a conoscenza.
|
{c.adesione.textAfter}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-4 text-nx-fg">
|
<p class="mt-4 text-nx-fg">
|
||||||
Firma: <span class="inline-block min-w-[12rem] border-b border-nx-border"> </span>
|
{page.signatureLabel}:{' '}
|
||||||
|
<span class="inline-block min-w-[12rem] border-b border-nx-border"> </span>
|
||||||
<span class="mx-3 text-nx-muted">·</span>
|
<span class="mx-3 text-nx-muted">·</span>
|
||||||
Data: <span class="inline-block min-w-[6rem] border-b border-nx-border"> </span>
|
{page.dateLabel}:{' '}
|
||||||
|
<span class="inline-block min-w-[6rem] border-b border-nx-border"> </span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 id="note-pratiche">Note pratiche e prossimi passi consigliati</h2>
|
<h2 id="note-pratiche">{c.notePratiche.title}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
{c.notePratiche.items.map((item) => <li>{item}</li>)}
|
||||||
Personalizzare il documento con il nome ufficiale della società (se diverso dal marchio
|
{c.notePraticheDPIA.map((t) => <li>{t}</li>)}
|
||||||
operativo), firme e riferimenti legali locali a Bangkok.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Allegare i template elencati come documenti separati e implementare i canali tecnici per
|
|
||||||
segnalazioni e bug bounty.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Predisporre un piano di formazione di 90 giorni per l’onboarding e formazione ricorrente
|
|
||||||
annuale.
|
|
||||||
</li>
|
|
||||||
{notePraticheDPIA.map((t) => <li>{t}</li>)}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</SubpageLayout>
|
</SubpageLayout>
|
||||||
|
|||||||
@ -1,252 +1,203 @@
|
|||||||
---
|
---
|
||||||
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
import { cookieConsentMeta } from '../data/cookie-consent';
|
import { cookieConsentMeta } from '../data/cookie-consent';
|
||||||
|
import type { SupportedLocale } from '../data/home/navigation';
|
||||||
|
import { getCookies } from '../data/cookies';
|
||||||
|
|
||||||
|
const path = Astro.url.pathname;
|
||||||
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
|
const currentLocale = (localeMatch?.[1] ?? 'it') as SupportedLocale;
|
||||||
|
const cookies = getCookies(currentLocale);
|
||||||
|
const { document: cookiesDocument, riferimenti, sections, page } = cookies;
|
||||||
|
|
||||||
|
const localize = (href: string) => {
|
||||||
|
if (currentLocale === 'it') return href;
|
||||||
|
if (href.startsWith(`/${currentLocale}`)) return href;
|
||||||
|
if (href.startsWith('/#')) return `/${currentLocale}${href}`;
|
||||||
|
if (href.startsWith('/')) return `/${currentLocale}${href}`;
|
||||||
|
return href;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveHref = (href: string) =>
|
||||||
|
href.startsWith('mailto:') || href.startsWith('http') ? href : localize(href);
|
||||||
|
|
||||||
const link = 'text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline';
|
const link = 'text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline';
|
||||||
const tbl = 'mt-4 w-full border-collapse text-left text-sm';
|
const tbl = 'mt-4 w-full border-collapse text-left text-sm';
|
||||||
const th = 'border border-nx-border bg-nx-surface/60 px-3 py-2 font-semibold text-nx-fg';
|
const th = 'border border-nx-border bg-nx-surface/60 px-3 py-2 font-semibold text-nx-fg';
|
||||||
const td = 'border border-nx-border px-3 py-2 align-top text-nx-muted';
|
const td = 'border border-nx-border px-3 py-2 align-top text-nx-muted';
|
||||||
|
|
||||||
const riferimenti = [
|
const indexTitle = (title: string) => title.replace(/^\d+\.\s*/, '');
|
||||||
{ label: 'Informativa privacy', href: '/privacy' },
|
|
||||||
{ label: "Condizioni d'uso", href: '/terms' },
|
|
||||||
{ label: 'Diritti GDPR', href: '/gdpr' },
|
|
||||||
] as const;
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<SubpageLayout
|
<SubpageLayout
|
||||||
title="Cookie Policy — NexStudio"
|
title={page.title}
|
||||||
description="Cookie Policy NexStudio S.r.l.: categorie, base giuridica, consenso, terze parti, diritti e contatti. Sede legale in Italia."
|
description={page.description}
|
||||||
heading="Cookie Policy"
|
heading={page.heading}
|
||||||
lead="Titolare del trattamento: NexStudio S.r.l., con sede legale in Italia, [INDIRIZZO_COMPLETO]."
|
lead={page.lead}
|
||||||
>
|
>
|
||||||
<div class="[&_h3]:mt-6 [&_h3]:text-base [&_h3]:font-semibold [&_h3]:text-nx-fg">
|
<div class="[&_h3]:mt-6 [&_h3]:text-base [&_h3]:font-semibold [&_h3]:text-nx-fg">
|
||||||
<p class="border-l-2 border-sky-500/35 pl-4 text-sm leading-relaxed text-nx-muted">
|
<p class="border-l-2 border-sky-500/35 pl-4 text-sm leading-relaxed text-nx-muted">
|
||||||
<span class="font-medium text-nx-fg">Versione</span> 1.1
|
<span class="font-medium text-nx-fg">{page.versionLabel}</span> {cookiesDocument.version}
|
||||||
<span class="text-nx-muted/80"> · </span>
|
<span class="text-nx-muted/80"> · </span>
|
||||||
<span class="font-medium text-nx-fg">In vigore dal</span> [DATA]
|
<span class="font-medium text-nx-fg">{page.inForceFromLabel}</span> {cookiesDocument.inVigoreDal}
|
||||||
<span class="text-nx-muted/80"> · </span>
|
<span class="text-nx-muted/80"> · </span>
|
||||||
<span class="font-medium text-nx-fg">Versione tecnica consenso</span>{' '}
|
<span class="font-medium text-nx-fg">{page.consentVersionLabel}</span>{' '}
|
||||||
<code class="rounded bg-nx-elevated px-1.5 py-0.5 font-mono text-xs text-sky-300/90"
|
<code class="rounded bg-nx-elevated px-1.5 py-0.5 font-mono text-xs text-sky-300/90"
|
||||||
>v{cookieConsentMeta.policyVersion}</code
|
>v{cookieConsentMeta.policyVersion}</code
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="!mt-5 text-sm text-nx-muted sm:text-base">
|
<p class="!mt-5 text-sm text-nx-muted sm:text-base">
|
||||||
Documenti connessi:{' '}
|
{page.relatedDocsLabel}:{' '}
|
||||||
{
|
{
|
||||||
riferimenti.map((r, i) => (
|
riferimenti.map((r, i) => (
|
||||||
<span>
|
<span>
|
||||||
{i > 0 && <span class="text-nx-muted/80"> · </span>}
|
{i > 0 && <span class="text-nx-muted/80"> · </span>}
|
||||||
<a class={link} href={r.href}>{r.label}</a>
|
<a class={link} href={localize(r.href)}>{r.label}</a>
|
||||||
</span>
|
</span>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 id="indice" class="!mt-8">Indice</h2>
|
<h2 id="indice" class="!mt-8">{page.indexLabel}</h2>
|
||||||
<ol class="!mt-3 list-decimal space-y-1.5 pl-5 text-sm leading-relaxed sm:text-base">
|
<ol class="!mt-3 list-decimal space-y-1.5 pl-5 text-sm leading-relaxed sm:text-base">
|
||||||
<li><a class={link} href="#premessa">Premessa</a></li>
|
{
|
||||||
<li><a class={link} href="#definizioni">Definizioni</a></li>
|
sections.map((s) => (
|
||||||
<li><a class={link} href="#finalita">Finalità dei cookie e categorie</a></li>
|
<li>
|
||||||
<li><a class={link} href="#elenco">Elenco dei cookie utilizzati</a></li>
|
<a class={link} href={`#${s.id}`}>{indexTitle(s.title)}</a>
|
||||||
<li><a class={link} href="#base-giuridica">Base giuridica</a></li>
|
</li>
|
||||||
<li><a class={link} href="#consenso">Modalità di raccolta del consenso e registro</a></li>
|
))
|
||||||
<li><a class={link} href="#revoca">Gestione e revoca del consenso</a></li>
|
}
|
||||||
<li><a class={link} href="#browser">Disabilitazione via browser e impatto</a></li>
|
|
||||||
<li><a class={link} href="#terze-parti">Cookie di terze parti e responsabilità</a></li>
|
|
||||||
<li><a class={link} href="#retention">Conservazione e criteri di retention</a></li>
|
|
||||||
<li><a class={link} href="#trasferimenti">Trasferimenti internazionali</a></li>
|
|
||||||
<li><a class={link} href="#minori">Minori età</a></li>
|
|
||||||
<li><a class={link} href="#sicurezza">Misure di sicurezza</a></li>
|
|
||||||
<li><a class={link} href="#advertising">Consenso per scopi pubblicitari e profiling</a></li>
|
|
||||||
<li><a class={link} href="#effetti">Effetti di rifiuto o accettazione</a></li>
|
|
||||||
<li><a class={link} href="#modifiche">Modifiche alla Cookie Policy</a></li>
|
|
||||||
<li><a class={link} href="#diritti">Diritti degli interessati e contatti</a></li>
|
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<h2 id="premessa">1. Premessa</h2>
|
{
|
||||||
<p>
|
sections.map((s) => (
|
||||||
Questa Cookie Policy descrive le tipologie di cookie e tecnologie simili utilizzate sul sito
|
<section class="mt-10" id={s.id}>
|
||||||
web di NexStudio S.r.l. (di seguito «NexStudio», «noi»), con sede legale in Italia,
|
<h2 class="scroll-mt-8 text-lg font-semibold text-nx-fg sm:text-xl">{s.title}</h2>
|
||||||
[INDIRIZZO_COMPLETO], e spiega come gestire le preferenze. Per il trattamento dei dati
|
|
||||||
personali e per i diritti degli interessati si rimanda all’<a class={link} href="/privacy"
|
|
||||||
>Informativa privacy</a
|
|
||||||
>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Per le cookie policy dei prodotti SaaS LexAura e MediAura, consultare le rispettive pagine sui
|
|
||||||
portali dedicati.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2 id="definizioni">2. Definizioni</h2>
|
{s.richParagraphs?.map((parts) => (
|
||||||
<ul>
|
<p class="mt-3 text-sm leading-relaxed text-nx-muted sm:text-base">
|
||||||
<li>
|
{parts.map((part) => {
|
||||||
<strong>Cookie:</strong> piccolo file di testo inviato dal sito al browser e memorizzato sul
|
if (part.t === 'text') return part.v;
|
||||||
dispositivo, re-inviato al sito alle visite successive.
|
if (part.t === 'em') return <em>{part.v}</em>;
|
||||||
</li>
|
if (part.t === 'strong') return <strong>{part.v}</strong>;
|
||||||
<li>
|
if (part.external) {
|
||||||
<strong>Cookie di prima parte:</strong> impostati dal dominio del sito che l’utente visita.
|
return (
|
||||||
</li>
|
<a class={link} href={part.href} target="_blank" rel="noopener noreferrer">
|
||||||
<li>
|
{part.label}
|
||||||
<strong>Cookie di terze parti:</strong> impostati da domini diversi (fornitori esterni,
|
</a>
|
||||||
embed, CDN, strumenti di misura).
|
);
|
||||||
</li>
|
}
|
||||||
<li>
|
return (
|
||||||
<strong>Tecnologie simili:</strong> web beacon, pixel, <em>local storage</em>, e in generale
|
<a class={link} href={resolveHref(part.href)}>
|
||||||
identificatori lato client.
|
{part.label}
|
||||||
</li>
|
</a>
|
||||||
</ul>
|
);
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
|
||||||
<h2 id="finalita">3. Finalità dei cookie e categorie</h2>
|
{s.paragraphs?.map((p) => (
|
||||||
|
<p class="mt-3 text-sm leading-relaxed text-nx-muted sm:text-base">{p}</p>
|
||||||
|
))}
|
||||||
|
|
||||||
<h3>3.1 Cookie strettamente necessari (essenziali)</h3>
|
{s.definitions && s.definitions.length > 0 ? (
|
||||||
<p>
|
<ul class="mt-3 list-disc space-y-2 pl-5 text-sm sm:text-base">
|
||||||
<strong>Finalità:</strong> navigazione, sicurezza, gestione sessione, memorizzazione delle
|
{s.definitions.map((d) => (
|
||||||
preferenze di consenso. <strong>Base giuridica:</strong> legittimo interesse del titolare
|
<li class="text-nx-muted">
|
||||||
(Art. 6.1.f GDPR) a garantire il funzionamento del servizio richiesto dall’utente.
|
<strong>{d.term}:</strong> {d.definition}
|
||||||
</p>
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<h3>3.2 Cookie di preferenza / funzionali</h3>
|
{s.subsections?.map((sub) => (
|
||||||
<p>
|
<>
|
||||||
<strong>Finalità:</strong> lingua, regione, tema o altre impostazioni di interfaccia.{' '}
|
<h3>{sub.title}</h3>
|
||||||
<strong>Base giuridica:</strong> consenso (Art. 6.1.a GDPR).
|
<p class="mt-3 text-sm leading-relaxed text-nx-muted sm:text-base">
|
||||||
</p>
|
{sub.purposeLabel ? (
|
||||||
|
<>
|
||||||
|
<strong>{sub.purposeLabel}:</strong> {sub.purpose}{' '}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
{sub.legalBasisLabel ? (
|
||||||
|
<>
|
||||||
|
<strong>{sub.legalBasisLabel}:</strong> {sub.legalBasis}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
|
||||||
<h3>3.3 Cookie di performance / analytics</h3>
|
{s.bullets && s.bullets.length > 0 ? (
|
||||||
<p>
|
<ul class="mt-3 list-disc space-y-2 pl-5 text-sm sm:text-base">
|
||||||
<strong>Finalità:</strong> statistiche aggregate su visite e percorsi per migliorare il sito.{' '}
|
{s.bullets.map((b) => (
|
||||||
<strong>Base giuridica:</strong> consenso (Art. 6.1.a GDPR).
|
<li class="text-nx-muted">
|
||||||
</p>
|
{typeof b === 'string' ? (
|
||||||
|
b
|
||||||
|
) : 'before' in b ? (
|
||||||
|
<>
|
||||||
|
{b.before}
|
||||||
|
<strong>{b.strong}</strong>
|
||||||
|
{b.after || ''}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<strong>{b.strong}</strong>
|
||||||
|
{b.after || ''}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<h3>3.4 Cookie per advertising e targeting</h3>
|
{s.paragraphsAfterBullets?.map((p) =>
|
||||||
<p>
|
typeof p === 'string' ? (
|
||||||
<strong>Finalità:</strong> pubblicità mirata, remarketing, misura delle campagne.{' '}
|
<p class="mt-3 text-sm leading-relaxed text-nx-muted sm:text-base">{p}</p>
|
||||||
<strong>Base giuridica:</strong> consenso esplicito.
|
) : (
|
||||||
</p>
|
<p class="mt-3 text-sm leading-relaxed text-nx-muted sm:text-base">
|
||||||
|
{p.map((part) => {
|
||||||
|
if (part.t === 'text') return part.v;
|
||||||
|
if (part.t === 'em') return <em>{part.v}</em>;
|
||||||
|
if (part.t === 'strong') return <strong>{part.v}</strong>;
|
||||||
|
if (part.external) {
|
||||||
|
return (
|
||||||
|
<a class={link} href={part.href} target="_blank" rel="noopener noreferrer">
|
||||||
|
{part.label}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<a class={link} href={resolveHref(part.href)}>
|
||||||
|
{part.label}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
|
||||||
<h3>3.5 Cookie di terze parti e integrazioni social</h3>
|
{s.table ? (
|
||||||
<p>
|
<table class={tbl}>
|
||||||
<strong>Finalità:</strong> video incorporati, pulsanti social, mappe, chat di terze parti. Il
|
<thead>
|
||||||
trattamento da parte di terzi è regolato dalle rispettive policy.
|
<tr>
|
||||||
</p>
|
{s.table.headers.map((h) => (
|
||||||
|
<th class={th}>{h}</th>
|
||||||
<h2 id="elenco">4. Elenco dei cookie utilizzati</h2>
|
))}
|
||||||
<p>
|
</tr>
|
||||||
Elenco in appendice tecnica (da mantenere aggiornato con scansione periodica).
|
</thead>
|
||||||
</p>
|
<tbody>
|
||||||
<table class={tbl}>
|
<tr>
|
||||||
<thead>
|
<td class={td} colspan={s.table.headers.length}>
|
||||||
<tr>
|
<em>{s.table.emptyNote}</em>
|
||||||
<th class={th}>Nome</th>
|
</td>
|
||||||
<th class={th}>Provider</th>
|
</tr>
|
||||||
<th class={th}>Categoria</th>
|
</tbody>
|
||||||
<th class={th}>Durata</th>
|
</table>
|
||||||
<th class={th}>Tipo</th>
|
) : null}
|
||||||
<th class={th}>Base giuridica</th>
|
</section>
|
||||||
</tr>
|
))
|
||||||
</thead>
|
}
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td class={td} colspan="6">
|
|
||||||
<em>Da compilare dopo scansione del sito in staging e produzione.</em>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h2 id="base-giuridica">5. Base giuridica</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Cookie tecnici essenziali: legittimo interesse del titolare (Art. 6.1.f GDPR).
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Cookie analitici e di marketing: consenso libero, informato e revocabile (Art. 6.1.a GDPR).
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="consenso">6. Modalità di raccolta del consenso e registro</h2>
|
|
||||||
<p>
|
|
||||||
Il sito utilizza una CMP first-party. Al primo accesso viene mostrato un banner con:
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Rifiuta non essenziali</strong></li>
|
|
||||||
<li><strong>Personalizza</strong> (apertura pannello categorie)</li>
|
|
||||||
<li><strong>Accetta tutto</strong></li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
La prova della scelta è memorizzata nel browser in <em>local storage</em>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2 id="revoca">7. Gestione e revoca del consenso</h2>
|
|
||||||
<ul>
|
|
||||||
<li>Tramite il banner o il pannello <strong>Personalizza</strong> al primo accesso.</li>
|
|
||||||
<li>
|
|
||||||
In qualsiasi momento tramite <strong>Impostazioni cookie</strong> nel footer del sito.
|
|
||||||
</li>
|
|
||||||
<li>Cancellando i dati locali del sito dalle impostazioni del browser.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="browser">8. Disabilitazione via browser e impatto</h2>
|
|
||||||
<p>
|
|
||||||
È possibile bloccare o cancellare i cookie dalle impostazioni del browser (Chrome, Firefox,
|
|
||||||
Safari, Edge e altri).
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2 id="terze-parti">9. Cookie di terze parti e responsabilità</h2>
|
|
||||||
<p>
|
|
||||||
Il sito può integrare strumenti di terze parti (analytics, CDN, social). Le terze parti
|
|
||||||
possono impostare cookie propri e trattare dati secondo le loro policy.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2 id="retention">10. Conservazione e criteri di retention</h2>
|
|
||||||
<p>
|
|
||||||
Le durate dei singoli cookie seguono le tabelle pubblicate e le policy dei fornitori. Cookie
|
|
||||||
di consenso: 365 giorni, rinnovabili. Log di sicurezza: massimo 30 giorni.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2 id="trasferimenti">11. Trasferimenti internazionali</h2>
|
|
||||||
<p>
|
|
||||||
Alcuni fornitori possono trattare dati in Paesi terzi (es. Stati Uniti). Ove applicabile si
|
|
||||||
adottano garanzie come clausole contrattuali standard, decisioni di adeguatezza o altre basi
|
|
||||||
previste dalla legge.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2 id="minori">12. Minori età</h2>
|
|
||||||
<p>Il sito non è rivolto alla raccolta intenzionale di dati di minori.</p>
|
|
||||||
|
|
||||||
<h2 id="sicurezza">13. Misure di sicurezza</h2>
|
|
||||||
<p>
|
|
||||||
Si applicano misure tecniche e organizzative appropriate: trasmissione su TLS, accesso
|
|
||||||
limitato ai log, principio di minimizzazione.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2 id="advertising">14. Consenso per scopi pubblicitari e profiling</h2>
|
|
||||||
<p>
|
|
||||||
Profilazione e pubblicità comportano consenso esplicito e tracciabilità della scelta.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2 id="effetti">15. Effetti di rifiuto o accettazione</h2>
|
|
||||||
<p>
|
|
||||||
Accettando le categorie opzionali si abilitano preferenze avanzate, analytics e funzioni di
|
|
||||||
marketing. Rifiutando i non essenziali il sito resta navigabile.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2 id="modifiche">16. Modifiche alla Cookie Policy</h2>
|
|
||||||
<p>
|
|
||||||
La Policy può essere aggiornata quando cambiano cookie, fornitori o norme. Sarà indicata la
|
|
||||||
data di revisione.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2 id="diritti">17. Diritti degli interessati e contatti</h2>
|
|
||||||
<p>
|
|
||||||
Diritti: accesso, rettifica, cancellazione, limitazione, portabilità, opposizione e revoca del
|
|
||||||
consenso. Per esercitarli:{' '}
|
|
||||||
<a class={link} href="mailto:privacy@nexstudio.com">privacy@nexstudio.com</a>
|
|
||||||
{' '}e tramite il form su nexstudio.ai. Autorità di controllo: Garante per la protezione dei
|
|
||||||
dati personali (<a class={link} href="https://www.garanteprivacy.it" target="_blank" rel="noopener noreferrer"
|
|
||||||
>www.garanteprivacy.it</a
|
|
||||||
>).
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</SubpageLayout>
|
</SubpageLayout>
|
||||||
|
|||||||
@ -1,22 +1,37 @@
|
|||||||
---
|
---
|
||||||
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
|
import type { SupportedLocale } from '../data/home/navigation';
|
||||||
|
import { getDoveSiamo } from '../data/dove-siamo';
|
||||||
|
|
||||||
|
const path = Astro.url.pathname;
|
||||||
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
|
const currentLocale = (localeMatch?.[1] ?? 'it') as SupportedLocale;
|
||||||
|
const doveSiamo = getDoveSiamo(currentLocale);
|
||||||
|
const { page, sections } = doveSiamo;
|
||||||
---
|
---
|
||||||
|
|
||||||
<SubpageLayout
|
<SubpageLayout
|
||||||
title="Dove siamo — NexStudio"
|
title={page.title}
|
||||||
description="Sede operativa a Bangkok, Thailandia; fusi orari e lingue per lavorare con clienti in Europa e altre regioni."
|
description={page.description}
|
||||||
heading="Dove siamo"
|
heading={page.heading}
|
||||||
lead="NexStudio ha base a Bangkok, in Thailandia. Sotto, come organizziamo fusi orari e lingue rispetto a clienti in Europa e oltre."
|
lead={page.lead}
|
||||||
>
|
>
|
||||||
<h2>Sede</h2>
|
{
|
||||||
<p>
|
sections.map((s) => (
|
||||||
<strong>Thailandia</strong> — <strong>Bangkok</strong>
|
<>
|
||||||
</p>
|
<h2>{s.title}</h2>
|
||||||
|
{s.paragraphs.map((p) =>
|
||||||
<h2>Fusi orari e lingue</h2>
|
s.locationLine ? (
|
||||||
<p>
|
<p>
|
||||||
Il sito e il supporto via web possono essere gestiti in italiano, inglese,
|
<strong>{p.split(' — ')[0]}</strong>
|
||||||
tedesco, francese, spagnolo e tailandese, con orari da concordare in base
|
{' — '}
|
||||||
al team e al fuso rispetto ai clienti in Europa e altre regioni.
|
<strong>{p.split(' — ')[1]}</strong>
|
||||||
</p>
|
</p>
|
||||||
|
) : (
|
||||||
|
<p>{p}</p>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
))
|
||||||
|
}
|
||||||
</SubpageLayout>
|
</SubpageLayout>
|
||||||
|
|||||||
5
src/pages/en/allegati.astro
Normal file
5
src/pages/en/allegati.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../allegati.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
@ -2,4 +2,4 @@
|
|||||||
import Page from '../index.astro';
|
import Page from '../index.astro';
|
||||||
---
|
---
|
||||||
|
|
||||||
<Page />
|
<Page locale="en" />
|
||||||
|
|||||||
4
src/pages/en/newsletter/conferma-errore.astro
Normal file
4
src/pages/en/newsletter/conferma-errore.astro
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
import Page from '../../newsletter/conferma-errore.astro';
|
||||||
|
---
|
||||||
|
<Page />
|
||||||
4
src/pages/en/newsletter/confermata.astro
Normal file
4
src/pages/en/newsletter/confermata.astro
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
import Page from '../../newsletter/confermata.astro';
|
||||||
|
---
|
||||||
|
<Page />
|
||||||
4
src/pages/en/newsletter/disiscrizione-errore.astro
Normal file
4
src/pages/en/newsletter/disiscrizione-errore.astro
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
import Page from '../../newsletter/disiscrizione-errore.astro';
|
||||||
|
---
|
||||||
|
<Page />
|
||||||
4
src/pages/en/newsletter/disiscrizione.astro
Normal file
4
src/pages/en/newsletter/disiscrizione.astro
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
import Page from '../../newsletter/disiscrizione.astro';
|
||||||
|
---
|
||||||
|
<Page />
|
||||||
@ -1,49 +1,56 @@
|
|||||||
---
|
---
|
||||||
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
import {
|
import type { SupportedLocale } from '../data/home/navigation';
|
||||||
gdprCrossReferences,
|
import { getGdpr } from '../data/gdpr';
|
||||||
gdprDocument,
|
|
||||||
gdprRiferimenti,
|
const path = Astro.url.pathname;
|
||||||
gdprSections,
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
} from '../data/gdpr';
|
const currentLocale = (localeMatch?.[1] ?? 'it') as SupportedLocale;
|
||||||
|
const gdpr = getGdpr(currentLocale);
|
||||||
|
const { document: gdprDocument, riferimenti, sections, page } = gdpr;
|
||||||
|
|
||||||
|
const localize = (href: string) => {
|
||||||
|
if (currentLocale === 'it') return href;
|
||||||
|
if (href.startsWith(`/${currentLocale}`)) return href;
|
||||||
|
if (href.startsWith('/#')) return `/${currentLocale}${href}`;
|
||||||
|
if (href.startsWith('/')) return `/${currentLocale}${href}`;
|
||||||
|
return href;
|
||||||
|
};
|
||||||
|
|
||||||
const link = 'text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline';
|
const link = 'text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline';
|
||||||
const lead =
|
|
||||||
"Diritti degli interessati e modalità di esercizio ai sensi del Regolamento (UE) 2016/679, in coerenza con l'informativa privacy quadro di NexStudio S.r.l. (Italia, [INDIRIZZO_COMPLETO]). " +
|
|
||||||
gdprCrossReferences.lead;
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<SubpageLayout
|
<SubpageLayout
|
||||||
title="GDPR — NexStudio"
|
title={page.title}
|
||||||
description="Pagina GDPR NexStudio S.r.l.: diritti degli interessati, modalità operative, ruoli tra portale corporate e SaaS LexAura/MediAura, coordinamento con privacy dedicate."
|
description={page.description}
|
||||||
heading="GDPR e diritti degli interessati"
|
heading={page.heading}
|
||||||
lead={lead}
|
lead={page.lead}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p class="border-l-2 border-sky-500/35 pl-4 text-sm leading-relaxed text-nx-muted">
|
<p class="border-l-2 border-sky-500/35 pl-4 text-sm leading-relaxed text-nx-muted">
|
||||||
<span class="font-medium text-nx-fg">Versione</span> {gdprDocument.version}
|
<span class="font-medium text-nx-fg">{page.versionLabel}</span> {gdprDocument.version}
|
||||||
<span class="text-nx-muted/80"> · </span>
|
<span class="text-nx-muted/80"> · </span>
|
||||||
<span class="font-medium text-nx-fg">In vigore dal</span> {gdprDocument.inVigoreDal}
|
<span class="font-medium text-nx-fg">{page.inForceFromLabel}</span> {gdprDocument.inVigoreDal}
|
||||||
<span class="text-nx-muted/80"> · </span>
|
<span class="text-nx-muted/80"> · </span>
|
||||||
<span class="font-medium text-nx-fg">Ultimo aggiornamento</span> {gdprDocument.ultimoAggiornamento}
|
<span class="font-medium text-nx-fg">{page.lastUpdateLabel}</span> {gdprDocument.ultimoAggiornamento}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="!mt-5 text-sm text-nx-muted sm:text-base">
|
<p class="!mt-5 text-sm text-nx-muted sm:text-base">
|
||||||
Documenti connessi:{' '}
|
{page.relatedDocsLabel}:{' '}
|
||||||
{
|
{
|
||||||
gdprRiferimenti.map((r, i) => (
|
riferimenti.map((r, i) => (
|
||||||
<span>
|
<span>
|
||||||
{i > 0 && <span class="text-nx-muted/80"> · </span>}
|
{i > 0 && <span class="text-nx-muted/80"> · </span>}
|
||||||
<a class={link} href={r.href}>{r.label}</a>
|
<a class={link} href={localize(r.href)}>{r.label}</a>
|
||||||
</span>
|
</span>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 id="indice" class="!mt-8">Indice</h2>
|
<h2 id="indice" class="!mt-8">{page.indexLabel}</h2>
|
||||||
<ol class="!mt-3 list-decimal space-y-1.5 pl-5 text-sm leading-relaxed sm:text-base">
|
<ol class="!mt-3 list-decimal space-y-1.5 pl-5 text-sm leading-relaxed sm:text-base">
|
||||||
{
|
{
|
||||||
gdprSections.map((s) => (
|
sections.map((s) => (
|
||||||
<li>
|
<li>
|
||||||
<a class={link} href={`#${s.id}`}>{s.title}</a>
|
<a class={link} href={`#${s.id}`}>{s.title}</a>
|
||||||
</li>
|
</li>
|
||||||
@ -52,7 +59,7 @@ const lead =
|
|||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
{
|
{
|
||||||
gdprSections.map((s) => (
|
sections.map((s) => (
|
||||||
<section class="mt-10" id={s.id}>
|
<section class="mt-10" id={s.id}>
|
||||||
<h2 class="scroll-mt-8 text-lg font-semibold text-nx-fg sm:text-xl">
|
<h2 class="scroll-mt-8 text-lg font-semibold text-nx-fg sm:text-xl">
|
||||||
{s.title}
|
{s.title}
|
||||||
|
|||||||
@ -9,22 +9,33 @@ import {
|
|||||||
getSiteMeta,
|
getSiteMeta,
|
||||||
getNavigation,
|
getNavigation,
|
||||||
getHero,
|
getHero,
|
||||||
stats,
|
getStats,
|
||||||
getServices,
|
getServices,
|
||||||
getStackSection,
|
getStackSection,
|
||||||
productsSection,
|
getProductsSection,
|
||||||
getFaq,
|
getFaq,
|
||||||
getCta,
|
getCta,
|
||||||
} from '../data/home';
|
} from '../data/home';
|
||||||
|
import type { SupportedLocale } from '../data/home/navigation';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/** Locale esplicito dalle route /en e /th (evita ambiguità se la page è importata come componente). */
|
||||||
|
locale?: SupportedLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { locale: localeProp } = Astro.props;
|
||||||
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') as 'it' | 'en' | 'th';
|
const currentLocale = (localeProp ??
|
||||||
|
(localeMatch?.[1] as SupportedLocale | undefined) ??
|
||||||
|
'it') as SupportedLocale;
|
||||||
const navigation = getNavigation(currentLocale);
|
const navigation = getNavigation(currentLocale);
|
||||||
const siteMeta = getSiteMeta(currentLocale);
|
const siteMeta = getSiteMeta(currentLocale);
|
||||||
const hero = getHero(currentLocale);
|
const hero = getHero(currentLocale);
|
||||||
|
const stats = getStats(currentLocale);
|
||||||
const services = getServices(currentLocale);
|
const services = getServices(currentLocale);
|
||||||
const stackSection = getStackSection(currentLocale);
|
const stackSection = getStackSection(currentLocale);
|
||||||
|
const productsSection = getProductsSection(currentLocale);
|
||||||
const faq = getFaq(currentLocale);
|
const faq = getFaq(currentLocale);
|
||||||
const cta = getCta(currentLocale);
|
const cta = getCta(currentLocale);
|
||||||
const infrastructureCarouselItems = stackSection.infrastructureCarousel.items;
|
const infrastructureCarouselItems = stackSection.infrastructureCarousel.items;
|
||||||
|
|||||||
@ -1,66 +1,66 @@
|
|||||||
---
|
---
|
||||||
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
import {
|
import { getModelloOrganizzativo } from '../data/modello-organizzativo';
|
||||||
modelloOrganizzativo,
|
|
||||||
modelloOrganizzativoRuoli,
|
const path = Astro.url.pathname;
|
||||||
modelloOrganizzativoGovernance,
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
modelloOrganizzativoResponsabilitaSintesi,
|
const currentLocale = (localeMatch?.[1] ?? 'it') as 'it' | 'en' | 'th';
|
||||||
modelloOrganizzativoControlliLean,
|
const m = getModelloOrganizzativo(currentLocale);
|
||||||
modelloOrganizzativoKpi,
|
const { page } = m;
|
||||||
modelloOrganizzativoDocumentazione,
|
|
||||||
modelloOrganizzativoPiano30,
|
const localize = (href: string) => {
|
||||||
modelloOrganizzativoOutsourcing,
|
if (currentLocale === 'it') return href;
|
||||||
modelloOrganizzativoNote,
|
if (href.startsWith(`/${currentLocale}`)) return href;
|
||||||
modelloOrganizzativoFlussiDecisionali,
|
if (href.startsWith('/#')) return `/${currentLocale}${href}`;
|
||||||
} from '../data/modello-organizzativo';
|
if (href.startsWith('/')) return `/${currentLocale}${href}`;
|
||||||
|
return href;
|
||||||
|
};
|
||||||
|
|
||||||
const link = 'text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline';
|
const link = 'text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline';
|
||||||
const tbl = 'min-w-[22rem] w-full border-collapse text-left text-sm';
|
const tbl = 'min-w-[22rem] w-full border-collapse text-left text-sm';
|
||||||
const th = 'border border-nx-border bg-nx-surface/60 px-3 py-2 font-semibold text-nx-fg';
|
const th = 'border border-nx-border bg-nx-surface/60 px-3 py-2 font-semibold text-nx-fg';
|
||||||
const td = 'border border-nx-border px-3 py-2 align-top text-nx-muted';
|
const td = 'border border-nx-border px-3 py-2 align-top text-nx-muted';
|
||||||
const tableWrap = 'mt-2 overflow-x-auto';
|
const tableWrap = 'mt-2 overflow-x-auto';
|
||||||
|
const mark = (on?: boolean) => (on ? '●' : '');
|
||||||
---
|
---
|
||||||
|
|
||||||
<SubpageLayout
|
<SubpageLayout
|
||||||
title="Modello organizzativo — NexStudio"
|
title={page.title}
|
||||||
description="Modello organizzativo NexStudio: governance, ruoli, RACI, controlli lean, KPI, piano 30 giorni — per le linee LexAura (Legal Tech) e MediAura (Health Tech)."
|
description={page.description}
|
||||||
heading="Modello organizzativo"
|
heading={page.heading}
|
||||||
lead="Ruoli, ritmi di governo, processi e controlli «lean» in una software house con SaaS a elevata regolamentazione. Complementare al codice etico. Non sostituisce statuti, modelli di compliance societari (es. 231) né pareri legali: andate in parallelo con struttura reale e consulenti."
|
lead={page.lead}
|
||||||
>
|
>
|
||||||
<div class="[&_h3]:mt-6 [&_h3]:text-base [&_h3]:font-semibold [&_h3]:text-nx-fg">
|
<div class="[&_h3]:mt-6 [&_h3]:text-base [&_h3]:font-semibold [&_h3]:text-nx-fg">
|
||||||
<p class="border-l-2 border-sky-500/35 pl-4 text-sm leading-relaxed text-nx-muted">
|
<p class="border-l-2 border-sky-500/35 pl-4 text-sm leading-relaxed text-nx-muted">
|
||||||
{modelloOrganizzativo.introBox}
|
{m.introBox}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-3 text-sm leading-relaxed text-nx-muted sm:text-base">
|
<p class="mt-3 text-sm leading-relaxed text-nx-muted sm:text-base">
|
||||||
{modelloOrganizzativo.perimetro}
|
{m.perimetro}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 id="indice" class="!mt-8">Indice</h2>
|
<h2 id="indice" class="!mt-8">{page.indexTitle}</h2>
|
||||||
<ol class="!mt-3 list-decimal space-y-1.5 pl-5 text-sm leading-relaxed sm:text-base">
|
<ol class="!mt-3 list-decimal space-y-1.5 pl-5 text-sm leading-relaxed sm:text-base">
|
||||||
<li><a class={link} href="#ruoli">Ruoli e perimetro</a></li>
|
{
|
||||||
<li><a class={link} href="#governance">Governance essenziale</a></li>
|
page.index.map((item) => (
|
||||||
<li><a class={link} href="#responsabilita">Principali responsabilità (sintesi)</a></li>
|
<li>
|
||||||
<li><a class={link} href="#raci">RACI per processi critici</a></li>
|
<a class={link} href={item.href}>
|
||||||
<li><a class={link} href="#flussi">Flussi decisionali rapidi</a></li>
|
{item.label}
|
||||||
<li><a class={link} href="#controlli">Controlli minimi obbligatori (lean)</a></li>
|
</a>
|
||||||
<li><a class={link} href="#kpi">KPI essenziali</a></li>
|
</li>
|
||||||
<li><a class={link} href="#documentazione">Documentazione minima da mantenere</a></li>
|
))
|
||||||
<li><a class={link} href="#piano-30">Primo piano operativo (30 giorni)</a></li>
|
}
|
||||||
<li><a class={link} href="#outsourcing">Outsourcing consigliato</a></li>
|
|
||||||
<li><a class={link} href="#note">Note pratiche e raccomandazioni</a></li>
|
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<h2 id="ruoli">1. Ruoli e perimetro</h2>
|
<h2 id="ruoli">{page.sectionTitles.ruoli}</h2>
|
||||||
<p>
|
<p>
|
||||||
Elenco sintetico delle funzioni e delle attese di impegno. Per allineare il dominio
|
{page.ruoliIntroBefore}{' '}
|
||||||
prodotto (Legal Tech, Health Tech) a ruoli e consiglieri, fate riferimento anche a <a
|
<a class={link} href={localize(page.codiceEticoHref)}>{page.codiceEticoLabel}</a>
|
||||||
class={link}
|
{' '}
|
||||||
href="/codice-etico">codice etico</a
|
{page.ruoliIntroAfter}
|
||||||
> e, per i trattamenti di dati, a privacy policy e DPA.
|
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
{
|
{
|
||||||
modelloOrganizzativoRuoli.map((r) => (
|
m.ruoli.map((r) => (
|
||||||
<li>
|
<li>
|
||||||
<strong>{r.label}:</strong> {r.text}
|
<strong>{r.label}:</strong> {r.text}
|
||||||
</li>
|
</li>
|
||||||
@ -68,217 +68,85 @@ const tableWrap = 'mt-2 overflow-x-auto';
|
|||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="governance">2. Governance essenziale</h2>
|
<h2 id="governance">{page.sectionTitles.governance}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{
|
{
|
||||||
modelloOrganizzativoGovernance.map((g) => (
|
m.governance.map((g) => (
|
||||||
<li>
|
<li>
|
||||||
<strong>{g.nome}:</strong> {g.partecipanti}. <span class="text-nx-muted/95">— {g.nota}</span>
|
<strong>{g.nome}:</strong> {g.partecipanti}.{' '}
|
||||||
|
<span class="text-nx-muted/95">— {g.nota}</span>
|
||||||
</li>
|
</li>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="responsabilita">3. Principali responsabilità (sintesi)</h2>
|
<h2 id="responsabilita">{page.sectionTitles.responsabilita}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{modelloOrganizzativoResponsabilitaSintesi.map((l) => <li>{l}</li>)}
|
{m.responsabilitaSintesi.map((l) => <li>{l}</li>)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="raci">4. RACI sintetico per processi critici</h2>
|
<h2 id="raci">{page.sectionTitles.raci}</h2>
|
||||||
<p>
|
<p>
|
||||||
Legenda: <strong>R</strong> = Responsible, <strong>A</strong> = Accountable,
|
<strong>{page.raciLegendBefore}</strong> {page.raciLegend}
|
||||||
<strong>C</strong> = Consulted, <strong>I</strong> = Informed.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="!mt-5" id="raci-release">Rilascio in produzione</h3>
|
{
|
||||||
<div class={tableWrap}>
|
m.raci.map((matrix) => (
|
||||||
<table class={tbl}>
|
<>
|
||||||
<thead>
|
<h3 class:list={[matrix.id === 'raci-release' && '!mt-5']} id={matrix.id}>
|
||||||
<tr>
|
{matrix.title}
|
||||||
<th class={th} scope="col">Ruolo</th>
|
</h3>
|
||||||
<th class={th} scope="col">R</th>
|
{'note' in matrix && matrix.note ? (
|
||||||
<th class={th} scope="col">A</th>
|
<p class="text-sm text-nx-muted sm:text-base">
|
||||||
<th class={th} scope="col">C</th>
|
{matrix.note}{' '}
|
||||||
<th class={th} scope="col">I</th>
|
<a class={link} href={localize(matrix.noteLinkHref!)}>
|
||||||
</tr>
|
{matrix.noteLinkLabel}
|
||||||
</thead>
|
</a>
|
||||||
<tbody>
|
{'noteAfter' in matrix ? matrix.noteAfter : null}
|
||||||
<tr>
|
</p>
|
||||||
<td class={td}>Lead engineer</td>
|
) : null}
|
||||||
<td class={td}>●</td>
|
<div class={tableWrap}>
|
||||||
<td class={td}></td>
|
<table class={tbl}>
|
||||||
<td class={td}></td>
|
<thead>
|
||||||
<td class={td}></td>
|
<tr>
|
||||||
</tr>
|
<th class={th} scope="col">
|
||||||
<tr>
|
{page.roleCol}
|
||||||
<td class={td}>CTO</td>
|
</th>
|
||||||
<td class={td}></td>
|
<th class={th} scope="col">
|
||||||
<td class={td}>●</td>
|
R
|
||||||
<td class={td}></td>
|
</th>
|
||||||
<td class={td}></td>
|
<th class={th} scope="col">
|
||||||
</tr>
|
A
|
||||||
<tr>
|
</th>
|
||||||
<td class={td}>Security lead, DPO, product advisor</td>
|
<th class={th} scope="col">
|
||||||
<td class={td}></td>
|
C
|
||||||
<td class={td}></td>
|
</th>
|
||||||
<td class={td}>●</td>
|
<th class={th} scope="col">
|
||||||
<td class={td}></td>
|
I
|
||||||
</tr>
|
</th>
|
||||||
<tr>
|
</tr>
|
||||||
<td class={td}>CEO, customer success</td>
|
</thead>
|
||||||
<td class={td}></td>
|
<tbody>
|
||||||
<td class={td}></td>
|
{matrix.rows.map((row) => (
|
||||||
<td class={td}></td>
|
<tr>
|
||||||
<td class={td}>●</td>
|
<td class={td}>{row.role}</td>
|
||||||
</tr>
|
<td class={td}>{mark(row.r)}</td>
|
||||||
</tbody>
|
<td class={td}>{mark(row.a)}</td>
|
||||||
</table>
|
<td class={td}>{mark(row.c)}</td>
|
||||||
</div>
|
<td class={td}>{mark(row.i)}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
<h3 id="raci-incident">Incident response (violazione dei dati o incidente P0)</h3>
|
<h2 id="flussi">{page.sectionTitles.flussi}</h2>
|
||||||
<div class={tableWrap}>
|
|
||||||
<table class={tbl}>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class={th} scope="col">Ruolo</th>
|
|
||||||
<th class={th} scope="col">R</th>
|
|
||||||
<th class={th} scope="col">A</th>
|
|
||||||
<th class={th} scope="col">C</th>
|
|
||||||
<th class={th} scope="col">I</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td class={td}>Security lead</td>
|
|
||||||
<td class={td}>●</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class={td}>CEO</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}>●</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class={td}>DPO, legal, CTO</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}>●</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class={td}>Clienti interessati, board (se impatto elevato)</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}>●</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 id="raci-vendor">Onboarding vendor (sub-processore)</h3>
|
|
||||||
<div class={tableWrap}>
|
|
||||||
<table class={tbl}>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class={th} scope="col">Ruolo</th>
|
|
||||||
<th class={th} scope="col">R</th>
|
|
||||||
<th class={th} scope="col">A</th>
|
|
||||||
<th class={th} scope="col">C</th>
|
|
||||||
<th class={th} scope="col">I</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td class={td}>Operations</td>
|
|
||||||
<td class={td}>●</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class={td}>Legal</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}>●</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class={td}>Security lead, DPO</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}>●</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class={td}>CTO, finance</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}>●</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 id="raci-dpia">DPIA (per perimetro: LexAura, MediAura, piattaforma)</h3>
|
|
||||||
<p class="text-sm text-nx-muted sm:text-base">
|
|
||||||
La matrice RACI non sostituisce il criterio legale (chi è titolare, chi responsabile) definito
|
|
||||||
in contratti e in <a class={link} href="/codice-etico#tutela-dati-ruoli">§5.1 del codice etico</a
|
|
||||||
>. Qui: chi coordina l’esercizio interno della valutazione d’impatto.
|
|
||||||
</p>
|
|
||||||
<div class={tableWrap}>
|
|
||||||
<table class={tbl}>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class={th} scope="col">Ruolo</th>
|
|
||||||
<th class={th} scope="col">R</th>
|
|
||||||
<th class={th} scope="col">A</th>
|
|
||||||
<th class={th} scope="col">C</th>
|
|
||||||
<th class={th} scope="col">I</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td class={td}>DPO</td>
|
|
||||||
<td class={td}>●</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class={td}>Legal</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}>●</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class={td}>Product advisor, CTO, security lead</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}>●</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class={td}>CEO</td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}></td>
|
|
||||||
<td class={td}>●</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 id="flussi">5. Flussi decisionali rapidi</h2>
|
|
||||||
<ul>
|
<ul>
|
||||||
{
|
{
|
||||||
modelloOrganizzativoFlussiDecisionali.map((f) => (
|
m.flussiDecisionali.map((f) => (
|
||||||
<li>
|
<li>
|
||||||
<strong>{f.titolo}:</strong> {f.testo}
|
<strong>{f.titolo}:</strong> {f.testo}
|
||||||
</li>
|
</li>
|
||||||
@ -286,15 +154,15 @@ const tableWrap = 'mt-2 overflow-x-auto';
|
|||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="controlli">6. Controlli minimi obbligatori (lean)</h2>
|
<h2 id="controlli">{page.sectionTitles.controlli}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{modelloOrganizzativoControlliLean.map((c) => <li>{c}</li>)}
|
{m.controlliLean.map((c) => <li>{c}</li>)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="kpi">7. KPI essenziali</h2>
|
<h2 id="kpi">{page.sectionTitles.kpi}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{
|
{
|
||||||
modelloOrganizzativoKpi.map((k) => (
|
m.kpi.map((k) => (
|
||||||
<li>
|
<li>
|
||||||
<strong>{k.area}:</strong> {k.testo}
|
<strong>{k.area}:</strong> {k.testo}
|
||||||
</li>
|
</li>
|
||||||
@ -302,15 +170,15 @@ const tableWrap = 'mt-2 overflow-x-auto';
|
|||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="documentazione">8. Documentazione minima da mantenere</h2>
|
<h2 id="documentazione">{page.sectionTitles.documentazione}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{modelloOrganizzativoDocumentazione.map((d) => <li>{d}</li>)}
|
{m.documentazione.map((d) => <li>{d}</li>)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="piano-30">9. Primo piano operativo (30 giorni)</h2>
|
<h2 id="piano-30">{page.sectionTitles.piano30}</h2>
|
||||||
<ol>
|
<ol>
|
||||||
{
|
{
|
||||||
modelloOrganizzativoPiano30.map((p) => (
|
m.piano30.map((p) => (
|
||||||
<li>
|
<li>
|
||||||
<strong>{p.quando}:</strong> {p.testo}
|
<strong>{p.quando}:</strong> {p.testo}
|
||||||
</li>
|
</li>
|
||||||
@ -318,10 +186,10 @@ const tableWrap = 'mt-2 overflow-x-auto';
|
|||||||
}
|
}
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<h2 id="outsourcing">10. Outsourcing consigliato (per restare snelli)</h2>
|
<h2 id="outsourcing">{page.sectionTitles.outsourcing}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{
|
{
|
||||||
modelloOrganizzativoOutsourcing.map((o) => (
|
m.outsourcing.map((o) => (
|
||||||
<li>
|
<li>
|
||||||
<strong>{o.titolo}:</strong> {o.testo}
|
<strong>{o.titolo}:</strong> {o.testo}
|
||||||
</li>
|
</li>
|
||||||
@ -329,9 +197,9 @@ const tableWrap = 'mt-2 overflow-x-auto';
|
|||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="note">11. Note pratiche e raccomandazioni</h2>
|
<h2 id="note">{page.sectionTitles.note}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{modelloOrganizzativoNote.map((n) => <li>{n}</li>)}
|
{m.note.map((n) => <li>{n}</li>)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</SubpageLayout>
|
</SubpageLayout>
|
||||||
|
|||||||
@ -1,37 +1,56 @@
|
|||||||
---
|
---
|
||||||
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
|
import type { SupportedLocale } from '../data/home/navigation';
|
||||||
|
import { getNews } from '../data/news';
|
||||||
|
|
||||||
|
const path = Astro.url.pathname;
|
||||||
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
|
const currentLocale = (localeMatch?.[1] ?? 'it') as SupportedLocale;
|
||||||
|
const news = getNews(currentLocale);
|
||||||
|
const { page, statusBanner, sections, contact } = news;
|
||||||
|
|
||||||
|
const localize = (href: string) => {
|
||||||
|
if (currentLocale === 'it') return href;
|
||||||
|
if (href.startsWith(`/${currentLocale}`)) return href;
|
||||||
|
if (href.startsWith('/#')) return `/${currentLocale}${href}`;
|
||||||
|
if (href.startsWith('/')) return `/${currentLocale}${href}`;
|
||||||
|
return href;
|
||||||
|
};
|
||||||
---
|
---
|
||||||
|
|
||||||
<SubpageLayout
|
<SubpageLayout
|
||||||
title="Novità — NexStudio"
|
title={page.title}
|
||||||
description="Novità, release e articoli da NexStudio."
|
description={page.description}
|
||||||
heading="Novità"
|
heading={page.heading}
|
||||||
lead="Aggiornamenti ufficiali su piattaforma, compliance, infrastruttura e contenuti editoriali."
|
lead={page.lead}
|
||||||
>
|
>
|
||||||
<p class="rounded-xl border border-emerald-500/30 bg-emerald-500/10 px-4 py-3 text-emerald-100">
|
<p class="rounded-xl border border-emerald-500/30 bg-emerald-500/10 px-4 py-3 text-emerald-100">
|
||||||
Stato editoriale: canale attivo. Le prossime pubblicazioni verranno aggiornate su questa pagina con frequenza periodica.
|
{statusBanner}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Programma editoriale</h2>
|
{
|
||||||
<ul>
|
sections.map((s) => (
|
||||||
<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>
|
<h2>{s.title}</h2>
|
||||||
<li><strong>Case e risultati:</strong> note operative su adozione, scalabilita e impatti di business.</li>
|
{s.bullets && s.bullets.length > 0 ? (
|
||||||
</ul>
|
<ul>
|
||||||
|
{s.bullets.map((b) => (
|
||||||
|
<li>
|
||||||
|
{b.title ? <strong>{b.title}</strong> : null} {b.text}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : null}
|
||||||
|
{s.paragraphs?.map((p) => (
|
||||||
|
<p class="text-nx-muted">{p}</p>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
<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>
|
|
||||||
<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>
|
<p>
|
||||||
Per richieste stampa, partnership o approfondimenti tecnici e possibile usare il form nella sezione
|
{contact.before}
|
||||||
<a href="/#contatti" class="text-sky-300 hover:text-sky-200">Contatti</a>.
|
<a href={localize(contact.href)} class="text-sky-300 hover:text-sky-200">{contact.linkLabel}</a>
|
||||||
|
{contact.after}
|
||||||
</p>
|
</p>
|
||||||
</SubpageLayout>
|
</SubpageLayout>
|
||||||
|
|||||||
18
src/pages/newsletter/conferma-errore.astro
Normal file
18
src/pages/newsletter/conferma-errore.astro
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
import SubpageLayout from '../../layouts/SubpageLayout.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<SubpageLayout
|
||||||
|
title="Conferma non riuscita — Newsletter NexStudio"
|
||||||
|
description="Il link di conferma newsletter non è valido o è già stato usato."
|
||||||
|
heading="Conferma non riuscita"
|
||||||
|
lead="Il link di conferma non è valido, è scaduto o è già stato utilizzato."
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Puoi riprovare iscrivendoti di nuovo dal form newsletter nel footer del
|
||||||
|
sito: riceverai un nuovo messaggio di conferma.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a class="text-sky-400/90 hover:text-sky-300" href="/">Torna alla home</a>
|
||||||
|
</p>
|
||||||
|
</SubpageLayout>
|
||||||
34
src/pages/newsletter/confermata.astro
Normal file
34
src/pages/newsletter/confermata.astro
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
import SubpageLayout from '../../layouts/SubpageLayout.astro';
|
||||||
|
|
||||||
|
const unsubToken = Astro.url.searchParams.get('unsub')?.trim() ?? '';
|
||||||
|
const unsubHref = unsubToken
|
||||||
|
? `/api/newsletter/unsubscribe?token=${encodeURIComponent(unsubToken)}`
|
||||||
|
: '';
|
||||||
|
---
|
||||||
|
|
||||||
|
<SubpageLayout
|
||||||
|
title="Iscrizione confermata — Newsletter NexStudio"
|
||||||
|
description="La tua iscrizione alla newsletter NexStudio è confermata."
|
||||||
|
heading="Iscrizione confermata"
|
||||||
|
lead="Il tuo indirizzo email è ora attivo per ricevere aggiornamenti su prodotti e roadmap NexStudio."
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Puoi disiscriverti in qualsiasi momento dal link presente in fondo a ogni
|
||||||
|
messaggio della newsletter.
|
||||||
|
</p>
|
||||||
|
{
|
||||||
|
unsubHref ? (
|
||||||
|
<p>
|
||||||
|
Oppure disiscriviti subito:{' '}
|
||||||
|
<a class="text-sky-400/90 hover:text-sky-300" href={unsubHref}>
|
||||||
|
Annulla iscrizione
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
<p>
|
||||||
|
<a class="text-sky-400/90 hover:text-sky-300" href="/">Torna alla home</a>
|
||||||
|
</p>
|
||||||
|
</SubpageLayout>
|
||||||
20
src/pages/newsletter/disiscrizione-errore.astro
Normal file
20
src/pages/newsletter/disiscrizione-errore.astro
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
import SubpageLayout from '../../layouts/SubpageLayout.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<SubpageLayout
|
||||||
|
title="Disiscrizione non riuscita — Newsletter NexStudio"
|
||||||
|
description="Il link di disiscrizione newsletter non è valido."
|
||||||
|
heading="Disiscrizione non riuscita"
|
||||||
|
lead="Il link di disiscrizione non è valido o è incompleto."
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Se continui a ricevere messaggi indesiderati, scrivi a
|
||||||
|
<a class="text-sky-400/90 hover:text-sky-300" href="mailto:privacy@nexstudio.com"
|
||||||
|
>privacy@nexstudio.com</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a class="text-sky-400/90 hover:text-sky-300" href="/">Torna alla home</a>
|
||||||
|
</p>
|
||||||
|
</SubpageLayout>
|
||||||
18
src/pages/newsletter/disiscrizione.astro
Normal file
18
src/pages/newsletter/disiscrizione.astro
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
import SubpageLayout from '../../layouts/SubpageLayout.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<SubpageLayout
|
||||||
|
title="Disiscrizione — Newsletter NexStudio"
|
||||||
|
description="Disiscrizione dalla newsletter NexStudio completata."
|
||||||
|
heading="Disiscrizione completata"
|
||||||
|
lead="Non riceverai più la newsletter di NexStudio su questo indirizzo."
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Se ti sei disiscritto per errore, puoi iscriverti di nuovo dal footer del
|
||||||
|
sito e confermare l’email.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a class="text-sky-400/90 hover:text-sky-300" href="/">Torna alla home</a>
|
||||||
|
</p>
|
||||||
|
</SubpageLayout>
|
||||||
@ -4,13 +4,14 @@ import SubpageLayout from '../../layouts/SubpageLayout.astro';
|
|||||||
|
|
||||||
<SubpageLayout
|
<SubpageLayout
|
||||||
title="Grazie — Newsletter NexStudio"
|
title="Grazie — Newsletter NexStudio"
|
||||||
description="Iscrizione alla newsletter ricevuta."
|
description="Richiesta di iscrizione alla newsletter ricevuta."
|
||||||
heading="Grazie"
|
heading="Controlla la tua email"
|
||||||
lead="La richiesta di iscrizione è stata inviata. Controlla la posta se è prevista una conferma (double opt-in)."
|
lead="Abbiamo ricevuto la richiesta di iscrizione. Per completarla, apri il messaggio di conferma e clicca sul link (double opt-in)."
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Se usi un servizio con conferma via email, apri il messaggio e segui il
|
Se non trovi l’email, controlla anche la cartella spam o promozioni. Il link
|
||||||
link per completare l’iscrizione.
|
di conferma scade solo se viene generata una nuova richiesta per lo stesso
|
||||||
|
indirizzo.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a class="text-sky-400/90 hover:text-sky-300" href="/">Torna alla home</a>
|
<a class="text-sky-400/90 hover:text-sky-300" href="/">Torna alla home</a>
|
||||||
|
|||||||
@ -1,50 +1,57 @@
|
|||||||
---
|
---
|
||||||
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
import {
|
import type { SupportedLocale } from '../data/home/navigation';
|
||||||
privacyCrossReferences,
|
import { getPrivacy } from '../data/privacy';
|
||||||
privacyDocument,
|
|
||||||
privacyRiferimenti,
|
const path = Astro.url.pathname;
|
||||||
privacySections,
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
} from '../data/privacy';
|
const currentLocale = (localeMatch?.[1] ?? 'it') as SupportedLocale;
|
||||||
|
const privacy = getPrivacy(currentLocale);
|
||||||
|
const { document: privacyDocument, riferimenti, sections, page } = privacy;
|
||||||
|
|
||||||
|
const localize = (href: string) => {
|
||||||
|
if (currentLocale === 'it') return href;
|
||||||
|
if (href.startsWith(`/${currentLocale}`)) return href;
|
||||||
|
if (href.startsWith('/#')) return `/${currentLocale}${href}`;
|
||||||
|
if (href.startsWith('/')) return `/${currentLocale}${href}`;
|
||||||
|
return href;
|
||||||
|
};
|
||||||
|
|
||||||
const link = 'text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline';
|
const link = 'text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline';
|
||||||
const lead =
|
|
||||||
"Informativa quadro sul trattamento dei dati personali per il portale istituzionale NexStudio S.r.l., con sede legale in Italia, [INDIRIZZO_COMPLETO], e per i servizi SaaS LexAura e MediAura. " +
|
|
||||||
privacyCrossReferences.lead;
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<SubpageLayout
|
<SubpageLayout
|
||||||
title="Privacy — NexStudio"
|
title={page.title}
|
||||||
description="Informativa privacy quadro NexStudio S.r.l.: sito istituzionale, contatti corporate, rimandi ai dettagli privacy dei SaaS LexAura (Legal Tech) e MediAura (Health Tech)."
|
description={page.description}
|
||||||
heading="Informativa sulla privacy"
|
heading={page.heading}
|
||||||
lead={lead}
|
lead={page.lead}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p class="border-l-2 border-sky-500/35 pl-4 text-sm leading-relaxed text-nx-muted">
|
<p class="border-l-2 border-sky-500/35 pl-4 text-sm leading-relaxed text-nx-muted">
|
||||||
<span class="font-medium text-nx-fg">Versione</span> {privacyDocument.version}
|
<span class="font-medium text-nx-fg">{page.versionLabel}</span> {privacyDocument.version}
|
||||||
<span class="text-nx-muted/80"> · </span>
|
<span class="text-nx-muted/80"> · </span>
|
||||||
<span class="font-medium text-nx-fg">In vigore dal</span> {privacyDocument.inVigoreDal}
|
<span class="font-medium text-nx-fg">{page.inForceFromLabel}</span> {privacyDocument.inVigoreDal}
|
||||||
<span class="text-nx-muted/80"> · </span>
|
<span class="text-nx-muted/80"> · </span>
|
||||||
<span class="font-medium text-nx-fg">Ultimo aggiornamento</span>{' '}
|
<span class="font-medium text-nx-fg">{page.lastUpdateLabel}</span>{' '}
|
||||||
{privacyDocument.ultimoAggiornamento}
|
{privacyDocument.ultimoAggiornamento}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="!mt-5 text-sm text-nx-muted sm:text-base">
|
<p class="!mt-5 text-sm text-nx-muted sm:text-base">
|
||||||
Documenti connessi:{' '}
|
{page.relatedDocsLabel}:{' '}
|
||||||
{
|
{
|
||||||
privacyRiferimenti.map((r, i) => (
|
riferimenti.map((r, i) => (
|
||||||
<span>
|
<span>
|
||||||
{i > 0 && <span class="text-nx-muted/80"> · </span>}
|
{i > 0 && <span class="text-nx-muted/80"> · </span>}
|
||||||
<a class={link} href={r.href}>{r.label}</a>
|
<a class={link} href={localize(r.href)}>{r.label}</a>
|
||||||
</span>
|
</span>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 id="indice" class="!mt-8">Indice</h2>
|
<h2 id="indice" class="!mt-8">{page.indexLabel}</h2>
|
||||||
<ol class="!mt-3 list-decimal space-y-1.5 pl-5 text-sm leading-relaxed sm:text-base">
|
<ol class="!mt-3 list-decimal space-y-1.5 pl-5 text-sm leading-relaxed sm:text-base">
|
||||||
{
|
{
|
||||||
privacySections.map((s) => (
|
sections.map((s) => (
|
||||||
<li>
|
<li>
|
||||||
<a class={link} href={`#${s.id}`}>{s.title}</a>
|
<a class={link} href={`#${s.id}`}>{s.title}</a>
|
||||||
</li>
|
</li>
|
||||||
@ -53,7 +60,7 @@ const lead =
|
|||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
{
|
{
|
||||||
privacySections.map((s) => (
|
sections.map((s) => (
|
||||||
<section class="mt-10" id={s.id}>
|
<section class="mt-10" id={s.id}>
|
||||||
<h2
|
<h2
|
||||||
class="scroll-mt-8 text-lg font-semibold text-nx-fg sm:text-xl"
|
class="scroll-mt-8 text-lg font-semibold text-nx-fg sm:text-xl"
|
||||||
|
|||||||
@ -1,42 +1,54 @@
|
|||||||
---
|
---
|
||||||
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
import { termsDocument, termsSections, termsRiferimenti, termsCrossReferences } from '../data/terms';
|
import type { SupportedLocale } from '../data/home/navigation';
|
||||||
|
import { getTerms } from '../data/terms';
|
||||||
|
|
||||||
|
const path = Astro.url.pathname;
|
||||||
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
|
const currentLocale = (localeMatch?.[1] ?? 'it') as SupportedLocale;
|
||||||
|
const terms = getTerms(currentLocale);
|
||||||
|
const { document: termsDocument, riferimenti, sections, page } = terms;
|
||||||
|
|
||||||
|
const localize = (href: string) => {
|
||||||
|
if (currentLocale === 'it') return href;
|
||||||
|
if (href.startsWith(`/${currentLocale}`)) return href;
|
||||||
|
if (href.startsWith('/#')) return `/${currentLocale}${href}`;
|
||||||
|
if (href.startsWith('/')) return `/${currentLocale}${href}`;
|
||||||
|
return href;
|
||||||
|
};
|
||||||
|
|
||||||
const link = 'text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline';
|
const link = 'text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline';
|
||||||
const lead =
|
|
||||||
"Si applicano alla navigazione e all'uso del sito informativo pubblico di NexStudio S.r.l., con sede legale in Italia, [INDIRIZZO_COMPLETO]. I contratti d'abbonamento o d'acquisto sui prodotti software restano retti da offerte, ordini o accordi separati. " +
|
|
||||||
termsCrossReferences.leadNote;
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<SubpageLayout
|
<SubpageLayout
|
||||||
title="Condizioni d’uso — NexStudio"
|
title={page.title}
|
||||||
description="Condizioni d’uso del sito web NexStudio S.r.l.: accettazione, utilizzo lecito, responsabilità, legge italiana e foro di Padova. LexAura e MediAura, servizi B2B per contratto a parte."
|
description={page.description}
|
||||||
heading="Condizioni d’uso"
|
heading={page.heading}
|
||||||
lead={lead}
|
lead={page.lead}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p class="border-l-2 border-sky-500/35 pl-4 text-sm leading-relaxed text-nx-muted">
|
<p class="border-l-2 border-sky-500/35 pl-4 text-sm leading-relaxed text-nx-muted">
|
||||||
<span class="font-medium text-nx-fg">Versione</span> {termsDocument.version}
|
<span class="font-medium text-nx-fg">{page.versionLabel}</span> {termsDocument.version}
|
||||||
<span class="text-nx-muted/80"> · </span>
|
<span class="text-nx-muted/80"> · </span>
|
||||||
<span class="font-medium text-nx-fg">In vigore dal</span> {termsDocument.inVigoreDal}
|
<span class="font-medium text-nx-fg">{page.inForceFromLabel}</span> {termsDocument.inVigoreDal}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="!mt-5 text-sm text-nx-muted sm:text-base">
|
<p class="!mt-5 text-sm text-nx-muted sm:text-base">
|
||||||
Documenti connessi:{' '}
|
{page.relatedDocsLabel}:{' '}
|
||||||
{
|
{
|
||||||
termsRiferimenti.map((r, i) => (
|
riferimenti.map((r, i) => (
|
||||||
<span>
|
<span>
|
||||||
{i > 0 && <span class="text-nx-muted/80"> · </span>}
|
{i > 0 && <span class="text-nx-muted/80"> · </span>}
|
||||||
<a class={link} href={r.href}>{r.label}</a>
|
<a class={link} href={localize(r.href)}>{r.label}</a>
|
||||||
</span>
|
</span>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 id="indice" class="!mt-8">Indice</h2>
|
<h2 id="indice" class="!mt-8">{page.indexLabel}</h2>
|
||||||
<ol class="!mt-3 list-decimal space-y-1.5 pl-5 text-sm leading-relaxed sm:text-base">
|
<ol class="!mt-3 list-decimal space-y-1.5 pl-5 text-sm leading-relaxed sm:text-base">
|
||||||
{
|
{
|
||||||
termsSections.map((s) => (
|
sections.map((s) => (
|
||||||
<li>
|
<li>
|
||||||
<a class={link} href={`#${s.id}`}>{s.title}</a>
|
<a class={link} href={`#${s.id}`}>{s.title}</a>
|
||||||
</li>
|
</li>
|
||||||
@ -45,7 +57,7 @@ const lead =
|
|||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
{
|
{
|
||||||
termsSections.map((s) => (
|
sections.map((s) => (
|
||||||
<section class="mt-10" id={s.id}>
|
<section class="mt-10" id={s.id}>
|
||||||
<h2
|
<h2
|
||||||
class="scroll-mt-8 text-lg font-semibold text-nx-fg sm:text-xl"
|
class="scroll-mt-8 text-lg font-semibold text-nx-fg sm:text-xl"
|
||||||
|
|||||||
5
src/pages/th/allegati.astro
Normal file
5
src/pages/th/allegati.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../allegati.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
@ -2,4 +2,4 @@
|
|||||||
import Page from '../index.astro';
|
import Page from '../index.astro';
|
||||||
---
|
---
|
||||||
|
|
||||||
<Page />
|
<Page locale="th" />
|
||||||
|
|||||||
4
src/pages/th/newsletter/conferma-errore.astro
Normal file
4
src/pages/th/newsletter/conferma-errore.astro
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
import Page from '../../newsletter/conferma-errore.astro';
|
||||||
|
---
|
||||||
|
<Page />
|
||||||
4
src/pages/th/newsletter/confermata.astro
Normal file
4
src/pages/th/newsletter/confermata.astro
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
import Page from '../../newsletter/confermata.astro';
|
||||||
|
---
|
||||||
|
<Page />
|
||||||
4
src/pages/th/newsletter/disiscrizione-errore.astro
Normal file
4
src/pages/th/newsletter/disiscrizione-errore.astro
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
import Page from '../../newsletter/disiscrizione-errore.astro';
|
||||||
|
---
|
||||||
|
<Page />
|
||||||
4
src/pages/th/newsletter/disiscrizione.astro
Normal file
4
src/pages/th/newsletter/disiscrizione.astro
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
import Page from '../../newsletter/disiscrizione.astro';
|
||||||
|
---
|
||||||
|
<Page />
|
||||||
@ -1,10 +1,11 @@
|
|||||||
/** Modulo contatti in pagina (#contatti): mailto o POST Formspree. */
|
/** Modulo contatti in pagina (#contatti): mailto o POST /api/contact. */
|
||||||
interface ContactFormCfg {
|
interface ContactFormCfg {
|
||||||
actionUrl: string;
|
actionUrl: string;
|
||||||
mailto: string;
|
mailto: string;
|
||||||
turnstileSiteKey: string;
|
turnstileSiteKey: string;
|
||||||
notifySubject: string;
|
notifySubject: string;
|
||||||
successRedirect: string;
|
successRedirect: string;
|
||||||
|
successMessage: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
fieldCompany: string;
|
fieldCompany: string;
|
||||||
fieldCountry: string;
|
fieldCountry: string;
|
||||||
@ -32,6 +33,7 @@ function readCfg(): ContactFormCfg {
|
|||||||
turnstileSiteKey: '',
|
turnstileSiteKey: '',
|
||||||
notifySubject: '',
|
notifySubject: '',
|
||||||
successRedirect: '',
|
successRedirect: '',
|
||||||
|
successMessage: 'Message sent.',
|
||||||
fieldName: 'name',
|
fieldName: 'name',
|
||||||
fieldCompany: 'company',
|
fieldCompany: 'company',
|
||||||
fieldCountry: 'country',
|
fieldCountry: 'country',
|
||||||
@ -52,14 +54,57 @@ function readCfg(): ContactFormCfg {
|
|||||||
return JSON.parse(raw) as ContactFormCfg;
|
return JSON.parse(raw) as ContactFormCfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showError(message: string): void {
|
||||||
|
const ok = document.getElementById('nx-contact-form-success');
|
||||||
|
if (ok) ok.classList.add('hidden');
|
||||||
|
const err = document.getElementById('nx-contact-form-error');
|
||||||
|
if (!err) return;
|
||||||
|
err.textContent = message;
|
||||||
|
err.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSuccess(message: string): void {
|
||||||
|
const err = document.getElementById('nx-contact-form-error');
|
||||||
|
if (err) err.classList.add('hidden');
|
||||||
|
const ok = document.getElementById('nx-contact-form-success');
|
||||||
|
if (!ok) return;
|
||||||
|
ok.textContent = message;
|
||||||
|
ok.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Dopo un POST fallito il token Turnstile è già consumato: ne serve uno nuovo. */
|
||||||
|
function resetTurnstile(form: HTMLFormElement): void {
|
||||||
|
const widget = form.querySelector<HTMLElement>('.cf-turnstile');
|
||||||
|
const turnstile = (
|
||||||
|
window as Window & {
|
||||||
|
turnstile?: { reset: (target?: string | HTMLElement) => void };
|
||||||
|
}
|
||||||
|
).turnstile;
|
||||||
|
if (turnstile?.reset) {
|
||||||
|
try {
|
||||||
|
if (widget) turnstile.reset(widget);
|
||||||
|
else turnstile.reset();
|
||||||
|
return;
|
||||||
|
} catch {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const tokenInput = form.querySelector<HTMLInputElement>(
|
||||||
|
'input[name="cf-turnstile-response"]',
|
||||||
|
);
|
||||||
|
if (tokenInput) tokenInput.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
function bind(): void {
|
function bind(): void {
|
||||||
const form = document.getElementById('nx-contact-form') as HTMLFormElement | null;
|
const form = document.getElementById('nx-contact-form') as HTMLFormElement | null;
|
||||||
if (!form) return;
|
if (!form) return;
|
||||||
|
|
||||||
form.addEventListener('submit', (e) => {
|
form.addEventListener('submit', async (e) => {
|
||||||
const cfg = readCfg();
|
const cfg = readCfg();
|
||||||
const err = document.getElementById('nx-contact-form-error');
|
const err = document.getElementById('nx-contact-form-error');
|
||||||
|
const ok = document.getElementById('nx-contact-form-success');
|
||||||
if (err) err.classList.add('hidden');
|
if (err) err.classList.add('hidden');
|
||||||
|
if (ok) ok.classList.add('hidden');
|
||||||
|
|
||||||
const hasTurnstile =
|
const hasTurnstile =
|
||||||
typeof cfg.turnstileSiteKey === 'string' && cfg.turnstileSiteKey.length > 0;
|
typeof cfg.turnstileSiteKey === 'string' && cfg.turnstileSiteKey.length > 0;
|
||||||
@ -69,17 +114,47 @@ function bind(): void {
|
|||||||
)?.value;
|
)?.value;
|
||||||
if (!token) {
|
if (!token) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (err) {
|
showError(cfg.turnstileError);
|
||||||
err.textContent = cfg.turnstileError;
|
|
||||||
err.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cfg.actionUrl) {
|
if (cfg.actionUrl) {
|
||||||
|
e.preventDefault();
|
||||||
|
const submitBtn = form.querySelector<HTMLButtonElement>('button[type="submit"]');
|
||||||
|
if (submitBtn) submitBtn.disabled = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch(cfg.actionUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { Accept: 'application/json' },
|
||||||
|
body: new FormData(form),
|
||||||
|
});
|
||||||
|
const data = (await res.json().catch(() => null)) as
|
||||||
|
| { ok?: boolean; error?: string; redirect?: string }
|
||||||
|
| null;
|
||||||
|
if (!res.ok) {
|
||||||
|
showError(
|
||||||
|
(data && typeof data.error === 'string' && data.error) ||
|
||||||
|
'Invio non riuscito. Riprova tra poco.',
|
||||||
|
);
|
||||||
|
if (hasTurnstile) resetTurnstile(form);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
form.reset();
|
||||||
|
if (hasTurnstile) resetTurnstile(form);
|
||||||
|
showSuccess(
|
||||||
|
cfg.successMessage || 'Messaggio inviato. Ti risponderemo al più presto.',
|
||||||
|
);
|
||||||
|
form.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
} catch {
|
||||||
|
showError('Errore di rete. Controlla la connessione e riprova.');
|
||||||
|
if (hasTurnstile) resetTurnstile(form);
|
||||||
|
} finally {
|
||||||
|
if (submitBtn) submitBtn.disabled = false;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const fd = new FormData(form);
|
const fd = new FormData(form);
|
||||||
const name = String(fd.get(cfg.fieldName) ?? '').trim();
|
const name = String(fd.get(cfg.fieldName) ?? '').trim();
|
||||||
@ -101,10 +176,7 @@ function bind(): void {
|
|||||||
window.location.href = `mailto:${cfg.mailto}?subject=${subject}&body=${body}`;
|
window.location.href = `mailto:${cfg.mailto}?subject=${subject}&body=${body}`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (err) {
|
showError(cfg.configError);
|
||||||
err.textContent = cfg.configError;
|
|
||||||
err.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -75,8 +75,11 @@ function measureHeaderAndSync(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function bind(): void {
|
function bind(): void {
|
||||||
const path = window.location.pathname;
|
const path = window.location.pathname.replace(/\/$/, '') || '/';
|
||||||
if (path !== '/' && path !== '/index.html') return;
|
// Home IT (`/`) e home localizzate (`/en`, `/th`).
|
||||||
|
if (path !== '/' && path !== '/index.html' && path !== '/en' && path !== '/th') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const header = document.querySelector('header');
|
const header = document.querySelector('header');
|
||||||
measureHeaderAndSync();
|
measureHeaderAndSync();
|
||||||
|
|||||||
@ -36,14 +36,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Canvas particelle: dentro .nx-grid-bg, sopra lo sfondo della sezione */
|
/* Canvas particelle: fisso al viewport (non alto quanto tutto il documento) */
|
||||||
.nx-particle-layer {
|
.nx-particle-layer {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Subtle grid (Vercel-like) + vignette */
|
/* Subtle grid (Vercel-like) + vignette */
|
||||||
@ -128,11 +129,16 @@
|
|||||||
/* Spine: prolunga i flussi hero verso la fascia “Prodotti” (dietro al contenuto) */
|
/* Spine: prolunga i flussi hero verso la fascia “Prodotti” (dietro al contenuto) */
|
||||||
.nx-dataflow-spine {
|
.nx-dataflow-spine {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
/* Solo fascia hero → prodotti: evita SVG filtrato alto quanto tutto il main */
|
||||||
|
height: min(160vh, 2200px);
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
opacity: 0.46;
|
opacity: 0.46;
|
||||||
|
contain: paint;
|
||||||
mask-image: linear-gradient(
|
mask-image: linear-gradient(
|
||||||
to bottom,
|
to bottom,
|
||||||
transparent 0,
|
transparent 0,
|
||||||
|
|||||||
@ -9,5 +9,22 @@
|
|||||||
},
|
},
|
||||||
"observability": {
|
"observability": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}
|
},
|
||||||
}
|
"vars": {
|
||||||
|
"CONTACT_FORM_FROM_EMAIL": "no-reply@nexstudio.ai",
|
||||||
|
"CONTACT_FORM_TO_EMAIL": "javaxman69@gmail.com"
|
||||||
|
},
|
||||||
|
"d1_databases": [
|
||||||
|
{
|
||||||
|
"binding": "NEWSLETTER_DB",
|
||||||
|
"database_name": "nexstudio-newsletter",
|
||||||
|
"database_id": "f2b30e34-6e63-4df8-8f92-f0a0dad08cda",
|
||||||
|
"migrations_dir": "migrations"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"send_email": [
|
||||||
|
{
|
||||||
|
"name": "EMAIL"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user