Initial commit: sito NexStudio (Astro) con Cloudflare, CMS cookie e contenuti legali
- Home: hero, prodotti, servizi, stack, FAQ, CTA con form contatti inline (glossy) e particelle - Header nero, menu con scroll-spy e alone su voce attiva, CTA Chattiamo (pre-chat) - Pagine: privacy, cookie, GDPR, terms, about, codice etico, modello organizzativo, dove siamo, news - Cookie consent first-party, chat gate, stampe-friendly su SubpageLayout - Footer: Sezioni con Contattaci, Azienda con Dove siamo senza link per ora - .gitignore: aggiunto .wrangler/ per stato locale Cloudflare Made-with: Cursor
27
.gitignore
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# build output
|
||||||
|
dist/
|
||||||
|
# generated types
|
||||||
|
.astro/
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
|
||||||
|
# environment variables
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# macOS-specific files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# jetbrains setting folder
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Cloudflare Wrangler (stato locale dev)
|
||||||
|
.wrangler/
|
||||||
4
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["astro-build.astro-vscode"],
|
||||||
|
"unwantedRecommendations": []
|
||||||
|
}
|
||||||
11
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"command": "./node_modules/.bin/astro dev",
|
||||||
|
"name": "Development server",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "node-terminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
43
README.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Astro Starter Kit: Minimal
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm create astro@latest -- --template minimal
|
||||||
|
```
|
||||||
|
|
||||||
|
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||||
|
|
||||||
|
## 🚀 Project Structure
|
||||||
|
|
||||||
|
Inside of your Astro project, you'll see the following folders and files:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/
|
||||||
|
├── public/
|
||||||
|
├── src/
|
||||||
|
│ └── pages/
|
||||||
|
│ └── index.astro
|
||||||
|
└── package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||||
|
|
||||||
|
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||||
|
|
||||||
|
Any static assets, like images, can be placed in the `public/` directory.
|
||||||
|
|
||||||
|
## 🧞 Commands
|
||||||
|
|
||||||
|
All commands are run from the root of the project, from a terminal:
|
||||||
|
|
||||||
|
| Command | Action |
|
||||||
|
| :------------------------ | :----------------------------------------------- |
|
||||||
|
| `npm install` | Installs dependencies |
|
||||||
|
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||||
|
| `npm run build` | Build your production site to `./dist/` |
|
||||||
|
| `npm run preview` | Preview your build locally, before deploying |
|
||||||
|
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||||
|
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||||
|
|
||||||
|
## 👀 Want to learn more?
|
||||||
|
|
||||||
|
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||||
40
astro.config.mjs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// @ts-check
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
|
||||||
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
|
import cloudflare from '@astrojs/cloudflare';
|
||||||
|
import alpinejs from '@astrojs/alpinejs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mitiga race su richieste SSR parallele durante l’ottimizzazione dipendenze
|
||||||
|
* (Astro + Cloudflare + Vite 7: file mancanti sotto `node_modules/.vite/deps_*`).
|
||||||
|
* Se l’errore persiste: `npm run dev:fresh` (cancella la cache Vite e riavvia).
|
||||||
|
*/
|
||||||
|
function viteSsrOptimizeIgnoreOutdated() {
|
||||||
|
return {
|
||||||
|
name: 'vite-ssr-optimize-ignore-outdated',
|
||||||
|
enforce: 'post',
|
||||||
|
configEnvironment(name) {
|
||||||
|
if (name === 'astro' || name === 'ssr' || name === 'prerender') {
|
||||||
|
return {
|
||||||
|
optimizeDeps: {
|
||||||
|
ignoreOutdatedRequests: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
|
export default defineConfig({
|
||||||
|
/** Usato da Astro per URL assoluti (es. sitemap). Imposta il dominio di produzione. */
|
||||||
|
// site: 'https://www.tuodominio.com',
|
||||||
|
|
||||||
|
vite: {
|
||||||
|
plugins: [tailwindcss(), viteSsrOptimizeIgnoreOutdated()],
|
||||||
|
},
|
||||||
|
|
||||||
|
adapter: cloudflare(),
|
||||||
|
integrations: [alpinejs()],
|
||||||
|
});
|
||||||
6224
package-lock.json
generated
Normal file
29
package.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "nexstudio",
|
||||||
|
"type": "module",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.12.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"dev:fresh": "node -e \"require('fs').rmSync('node_modules/.vite',{recursive:true,force:true});\" && astro dev",
|
||||||
|
"build": "astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"astro": "astro",
|
||||||
|
"generate-types": "wrangler types"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/alpinejs": "^0.5.0",
|
||||||
|
"@astrojs/cloudflare": "^13.1.10",
|
||||||
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
|
"@types/alpinejs": "^3.13.11",
|
||||||
|
"alpinejs": "^3.15.11",
|
||||||
|
"astro": "^6.1.8",
|
||||||
|
"tailwindcss": "^4.2.2",
|
||||||
|
"wrangler": "^4.83.0"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"vite": "^7"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 655 B |
10
public/favicon.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
||||||
|
<rect width="32" height="32" rx="8" fill="url(#g)"/>
|
||||||
|
<path fill="#fff" d="M8 22V10h3.2l4.1 6.1h.1L19.4 10H22.8v12h-3.1V15.5l-3.4 4.7h-1.8L11.1 15.5V22H8Z"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="g" x1="6" y1="4" x2="28" y2="28" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#38bdf8"/>
|
||||||
|
<stop offset="1" stop-color="#a78bfa"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 452 B |
BIN
public/hero-ambient.jpg
Normal file
|
After Width: | Height: | Size: 374 KiB |
106
public/images/cta-comet.svg
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Cometa — Openclipart (public domain), jhnri4
|
||||||
|
https://openclipart.org/detail/120871/comet-by-jhnri4
|
||||||
|
Adattata per uso web: XML valido, viewBox invariato.
|
||||||
|
-->
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 377.03 369.56"
|
||||||
|
width="377.03"
|
||||||
|
height="369.56"
|
||||||
|
fill="none"
|
||||||
|
style="background-color: transparent"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter4849" color-interpolation-filters="sRGB">
|
||||||
|
<feGaussianBlur in="SourceGraphic" stdDeviation="5.7209681" />
|
||||||
|
</filter>
|
||||||
|
<filter id="filter4853" color-interpolation-filters="sRGB">
|
||||||
|
<feGaussianBlur in="SourceGraphic" stdDeviation="5.7209681" />
|
||||||
|
</filter>
|
||||||
|
<filter id="filter4965" color-interpolation-filters="sRGB">
|
||||||
|
<feGaussianBlur in="SourceGraphic" stdDeviation="8.3324219" />
|
||||||
|
</filter>
|
||||||
|
<filter
|
||||||
|
id="filter5037"
|
||||||
|
width="1.3246"
|
||||||
|
height="1.3246"
|
||||||
|
x="-0.16230"
|
||||||
|
y="-0.16230"
|
||||||
|
color-interpolation-filters="sRGB"
|
||||||
|
>
|
||||||
|
<feGaussianBlur in="SourceGraphic" stdDeviation="4.767418" />
|
||||||
|
</filter>
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient5061"
|
||||||
|
x1="243.24"
|
||||||
|
y1="651.43"
|
||||||
|
x2="441.94"
|
||||||
|
y2="372.13"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<stop offset="0" style="stop-color:#003380" />
|
||||||
|
<stop offset="1" style="stop-color:#003380;stop-opacity:0" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient5063"
|
||||||
|
x1="238.29"
|
||||||
|
y1="648.6"
|
||||||
|
x2="450.43"
|
||||||
|
y2="441.42"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<stop offset="0" style="stop-color:#0066ff" />
|
||||||
|
<stop offset="1" style="stop-color:#0066ff;stop-opacity:0" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient5065"
|
||||||
|
x1="188.09"
|
||||||
|
y1="633.76"
|
||||||
|
x2="350.72"
|
||||||
|
y2="435.06"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<stop offset="0" style="stop-color:#d5e5ff" />
|
||||||
|
<stop offset="1" style="stop-color:#d5e5ff;stop-opacity:0" />
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient
|
||||||
|
id="radialGradient5067"
|
||||||
|
cx="200"
|
||||||
|
cy="613.11"
|
||||||
|
r="35.25"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<stop offset="0" style="stop-color:#ffffff" />
|
||||||
|
<stop offset="1" style="stop-color:#9dc4ff" />
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(-141 -303.31)">
|
||||||
|
<g id="g5055">
|
||||||
|
<path
|
||||||
|
id="path4754"
|
||||||
|
style="filter:url(#filter4965);fill:url(#linearGradient5061)"
|
||||||
|
d="m335.06 323.31-161.56 256.62h0.0312c-7.7476 7.7318-12.531 18.408-12.531 30.219 0 23.589 19.098 42.719 42.688 42.719 12.828 0 24.327-5.6712 32.156-14.625l0.0937 0.0937 262.09-224.06-61.125 10.875 28.531-32.594-96.406 47.531 73.312-67.906-116.78 71.969 65.188-93.688-84.188 62.469 74.688-89.625-71.969 58.375 25.781-58.375z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id="path4777"
|
||||||
|
style="filter:url(#filter4853);fill:url(#linearGradient5063)"
|
||||||
|
d="m313.09 388.97c-3.7612 3.2608-8.2376 2.2626-12.594 2.9688-4.6066 2.118-4.9426 8.2697-8.4279 11.674-38.816 61.218-76.527 123.25-116.48 183.67-17.524 18.493-7.359 54.418 17.781 59.719 15.84 4.5584 32.857-3.4636 42.824-15.805 80.37-68.42 160.43-137.18 240.72-205.69-18.69 3.3066-37.375 6.6468-56.062 9.9688 5.463-5.7576 10.146-12.099 14.312-18.875-24.476 8.7216-46.281 24.839-71.188 31.344-4.8147-2.5932-2.4291-9.3295 0.6875-12.344 2.4308-3.6028-2.5862-5.8736-5.2188-3.5938-13.314 6.5146-25.041 16.972-39.406 21-4.7021-2.8901-2.2828-9.7582 1.338-12.487 10.101-15.707 22.491-30.09 31.068-46.607 0.20289-4.7289-5.5909-2.7139-7.5938-0.6875-13.894 9.3735-26.437 22.249-42.375 28.125-4.8122-2.2342-4.0238-9.3549 0.21049-11.748 5.9586-7.3842 11.842-14.825 16.165-23.314-1.9167 0.89583-3.8333 1.7917-5.75 2.6875z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id="path4809"
|
||||||
|
style="filter:url(#filter4849);fill:url(#linearGradient5065)"
|
||||||
|
d="m303.91 397.59c-3.7934 0.65787-3.855 6.4469-5.7812 7.8125 2.5052-2.2631 4.3057-5.3698 6.4688-7.9688l-0.6875 0.15625zm33 9.0312c-10.706 8.2175-21.838 17.425-35 20.969-6.6546-1.3897-10.041-9.2626-8.1875-15.438-39.161 61.722-77.014 124.72-117.88 185.06-9.4552 18.144 3.6863 45.068 25.062 45.031 15.193 1.0504 26.959-10.125 36.72-20.228 72.93-62.15 145.68-124.52 218.54-186.76-17.322 3.1007-34.647 6.1836-51.969 9.2812 4.1169-4.4793 8.3736-8.7936 12.438-13.312-17.508 8.4481-34.7 19.218-53.844 22.875-5.7627-1.3494-7.8088-8.7581-7.6562-12.875-11.633 6.6589-23.045 15.927-36.438 17.969-9.1363-3.4294-9.1462-17.297-1.875-22.719 6.8834-10.736 14.952-20.731 22-31.375-0.63542 0.51042-1.2708 1.0208-1.9062 1.5312z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id="path4787"
|
||||||
|
style="filter:url(#filter5037);fill-rule:evenodd;fill:url(#radialGradient5067)"
|
||||||
|
transform="matrix(.99124 0 0 .99124 7.8314 2.0454)"
|
||||||
|
d="m235.25 613.11c0 19.468-15.782 35.25-35.25 35.25s-35.25-15.782-35.25-35.25 15.782-35.25 35.25-35.25 35.25 15.782 35.25 35.25z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.8 KiB |
1
public/logos/alpinejs.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#94a3b8" d="m24 12-5.72 5.746-5.724-5.741 5.724-5.75L24 12zM5.72 6.254 0 12l5.72 5.746h11.44L5.72 6.254z"/></svg>
|
||||||
|
After Width: | Height: | Size: 196 B |
1
public/logos/astro.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#94a3b8" d="M8.358 20.162c-1.186-1.07-1.532-3.316-1.038-4.944.856 1.026 2.043 1.352 3.272 1.535 1.897.283 3.76.177 5.522-.678.202-.098.388-.229.608-.36.166.473.209.95.151 1.437-.14 1.185-.738 2.1-1.688 2.794-.38.277-.782.525-1.175.787-1.205.804-1.531 1.747-1.078 3.119l.044.148a3.158 3.158 0 0 1-1.407-1.188 3.31 3.31 0 0 1-.544-1.815c-.004-.32-.004-.642-.048-.958-.106-.769-.472-1.113-1.161-1.133-.707-.02-1.267.411-1.415 1.09-.012.053-.028.104-.045.165h.002zm-5.961-4.445s3.24-1.575 6.49-1.575l2.451-7.565c.092-.366.36-.614.662-.614.302 0 .57.248.662.614l2.45 7.565c3.85 0 6.491 1.575 6.491 1.575L16.088.727C15.93.285 15.663 0 15.303 0H8.697c-.36 0-.615.285-.784.727l-5.516 14.99z"/></svg>
|
||||||
|
After Width: | Height: | Size: 774 B |
1
public/logos/cloudflare.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#94a3b8" d="M16.5088 16.8447c.1475-.5068.0908-.9707-.1553-1.3154-.2246-.3164-.6045-.499-1.0615-.5205l-8.6592-.1123a.1559.1559 0 0 1-.1333-.0713c-.0283-.042-.0351-.0986-.021-.1553.0278-.084.1123-.1484.2036-.1562l8.7359-.1123c1.0351-.0489 2.1601-.8868 2.5537-1.9136l.499-1.3013c.0215-.0561.0293-.1128.0147-.168-.5625-2.5463-2.835-4.4453-5.5499-4.4453-2.5039 0-4.6284 1.6177-5.3876 3.8614-.4927-.3658-1.1187-.5625-1.794-.499-1.2026.119-2.1665 1.083-2.2861 2.2856-.0283.31-.0069.6128.0635.894C1.5683 13.171 0 14.7754 0 16.752c0 .1748.0142.3515.0352.5273.0141.083.0844.1475.1689.1475h15.9814c.0909 0 .1758-.0645.2032-.1553l.12-.4268zm2.7568-5.5634c-.0771 0-.1611 0-.2383.0112-.0566 0-.1054.0415-.127.0976l-.3378 1.1744c-.1475.5068-.0918.9707.1543 1.3164.2256.3164.6055.498 1.0625.5195l1.8437.1133c.0557 0 .1055.0263.1329.0703.0283.043.0351.1074.0214.1562-.0283.084-.1132.1485-.204.1553l-1.921.1123c-1.041.0488-2.1582.8867-2.5527 1.914l-.1406.3585c-.0283.0713.0215.1416.0986.1416h6.5977c.0771 0 .1474-.0489.169-.126.1122-.4082.1757-.837.1757-1.2803 0-2.6025-2.125-4.727-4.7344-4.727"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
1
public/logos/docker.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#94a3b8" d="M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
1
public/logos/github.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#94a3b8" d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
|
||||||
|
After Width: | Height: | Size: 816 B |
1
public/logos/hl7-fhir.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 92 26" fill="none" aria-hidden="true"><rect x="1" y="2.5" width="90" height="21" rx="5" stroke="#94a3b8" stroke-width="1.1"/><text x="46" y="17.25" text-anchor="middle" fill="#94a3b8" font-family="ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif" font-size="11" font-weight="700" letter-spacing="0.04em">HL7 FHIR</text></svg>
|
||||||
|
After Width: | Height: | Size: 390 B |
1
public/logos/kubernetes.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#94a3b8" d="M10.204 14.35l.007.01-.999 2.413a5.171 5.171 0 0 1-2.075-2.597l2.578-.437.004.005a.44.44 0 0 1 .484.606zm-.833-2.129a.44.44 0 0 0 .173-.756l.002-.011L7.585 9.7a5.143 5.143 0 0 0-.73 3.255l2.514-.725.002-.009zm1.145-1.98a.44.44 0 0 0 .699-.337l.01-.005.15-2.62a5.144 5.144 0 0 0-3.01 1.442l2.147 1.523.004-.002zm.76 2.75l.723.349.722-.347.18-.78-.5-.623h-.804l-.5.623.179.779zm1.5-3.095a.44.44 0 0 0 .7.336l.008.003 2.134-1.513a5.188 5.188 0 0 0-2.992-1.442l.148 2.615.002.001zm10.876 5.97l-5.773 7.181a1.6 1.6 0 0 1-1.248.594l-9.261.003a1.6 1.6 0 0 1-1.247-.596l-5.776-7.18a1.583 1.583 0 0 1-.307-1.34L2.1 5.573c.108-.47.425-.864.863-1.073L11.305.513a1.606 1.606 0 0 1 1.385 0l8.345 3.985c.438.209.755.604.863 1.073l2.062 8.955c.108.47-.005.963-.308 1.34zm-3.289-2.057c-.042-.01-.103-.026-.145-.034-.174-.033-.315-.025-.479-.038-.35-.037-.638-.067-.895-.148-.105-.04-.18-.165-.216-.216l-.201-.059a6.45 6.45 0 0 0-.105-2.332 6.465 6.465 0 0 0-.936-2.163c.052-.047.15-.133.177-.159.008-.09.001-.183.094-.282.197-.185.444-.338.743-.522.142-.084.273-.137.415-.242.032-.024.076-.062.11-.089.24-.191.295-.52.123-.736-.172-.216-.506-.236-.745-.045-.034.027-.08.062-.111.088-.134.116-.217.23-.33.35-.246.25-.45.458-.673.609-.097.056-.239.037-.303.033l-.19.135a6.545 6.545 0 0 0-4.146-2.003l-.012-.223c-.065-.062-.143-.115-.163-.25-.022-.268.015-.557.057-.905.023-.163.061-.298.068-.475.001-.04-.001-.099-.001-.142 0-.306-.224-.555-.5-.555-.275 0-.499.249-.499.555l.001.014c0 .041-.002.092 0 .128.006.177.044.312.067.475.042.348.078.637.056.906a.545.545 0 0 1-.162.258l-.012.211a6.424 6.424 0 0 0-4.166 2.003 8.373 8.373 0 0 1-.18-.128c-.09.012-.18.04-.297-.029-.223-.15-.427-.358-.673-.608-.113-.12-.195-.234-.329-.349-.03-.026-.077-.062-.111-.088a.594.594 0 0 0-.348-.132.481.481 0 0 0-.398.176c-.172.216-.117.546.123.737l.007.005.104.083c.142.105.272.159.414.242.299.185.546.338.743.522.076.082.09.226.1.288l.16.143a6.462 6.462 0 0 0-1.02 4.506l-.208.06c-.055.072-.133.184-.215.217-.257.081-.546.11-.895.147-.164.014-.305.006-.48.039-.037.007-.09.02-.133.03l-.004.002-.007.002c-.295.071-.484.342-.423.608.061.267.349.429.645.365l.007-.001.01-.003.129-.029c.17-.046.294-.113.448-.172.33-.118.604-.217.87-.256.112-.009.23.069.288.101l.217-.037a6.5 6.5 0 0 0 2.88 3.596l-.09.218c.033.084.069.199.044.282-.097.252-.263.517-.452.813-.091.136-.185.242-.268.399-.02.037-.045.095-.064.134-.128.275-.034.591.213.71.248.12.556-.007.69-.282v-.002c.02-.039.046-.09.062-.127.07-.162.094-.301.144-.458.132-.332.205-.68.387-.897.05-.06.13-.082.215-.105l.113-.205a6.453 6.453 0 0 0 4.609.012l.106.192c.086.028.18.042.256.155.136.232.229.507.342.84.05.156.074.295.145.457.016.037.043.09.062.129.133.276.442.402.69.282.247-.118.341-.435.213-.71-.02-.039-.045-.096-.065-.134-.083-.156-.177-.261-.268-.398-.19-.296-.346-.541-.443-.793-.04-.13.007-.21.038-.294-.018-.022-.059-.144-.083-.202a6.499 6.499 0 0 0 2.88-3.622c.064.01.176.03.213.038.075-.05.144-.114.28-.104.266.039.54.138.87.256.154.06.277.128.448.173.036.01.088.019.13.028l.009.003.007.001c.297.064.584-.098.645-.365.06-.266-.128-.537-.423-.608zM16.4 9.701l-1.95 1.746v.005a.44.44 0 0 0 .173.757l.003.01 2.526.728a5.199 5.199 0 0 0-.108-1.674A5.208 5.208 0 0 0 16.4 9.7zm-4.013 5.325a.437.437 0 0 0-.404-.232.44.44 0 0 0-.372.233h-.002l-1.268 2.292a5.164 5.164 0 0 0 3.326.003l-1.27-2.296h-.01zm1.888-1.293a.44.44 0 0 0-.27.036.44.44 0 0 0-.214.572l-.003.004 1.01 2.438a5.15 5.15 0 0 0 2.081-2.615l-2.6-.44-.004.005z"/></svg>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
1
public/logos/okta.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#94a3b8" d="M12 0C5.389 0 0 5.35 0 12s5.35 12 12 12 12-5.35 12-12S18.611 0 12 0zm0 18c-3.325 0-6-2.675-6-6s2.675-6 6-6 6 2.675 6 6-2.675 6-6 6z"/></svg>
|
||||||
|
After Width: | Height: | Size: 235 B |
1
public/logos/openai.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#94a3b8" d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
1
public/logos/tailwindcss.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#94a3b8" d="M12.001,4.8c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 C13.666,10.618,15.027,12,18.001,12c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C16.337,6.182,14.976,4.8,12.001,4.8z M6.001,12c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 c1.177,1.194,2.538,2.576,5.512,2.576c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C10.337,13.382,8.976,12,6.001,12z"/></svg>
|
||||||
|
After Width: | Height: | Size: 590 B |
173
public/particle-bg.js
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
/**
|
||||||
|
* Rete di particelle + linee (canvas #particleCanvas).
|
||||||
|
* Deve stare DENTRO il contenitore della pagina (es. .nx-grid-bg), non dietro body,
|
||||||
|
* altrimenti lo sfondo opaco della pagina lo copre.
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
|
var canvas = document.getElementById('particleCanvas');
|
||||||
|
if (!canvas) return;
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
|
||||||
|
var particles = [];
|
||||||
|
var mouse = { x: null, y: null, radius: 150 };
|
||||||
|
var raf = 0;
|
||||||
|
|
||||||
|
function canvasSize() {
|
||||||
|
var p = canvas.parentElement;
|
||||||
|
if (!p) return { w: 0, h: 0 };
|
||||||
|
var w = Math.max(1, Math.floor(p.clientWidth));
|
||||||
|
var h = Math.max(1, Math.floor(p.clientHeight));
|
||||||
|
return { w: w, h: h };
|
||||||
|
}
|
||||||
|
|
||||||
|
function Particle(w, h) {
|
||||||
|
this.x = Math.random() * w;
|
||||||
|
this.y = Math.random() * h;
|
||||||
|
this.size = Math.random() * 2 + 1;
|
||||||
|
this.speedX = (Math.random() - 0.5) * 1.5;
|
||||||
|
this.speedY = (Math.random() - 0.5) * 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
Particle.prototype.draw = function () {
|
||||||
|
ctx.fillStyle = 'rgba(148, 163, 184, 0.85)';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
};
|
||||||
|
|
||||||
|
Particle.prototype.update = function (w, h) {
|
||||||
|
this.x += this.speedX;
|
||||||
|
this.y += this.speedY;
|
||||||
|
|
||||||
|
if (this.x > w || this.x < 0) this.speedX *= -1;
|
||||||
|
if (this.y > h || this.y < 0) this.speedY *= -1;
|
||||||
|
|
||||||
|
if (mouse.x == null || mouse.y == null) return;
|
||||||
|
|
||||||
|
var dx = mouse.x - this.x;
|
||||||
|
var dy = mouse.y - this.y;
|
||||||
|
var distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
if (distance > 1 && distance < mouse.radius) {
|
||||||
|
var fx = dx / distance;
|
||||||
|
var fy = dy / distance;
|
||||||
|
var force = (mouse.radius - distance) / mouse.radius;
|
||||||
|
this.x -= fx * force * 5;
|
||||||
|
this.y -= fy * force * 5;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
var sz = canvasSize();
|
||||||
|
var w = sz.w;
|
||||||
|
var h = sz.h;
|
||||||
|
if (w < 8 || h < 8) return;
|
||||||
|
|
||||||
|
var dpr = Math.min(window.devicePixelRatio || 1, 2);
|
||||||
|
canvas.width = Math.floor(w * dpr);
|
||||||
|
canvas.height = Math.floor(h * dpr);
|
||||||
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||||
|
|
||||||
|
particles = [];
|
||||||
|
var n = Math.floor((w * h) / 9000);
|
||||||
|
if (n > 130) n = 130;
|
||||||
|
if (n < 45) n = 45;
|
||||||
|
for (var i = 0; i < n; i++) {
|
||||||
|
particles.push(new Particle(w, h));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect(w, h) {
|
||||||
|
var a;
|
||||||
|
var b;
|
||||||
|
var dx;
|
||||||
|
var dy;
|
||||||
|
var distance;
|
||||||
|
var opacity;
|
||||||
|
for (a = 0; a < particles.length; a++) {
|
||||||
|
for (b = a + 1; b < particles.length; b++) {
|
||||||
|
dx = particles[a].x - particles[b].x;
|
||||||
|
dy = particles[a].y - particles[b].y;
|
||||||
|
distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
if (distance < 150) {
|
||||||
|
opacity = (1 - distance / 150) * 0.22;
|
||||||
|
ctx.strokeStyle = 'rgba(59, 130, 246, ' + opacity + ')';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(particles[a].x, particles[a].y);
|
||||||
|
ctx.lineTo(particles[b].x, particles[b].y);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function frame() {
|
||||||
|
var sz = canvasSize();
|
||||||
|
var w = sz.w;
|
||||||
|
var h = sz.h;
|
||||||
|
ctx.clearRect(0, 0, w, h);
|
||||||
|
|
||||||
|
var i;
|
||||||
|
for (i = 0; i < particles.length; i++) {
|
||||||
|
particles[i].update(w, h);
|
||||||
|
particles[i].draw();
|
||||||
|
}
|
||||||
|
connect(w, h);
|
||||||
|
raf = requestAnimationFrame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncCanvasCssSize() {
|
||||||
|
var sz = canvasSize();
|
||||||
|
canvas.style.width = sz.w + 'px';
|
||||||
|
canvas.style.height = sz.h + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
function onResize() {
|
||||||
|
cancelAnimationFrame(raf);
|
||||||
|
syncCanvasCssSize();
|
||||||
|
init();
|
||||||
|
if (!reduced && particles.length) {
|
||||||
|
raf = requestAnimationFrame(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMouseFromEvent(e) {
|
||||||
|
var rect = canvas.getBoundingClientRect();
|
||||||
|
mouse.x = e.clientX - rect.left;
|
||||||
|
mouse.y = e.clientY - rect.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener(
|
||||||
|
'mousemove',
|
||||||
|
function (e) {
|
||||||
|
setMouseFromEvent(e);
|
||||||
|
},
|
||||||
|
{ passive: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
var parent = canvas.parentElement;
|
||||||
|
if (parent) {
|
||||||
|
var ro = new ResizeObserver(onResize);
|
||||||
|
ro.observe(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', onResize, { passive: true });
|
||||||
|
|
||||||
|
syncCanvasCssSize();
|
||||||
|
init();
|
||||||
|
|
||||||
|
if (reduced) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (particles.length) {
|
||||||
|
raf = requestAnimationFrame(frame);
|
||||||
|
}
|
||||||
|
})();
|
||||||
27
src/components/CardBottomRightTexture.astro
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
/**
|
||||||
|
* Metà inferiore card: globo spostato in basso a destra così che ~15–20% resti
|
||||||
|
* fuori dall’area utile ma sia tagliato da overflow-hidden sulla card.
|
||||||
|
*/
|
||||||
|
import WireframeGlobeSvg from './WireframeGlobeSvg.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute right-0 bottom-0 left-0 top-1/2 z-0 overflow-hidden rounded-b-[inherit]"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<!--
|
||||||
|
translate ~18%: parte del globo va oltre bordo dx/basso; overflow-hidden sulla card la nasconde.
|
||||||
|
-->
|
||||||
|
<WireframeGlobeSvg
|
||||||
|
class="absolute bottom-0 right-0 h-full w-auto max-w-none origin-bottom-right
|
||||||
|
translate-x-[18%] translate-y-[18%]
|
||||||
|
select-none opacity-[0.42] mix-blend-screen
|
||||||
|
min-[500px]:translate-x-[19%] min-[500px]:translate-y-[19%] min-[500px]:opacity-[0.5]
|
||||||
|
sm:translate-x-[20%] sm:translate-y-[20%]"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute inset-0 bg-gradient-to-t from-transparent to-nx-elevated/20"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
187
src/components/ChatGateModal.astro
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
---
|
||||||
|
import { chat } from '../data/home';
|
||||||
|
|
||||||
|
const pc = chat.prechat;
|
||||||
|
const hasFormspree =
|
||||||
|
typeof pc.actionUrl === 'string' && pc.actionUrl.length > 0;
|
||||||
|
const nextUrl =
|
||||||
|
typeof pc.successRedirect === 'string' && pc.successRedirect.length > 0
|
||||||
|
? pc.successRedirect
|
||||||
|
: '';
|
||||||
|
const notifySubject =
|
||||||
|
typeof pc.notifySubject === 'string' && pc.notifySubject.length > 0
|
||||||
|
? pc.notifySubject
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const model = {
|
||||||
|
entryUrl: chat.entryUrl,
|
||||||
|
openInNewTab: chat.openInNewTab,
|
||||||
|
actionUrl: pc.actionUrl,
|
||||||
|
mailto: pc.mailto,
|
||||||
|
notifySubject: pc.notifySubject,
|
||||||
|
successRedirect: pc.successRedirect,
|
||||||
|
fieldName: pc.fieldName,
|
||||||
|
fieldEmail: pc.fieldEmail,
|
||||||
|
openChatAfterSubmit: pc.openChatAfterSubmit,
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<script
|
||||||
|
type="application/json"
|
||||||
|
id="nx-chat-gate-model"
|
||||||
|
set:html={JSON.stringify(model)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="nx-chat-gate-overlay"
|
||||||
|
class="fixed inset-0 z-[203] flex items-center justify-center bg-black/70 p-4 sm:p-6"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="nx-chat-gate-title"
|
||||||
|
aria-hidden="true"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex max-h-[min(92vh,40rem)] w-full max-w-lg flex-col overflow-hidden rounded-xl border border-nx-border bg-nx-elevated text-nx-fg shadow-2xl"
|
||||||
|
data-nx-chat-gate-panel
|
||||||
|
>
|
||||||
|
<div class="border-b border-nx-border px-5 py-4">
|
||||||
|
<h2 id="nx-chat-gate-title" class="text-lg font-semibold tracking-tight">
|
||||||
|
{pc.title}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-1 text-sm leading-relaxed text-nx-muted">{pc.intro}</p>
|
||||||
|
<div
|
||||||
|
class="mt-4 rounded-lg border border-sky-500/25 bg-sky-500/10 px-3 py-2.5"
|
||||||
|
role="status"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="text-xs font-semibold uppercase tracking-wide text-sky-200/95"
|
||||||
|
>
|
||||||
|
{chat.availability.badge}
|
||||||
|
</p>
|
||||||
|
<p class="mt-1.5 text-sm leading-relaxed text-nx-muted">
|
||||||
|
{chat.availability.body}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="relative min-h-0 flex-1 overflow-y-auto px-5 py-4">
|
||||||
|
<div id="nx-chat-gate-step-form">
|
||||||
|
<form
|
||||||
|
id="nx-chat-gate-form"
|
||||||
|
class="flex flex-col gap-4"
|
||||||
|
method="post"
|
||||||
|
action={hasFormspree ? pc.actionUrl : '#'}
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
hasFormspree && nextUrl ? (
|
||||||
|
<input type="hidden" name="_next" value={nextUrl} />
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
{
|
||||||
|
hasFormspree && notifySubject ? (
|
||||||
|
<input type="hidden" name="_subject" value={notifySubject} />
|
||||||
|
) : 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>
|
||||||
|
<label
|
||||||
|
class="block text-xs font-medium text-nx-muted"
|
||||||
|
for="nx-cg-name"
|
||||||
|
>
|
||||||
|
{pc.nameLabel}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="nx-cg-name"
|
||||||
|
type="text"
|
||||||
|
name={pc.fieldName}
|
||||||
|
autocomplete="name"
|
||||||
|
class="mt-1.5 min-h-10 w-full rounded-lg border border-nx-border bg-nx-bg/80 px-3 text-sm text-nx-fg outline-none ring-sky-500/30 focus:border-sky-500/40 focus:ring-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
class="block text-xs font-medium text-nx-muted"
|
||||||
|
for="nx-cg-email"
|
||||||
|
>
|
||||||
|
{pc.emailLabel}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="nx-cg-email"
|
||||||
|
type="email"
|
||||||
|
name={pc.fieldEmail}
|
||||||
|
required
|
||||||
|
autocomplete="email"
|
||||||
|
class="mt-1.5 min-h-10 w-full rounded-lg border border-nx-border bg-nx-bg/80 px-3 text-sm text-nx-fg outline-none ring-sky-500/30 focus:border-sky-500/40 focus:ring-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p
|
||||||
|
id="nx-chat-gate-form-error"
|
||||||
|
class="hidden text-sm leading-relaxed text-red-400/90"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex flex-col-reverse gap-2 pt-1 sm:flex-row sm:justify-end">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-nx-chat-gate-close
|
||||||
|
class="h-10 rounded-lg border border-nx-border px-4 text-sm font-medium text-nx-muted hover:text-nx-fg"
|
||||||
|
>
|
||||||
|
{pc.closeLabel}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="inline-flex h-10 items-center justify-center rounded-lg bg-nx-fg px-5 text-sm font-semibold text-nx-bg hover:bg-white"
|
||||||
|
>
|
||||||
|
{pc.submitLabel}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="nx-chat-gate-step-success" class="hidden">
|
||||||
|
<p
|
||||||
|
id="nx-chat-gate-success-text"
|
||||||
|
class="text-sm leading-relaxed text-nx-muted"
|
||||||
|
>
|
||||||
|
{pc.successMessage}
|
||||||
|
</p>
|
||||||
|
<div class="mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-nx-chat-gate-close
|
||||||
|
class="h-10 rounded-lg border border-nx-border px-4 text-sm font-medium text-nx-muted hover:text-nx-fg"
|
||||||
|
>
|
||||||
|
{pc.closeLabel}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="nx-chat-gate-open-chat"
|
||||||
|
class="inline-flex h-10 items-center justify-center rounded-lg bg-nx-fg px-5 text-sm font-semibold text-nx-bg hover:bg-white"
|
||||||
|
>
|
||||||
|
{pc.enterChatLabel}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import '../scripts/nx-chat-gate-modal.ts';
|
||||||
|
</script>
|
||||||
196
src/components/ContactFormInline.astro
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
---
|
||||||
|
import { cta } from '../data/home';
|
||||||
|
|
||||||
|
const cf = cta.contactForm;
|
||||||
|
const model = {
|
||||||
|
actionUrl: cf.actionUrl,
|
||||||
|
mailto: cf.mailto,
|
||||||
|
notifySubject: cf.notifySubject,
|
||||||
|
successRedirect: cf.successRedirect,
|
||||||
|
fieldName: cf.fieldName,
|
||||||
|
fieldCompany: cf.fieldCompany,
|
||||||
|
fieldCountry: cf.fieldCountry,
|
||||||
|
fieldEmail: cf.fieldEmail,
|
||||||
|
fieldMessage: cf.fieldMessage,
|
||||||
|
};
|
||||||
|
const hasFormspree =
|
||||||
|
typeof cf.actionUrl === 'string' && cf.actionUrl.length > 0;
|
||||||
|
const nextUrl =
|
||||||
|
typeof cf.successRedirect === 'string' && cf.successRedirect.length > 0
|
||||||
|
? cf.successRedirect
|
||||||
|
: '';
|
||||||
|
const notifySubject =
|
||||||
|
typeof cf.notifySubject === 'string' && cf.notifySubject.length > 0
|
||||||
|
? cf.notifySubject
|
||||||
|
: '';
|
||||||
|
const fieldClass =
|
||||||
|
'mt-1.5 min-h-10 w-full rounded-lg border border-nx-border bg-nx-bg/80 px-3 text-sm text-nx-fg outline-none ring-sky-500/30 focus:border-sky-500/40 focus:ring-2';
|
||||||
|
---
|
||||||
|
|
||||||
|
<script
|
||||||
|
type="application/json"
|
||||||
|
id="nx-contact-model"
|
||||||
|
set:html={JSON.stringify(model)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="nx-contact-glossy group relative rounded-2xl p-px shadow-[0_0_0_1px_rgba(255,255,255,0.07),0_22px_60px_-18px_rgba(0,0,0,0.55),0_0_50px_-10px_rgba(56,189,248,0.22),0_0_80px_-20px_rgba(167,139,250,0.12)]"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-br from-white/35 via-sky-400/25 to-violet-500/30 opacity-90"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute -inset-px rounded-2xl bg-gradient-to-tl from-sky-500/10 via-transparent to-violet-500/15 opacity-70 blur-sm"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="relative overflow-hidden rounded-2xl border border-white/10 bg-nx-elevated/65 p-4 shadow-[inset_0_1px_0_0_rgba(255,255,255,0.14),inset_0_-1px_0_0_rgba(0,0,0,0.12)] backdrop-blur-md sm:p-5"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-white/50 to-transparent"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute -right-6 -top-8 h-28 w-28 rounded-full bg-sky-400/15 blur-2xl"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute -bottom-10 -left-4 h-32 w-40 rounded-full bg-violet-500/10 blur-3xl"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="relative z-[1]">
|
||||||
|
<h3
|
||||||
|
id="nx-contact-form-title"
|
||||||
|
class="text-base font-semibold tracking-tight text-nx-fg drop-shadow-sm"
|
||||||
|
>
|
||||||
|
{cf.title}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-1 text-xs leading-relaxed text-nx-muted sm:text-sm">
|
||||||
|
{cf.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form
|
||||||
|
id="nx-contact-form"
|
||||||
|
class="relative mt-4 space-y-3.5"
|
||||||
|
method="post"
|
||||||
|
action={hasFormspree ? cf.actionUrl : '#'}
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
aria-labelledby="nx-contact-form-title"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
hasFormspree && nextUrl ? (
|
||||||
|
<input type="hidden" name="_next" value={nextUrl} />
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
{
|
||||||
|
hasFormspree && notifySubject ? (
|
||||||
|
<input type="hidden" name="_subject" value={notifySubject} />
|
||||||
|
) : 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>
|
||||||
|
<label class="block text-xs font-medium text-nx-muted" for="nx-cf-name">
|
||||||
|
{cf.nameLabel}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="nx-cf-name"
|
||||||
|
type="text"
|
||||||
|
name={cf.fieldName}
|
||||||
|
required
|
||||||
|
autocomplete="name"
|
||||||
|
class={fieldClass}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-medium text-nx-muted" for="nx-cf-company">
|
||||||
|
{cf.companyLabel}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="nx-cf-company"
|
||||||
|
type="text"
|
||||||
|
name={cf.fieldCompany}
|
||||||
|
autocomplete="organization"
|
||||||
|
class={fieldClass}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-medium text-nx-muted" for="nx-cf-email">
|
||||||
|
{cf.emailLabel}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="nx-cf-email"
|
||||||
|
type="email"
|
||||||
|
name={cf.fieldEmail}
|
||||||
|
required
|
||||||
|
autocomplete="email"
|
||||||
|
class={fieldClass}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-medium text-nx-muted" for="nx-cf-country">
|
||||||
|
{cf.countryLabel}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="nx-cf-country"
|
||||||
|
type="text"
|
||||||
|
name={cf.fieldCountry}
|
||||||
|
required
|
||||||
|
autocomplete="country-name"
|
||||||
|
class={fieldClass}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-medium text-nx-muted" for="nx-cf-msg">
|
||||||
|
{cf.messageLabel}
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="nx-cf-msg"
|
||||||
|
name={cf.fieldMessage}
|
||||||
|
required
|
||||||
|
rows={4}
|
||||||
|
class="mt-1.5 min-h-[6.5rem] w-full resize-y rounded-lg border border-nx-border bg-nx-bg/80 px-3 py-2 text-sm leading-relaxed text-nx-fg outline-none ring-sky-500/30 focus:border-sky-500/40 focus:ring-2"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p
|
||||||
|
id="nx-contact-form-error"
|
||||||
|
class="hidden text-sm leading-relaxed text-red-400/90"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="pt-1">
|
||||||
|
<button
|
||||||
|
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]"
|
||||||
|
>
|
||||||
|
{cf.submitLabel}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import '../scripts/nx-contact-form.ts';
|
||||||
|
</script>
|
||||||
156
src/components/CookieConsent.astro
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
---
|
||||||
|
import { cookieConsentMeta, cookieCategories } from '../data/cookie-consent';
|
||||||
|
|
||||||
|
const model = {
|
||||||
|
storageKey: cookieConsentMeta.storageKey,
|
||||||
|
policyVersion: cookieConsentMeta.policyVersion,
|
||||||
|
categories: cookieCategories.map((c) => ({ id: c.id, required: c.required })),
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<script
|
||||||
|
type="application/json"
|
||||||
|
id="nx-cc-model"
|
||||||
|
set:html={JSON.stringify(model)}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
id="nx-cc-banner"
|
||||||
|
class="fixed inset-x-0 bottom-0 z-[200] border-t border-nx-border bg-nx-elevated/95 px-4 py-4 shadow-[0_-12px_40px_rgba(0,0,0,0.45)] backdrop-blur-xl sm:px-6"
|
||||||
|
role="region"
|
||||||
|
aria-labelledby="nx-cc-banner-title"
|
||||||
|
aria-describedby="nx-cc-banner-desc"
|
||||||
|
aria-hidden="true"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<div class="mx-auto flex max-w-6xl flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<p
|
||||||
|
id="nx-cc-banner-title"
|
||||||
|
class="text-sm font-semibold tracking-tight text-nx-fg"
|
||||||
|
>
|
||||||
|
Utilizziamo cookie e memorizzazione locale
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
id="nx-cc-banner-desc"
|
||||||
|
class="mt-1 text-xs leading-relaxed text-nx-muted sm:text-sm"
|
||||||
|
>
|
||||||
|
Seleziona le categorie che preferisci. I necessari restano attivi per
|
||||||
|
registrare la tua scelta. Dettagli nella{' '}
|
||||||
|
<a class="text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline" href="/cookies">informativa cookie</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex flex-shrink-0 flex-col gap-2 sm:flex-row sm:flex-wrap sm:justify-end"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
Rifiuta non essenziali
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
Personalizza
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
Accetta tutto
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Overlay custom (no <dialog>): in dev Cloudflare/Vite il top layer nativo
|
||||||
|
può dare focus e clic incoerenti; qui usiamo hidden + flex come il banner.
|
||||||
|
-->
|
||||||
|
<div
|
||||||
|
id="nx-cc-dialog"
|
||||||
|
class="fixed inset-0 z-[201] flex items-center justify-center bg-black/70 p-4 sm:p-6"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="nx-cc-dialog-title"
|
||||||
|
aria-hidden="true"
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex max-h-[min(90vh,32rem)] w-full max-w-md flex-col overflow-hidden rounded-xl border border-nx-border bg-nx-elevated text-nx-fg shadow-2xl"
|
||||||
|
data-nx-cc-panel
|
||||||
|
>
|
||||||
|
<div class="border-b border-nx-border px-5 py-4">
|
||||||
|
<h2 id="nx-cc-dialog-title" class="text-base font-semibold tracking-tight">
|
||||||
|
Preferenze cookie
|
||||||
|
</h2>
|
||||||
|
<p class="mt-1 text-xs leading-relaxed text-nx-muted">
|
||||||
|
Versione informativa v{cookieConsentMeta.policyVersion}. Puoi modificare
|
||||||
|
le scelte in qualsiasi momento dal footer.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="max-h-[min(52vh,22rem)] space-y-4 overflow-y-auto overscroll-contain px-5 py-4"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
cookieCategories.map((cat) => (
|
||||||
|
<div class="rounded-lg border border-nx-border/80 bg-nx-bg/40 px-3 py-3">
|
||||||
|
<div class="flex items-start justify-between gap-3">
|
||||||
|
<div class="min-w-0">
|
||||||
|
<p class="text-sm font-medium text-nx-fg">{cat.label}</p>
|
||||||
|
<p class="mt-1 text-xs leading-relaxed text-nx-muted">
|
||||||
|
{cat.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{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">
|
||||||
|
Sempre attivi
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<label
|
||||||
|
class="relative inline-flex h-7 w-12 shrink-0 cursor-pointer items-center rounded-full border border-nx-border bg-nx-surface transition has-[:checked]:border-sky-500/40 has-[:checked]:bg-sky-500/10"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
data-nx-cc-cat={cat.id}
|
||||||
|
class="peer sr-only"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="pointer-events-none absolute left-0.5 top-1/2 h-5 w-5 -translate-y-1/2 rounded-full bg-nx-muted shadow peer-checked:left-[calc(100%-1.375rem)] peer-checked:bg-sky-400"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex flex-shrink-0 flex-col-reverse gap-2 border-t border-nx-border bg-nx-bg/30 px-5 py-4 sm:flex-row sm:justify-end"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
Chiudi
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-nx-cc-save
|
||||||
|
class="h-10 rounded-lg bg-nx-fg px-4 text-sm font-semibold text-nx-bg hover:bg-white"
|
||||||
|
>
|
||||||
|
Salva preferenze
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import '../scripts/nx-cookie-consent.ts';
|
||||||
|
</script>
|
||||||
82
src/components/DataflowSpine.astro
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
/**
|
||||||
|
* Continuazione dei flussi dati dell'hero: dalla destra verso sinistra,
|
||||||
|
* inclinazione verso il basso, fino alla fascia del titolo "Prodotti".
|
||||||
|
* Dietro a testo e card (main ha stacking; questo blocco è z-0).
|
||||||
|
*/
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="nx-dataflow-spine" aria-hidden="true">
|
||||||
|
<svg
|
||||||
|
class="nx-dataflow-spine-svg"
|
||||||
|
viewBox="0 0 1480 2800"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<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%">
|
||||||
|
<stop offset="0%" stop-color="#a5f3fc" stop-opacity="0.75" />
|
||||||
|
<stop offset="40%" stop-color="#38bdf8" stop-opacity="0.9" />
|
||||||
|
<stop offset="100%" stop-color="#22d3ee" stop-opacity="0.25" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<g filter="url(#nx-spine-glow)" 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 -->
|
||||||
|
<path
|
||||||
|
class="nx-flow-line nx-flow-line--a"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
d="M 1460 80 C 1320 320 1240 520 1080 720 C 920 920 680 1020 400 1080 C 260 1100 140 1140 60 1180"
|
||||||
|
stroke="url(#nx-spine-stroke)"
|
||||||
|
stroke-width="2.1"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-dasharray="8 22"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class="nx-flow-line nx-flow-line--b"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
d="M 1420 200 C 1280 440 1180 660 1020 860 C 860 1060 600 1120 340 1180 C 200 1210 100 1240 40 1280"
|
||||||
|
stroke="url(#nx-spine-stroke)"
|
||||||
|
stroke-width="1.55"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-dasharray="6 18"
|
||||||
|
opacity="0.78"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class="nx-flow-line nx-flow-line--c"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
d="M 1380 40 C 1240 260 1140 480 960 680 C 800 880 520 960 280 1020 C 160 1040 60 1080 20 1120"
|
||||||
|
stroke="#67e8f9"
|
||||||
|
stroke-width="1.15"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-dasharray="4 16"
|
||||||
|
opacity="0.45"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class="nx-flow-line nx-flow-line--d"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
d="M 1440 280 C 1300 520 1200 740 1040 940 C 880 1140 640 1200 380 1260 C 240 1290 120 1320 80 1360"
|
||||||
|
stroke="#22d3ee"
|
||||||
|
stroke-width="1.35"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-dasharray="5 24"
|
||||||
|
opacity="0.52"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
84
src/components/HeroDataflow.astro
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
/**
|
||||||
|
* Decorazione hero: flusso dati stile “big data” (ciano su trasparente),
|
||||||
|
* percorso verticale verso il basso-destra — ispirazione visiva tipo stock analytics.
|
||||||
|
*/
|
||||||
|
---
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class="nx-hero-dataflow-svg h-full w-full"
|
||||||
|
viewBox="0 0 520 960"
|
||||||
|
preserveAspectRatio="xMaxYMid slice"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<filter
|
||||||
|
id="nx-flow-glow"
|
||||||
|
x="-30%"
|
||||||
|
y="-30%"
|
||||||
|
width="160%"
|
||||||
|
height="160%"
|
||||||
|
color-interpolation-filters="sRGB"
|
||||||
|
>
|
||||||
|
<feGaussianBlur stdDeviation="2.5" result="blur" />
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="blur" />
|
||||||
|
<feMergeNode in="SourceGraphic" />
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="nx-flow-stroke" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
|
<stop offset="0%" stop-color="#22d3ee" stop-opacity="0.2" />
|
||||||
|
<stop offset="35%" stop-color="#38bdf8" stop-opacity="0.95" />
|
||||||
|
<stop offset="100%" stop-color="#a5f3fc" stop-opacity="0.85" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="nx-flow-reflect" x1="0.5" y1="0" x2="0.5" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#38bdf8" stop-opacity="0.14" />
|
||||||
|
<stop offset="55%" stop-color="#0ea5e9" stop-opacity="0.04" />
|
||||||
|
<stop offset="100%" stop-color="#030712" stop-opacity="0" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Riflesso leggero in basso (come superficie lucida) -->
|
||||||
|
<ellipse cx="360" cy="915" rx="220" ry="52" fill="url(#nx-flow-reflect)" />
|
||||||
|
|
||||||
|
<!-- Flussi: dall’alto della sezione hero verso basso-destra (escono dal bordo destro) -->
|
||||||
|
<g filter="url(#nx-flow-glow)" opacity="0.9">
|
||||||
|
<path
|
||||||
|
class="nx-flow-line nx-flow-line--a"
|
||||||
|
d="M 228 0 C 232 160 140 280 268 420 C 320 500 200 600 352 720 C 400 780 388 860 508 960"
|
||||||
|
stroke="url(#nx-flow-stroke)"
|
||||||
|
stroke-width="2.2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-dasharray="8 22"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class="nx-flow-line nx-flow-line--b"
|
||||||
|
d="M 268 0 C 275 200 185 320 305 460 C 350 540 245 640 395 760 C 438 830 428 900 520 960"
|
||||||
|
stroke="url(#nx-flow-stroke)"
|
||||||
|
stroke-width="1.6"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-dasharray="6 18"
|
||||||
|
opacity="0.78"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class="nx-flow-line nx-flow-line--c"
|
||||||
|
d="M 188 40 C 195 220 95 340 238 500 C 290 590 165 700 328 820 C 368 880 352 928 472 960"
|
||||||
|
stroke="#67e8f9"
|
||||||
|
stroke-width="1.2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-dasharray="4 16"
|
||||||
|
opacity="0.48"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class="nx-flow-line nx-flow-line--d"
|
||||||
|
d="M 308 20 C 318 240 220 360 338 520 C 385 600 285 710 418 840 C 455 895 442 938 518 960"
|
||||||
|
stroke="#22d3ee"
|
||||||
|
stroke-width="1.4"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-dasharray="5 24"
|
||||||
|
opacity="0.58"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
217
src/components/SiteFooter.astro
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
---
|
||||||
|
import { footer } from '../data/home';
|
||||||
|
|
||||||
|
const hasNewsletterAction =
|
||||||
|
typeof footer.newsletter.actionUrl === 'string' &&
|
||||||
|
footer.newsletter.actionUrl.length > 0;
|
||||||
|
|
||||||
|
const nl = footer.newsletter;
|
||||||
|
const nextUrl =
|
||||||
|
typeof nl.successRedirect === 'string' && nl.successRedirect.length > 0
|
||||||
|
? nl.successRedirect
|
||||||
|
: '';
|
||||||
|
const notifySubject =
|
||||||
|
typeof nl.notifySubject === 'string' && nl.notifySubject.length > 0
|
||||||
|
? nl.notifySubject
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const linkClass =
|
||||||
|
'text-sm text-sky-400/90 transition-colors hover:text-sky-300';
|
||||||
|
---
|
||||||
|
|
||||||
|
<footer class="relative z-[1] border-t border-nx-border px-4 py-12 sm:px-6">
|
||||||
|
<div class="mx-auto max-w-6xl">
|
||||||
|
<div class="border-b border-nx-border pb-10">
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
class="inline-block text-base font-semibold tracking-tight text-nx-fg hover:text-white sm:text-lg"
|
||||||
|
>{footer.brandName}</a
|
||||||
|
>
|
||||||
|
<p class="mt-2 max-w-xl text-sm leading-relaxed text-nx-muted">
|
||||||
|
{footer.tagline}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quattro colonne omogenee (solo link / elenchi); preferenze cookie sotto, staccate -->
|
||||||
|
<div class="border-b border-nx-border py-10">
|
||||||
|
<div
|
||||||
|
class="grid grid-cols-1 gap-10 sm:grid-cols-2 lg:grid-cols-4 lg:items-start lg:gap-x-10 xl:gap-x-12"
|
||||||
|
>
|
||||||
|
<div class="min-w-0">
|
||||||
|
<p
|
||||||
|
class="mb-4 text-xs font-semibold uppercase tracking-wider text-nx-fg"
|
||||||
|
>
|
||||||
|
{footer.columnTitles.sections}
|
||||||
|
</p>
|
||||||
|
<ul class="space-y-2.5">
|
||||||
|
{footer.homeNav.map((link) => (
|
||||||
|
<li>
|
||||||
|
<a class={linkClass} href={link.href}>
|
||||||
|
{link.label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="min-w-0">
|
||||||
|
<p
|
||||||
|
class="mb-4 text-xs font-semibold uppercase tracking-wider text-nx-fg"
|
||||||
|
>
|
||||||
|
{footer.columnTitles.legal}
|
||||||
|
</p>
|
||||||
|
<ul class="space-y-2.5">
|
||||||
|
{footer.legalColumnNav.map((link) => (
|
||||||
|
<li>
|
||||||
|
<a class={linkClass} href={link.href}>
|
||||||
|
{link.label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="min-w-0">
|
||||||
|
<p
|
||||||
|
class="mb-4 text-xs font-semibold uppercase tracking-wider text-nx-fg"
|
||||||
|
>
|
||||||
|
{footer.columnTitles.social}
|
||||||
|
</p>
|
||||||
|
<ul class="space-y-2.5 text-sm">
|
||||||
|
{footer.socialLinks.map((item) => (
|
||||||
|
<li>
|
||||||
|
{item.href ? (
|
||||||
|
<a
|
||||||
|
class={linkClass}
|
||||||
|
href={item.href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
class="cursor-not-allowed text-nx-muted opacity-50"
|
||||||
|
title="URL da configurare in footer.ts"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="min-w-0">
|
||||||
|
<p
|
||||||
|
class="mb-4 text-xs font-semibold uppercase tracking-wider text-nx-fg"
|
||||||
|
>
|
||||||
|
{footer.columnTitles.company}
|
||||||
|
</p>
|
||||||
|
<ul class="space-y-2.5">
|
||||||
|
{footer.companyNav.map((link) => (
|
||||||
|
<li>
|
||||||
|
{link.href ? (
|
||||||
|
<a class={linkClass} href={link.href}>
|
||||||
|
{link.label}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<span class="text-sm text-nx-muted" title="Collegamento in arrivo">
|
||||||
|
{link.label}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="mt-10 flex flex-col gap-3 border-t border-nx-border/70 pt-8 sm:flex-row sm:items-center sm:gap-6"
|
||||||
|
data-nx-footer-print-collapsible
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="inline-flex h-10 shrink-0 items-center justify-center self-start rounded-lg border border-nx-border bg-nx-bg/50 px-4 text-sm font-medium text-sky-400/90 transition-colors hover:border-sky-500/35 hover:bg-sky-500/5 hover:text-sky-300 sm:self-auto"
|
||||||
|
data-nx-cc-open
|
||||||
|
>
|
||||||
|
{footer.cookiePreferences.label}
|
||||||
|
</button>
|
||||||
|
<p class="max-w-xl text-xs leading-relaxed text-nx-muted sm:flex-1">
|
||||||
|
{footer.cookiePreferences.hint}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Una sola area newsletter, allineata a sinistra come la colonna Sezioni -->
|
||||||
|
<div class="pt-10 lg:max-w-md" data-nx-footer-print-collapsible>
|
||||||
|
<p class="text-xs font-semibold uppercase tracking-wider text-nx-fg">
|
||||||
|
{footer.newsletter.title}
|
||||||
|
</p>
|
||||||
|
<p class="mt-1.5 text-xs leading-relaxed text-nx-muted">
|
||||||
|
{footer.newsletter.description}
|
||||||
|
</p>
|
||||||
|
{
|
||||||
|
hasNewsletterAction ? (
|
||||||
|
<form
|
||||||
|
class="relative mt-3 flex flex-col gap-2 sm:flex-row sm:items-stretch"
|
||||||
|
action={nl.actionUrl}
|
||||||
|
method="post"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
>
|
||||||
|
{nextUrl ? <input type="hidden" name="_next" value={nextUrl} /> : null}
|
||||||
|
{notifySubject ? (
|
||||||
|
<input type="hidden" name="_subject" value={notifySubject} />
|
||||||
|
) : null}
|
||||||
|
<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"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name={nl.fieldName}
|
||||||
|
required
|
||||||
|
autocomplete="email"
|
||||||
|
placeholder={nl.placeholder}
|
||||||
|
class="min-h-10 w-full rounded-lg border border-nx-border bg-nx-bg/80 px-3 text-sm text-nx-fg placeholder:text-nx-muted/70 outline-none ring-sky-500/30 focus:border-sky-500/40 focus:ring-2"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="inline-flex h-10 shrink-0 items-center justify-center rounded-lg bg-nx-fg px-4 text-sm font-semibold text-nx-bg hover:bg-white sm:px-5"
|
||||||
|
>
|
||||||
|
{nl.buttonLabel}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
) : (
|
||||||
|
<div class="mt-3 space-y-2">
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
disabled
|
||||||
|
placeholder={nl.placeholder}
|
||||||
|
class="min-h-10 w-full cursor-not-allowed rounded-lg border border-nx-border bg-nx-bg/40 px-3 text-sm text-nx-muted opacity-60"
|
||||||
|
aria-describedby="nx-newsletter-hint"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled
|
||||||
|
class="inline-flex h-10 w-full cursor-not-allowed items-center justify-center rounded-lg border border-nx-border bg-nx-elevated/50 text-sm font-medium text-nx-muted opacity-70 sm:w-auto sm:px-5"
|
||||||
|
>
|
||||||
|
{nl.buttonLabel}
|
||||||
|
</button>
|
||||||
|
<p id="nx-newsletter-hint" class="text-xs leading-relaxed text-nx-muted">
|
||||||
|
Iscrizione in arrivo: configura l’endpoint nel progetto per attivare il modulo.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="pt-8 text-xs leading-relaxed text-nx-muted sm:text-sm">
|
||||||
|
© {new Date().getFullYear()} NexStudio. {footer.legalLine}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
271
src/components/WireframeGlobeSvg.astro
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
---
|
||||||
|
/**
|
||||||
|
* Sfera wireframe geodetica (icosfera) generata a build, solo stroke, sfondo trasparente.
|
||||||
|
* Profondità: opacità da z proiettato come nelle linee canvas delle particelle.
|
||||||
|
*/
|
||||||
|
type Vec3 = [number, number, number];
|
||||||
|
|
||||||
|
function norm(v: Vec3): Vec3 {
|
||||||
|
const L = Math.hypot(v[0], v[1], v[2]);
|
||||||
|
if (L < 1e-8) return [0, 0, 1];
|
||||||
|
return [v[0] / L, v[1] / L, v[2] / L];
|
||||||
|
}
|
||||||
|
|
||||||
|
function addV(vs: Vec3[], v: Vec3): number {
|
||||||
|
vs.push(norm(v));
|
||||||
|
return vs.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const T = (1 + Math.sqrt(5)) / 2;
|
||||||
|
const verts: Vec3[] = [
|
||||||
|
[-1, T, 0],
|
||||||
|
[1, T, 0],
|
||||||
|
[-1, -T, 0],
|
||||||
|
[1, -T, 0],
|
||||||
|
[0, -1, T],
|
||||||
|
[0, 1, T],
|
||||||
|
[0, -1, -T],
|
||||||
|
[0, 1, -T],
|
||||||
|
[T, 0, -1],
|
||||||
|
[T, 0, 1],
|
||||||
|
[-T, 0, -1],
|
||||||
|
[-T, 0, 1],
|
||||||
|
].map(norm);
|
||||||
|
|
||||||
|
/** Ordine vertici come three.js IcosahedronGeometry */
|
||||||
|
let faces: [number, number, number][] = [
|
||||||
|
[0, 11, 5],
|
||||||
|
[0, 5, 1],
|
||||||
|
[0, 1, 7],
|
||||||
|
[0, 7, 10],
|
||||||
|
[0, 10, 11],
|
||||||
|
[1, 5, 9],
|
||||||
|
[5, 11, 4],
|
||||||
|
[11, 10, 2],
|
||||||
|
[10, 7, 6],
|
||||||
|
[7, 1, 8],
|
||||||
|
[3, 9, 4],
|
||||||
|
[3, 4, 2],
|
||||||
|
[3, 2, 6],
|
||||||
|
[3, 6, 8],
|
||||||
|
[3, 8, 9],
|
||||||
|
[4, 9, 5],
|
||||||
|
[2, 4, 11],
|
||||||
|
[6, 2, 10],
|
||||||
|
[8, 6, 7],
|
||||||
|
[9, 8, 1],
|
||||||
|
];
|
||||||
|
|
||||||
|
const midCache = new Map<string, number>();
|
||||||
|
function mid(a: number, b: number): number {
|
||||||
|
const z = a < b ? `${a}_${b}` : `${b}_${a}`;
|
||||||
|
if (midCache.has(z)) return midCache.get(z)!;
|
||||||
|
const id = addV(verts, [
|
||||||
|
verts[a][0] + verts[b][0],
|
||||||
|
verts[a][1] + verts[b][1],
|
||||||
|
verts[a][2] + verts[b][2],
|
||||||
|
]);
|
||||||
|
midCache.set(z, id);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function subdivide(): void {
|
||||||
|
midCache.clear();
|
||||||
|
const next: [number, number, number][] = [];
|
||||||
|
for (const [i, j, k] of faces) {
|
||||||
|
const a = mid(i, j);
|
||||||
|
const b = mid(j, k);
|
||||||
|
const c = mid(k, i);
|
||||||
|
next.push([i, a, c], [j, b, a], [k, c, b], [a, b, c]);
|
||||||
|
}
|
||||||
|
faces = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 2 suddivisioni → mesh fine tipo riferimento (~162 vertici) */
|
||||||
|
subdivide();
|
||||||
|
subdivide();
|
||||||
|
|
||||||
|
const edges = new Set<string>();
|
||||||
|
for (const [i, j, k] of faces) {
|
||||||
|
const pushE = (a: number, b: number) => {
|
||||||
|
const e = a < b ? `${a}_${b}` : `${b}_${a}`;
|
||||||
|
edges.add(e);
|
||||||
|
};
|
||||||
|
pushE(i, j);
|
||||||
|
pushE(j, k);
|
||||||
|
pushE(k, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rotazione: sfera “in basso a destra”, bulge verso osservatore */
|
||||||
|
function rotY(v: Vec3, ang: number): Vec3 {
|
||||||
|
const c = Math.cos(ang);
|
||||||
|
const s = Math.sin(ang);
|
||||||
|
return [c * v[0] + s * v[2], v[1], -s * v[0] + c * v[2]];
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotX(v: Vec3, ang: number): Vec3 {
|
||||||
|
const c = Math.cos(ang);
|
||||||
|
const s = Math.sin(ang);
|
||||||
|
return [v[0], c * v[1] - s * v[2], s * v[1] + c * v[2]];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ry = -0.62;
|
||||||
|
const rx = -0.35;
|
||||||
|
const rz = 0.12;
|
||||||
|
|
||||||
|
function rotZ(v: Vec3, ang: number): Vec3 {
|
||||||
|
const c = Math.cos(ang);
|
||||||
|
const s = Math.sin(ang);
|
||||||
|
return [c * v[0] - s * v[1], s * v[0] + c * v[1], v[2]];
|
||||||
|
}
|
||||||
|
|
||||||
|
function transform(v: Vec3): Vec3 {
|
||||||
|
let p: Vec3 = [...v] as Vec3;
|
||||||
|
p = rotY(p, ry);
|
||||||
|
p = rotX(p, rx);
|
||||||
|
p = rotZ(p, rz);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
const D = 2.65;
|
||||||
|
const SCALE = 110;
|
||||||
|
|
||||||
|
type P2 = { x: number; y: number; z: number };
|
||||||
|
|
||||||
|
const projected: P2[] = verts.map((v) => {
|
||||||
|
const p = transform(v);
|
||||||
|
const d = D - p[2];
|
||||||
|
const s = d > 0.15 ? D / d : 0;
|
||||||
|
return {
|
||||||
|
x: p[0] * s * SCALE,
|
||||||
|
y: -p[1] * s * SCALE,
|
||||||
|
z: p[2],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let minX = Infinity;
|
||||||
|
let minY = Infinity;
|
||||||
|
let maxX = -Infinity;
|
||||||
|
let maxY = -Infinity;
|
||||||
|
for (const p of projected) {
|
||||||
|
minX = Math.min(minX, p.x);
|
||||||
|
maxX = Math.max(maxX, p.x);
|
||||||
|
minY = Math.min(minY, p.y);
|
||||||
|
maxY = Math.max(maxY, p.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pad = 4;
|
||||||
|
const vbW = maxX - minX + pad * 2;
|
||||||
|
const vbH = maxY - minY + pad * 2;
|
||||||
|
|
||||||
|
function toSvg(p: P2): [number, number] {
|
||||||
|
return [p.x - minX + pad, p.y - minY + pad];
|
||||||
|
}
|
||||||
|
|
||||||
|
const zMin = Math.min(...projected.map((p) => p.z));
|
||||||
|
const zMax = Math.max(...projected.map((p) => p.z));
|
||||||
|
function depthT(z: number): number {
|
||||||
|
return zMax > zMin ? (z - zMin) / (zMax - zMin) : 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Raggruppa segmenti per profondità per rendere la tridimensionalità più leggibile */
|
||||||
|
const buckets: Record<string, string[]> = { b0: [], b1: [], b2: [], b3: [] };
|
||||||
|
for (const e of edges) {
|
||||||
|
const [a, b] = e.split('_').map(Number);
|
||||||
|
const pa = projected[a];
|
||||||
|
const pb = projected[b];
|
||||||
|
const [xa, ya] = toSvg(pa);
|
||||||
|
const [xb, yb] = toSvg(pb);
|
||||||
|
const t = depthT((pa.z + pb.z) * 0.5);
|
||||||
|
const key = t < 0.28 ? 'b0' : t < 0.5 ? 'b1' : t < 0.74 ? 'b2' : 'b3';
|
||||||
|
buckets[key].push(`M${xa} ${ya}L${xb} ${yb}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathD0 = buckets.b0.join('');
|
||||||
|
const pathD1 = buckets.b1.join('');
|
||||||
|
const pathD2 = buckets.b2.join('');
|
||||||
|
const pathD3 = buckets.b3.join('');
|
||||||
|
|
||||||
|
const nodeR = Math.max(0.34, Math.min(vbW, vbH) * 0.0038);
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
const { class: className } = Astro.props as Props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox={`0 0 ${vbW} ${vbH}`}
|
||||||
|
class={className}
|
||||||
|
fill="none"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<filter id="nx-globe-front-glow" x="-60%" y="-60%" width="220%" height="220%">
|
||||||
|
<feGaussianBlur stdDeviation="1.4" result="blur"></feGaussianBlur>
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="blur"></feMergeNode>
|
||||||
|
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g stroke-linecap="round">
|
||||||
|
{pathD0 && (
|
||||||
|
<path
|
||||||
|
d={pathD0}
|
||||||
|
stroke="rgb(56 189 248)"
|
||||||
|
stroke-opacity="0.115"
|
||||||
|
stroke-width="0.86"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{pathD1 && (
|
||||||
|
<path
|
||||||
|
d={pathD1}
|
||||||
|
stroke="rgb(56 189 248)"
|
||||||
|
stroke-opacity="0.19"
|
||||||
|
stroke-width="1.05"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{pathD2 && (
|
||||||
|
<path
|
||||||
|
d={pathD2}
|
||||||
|
stroke="rgb(56 189 248)"
|
||||||
|
stroke-opacity="0.24"
|
||||||
|
stroke-width="1.22"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{pathD3 && (
|
||||||
|
<path
|
||||||
|
d={pathD3}
|
||||||
|
stroke="rgb(125 211 252)"
|
||||||
|
stroke-opacity="0.41"
|
||||||
|
stroke-width="1.52"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</g>
|
||||||
|
<g fill="rgb(147 197 253)">
|
||||||
|
{verts.map((_, idx) => {
|
||||||
|
const p = projected[idx];
|
||||||
|
const [cx, cy] = toSvg(p);
|
||||||
|
const t = depthT(p.z);
|
||||||
|
const op = 0.14 + t * 0.62;
|
||||||
|
const radius = nodeR * (0.86 + t * 1.3);
|
||||||
|
const isFront = t > 0.75;
|
||||||
|
return (
|
||||||
|
<circle
|
||||||
|
cx={cx}
|
||||||
|
cy={cy}
|
||||||
|
fill-opacity={op}
|
||||||
|
fill={isFront ? 'rgb(224 242 254)' : 'rgb(147 197 253)'}
|
||||||
|
r={radius}
|
||||||
|
filter={isFront ? 'url(#nx-globe-front-glow)' : undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
45
src/data/cookie-consent.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Consenso cookie — NexStudio CMP (dimostrativo / produzione light).
|
||||||
|
* Allineare `policyVersion` quando cambiano categorie o testi legali rilevanti.
|
||||||
|
*/
|
||||||
|
export const cookieConsentMeta = {
|
||||||
|
storageKey: 'nexstudio.cookieConsent',
|
||||||
|
policyVersion: '1.0',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type CookieCategoryId =
|
||||||
|
| 'necessary'
|
||||||
|
| 'preferences'
|
||||||
|
| 'analytics'
|
||||||
|
| 'marketing';
|
||||||
|
|
||||||
|
export const cookieCategories = [
|
||||||
|
{
|
||||||
|
id: 'necessary' as const,
|
||||||
|
label: 'Strettamente necessari',
|
||||||
|
description:
|
||||||
|
'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,
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
57
src/data/home/chat.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Chat live: prima identificazione (email → link/codice), poi sessione.
|
||||||
|
*
|
||||||
|
* `entryUrl`: dove aprire la chat dopo l’accesso (es. `/#contatti`, widget https, wa.me).
|
||||||
|
* `prechat`: invio richiesta con Formspree (`actionUrl`) o `mailto:` se l’URL è vuoto.
|
||||||
|
* `prechat.openChatAfterSubmit`: se `true`, dopo invio riuscito apre subito `entryUrl` (es. prova interna);
|
||||||
|
* con flusso token via email lasciare `false` e usare `successMessage`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Destinazione della chat dopo identificazione / click su «Entra» */
|
||||||
|
const entryUrl = '/#contatti';
|
||||||
|
// Esempio WhatsApp: `https://wa.me/TUO_NUMERO?text=` + encodeURIComponent('Ciao NexStudio...');
|
||||||
|
|
||||||
|
export const chat = {
|
||||||
|
entryUrl,
|
||||||
|
/** Nuova scheda solo per link esterni (wa.me, widget, ecc.) */
|
||||||
|
openInNewTab: entryUrl.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: {
|
||||||
|
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` */
|
||||||
|
actionUrl: '',
|
||||||
|
/** Se `actionUrl` è vuoto */
|
||||||
|
mailto: '',
|
||||||
|
notifySubject: 'Richiesta accesso chat — sito NexStudio',
|
||||||
|
successRedirect: '',
|
||||||
|
fieldName: 'name',
|
||||||
|
fieldEmail: 'email',
|
||||||
|
/**
|
||||||
|
* Dopo POST Formspree riuscito: apri subito `entryUrl` (es. test con pagina contatti).
|
||||||
|
* Con invio token via email da backend: `false`.
|
||||||
|
*/
|
||||||
|
openChatAfterSubmit: false,
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
40
src/data/home/cta.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/** Call-to-action (blocco contatti / chiusura pagina) */
|
||||||
|
export const cta = {
|
||||||
|
/** Titolo CTA su due righe (dopo il punto) */
|
||||||
|
titleLine1: 'Automatizza il complesso.',
|
||||||
|
titleLine2: 'Velocizza il processo.',
|
||||||
|
body:
|
||||||
|
'Dai modelli LLM alla distribuzione globale in pochi millisecondi. La directory definitiva per il LegalTech e MedTech. Utilizza il modulo contatto qui a fianco e ti forniremo tutti i chiarimenti che ti servono.',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modulo contatti in pagina (sezione Contatti).
|
||||||
|
*
|
||||||
|
* **Formspree:** crea un form su https://formspree.io e incolla l’URL in `actionUrl`.
|
||||||
|
* **Solo mailto:** lascia `actionUrl` vuoto e imposta `mailto` con l’indirizzo destinatario
|
||||||
|
* (si apre il client di posta dell’utente).
|
||||||
|
*/
|
||||||
|
contactForm: {
|
||||||
|
title: 'Parliamone',
|
||||||
|
description:
|
||||||
|
'Lascia i tuoi dati e il messaggio: ti risponde il team NexStudio.',
|
||||||
|
nameLabel: 'Nome',
|
||||||
|
companyLabel: 'Azienda (opzionale)',
|
||||||
|
countryLabel: 'Paese',
|
||||||
|
emailLabel: 'Email',
|
||||||
|
messageLabel: 'Scrivi qui il tuo messaggio',
|
||||||
|
submitLabel: 'Invia messaggio',
|
||||||
|
/** Formspree: `https://formspree.io/f/xxxx` — vuoto = prova `mailto` */
|
||||||
|
actionUrl: '',
|
||||||
|
/** Formspree: redirect dopo invio (`_next`) — URL **assoluta** in produzione */
|
||||||
|
successRedirect: '',
|
||||||
|
/** Formspree `_subject` e oggetto mailto */
|
||||||
|
notifySubject: 'Richiesta contatto — sito NexStudio',
|
||||||
|
/** Se `actionUrl` è vuoto: email per `mailto:` (es. `info@tuodominio.com`) */
|
||||||
|
mailto: '',
|
||||||
|
fieldName: 'name',
|
||||||
|
fieldCompany: 'company',
|
||||||
|
fieldCountry: 'country',
|
||||||
|
fieldEmail: 'email',
|
||||||
|
fieldMessage: 'message',
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
47
src/data/home/faq.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/** FAQ — domande/risposte allineate a hero, prodotti, servizi e stack */
|
||||||
|
export const faq = {
|
||||||
|
eyebrow: 'FAQ',
|
||||||
|
title: 'Domande frequenti',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
question: 'Che tipo di organizzazioni seguite con LexAura e MediAura?',
|
||||||
|
answer:
|
||||||
|
'Lavoriamo con studi legali e strutture sanitarie — dal professionista singolo a team e sedi multi‑unità — che gestiscono volumi elevati di informazione e hanno bisogno di strumenti affidabili in ambiti regolamentati. La verticalità su Legal Tech e Health Tech è parte della nostra proposta: meno genericismo, più requisiti operativi, tracciabilità e continuità del servizio.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'L’intelligenza artificiale sostituisce avvocati o medici?',
|
||||||
|
answer:
|
||||||
|
'No. I nostri prodotti sono pensati per integrarsi nei flussi professionali: sintesi, ricerca, drafting assistito, automazione della documentazione e interfacce vocali liberano tempo, ma la decisione finale e la responsabilità restano del professionista. L’obiettivo è ridurre il carico ripetitivo e offrire una base analitica solida, non sostituire il giudizio umano.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Cosa succede dopo l’adozione del software?',
|
||||||
|
answer:
|
||||||
|
'Il modello di servizio va oltre la licenza: onboarding e formazione specialistica per team legali e sanitari, supporto tecnico e consulenza operativa continuativi, aggiornamento dei motori di analisi e manutenzione evolutiva rispetto a normative e contesto di mercato. Per criticità urgenti sono previsti protocolli di intervento prioritario, così che studi e strutture non restino isolati dai propri strumenti.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Potete collegare LexAura o MediAura ai nostri sistemi già in uso?',
|
||||||
|
answer:
|
||||||
|
'Sì, quando serve. Oltre ai prodotti standard supportiamo integrazioni tramite API e moduli personalizzati nel perimetro dei nostri SaaS, per collegare gestionali o flussi documentali preesistenti senza reinventare l’intero stack informativo.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Come gestite sicurezza, disponibilità e dati sensibili?',
|
||||||
|
answer:
|
||||||
|
'Progettiamo su architetture cloud‑native ad alta disponibilità, con attenzione a crittografia, protezione di perimetro (es. WAF, mitigazione DDoS) e continuità operativa. Il trattamento di informazioni legali e sanitarie è affrontato con criteri di integrità dei dati e conformità alle normative applicabili, inclusa la chiarezza su dove risiedono i dati e sui requisiti di due diligence che i settori regolamentati si attendono.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Lavorate solo in loco o anche con clienti in Italia e all’estero?',
|
||||||
|
answer:
|
||||||
|
'Siamo una software company con base operativa a Bangkok e collaboriamo con clienti a livello internazionale. Workshop e incontri possono essere online o in presenza in base al progetto; supporto e comunicazioni operative in italiano, inglese, tedesco, francese, spagnolo o tailandese, con orari concordati.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Diritto d’uso e proprietà del software: come le regolate?',
|
||||||
|
answer:
|
||||||
|
'Dipende dal contratto: in genere cediamo l’esclusiva sul deliverable a saldo, con clausole esplicite su componenti open source e librerie di terze parti. Per licenze SaaS, permessi d’uso e responsabilità sono definiti nei termini commerciali e nel contratto quadro.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Qual è il modo più rapido per parlarne con voi?',
|
||||||
|
answer:
|
||||||
|
'Il punto di ingresso principale è la chat dal pulsante «Chattiamo» in header: prima vedi se il canale è con operatore umano, solo chatbot o misto, poi lasci email (e facoltativamente il nome) per ricevere un link o un codice monouso e aprire la sessione senza perdite di tempo. In chat operatore umano e chatbot collaborano per orientamento iniziale, requisiti e passi successivi. Per il supporto via web ci interfacciamo in italiano, inglese, tedesco, francese, spagnolo o tailandese. Per materiale sensibile useremo sempre i canali concordati in fase di rapporto commerciale. Se ci si sente più a proprio agio usando un form di contatto, in fondo alla homepage nella sezione Contatti trovate il modulo a destra del testo introduttivo.',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
} as const;
|
||||||
89
src/data/home/footer.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/** Footer — griglia 4 colonne (link); newsletter in blocco unico sotto, allineata a sinistra */
|
||||||
|
export const footer = {
|
||||||
|
brandName: 'NexStudio',
|
||||||
|
tagline: 'Ingegneria del software e IA per settori regolamentati.',
|
||||||
|
legalLine: 'P.IVA e note legali: da inserire.',
|
||||||
|
|
||||||
|
columnTitles: {
|
||||||
|
sections: 'Sezioni',
|
||||||
|
legal: 'Normative',
|
||||||
|
social: 'Reti sociali',
|
||||||
|
company: 'Azienda',
|
||||||
|
} as const,
|
||||||
|
|
||||||
|
/** Colonna 1 — ancore sulla homepage */
|
||||||
|
homeNav: [
|
||||||
|
{ label: 'Prodotti', href: '/#prodotti' },
|
||||||
|
{ label: 'Servizi', href: '/#servizi' },
|
||||||
|
{ label: 'Stack', href: '/#stack' },
|
||||||
|
{ label: 'FAQ', href: '/#faq' },
|
||||||
|
{ label: 'Contattaci', href: '/#contatti' },
|
||||||
|
] as const,
|
||||||
|
|
||||||
|
/** Pannello preferenze (CMP): non è un documento legale; tenuto fuori dalla colonna Normative */
|
||||||
|
cookiePreferences: {
|
||||||
|
label: 'Impostazioni cookie',
|
||||||
|
hint: 'Preferenze di consenso sul tuo browser (non è un testo legale).',
|
||||||
|
} as const,
|
||||||
|
|
||||||
|
/** Colonna 2 — documenti legali (testi bozza nelle rispettive pagine) */
|
||||||
|
legalColumnNav: [
|
||||||
|
{ label: 'Condizioni d’uso', href: '/terms' },
|
||||||
|
{ label: 'Informativa cookie', href: '/cookies' },
|
||||||
|
{ label: 'Privacy', href: '/privacy' },
|
||||||
|
{ label: 'Diritti privacy (GDPR)', href: '/gdpr' },
|
||||||
|
] as const,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Colonna 3 — social (sostituire gli URL con i profili reali).
|
||||||
|
* Lasciare `href` vuoto per mostrare solo etichetta disabilitata finché non configurato.
|
||||||
|
*/
|
||||||
|
socialLinks: [
|
||||||
|
{ label: 'Facebook', href: '' },
|
||||||
|
{ label: 'LinkedIn', href: '' },
|
||||||
|
{ label: 'Twitter / X', href: '' },
|
||||||
|
{ label: 'GitHub', href: '' },
|
||||||
|
] as const,
|
||||||
|
|
||||||
|
/** Colonna 4 — pagine istituzionali e compliance */
|
||||||
|
companyNav: [
|
||||||
|
{ label: 'Chi siamo', href: '/about' },
|
||||||
|
{ label: 'Codice etico', href: '/codice-etico' },
|
||||||
|
{ label: 'Modello organizzativo', href: '/modello-organizzativo' },
|
||||||
|
/** `href` vuoto = etichetta senza link (es. in attesa di pagina) */
|
||||||
|
{ label: 'Dove siamo', href: '' },
|
||||||
|
] as const,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Newsletter — invio via POST a un endpoint esterno.
|
||||||
|
*
|
||||||
|
* Opzione A (consigliata per partire): Formspree
|
||||||
|
* 1. Crea account su https://formspree.io e un nuovo form.
|
||||||
|
* 2. Copia l’URL tipo `https://formspree.io/f/abcdefgh` in `actionUrl`.
|
||||||
|
* 3. Opzionale: `successRedirect` = URL assoluta della pagina di ringraziamento
|
||||||
|
* (es. `https://tuodominio.com/newsletter/grazie` — vedi `src/pages/newsletter/grazie.astro`).
|
||||||
|
* 4. In Formspree collega l’invio a email / Slack / Google Sheets.
|
||||||
|
*
|
||||||
|
* Opzione B: Brevo, Mailchimp, Buttondown, ConvertKit — usano spesso un URL
|
||||||
|
* dedicato o un embed; adatta `fieldName` al nome campo richiesto dalla doc.
|
||||||
|
*
|
||||||
|
* Opzione C: Worker Cloudflare + lista in KV/D1 (più controllo, più codice).
|
||||||
|
*/
|
||||||
|
newsletter: {
|
||||||
|
title: 'Newsletter',
|
||||||
|
description: 'Aggiornamenti su prodotti e roadmap.',
|
||||||
|
placeholder: 'La tua email',
|
||||||
|
buttonLabel: 'Iscriviti',
|
||||||
|
/** Formspree: `https://formspree.io/f/xxxx` — vuoto = modulo disabilitato */
|
||||||
|
actionUrl: '',
|
||||||
|
/** Nome campo email richiesto dal provider (Formspree: di solito `email`) */
|
||||||
|
fieldName: 'email',
|
||||||
|
/**
|
||||||
|
* Formspree: redirect dopo invio (`_next`). Deve essere URL **assoluta**
|
||||||
|
* (es. produzione). Vuoto = pagina di ringrazio predefinita Formspree.
|
||||||
|
*/
|
||||||
|
successRedirect: '',
|
||||||
|
/** Formspree: oggetto della mail di notifica (`_subject`) */
|
||||||
|
notifySubject: 'Nuova iscrizione newsletter — NexStudio',
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
29
src/data/home/hero.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/** Sezione hero */
|
||||||
|
export const hero = {
|
||||||
|
badge: 'Restituiamo valore al tuo tempo',
|
||||||
|
/** Parte prima dell’evidenziazione nel titolo */
|
||||||
|
titleBefore: "Ingegneria del software per l'era dell'",
|
||||||
|
/** Segmento con gradiente nel titolo */
|
||||||
|
titleHighlight: 'Intelligenza Artificiale',
|
||||||
|
titleAfter: '.',
|
||||||
|
subtitle:
|
||||||
|
'Sviluppiamo soluzioni SaaS ad alta complessità per settori regolamentati.',
|
||||||
|
/** Paragrafi introduttivi */
|
||||||
|
paragraphs: [
|
||||||
|
"Ci occupiamo di progettazione di ecosistemi digitali evoluti. La nostra specializzazione risiede nell'integrazione di modelli di Intelligenza Artificiale all'interno di flussi di lavoro professionali dove precisione, sicurezza e affidabilità sono requisiti non negoziabili.",
|
||||||
|
'Il nostro approccio si concentra sulla trasformazione di dati complessi in strumenti operativi fluidi, attraverso lo sviluppo di algoritmi proprietari, interfacce vocali e architetture cloud scalabili.',
|
||||||
|
] as const,
|
||||||
|
businessUnitsHeading: 'Le nostre Business Unit:',
|
||||||
|
businessUnitsIntro:
|
||||||
|
'Operiamo attraverso divisioni specializzate per garantire la massima verticalità e competenza di dominio:',
|
||||||
|
businessUnits: [
|
||||||
|
{
|
||||||
|
name: 'Legal Tech',
|
||||||
|
description: "Soluzioni per l'automazione e l'analisi normativa.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Health Tech',
|
||||||
|
description: 'Sistemi avanzati per la gestione e l’automazione sanitaria.',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
} as const;
|
||||||
15
src/data/home/index.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Contenuti della home, per categoria.
|
||||||
|
* Modifica i file in questa cartella per aggiornare testi senza toccare il markup.
|
||||||
|
*/
|
||||||
|
export { siteMeta } from './site-meta';
|
||||||
|
export { chat } from './chat';
|
||||||
|
export { navigation } from './navigation';
|
||||||
|
export { hero } from './hero';
|
||||||
|
export { stats } from './stats';
|
||||||
|
export { services } from './services';
|
||||||
|
export { stackSection } from './stack';
|
||||||
|
export { productsSection } from './products';
|
||||||
|
export { faq } from './faq';
|
||||||
|
export { cta } from './cta';
|
||||||
|
export { footer } from './footer';
|
||||||
19
src/data/home/navigation.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { chat } from './chat';
|
||||||
|
|
||||||
|
/** Header — CTA unico: ingresso chat live (config in `chat.ts`) */
|
||||||
|
export const navigation = {
|
||||||
|
brandName: 'NexStudio',
|
||||||
|
items: [
|
||||||
|
{ label: 'Prodotti', href: '/#prodotti' },
|
||||||
|
{ label: 'Servizi', href: '/#servizi' },
|
||||||
|
{ label: 'Stack', href: '/#stack' },
|
||||||
|
{ label: 'FAQ', href: '/#faq' },
|
||||||
|
{ label: 'Contattaci', href: '/#contatti' },
|
||||||
|
] as const,
|
||||||
|
cta: {
|
||||||
|
label: 'Chattiamo',
|
||||||
|
linkTitle: chat.linkTitle,
|
||||||
|
ariaLabel:
|
||||||
|
'Richiedi accesso alla chat NexStudio: stato operatore e chatbot, identificazione con email (link o codice) prima della sessione.',
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
57
src/data/home/products.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/** Sezione prodotti — LexAura (Legal Tech), MediAura (Health Tech) */
|
||||||
|
export const productsSection = {
|
||||||
|
eyebrow: 'Prodotti',
|
||||||
|
title: 'LexAura e MediAura',
|
||||||
|
lead:
|
||||||
|
'Due piattaforme SaaS verticali per professionisti del diritto e della sanità: AI integrata dove precisione e conformità sono fondamentali.',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'lexaura',
|
||||||
|
name: 'LexAura',
|
||||||
|
badge: 'Legal Tech',
|
||||||
|
tagline: 'Il supporto analitico per l’eccellenza legale.',
|
||||||
|
focus: 'Gestione dati, memorie e contrattualistica.',
|
||||||
|
body:
|
||||||
|
'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.',
|
||||||
|
highlights: [
|
||||||
|
{
|
||||||
|
title: 'Sintesi normativa',
|
||||||
|
text: 'Analisi accelerata dei precedenti e delle fonti.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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.',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
closing:
|
||||||
|
'L’obiettivo di LexAura non è sostituire l’avvocato, ma liberarne il tempo, garantendo una base analitica solida e tempestiva.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mediaura',
|
||||||
|
name: 'MediAura',
|
||||||
|
badge: 'Health Tech',
|
||||||
|
tagline: 'L’automazione intelligente al servizio della medicina.',
|
||||||
|
focus: 'Flussi di lavoro, comandi vocali, scalabilità.',
|
||||||
|
body:
|
||||||
|
'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: '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.',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
} as const;
|
||||||
52
src/data/home/services.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Servizi (griglia bento).
|
||||||
|
* layout: wide = 2 colonne, tall = 2 righe + elenco puntato
|
||||||
|
*/
|
||||||
|
export const services = {
|
||||||
|
eyebrow: 'I nostri servizi',
|
||||||
|
title: 'Supporto, implementazione ed evoluzione',
|
||||||
|
lead:
|
||||||
|
'In NexStudio, il nostro impegno non si esaurisce con la fornitura della licenza software. Il nostro modello di servizio è progettato per garantire che ogni professionista e struttura possa estrarre il massimo valore dalle potenzialità dell’Intelligenza Artificiale integrata in LexAura e MediAura.',
|
||||||
|
cards: [
|
||||||
|
{
|
||||||
|
layout: 'tall' as const,
|
||||||
|
/** Sfera geodetica vettoriale (SVG) in basso a destra */
|
||||||
|
decoration: 'wireframe-globe' as const,
|
||||||
|
title: 'Onboarding e formazione specialistica',
|
||||||
|
body:
|
||||||
|
'L’adozione di un sistema basato su IA richiede un passaggio metodologico, non solo tecnico. Offriamo programmi di formazione dedicati per team legali e medici, finalizzati a:',
|
||||||
|
bullets: [
|
||||||
|
'Ottimizzare l’interazione con gli algoritmi di ricerca e sintesi.',
|
||||||
|
'Padroneggiare i comandi vocali e l’automazione dei flussi documentali.',
|
||||||
|
'Ridurre drasticamente la curva di apprendimento tecnologico.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
layout: 'wide' as const,
|
||||||
|
title: 'Supporto Tecnico e Consulenza Operativa',
|
||||||
|
body:
|
||||||
|
'Forniamo un’assistenza continua e di alto profilo per garantire la costante continuità operativa dei vostri sistemi. NexStudio non agisce come un semplice fornitore di supporto tecnico, ma come un partner strategico che affianca lo studio o la struttura sanitaria nell’ottimizzazione dei processi interni attraverso l’uso dei nostri SaaS.',
|
||||||
|
callout: {
|
||||||
|
title: 'Intervento «Breaking Glass»',
|
||||||
|
body:
|
||||||
|
'Per le situazioni di estrema urgenza o criticità sistemica, garantiamo protocolli di accesso rapido e intervento prioritario. Questa procedura di emergenza permette di superare i normali flussi di assistenza per risolvere istantaneamente anomalie che potrebbero impattare sulla continuità del servizio, assicurando che il professionista non resti mai isolato dai propri dati o strumenti decisionali.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
layout: 'default' as const,
|
||||||
|
title: 'Estensioni e integrazioni personalizzate',
|
||||||
|
body:
|
||||||
|
'Sebbene LexAura e MediAura siano prodotti finiti e pronti all’uso, comprendiamo che strutture complesse possano avere esigenze di interconnessione specifiche.',
|
||||||
|
bullets: [
|
||||||
|
'API integration: supportiamo l’integrazione dei nostri SaaS con sistemi gestionali preesistenti.',
|
||||||
|
'Custom modules: progettiamo estensioni su misura che aggiungono funzionalità specifiche all’interno del perimetro dei nostri prodotti, per rispondere a flussi di lavoro unici.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
layout: 'default' as const,
|
||||||
|
title: 'Aggiornamento e manutenzione evolutiva',
|
||||||
|
body:
|
||||||
|
'Il mondo dell’IA e delle normative (legali e sanitarie) è in costante mutamento. Il nostro servizio include l’aggiornamento continuo dei motori di analisi, garantendo che il software sia sempre allineato con le ultime tecnologie e le evoluzioni del mercato di riferimento.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const;
|
||||||
6
src/data/home/site-meta.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/** Meta pagina principale — usato da BaseLayout */
|
||||||
|
export const siteMeta = {
|
||||||
|
title: 'NexStudio — Ingegneria del software e IA per settori regolamentati',
|
||||||
|
description:
|
||||||
|
'NexStudio: software company con sede a Bangkok. SaaS ad alta complessità, integrazione di IA in flussi professionali, Legal Tech e Health Tech.',
|
||||||
|
} as const;
|
||||||
106
src/data/home/stack.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/** Sezione stack — infrastruttura Cloudflare e sicurezza */
|
||||||
|
export const stackSection = {
|
||||||
|
eyebrow: 'Lo stack tecnologico',
|
||||||
|
title: 'Infrastruttura e sicurezza',
|
||||||
|
lead:
|
||||||
|
'In NexStudio, la qualità dell’esperienza utente è inscindibile dalla solidità dell’infrastruttura. Per questo motivo ci affidiamo a partner globali ad alta efficienza che garantiscono i più elevati standard di disponibilità e protezione dei dati.',
|
||||||
|
cloudflare: {
|
||||||
|
title: 'Ecosistema Cloudflare',
|
||||||
|
intro:
|
||||||
|
"«Cloudflare punta ad accelerare le prestazioni, garantire la disponibilità delle app e ottimizzare l'esperienza web per gli utenti a livello globale.»",
|
||||||
|
points: [
|
||||||
|
{
|
||||||
|
title: 'Performance senza compromessi',
|
||||||
|
body:
|
||||||
|
'L’accesso ai nostri servizi è immediato e a bassa latenza in qualsiasi parte del mondo, assicurando fluidità anche durante le operazioni di elaborazione dati più complesse.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Sicurezza Advanced',
|
||||||
|
body:
|
||||||
|
'Utilizziamo implementazioni di soluzioni di protezione di ultima generazione, tra cui crittografia end-to-end, protezione da attacchi DDoS e Web Application Firewall (WAF). Ogni interazione con i nostri software avviene in un ambiente protetto e isolato.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Compliance e data integrity',
|
||||||
|
body:
|
||||||
|
'Sfruttiamo le tecnologie di gestione dei dati geografici per garantire che il trattamento delle informazioni sensibili — legali e mediche — avvenga sempre nel pieno rispetto delle normative locali e internazionali sulla privacy e sulla sovranità del dato.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
continuity: {
|
||||||
|
title: 'Continuità e scalabilità',
|
||||||
|
body:
|
||||||
|
'Garantiamo una uptime SLA (Service Level Agreement) di livello professionale. La nostra architettura è cloud-native: può scalare istantaneamente per gestire picchi di lavoro o grandi uffici e strutture multi-sede senza mai degradare le prestazioni.',
|
||||||
|
},
|
||||||
|
/** Loghi stack (`public/logos/`; Simple Icons salvo badge testuale `hl7-fhir.svg`) */
|
||||||
|
techSlider: {
|
||||||
|
ariaLabel: 'Tecnologie e piattaforme del nostro ecosistema',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'Cloudflare',
|
||||||
|
href: 'https://www.cloudflare.com/',
|
||||||
|
iconSrc: '/logos/cloudflare.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'GitHub',
|
||||||
|
href: 'https://github.com/',
|
||||||
|
iconSrc: '/logos/github.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Docker',
|
||||||
|
href: 'https://www.docker.com/',
|
||||||
|
iconSrc: '/logos/docker.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Tailwind CSS',
|
||||||
|
href: 'https://tailwindcss.com/',
|
||||||
|
iconSrc: '/logos/tailwindcss.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Alpine.js',
|
||||||
|
href: 'https://alpinejs.dev/',
|
||||||
|
iconSrc: '/logos/alpinejs.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Astro',
|
||||||
|
href: 'https://astro.build/',
|
||||||
|
iconSrc: '/logos/astro.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Kubernetes',
|
||||||
|
href: 'https://kubernetes.io/',
|
||||||
|
iconSrc: '/logos/kubernetes.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'HL7 FHIR',
|
||||||
|
href: 'https://www.hl7.org/fhir/',
|
||||||
|
iconSrc: '/logos/hl7-fhir.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Okta',
|
||||||
|
href: 'https://www.okta.com/',
|
||||||
|
iconSrc: '/logos/okta.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'OpenAI',
|
||||||
|
href: 'https://openai.com/',
|
||||||
|
iconSrc: '/logos/openai.svg',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
|
},
|
||||||
|
/** Punti chiave che ci distinguono dalla concorrenza (NexStudio, non il partner cloud) */
|
||||||
|
whyThisChoice: {
|
||||||
|
title: 'Perché NexStudio',
|
||||||
|
points: [
|
||||||
|
'Verticalità regolamentata: progettiamo per Legal Tech e Health Tech, dove tracciabilità, privacy e continuità operativa sono parte del prodotto, non un accessorio.',
|
||||||
|
'IA nei processi professionali: integriamo modelli e automazioni nei flussi reali di studi e strutture, con attenzione a precisione, audit e supervisione umana.',
|
||||||
|
'Responsabilità end-to-end: architettura, sviluppo ed evoluzione restano sotto il nostro cappello — meno frammentazione tra fornitori, più chiarezza su priorità, tempi e risultati.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
cloudflareBadge: {
|
||||||
|
label: 'Cloudflare',
|
||||||
|
href: 'https://www.cloudflare.com/',
|
||||||
|
/** Simple Icons, in `public/logos/cloudflare.svg` */
|
||||||
|
iconSrc: '/logos/cloudflare.svg',
|
||||||
|
iconAlt: 'Logo Cloudflare',
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
20
src/data/home/stats.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/** Fascia metriche — merge performance Edge + AI + compliance settoriale (Legal/Health) */
|
||||||
|
export const stats = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
value: '< 30ms',
|
||||||
|
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:
|
||||||
|
'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:
|
||||||
|
'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;
|
||||||
45
src/layouts/BaseLayout.astro
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
import '../styles/global.css';
|
||||||
|
import CookieConsent from '../components/CookieConsent.astro';
|
||||||
|
import ChatGateModal from '../components/ChatGateModal.astro';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
title = 'NexStudio — Software su misura',
|
||||||
|
description = 'Studio di sviluppo software: web app, API, automazioni e prodotti digitali, con focus su qualità e time-to-market.',
|
||||||
|
} = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it" class="bg-nx-bg">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="description" content={description} />
|
||||||
|
<meta name="theme-color" content="#030712" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
<title>{title}</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=JetBrains+Mono:wght@400;500&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body class="min-h-screen">
|
||||||
|
<slot />
|
||||||
|
<CookieConsent />
|
||||||
|
<ChatGateModal />
|
||||||
|
<script src="/particle-bg.js" defer></script>
|
||||||
|
<!--
|
||||||
|
Widget chat / analytics: caricare solo dopo consenso, es. ascoltando
|
||||||
|
`nexstudio:cookie-consent` (vedi informativa cookie) o usando
|
||||||
|
`window.NexStudioCookieConsent?.getConsent()`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
88
src/layouts/SubpageLayout.astro
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
---
|
||||||
|
import BaseLayout from './BaseLayout.astro';
|
||||||
|
import SiteFooter from '../components/SiteFooter.astro';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
heading: string;
|
||||||
|
/** Sottotitolo opzionale sotto l’H1 */
|
||||||
|
lead?: string;
|
||||||
|
/**
|
||||||
|
* Stili `@media print` (sfondo chiaro, moduli footer ridotti, modali nascosti).
|
||||||
|
* Imposta `false` se la pagina non deve attivare il foglio stampa dedicato.
|
||||||
|
*/
|
||||||
|
printFriendly?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
description = 'NexStudio — software e IA per settori regolamentati.',
|
||||||
|
heading,
|
||||||
|
lead,
|
||||||
|
printFriendly = true,
|
||||||
|
} = Astro.props;
|
||||||
|
|
||||||
|
const printDate = new Intl.DateTimeFormat('it-IT', {
|
||||||
|
dateStyle: 'long',
|
||||||
|
}).format(new Date());
|
||||||
|
const printSource = Astro.url.href;
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title={title} description={description}>
|
||||||
|
<div
|
||||||
|
class:list={[
|
||||||
|
'nx-grid-bg relative flex min-h-screen flex-col',
|
||||||
|
printFriendly && 'nx-print-sheet',
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<header
|
||||||
|
class="sticky top-0 z-50 border-b border-nx-border/40 bg-black"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-3 sm:px-6"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
class="text-lg font-semibold tracking-tight text-nx-fg hover:text-white sm:text-xl"
|
||||||
|
>NexStudio</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="text-sm font-medium text-sky-400/90 hover:text-sky-300"
|
||||||
|
href="/#contatti">Contattaci</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main class="relative z-[1] flex-1 px-4 py-12 sm:px-6 sm:py-16">
|
||||||
|
<article class="mx-auto max-w-3xl">
|
||||||
|
{
|
||||||
|
printFriendly ? (
|
||||||
|
<p class="nx-print-meta mb-6 hidden border-b border-slate-300 pb-3 text-xs leading-relaxed text-slate-600">
|
||||||
|
{printDate}
|
||||||
|
<span class="text-slate-500"> · </span>
|
||||||
|
<span class="break-all">{printSource}</span>
|
||||||
|
</p>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
<h1
|
||||||
|
class="text-3xl font-semibold tracking-tight text-nx-fg sm:text-4xl"
|
||||||
|
>
|
||||||
|
{heading}
|
||||||
|
</h1>
|
||||||
|
{
|
||||||
|
lead && (
|
||||||
|
<p class="mt-3 text-base leading-relaxed text-nx-muted sm:text-lg">
|
||||||
|
{lead}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<div
|
||||||
|
class="mt-10 space-y-6 text-sm leading-relaxed text-nx-muted sm:text-base [&_h2]:mt-10 [&_h2]:text-lg [&_h2]:font-semibold [&_h2]:text-nx-fg [&_h2]:first:mt-0 [&_li]:mt-2 [&_ol]:list-decimal [&_ol]:space-y-2 [&_ol]:pl-5 [&_p]:mt-4 [&_p]:first:mt-0 [&_strong]:text-nx-fg [&_ul]:list-disc [&_ul]:space-y-2 [&_ul]:pl-5"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
<SiteFooter />
|
||||||
|
</div>
|
||||||
|
</BaseLayout>
|
||||||
32
src/pages/about.astro
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<SubpageLayout
|
||||||
|
title="Chi siamo — NexStudio"
|
||||||
|
description="Chi è NexStudio: missione, team e ambiti Legal Tech e Health Tech."
|
||||||
|
heading="Chi siamo"
|
||||||
|
lead="Un team giovane e dinamico di programmatori dalla Tailandia e dall'Italia, uniti per creare
|
||||||
|
applicazioni ad alta usabilità ed efficienza"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Ci impegnamo nel rendere invisibile la tecnologia. Creiamo software che si fanno carico di processi strutturati
|
||||||
|
e calcoli intricati, sollevando l'utilizzatore da compiti impegnativi, dispersivi e soggetti ad errori.
|
||||||
|
Siamo focalizzati su software SaaS ad alta complessità per settori
|
||||||
|
regolamentati, specificatamente legale e medico, con focus su intelligenza artificiale integrata nei flussi
|
||||||
|
professionali (LexAura, MediAura). Nascondiamo sotto una superficie fluida e intuitiva motori
|
||||||
|
ad alta ingegneria. Abbiamo racchiuso una logica complessa in un’interfaccia essenziale, affinché basti solo
|
||||||
|
premere un tasto dove prima servivano ore di lavoro.
|
||||||
|
</p>
|
||||||
|
<h2>Dove operiamo</h2>
|
||||||
|
<p>
|
||||||
|
Base operativa a Bangkok; collaborazione con clienti a livello internazionale. Dettagli in <a
|
||||||
|
class="text-sky-400/90 hover:text-sky-300"
|
||||||
|
href="/dove-siamo">Dove siamo</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
<h2>Contatti</h2>
|
||||||
|
<p>
|
||||||
|
<a class="text-sky-400/90 hover:text-sky-300" href="/#contatti">Vai alla sezione Contatti</a> sulla homepage.
|
||||||
|
</p>
|
||||||
|
</SubpageLayout>
|
||||||
494
src/pages/codice-etico.astro
Normal file
@ -0,0 +1,494 @@
|
|||||||
|
---
|
||||||
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
|
|
||||||
|
const link = 'text-sky-400/90 underline-offset-2 hover:text-sky-300 hover:underline';
|
||||||
|
---
|
||||||
|
|
||||||
|
<SubpageLayout
|
||||||
|
title="Codice etico — NexStudio"
|
||||||
|
description="Codice etico definitivo NexStudio: valori, conformità legale e sanitaria, privacy, sicurezza, AI, governance e segnalazioni."
|
||||||
|
heading="Codice etico definitivo"
|
||||||
|
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à."
|
||||||
|
>
|
||||||
|
<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">
|
||||||
|
<span class="font-medium text-nx-fg">Versione</span> 1.0
|
||||||
|
<span class="text-nx-muted/80"> · </span>
|
||||||
|
<span class="font-medium text-nx-fg">Entrata in vigore</span>: 21 aprile 2026
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="indice" class="!mt-8">Indice sintetico</h2>
|
||||||
|
<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>
|
||||||
|
<li><a class={link} href="#valori">Valori e principi fondamentali</a></li>
|
||||||
|
<li>
|
||||||
|
<a class={link} href="#conformita">Conformità normativa specifica (ambito legale e medico)</a>
|
||||||
|
</li>
|
||||||
|
<li><a class={link} href="#tutela-dati">Tutela dei dati personali e dati sensibili</a></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>
|
||||||
|
|
||||||
|
<h2 id="premessa">1. Premessa</h2>
|
||||||
|
<p>
|
||||||
|
Questo Codice Etico stabilisce i principi, i doveri e le regole di comportamento che tutti i
|
||||||
|
soggetti coinvolti in <strong>NexStudio</strong> (di seguito «Società») devono osservare nello
|
||||||
|
sviluppo, nella commercializzazione e nella gestione di soluzioni SaaS destinate ai settori
|
||||||
|
legale e medico. Mira a garantire conformità normativa, tutela della privacy, sicurezza,
|
||||||
|
qualità tecnica, protezione dei diritti degli utenti e responsabilità professionale.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="ambito">2. Ambito di applicazione</h2>
|
||||||
|
<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>
|
||||||
|
<ul>
|
||||||
|
<li>Integrità, onestà e trasparenza.</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>
|
||||||
|
|
||||||
|
<h2 id="conformita">4. Conformità normativa specifica (ambito legale e medico)</h2>
|
||||||
|
<p>La Società osserva, fra gli altri:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Thailand Personal Data Protection Act (PDPA).</li>
|
||||||
|
<li>
|
||||||
|
Regolamento generale sulla protezione dei dati (GDPR), quando applicabile per utenti o
|
||||||
|
servizi nell’Unione europea.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Normative sanitarie e standard locali o internazionali pertinenti (ad esempio requisiti
|
||||||
|
nazionali per la gestione dei dati clinici, standard ISO rilevanti).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Requisiti normativi per dispositivi medici o software medicale (ad esempio EU MDR,
|
||||||
|
regolamenti locali) se le funzionalità rientrano nella definizione di dispositivo medico.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Norme professionali e di segreto professionale per avvocati e operatori sanitari;
|
||||||
|
rispetto di privilegi professionali (<em>attorney–client privilege</em>, segreto medico).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Se applicabile per mercati esteri: HIPAA per dati sanitari negli Stati Uniti e normative
|
||||||
|
sull’esportazione di dati o tecnologie.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
La Società effettua DPIA (Data Protection Impact Assessment) per trattamenti ad alto rischio e
|
||||||
|
consulta autorità o <em>regulatory counsel</em> quando necessario.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="tutela-dati">5. Tutela dei dati personali e dati sensibili</h2>
|
||||||
|
<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>
|
||||||
|
<li>
|
||||||
|
<strong>Validazione clinica:</strong> per funzionalità che assistono decisioni cliniche o
|
||||||
|
legali, procedere a validazione con esperti (clinici e legali), studi pilota, documentazione
|
||||||
|
di limitazioni e avvertenze.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Classificazione del rischio:</strong> valutare impatto su salute o diritti e
|
||||||
|
applicare misure di mitigazione proporzionate.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Non sostituzione del professionista:</strong> la piattaforma fornisce supporto e
|
||||||
|
strumenti informativi; non si presenta come sostitutiva di pareri professionali senza
|
||||||
|
esplicita supervisione e avallo professionale.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Registri ed evidenze:</strong> mantenere documentazione di test clinici, valutazioni
|
||||||
|
di rischio e approvazioni regolatorie.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="proprieta-intellettuale">8. Proprietà intellettuale e uso di componenti esterni</h2>
|
||||||
|
<ul>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<h2 id="ai-ml">9. Uso responsabile di AI/ML e automazione</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Documentare dataset di training, provenienza, pre-elaborazione e limiti noti.</li>
|
||||||
|
<li>
|
||||||
|
<strong>Bias assessment:</strong> condurre analisi per identificare e mitigare pregiudizi
|
||||||
|
che possano discriminare o causare danni.
|
||||||
|
</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
|
||||||
|
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>
|
||||||
|
|
||||||
|
<h2 id="conflitti">10. Conflitti di interesse e integrità commerciale</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Dichiarazione obbligatoria di conflitti reali o potenziali (investimenti, relazioni con
|
||||||
|
fornitori o clienti).
|
||||||
|
</li>
|
||||||
|
<li>Politiche anticorruzione e anti-tangenti conformi a leggi nazionali e internazionali.</li>
|
||||||
|
<li>
|
||||||
|
Comunicazioni commerciali veritiere; non ingannare su capacità, approvazioni regolatorie o
|
||||||
|
risultati.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Trasparenza contrattuale: SLA, limitazioni di responsabilità e condizioni d’uso chiare e
|
||||||
|
accessibili.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="linee-operative">11. Linee operative: sviluppo, rilascio e gestione vulnerabilità</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Sviluppo:</strong> strategia di branch, code review obbligatoria, merge solo con CI
|
||||||
|
positiva, checklist pre-release (sicurezza, privacy, compliance).
|
||||||
|
</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>
|
||||||
|
<li>
|
||||||
|
<strong>Deployment:</strong> CI/CD standardizzato con piano di rollback e rilasci graduali
|
||||||
|
per funzionalità critiche.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Gestione vulnerabilità:</strong> canale pubblico o privato per segnalazioni (bug
|
||||||
|
bounty o indirizzo dedicato <em>security@</em>), conferma di ricezione entro 72 ore, piano di
|
||||||
|
remediation con tempi stimati.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Patch management:</strong> tempi di rilascio delle patch definiti in base alla
|
||||||
|
severità.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Documentazione di rilascio:</strong> changelog, impatti noti e raccomandazioni
|
||||||
|
operative.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="governance">12. Governance, ruoli e responsabilità</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, normative mediche e legali rilevanti, 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>
|
||||||
|
Dichiaro di aver letto e compreso il Codice Etico di <strong>NexStudio</strong> e mi impegno a
|
||||||
|
rispettarne i principi e le procedure. Mi impegno inoltre a segnalare, in buona fede, eventuali
|
||||||
|
violazioni di cui dovessi venire a conoscenza.
|
||||||
|
</p>
|
||||||
|
<p class="mt-4 text-nx-fg">
|
||||||
|
Firma: <span class="inline-block min-w-[12rem] border-b border-nx-border"> </span>
|
||||||
|
<span class="mx-3 text-nx-muted">·</span>
|
||||||
|
Data: <span class="inline-block min-w-[6rem] border-b border-nx-border"> </span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="note-pratiche">Note pratiche e prossimi passi consigliati</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Personalizzare il documento con il nome ufficiale della società (se diverso dal marchio
|
||||||
|
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>
|
||||||
|
<li>
|
||||||
|
Eseguire DPIA per i trattamenti sanitari e legali critici e verificare se parti del prodotto
|
||||||
|
rientrano nella definizione di dispositivo medico ai fini di registrazione o approvazione
|
||||||
|
normativa.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Collegamenti</h2>
|
||||||
|
<p>
|
||||||
|
<a class={link} href="/modello-organizzativo">Modello organizzativo</a>
|
||||||
|
<span class="text-nx-muted"> · </span>
|
||||||
|
<a class={link} href="/about">Chi siamo</a>
|
||||||
|
<span class="text-nx-muted"> · </span>
|
||||||
|
<a class={link} href="#indice">Torna all’indice</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</SubpageLayout>
|
||||||
472
src/pages/cookies.astro
Normal file
@ -0,0 +1,472 @@
|
|||||||
|
---
|
||||||
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
|
import { cookieConsentMeta, cookieCategories } from '../data/cookie-consent';
|
||||||
|
|
||||||
|
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 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';
|
||||||
|
---
|
||||||
|
|
||||||
|
<SubpageLayout
|
||||||
|
title="Cookie Policy — NexStudio"
|
||||||
|
description="Cookie Policy NexStudio: categorie, base giuridica, consenso, terze parti, diritti e contatti. Sede Bangkok."
|
||||||
|
heading="Cookie Policy"
|
||||||
|
lead="Per NexStudio — sede: Bangkok, Thailandia. Versione documento 1.0, in vigore dal 21 aprile 2026. Integra l’uso del modulo di consenso first-party descritto sotto; per il trattamento dei dati personali vedi l’Informativa privacy."
|
||||||
|
>
|
||||||
|
<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">
|
||||||
|
<span class="font-medium text-nx-fg">Versione</span> 1.0
|
||||||
|
<span class="text-nx-muted/80"> · </span>
|
||||||
|
<span class="font-medium text-nx-fg">In vigore dal</span> 21 aprile 2026
|
||||||
|
<span class="text-nx-muted/80"> · </span>
|
||||||
|
<span class="font-medium text-nx-fg">Versione tecnica consenso</span>{' '}
|
||||||
|
<code class="rounded bg-nx-elevated px-1.5 py-0.5 font-mono text-xs text-sky-300/90"
|
||||||
|
>v{cookieConsentMeta.policyVersion}</code
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="indice" class="!mt-8">Indice</h2>
|
||||||
|
<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>
|
||||||
|
<li><a class={link} href="#finalita">Finalità dei cookie e categorie</a></li>
|
||||||
|
<li><a class={link} href="#elenco">Elenco dei cookie (modello da compilare)</a></li>
|
||||||
|
<li><a class={link} href="#sanitario-legale">Base giuridica e ambito sanitario/legale</a></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>
|
||||||
|
<li><a class={link} href="#appendice-tabella">Registro dei cookie (appendice)</a></li>
|
||||||
|
<li><a class={link} href="#implementazione">Implementazione tecnica e strumenti</a></li>
|
||||||
|
<li><a class={link} href="#test-qc">Test e controllo qualità</a></li>
|
||||||
|
<li><a class={link} href="#banner-testi">Esempio di testi per il banner (IT / EN)</a></li>
|
||||||
|
<li><a class={link} href="#note-legali">Note legali</a></li>
|
||||||
|
<li><a class={link} href="#versioning">Versioning e storico revisioni</a></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2 id="premessa">1. Premessa</h2>
|
||||||
|
<p>
|
||||||
|
Questa Cookie Policy descrive le tipologie di cookie e tecnologie simili utilizzate sul sito
|
||||||
|
web di <strong>NexStudio</strong> (di seguito «Società», «noi»), con sede a Bangkok,
|
||||||
|
Thailandia, 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>
|
||||||
|
|
||||||
|
<h2 id="definizioni">2. Definizioni</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Cookie:</strong> piccolo file di testo inviato dal sito al browser e memorizzato sul
|
||||||
|
dispositivo, re-inviato al sito alle visite successive.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Cookie di prima parte:</strong> impostati dal dominio del sito che l’utente visita.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Cookie di terze parti:</strong> impostati da domini diversi (fornitori esterni,
|
||||||
|
embed, CDN, strumenti di misura).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Tecnologie simili:</strong> web beacon, pixel, <em>local storage</em>, e in generale
|
||||||
|
identificatori lato client. Tecnologie invasive (es. fingerprinting) richiedono base legale
|
||||||
|
adeguata e massima trasparenza.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="finalita">3. Finalità dei cookie e categorie</h2>
|
||||||
|
<p>I cookie sul sito possono essere raggruppati come segue.</p>
|
||||||
|
|
||||||
|
<h3>3.1 Cookie strettamente necessari (essenziali)</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Finalità:</strong> navigazione, sicurezza, gestione sessione ove prevista,
|
||||||
|
memorizzazione delle preferenze di consenso.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Base giuridica:</strong> esecuzione di misure precontrattuali / necessità tecnica e,
|
||||||
|
ove applicabile, interesse legittimo limitato al funzionamento del servizio richiesto
|
||||||
|
dall’utente.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Esempi tipici:</strong> token di sessione, cookie anti-CSRF, chiave di consenso in
|
||||||
|
<em>local storage</em> (se usata al posto di cookie HTTP).
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>3.2 Cookie di preferenza / funzionali</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Finalità:</strong> lingua, regione, tema o altre impostazioni di interfaccia non
|
||||||
|
strettamente indispensabili.
|
||||||
|
</li>
|
||||||
|
<li><strong>Base giuridica:</strong> consenso, salvo trattamenti minimi e non invasivi.</li>
|
||||||
|
<li><strong>Esempi:</strong> <code class="font-mono text-xs text-sky-300/90">lang</code>, preferenze UI.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>3.3 Cookie di performance / analytics</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Finalità:</strong> statistiche aggregate su visite e percorsi per migliorare il
|
||||||
|
sito.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Base giuridica:</strong> consenso, salvo misurazione strettamente anonimizzata e
|
||||||
|
non identificativa secondo orientamenti applicabili.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Esempi:</strong> <code class="font-mono text-xs text-sky-300/90">_ga</code>, <code
|
||||||
|
class="font-mono text-xs text-sky-300/90">_gid</code
|
||||||
|
> (Google Analytics), strumenti tipo Hotjar, se integrati.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>3.4 Cookie per advertising e targeting</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Finalità:</strong> pubblicità mirata, remarketing, misura delle campagne.
|
||||||
|
</li>
|
||||||
|
<li><strong>Base giuridica:</strong> consenso esplicito.</li>
|
||||||
|
<li>
|
||||||
|
<strong>Esempi:</strong> identificatori di reti pubblicitarie, pixel di retargeting, cookie
|
||||||
|
DSP.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>3.5 Cookie di terze parti e integrazioni social</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Finalità:</strong> video incorporati, pulsanti social, mappe, chat di terze parti.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Nota:</strong> il trattamento da parte di terzi è regolato dalle rispettive policy;
|
||||||
|
NexStudio non controlla cookie impostati senza adeguata configurazione e consenso.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="elenco">4. Elenco dei cookie utilizzati (modello da compilare)</h2>
|
||||||
|
<p>
|
||||||
|
Per ogni cookie reale indicare: nome, fornitore, categoria, finalità, durata, prima o terza
|
||||||
|
parte, base giuridica, link alla privacy del fornitore e, se utile, opt-out.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
La tabella sotto è <strong>esemplificativa</strong>: va sostituita con l’inventario aggiornato
|
||||||
|
(ideale: hash o versione e data di aggiornamento in calce alla tabella pubblicata).
|
||||||
|
</p>
|
||||||
|
<table class={tbl}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class={th}>Nome</th>
|
||||||
|
<th class={th}>Provider</th>
|
||||||
|
<th class={th}>Categoria</th>
|
||||||
|
<th class={th}>Durata</th>
|
||||||
|
<th class={th}>Tipo</th>
|
||||||
|
<th class={th}>Base giuridica</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class={td}><code class="font-mono text-xs">_ga</code> (es.)</td>
|
||||||
|
<td class={td}>Google LLC</td>
|
||||||
|
<td class={td}>Analytics</td>
|
||||||
|
<td class={td}>24 mesi (tipico)</td>
|
||||||
|
<td class={td}>3rd</td>
|
||||||
|
<td class={td}>Consenso</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class={td}>
|
||||||
|
<code class="font-mono text-xs">{cookieConsentMeta.storageKey}</code> (non cookie HTTP)
|
||||||
|
</td>
|
||||||
|
<td class={td}>NexStudio</td>
|
||||||
|
<td class={td}>Necessario</td>
|
||||||
|
<td class={td}>Fino a revoca / aggiornamento policy</td>
|
||||||
|
<td class={td}>1st (local)</td>
|
||||||
|
<td class={td}>Necessario / consenso informato</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class={td}><code class="font-mono text-xs">_fbp</code> (es.)</td>
|
||||||
|
<td class={td}>Meta</td>
|
||||||
|
<td class={td}>Advertising</td>
|
||||||
|
<td class={td}>90 giorni (tipico)</td>
|
||||||
|
<td class={td}>3rd</td>
|
||||||
|
<td class={td}>Consenso</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2 id="sanitario-legale">5. Base giuridica e trattamento in ambito sanitario o legale</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Per cookie che comportano trattamento di dati particolari (salute, dati giudiziari o
|
||||||
|
assimilabili) è richiesto <strong>consenso esplicito</strong> informato e misure tecniche e
|
||||||
|
organizzative aggiuntive, salvo diversa base prevista dalla legge applicabile.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Nei servizi SaaS medico-legali la raccolta tramite cookie sul sito vetrina è da
|
||||||
|
<strong>limitare e segregare</strong>; per test e analytics privilegiare dati sintetici o
|
||||||
|
anonimizzazione efficace.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="consenso">6. Modalità di raccolta del consenso e registro</h2>
|
||||||
|
<p>
|
||||||
|
Il sito utilizza una <strong>CMP first-party</strong> sviluppata per NexStudio, orientata ai
|
||||||
|
requisiti di trasparenza e granularità (inclusi riferimenti GDPR e PDPA tailandese come quadro
|
||||||
|
di riferimento per le descrizioni). Al primo accesso, o dopo aumento della versione della
|
||||||
|
policy tecnica, 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> alla chiave{' '}
|
||||||
|
<code class="rounded bg-nx-elevated px-1.5 py-0.5 font-mono text-xs text-sky-300/90"
|
||||||
|
>{cookieConsentMeta.storageKey}</code
|
||||||
|
>, con versione <code class="rounded bg-nx-elevated px-1.5 py-0.5 font-mono text-xs text-sky-300/90"
|
||||||
|
>v{cookieConsentMeta.policyVersion}</code
|
||||||
|
>, timestamp di aggiornamento e stato per categoria. I cookie strettamente necessari possono
|
||||||
|
restare attivi compatibilmente con informativa chiara. Per audit avanzati si può integrare in
|
||||||
|
futuro un log lato server (IP, user-agent, ID sessione) con informativa dedicata.
|
||||||
|
</p>
|
||||||
|
<p class="font-medium text-nx-fg">Categorie gestite dal modulo attuale</p>
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
cookieCategories.map((c) => (
|
||||||
|
<li>
|
||||||
|
<strong class="text-nx-fg">{c.label}</strong>
|
||||||
|
{c.required ? ' — sempre attiva.' : ' — attiva solo se accettata.'}{' '}
|
||||||
|
{c.description}
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Il codice espone <code class="rounded bg-nx-elevated px-1.5 py-0.5 font-mono text-xs text-sky-300/90"
|
||||||
|
>window.NexStudioCookieConsent</code
|
||||||
|
> e l’evento <code class="rounded bg-nx-elevated px-1.5 py-0.5 font-mono text-xs text-sky-300/90"
|
||||||
|
>nexstudio:cookie-consent</code
|
||||||
|
> per caricare script non necessari solo dopo consenso.
|
||||||
|
</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 (sotto
|
||||||
|
le colonne di navigazione).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Cancellando i dati locali del sito dalle impostazioni del browser (inclusa la voce di{' '}
|
||||||
|
<em>local storage</em> citata sopra): al successivo caricamento il banner si ripresenta.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Dopo la revoca non devono essere impostati cookie non essenziali; alcune funzioni (es.
|
||||||
|
preferenze memorizzate, statistiche) possono risultare limitate.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<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). Disabilitando i cookie possono smettere di funzionare login, preferenze
|
||||||
|
o strumenti che ne dipendono.
|
||||||
|
</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, gateway di pagamento,
|
||||||
|
video). Le terze parti possono impostare cookie propri e trattare dati secondo le loro policy.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Esempi di fornitori</strong> spesso usati in contesti analoghi (elenco da adattare al
|
||||||
|
sito reale): Google Analytics, Google Tag Manager, Cloudflare, Stripe, Vimeo, YouTube, Meta,
|
||||||
|
LinkedIn, Hotjar. Per ciascuno vanno indicati link a privacy/cookie policy e strumenti di
|
||||||
|
opt-out ove disponibili.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="retention">10. Conservazione e criteri di retention</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Le durate dei singoli cookie seguono le tabelle pubblicate e le policy dei fornitori (es.
|
||||||
|
finestra indicativa <strong>24 mesi</strong> per dati aggregati di analytics, salvo obblighi
|
||||||
|
di legge o contratto diversi — da confermare con il legale).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Prove di consenso e gestione DSR: periodi di conservazione da definire nella retention
|
||||||
|
policy (orientamento comune per documentazione di conformità: <strong
|
||||||
|
>3–7 anni</strong
|
||||||
|
>, da calibrare su base legale e rischio — non vincolante in questa pagina).
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<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. Per dettagli su trasferimenti specifici contattare il referente privacy
|
||||||
|
indicato sotto.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="minori">12. Minori età</h2>
|
||||||
|
<p>
|
||||||
|
Il sito non è rivolto alla raccolta intenzionale di dati di minori. Per utenti sotto la soglia
|
||||||
|
prevista dalla legge locale, i trattamenti non essenziali richiedono consenso del titore
|
||||||
|
della responsabilità genitoriale ove imposto dalla normativa applicabile.
|
||||||
|
</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, monitoraggio anomalie ove in uso.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="advertising">14. Consenso per scopi pubblicitari e profiling</h2>
|
||||||
|
<p>
|
||||||
|
Profilazione e pubblicità comportano <strong>consenso esplicito</strong> e tracciabilità
|
||||||
|
della scelta. Ove si usino reti pubblicitarie, fornire link agli strumenti di opt-out del
|
||||||
|
fornitore (es. impostazioni annunci Google).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="effetti">15. Effetti di rifiuto o accettazione</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Accettando</strong> le categorie opzionali si abilitano preferenze avanzate,
|
||||||
|
analytics e, se configurate, funzioni di marketing.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Rifiutando</strong> i non essenziali il sito resta navigabile con funzioni core; le
|
||||||
|
altre possono essere limitate.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<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; ove richiesto dalla legge o dall’impatto del cambiamento, si richiederà un
|
||||||
|
nuovo consenso (incrementando la versione tecnica nel modulo).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="diritti">17. Diritti degli interessati e contatti</h2>
|
||||||
|
<p>
|
||||||
|
Diritti: accesso, rettifica, cancellazione, limitazione, portabilità, opposizione e revoca del
|
||||||
|
consenso ove previsti. Per esercitarli: <a class={link} href="mailto:privacy@nexstudio.com"
|
||||||
|
>privacy@nexstudio.com</a
|
||||||
|
> (DPO o referente privacy, da formalizzare).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="appendice-tabella">18. Registro dei cookie e informazioni tecniche (appendice)</h2>
|
||||||
|
<p>
|
||||||
|
Pubblicare qui la tabella completa con colonne: nome, provider, categoria, descrizione
|
||||||
|
tecnica, durata, 1st/3rd, link opt-out, link privacy, note (GTM, embed, CDN, ecc.).
|
||||||
|
</p>
|
||||||
|
<table class={tbl}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class={th}>Nome</th>
|
||||||
|
<th class={th}>Provider</th>
|
||||||
|
<th class={th}>Categoria</th>
|
||||||
|
<th class={th}>Scopo / durata</th>
|
||||||
|
<th class={th}>Note</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class={td} colspan="5">
|
||||||
|
<em>Da compilare dopo scansione del sito in staging e produzione.</em>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2 id="implementazione">19. Implementazione tecnica e strumenti</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>CMP:</strong> modulo first-party NexStudio (nessun OneTrust/Cookiebot obbligatorio
|
||||||
|
in questa build); estensibile verso CMP commerciali se richiesto da clienti o policy
|
||||||
|
aziendale.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Log consenso:</strong> attualmente payload JSON in <em>local storage</em> con
|
||||||
|
versione policy, timestamp ISO e booleani per categoria; valutare integrazione server-side
|
||||||
|
per audit e correlazione sessione.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Banner:</strong> testo breve, link a questa Cookie Policy, tre azioni come da §6.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Sviluppo:</strong> caricare script di analytics, marketing o embed solo dopo lettura
|
||||||
|
del consenso tramite API/evento sopra citati.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="test-qc">20. Test e controllo qualità</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Scansione periodica del sito per elenco cookie (strumenti automatici + revisione manuale).</li>
|
||||||
|
<li>Allineamento tra tabella pubblicata, CMP e script effettivamente caricati.</li>
|
||||||
|
<li>Audit trimestrale delle terze parti e aggiornamento categorie/testi.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="banner-testi">21. Esempio di testi per il banner (IT / EN)</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Italiano (allineato al sito):</strong> «Utilizziamo cookie e memorizzazione locale.
|
||||||
|
Seleziona le categorie che preferisci. I necessari restano attivi per registrare la tua
|
||||||
|
scelta. Dettagli nell’informativa cookie.» — pulsanti: <strong>Rifiuta non essenziali</strong
|
||||||
|
>, <strong>Personalizza</strong>, <strong>Accetta tutto</strong>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>English (per versione bilingue futura):</strong> «We use cookies and local storage.
|
||||||
|
Choose the categories you prefer. Necessary ones stay on to record your choice. See our
|
||||||
|
cookie policy.» — <strong>Reject non-essential</strong>, <strong>Customize</strong>, <strong
|
||||||
|
>Accept all</strong
|
||||||
|
>.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="note-legali">22. Note legali</h2>
|
||||||
|
<p>
|
||||||
|
Questa Cookie Policy <strong>integra ma non sostituisce</strong> l’Informativa privacy. Per
|
||||||
|
aspetti di trattamento dei dati personali collegati ai cookie fare riferimento anche a{' '}
|
||||||
|
<a class={link} href="/gdpr">GDPR</a> / documenti privacy pubblicati e al counsel locale per
|
||||||
|
Thailandia, UE o altri mercati serviti.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="versioning">23. Versioning e storico revisioni</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Versione 1.0</strong> — 21 aprile 2026 — Prima pubblicazione strutturata.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Appendice: checklist tecnica per il rilascio</h2>
|
||||||
|
<ul class="list-none space-y-2 pl-0 [&_li]:flex [&_li]:gap-2">
|
||||||
|
<li><span class="text-nx-muted">☐</span> Scansione cookie completa e tabella aggiornata</li>
|
||||||
|
<li><span class="text-nx-muted">☐</span> CMP e prova consenso / revoca funzionanti</li>
|
||||||
|
<li><span class="text-nx-muted">☐</span> Traduzioni banner (es. EN / TH) se richieste dal mercato</li>
|
||||||
|
<li><span class="text-nx-muted">☐</span> Script non essenziali bloccati fino a consenso</li>
|
||||||
|
<li><span class="text-nx-muted">☐</span> Tabella cookie con metadati e versioning pubblico</li>
|
||||||
|
<li><span class="text-nx-muted">☐</span> Procedure DSR e revoca collegate dall’informativa privacy</li>
|
||||||
|
<li><span class="text-nx-muted">☐</span> Link a privacy e contatti DPO/referente nel footer</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Collegamenti</h2>
|
||||||
|
<p>
|
||||||
|
<a class={link} href="/privacy">Informativa privacy</a>
|
||||||
|
<span class="text-nx-muted"> · </span>
|
||||||
|
<a class={link} href="/codice-etico">Codice etico</a>
|
||||||
|
<span class="text-nx-muted"> · </span>
|
||||||
|
<a class={link} href="/modello-organizzativo">Modello organizzativo</a>
|
||||||
|
<span class="text-nx-muted"> · </span>
|
||||||
|
<a class={link} href="#indice">Torna all’indice</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</SubpageLayout>
|
||||||
34
src/pages/dove-siamo.astro
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<SubpageLayout
|
||||||
|
title="Dove siamo — NexStudio"
|
||||||
|
description="Sede operativa e contatti NexStudio."
|
||||||
|
heading="Dove siamo"
|
||||||
|
lead="Sede operativa a Bangkok (Thailandia). Completate l’indirizzo, i recapiti e gli orari di riferimento per il pubblico."
|
||||||
|
>
|
||||||
|
<h2>Sede</h2>
|
||||||
|
<p>
|
||||||
|
NexStudio opera con base operativa a <strong>Bangkok</strong>, in
|
||||||
|
Thailandia. Inserite qui indirizzo completo, eventuale sede legale in altro
|
||||||
|
Stato, e se il rapporto con i clienti avviene prevalentemente da remoto.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Fusi orari e lingue</h2>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Contatti</h2>
|
||||||
|
<p>
|
||||||
|
Per richieste commerciali o tecniche utilizzate la sezione <a
|
||||||
|
class="text-sky-400/90 hover:text-sky-300"
|
||||||
|
href="/#contatti">Contatti</a
|
||||||
|
> sulla homepage (chat e altri canali che attiverete). Aggiungete in questa
|
||||||
|
pagina email istituzionali, PEC se applicabile, e numeri di telefono solo se
|
||||||
|
desiderate renderli pubblici.
|
||||||
|
</p>
|
||||||
|
</SubpageLayout>
|
||||||
55
src/pages/gdpr.astro
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<SubpageLayout
|
||||||
|
title="GDPR — NexStudio"
|
||||||
|
description="Informazioni sul trattamento dei dati personali ai sensi del Regolamento UE 2016/679 (GDPR)."
|
||||||
|
heading="GDPR e diritti degli interessati"
|
||||||
|
lead="Documento provvisorio: integrare riferimenti contrattuali, responsabile della protezione dei dati (DPO) se nominato, e procedure interne."
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Il Regolamento (UE) 2016/679 (GDPR) disciplina il trattamento dei dati
|
||||||
|
personali nel territorio dell’Unione. La presente pagina riassume i diritti
|
||||||
|
riconosciuti agli interessati e come possono esercitarli nei confronti di
|
||||||
|
NexStudio, senza sostituire l’informativa privacy completa.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Diritti riconosciuti</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Accesso ai propri dati e copia in formato intelligibile</li>
|
||||||
|
<li>Rettifica di dati inesatti o incompleti</li>
|
||||||
|
<li>Cancellazione (“diritto all’oblio”) nei casi previsti dalla legge</li>
|
||||||
|
<li>Limitazione del trattamento</li>
|
||||||
|
<li>Portabilità dei dati forniti con consenso o contratto, ove applicabile</li>
|
||||||
|
<li>Opposizione al trattamento basato su legittimo interesse o per fini di marketing</li>
|
||||||
|
<li>Revoca del consenso in qualsiasi momento, senza pregiudicare la liceità del trattamento precedente</li>
|
||||||
|
<li>Reclamo all’autorità di controllo competente</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Modalità di esercizio</h2>
|
||||||
|
<p>
|
||||||
|
Indicare un canale dedicato (es. email privacy@…, modulo PEC o ticket). Per
|
||||||
|
richieste generiche potete anche utilizzare la sezione <a
|
||||||
|
class="text-sky-400/90 hover:text-sky-300"
|
||||||
|
href="/#contatti">Contatti</a
|
||||||
|
> sul sito principale, specificando oggetto e dati utili a identificare la
|
||||||
|
pratica.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Trasferimenti extra-UE</h2>
|
||||||
|
<p>
|
||||||
|
Se utilizzate fornitori con sede fuori dallo Spazio Economico Europeo,
|
||||||
|
descrivete garanzie (Clausole contrattuali tipo, decisioni di adeguatezza,
|
||||||
|
norme vincolanti d’impresa) e il riferimento documentale.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Documenti correlati</h2>
|
||||||
|
<p>
|
||||||
|
<a class="text-sky-400/90 hover:text-sky-300" href="/privacy"
|
||||||
|
>Informativa privacy</a
|
||||||
|
>
|
||||||
|
·
|
||||||
|
<a class="text-sky-400/90 hover:text-sky-300" href="/cookies">Informativa cookie</a>
|
||||||
|
</p>
|
||||||
|
</SubpageLayout>
|
||||||
618
src/pages/index.astro
Normal file
@ -0,0 +1,618 @@
|
|||||||
|
---
|
||||||
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
|
import HeroDataflow from '../components/HeroDataflow.astro';
|
||||||
|
import DataflowSpine from '../components/DataflowSpine.astro';
|
||||||
|
import ContactFormInline from '../components/ContactFormInline.astro';
|
||||||
|
import CardBottomRightTexture from '../components/CardBottomRightTexture.astro';
|
||||||
|
import SiteFooter from '../components/SiteFooter.astro';
|
||||||
|
import {
|
||||||
|
siteMeta,
|
||||||
|
navigation,
|
||||||
|
hero,
|
||||||
|
stats,
|
||||||
|
services,
|
||||||
|
stackSection,
|
||||||
|
productsSection,
|
||||||
|
faq,
|
||||||
|
cta,
|
||||||
|
} from '../data/home';
|
||||||
|
|
||||||
|
const techSliderItems = stackSection.techSlider.items;
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title={siteMeta.title} description={siteMeta.description}>
|
||||||
|
<div class="nx-grid-bg relative min-h-screen">
|
||||||
|
<canvas
|
||||||
|
id="particleCanvas"
|
||||||
|
class="nx-particle-layer pointer-events-none"
|
||||||
|
aria-hidden="true"></canvas>
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute inset-0 z-[1] bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(56,189,248,0.18),transparent)]"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<header
|
||||||
|
class="sticky top-0 z-50 border-b border-nx-border/40 bg-black"
|
||||||
|
x-data="{ mobile: false }"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-3 sm:px-6"
|
||||||
|
>
|
||||||
|
<a href="/" class="group no-underline">
|
||||||
|
<span
|
||||||
|
class="text-2xl font-semibold tracking-tight text-nx-fg group-hover:text-white sm:text-3xl"
|
||||||
|
>{navigation.brandName}</span
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<nav
|
||||||
|
class="hidden items-center gap-8 text-sm md:flex"
|
||||||
|
aria-label="Principale"
|
||||||
|
>
|
||||||
|
{navigation.items.map((item) => {
|
||||||
|
const sectionId = item.href.includes('#')
|
||||||
|
? item.href.split('#').pop()!
|
||||||
|
: '';
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
class="nx-nav-link"
|
||||||
|
href={item.href}
|
||||||
|
data-section={sectionId}
|
||||||
|
data-nav-active="false"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2 sm:gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-nx-chat-open
|
||||||
|
class="inline-flex cursor-pointer rounded-lg bg-gradient-to-r from-sky-500 to-violet-500 px-4 py-2 text-sm font-semibold text-white shadow-lg shadow-sky-500/25 hover:brightness-110"
|
||||||
|
title={navigation.cta.linkTitle}
|
||||||
|
aria-label={navigation.cta.ariaLabel}
|
||||||
|
>{navigation.cta.label}</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="inline-flex h-10 w-10 items-center justify-center rounded-lg border border-nx-border text-nx-fg md:hidden"
|
||||||
|
@click="mobile = !mobile"
|
||||||
|
:aria-expanded="mobile"
|
||||||
|
aria-controls="mobile-nav"
|
||||||
|
aria-label="Apri menu"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
x-show="!mobile"
|
||||||
|
class="h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
x-cloak
|
||||||
|
>
|
||||||
|
<path d="M4 6h16M4 12h16M4 18h16" stroke-linecap="round"></path>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
x-show="mobile"
|
||||||
|
class="h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
x-cloak
|
||||||
|
>
|
||||||
|
<path d="M6 18L18 6M6 6l12 12" stroke-linecap="round"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="mobile-nav"
|
||||||
|
x-show="mobile"
|
||||||
|
x-transition
|
||||||
|
x-cloak
|
||||||
|
class="border-b border-nx-border bg-black px-4 py-4 md:hidden"
|
||||||
|
>
|
||||||
|
<nav class="flex flex-col gap-3 text-sm" aria-label="Mobile">
|
||||||
|
{navigation.items.map((item) => {
|
||||||
|
const sectionId = item.href.includes('#')
|
||||||
|
? item.href.split('#').pop()!
|
||||||
|
: '';
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
class="nx-nav-link block py-1"
|
||||||
|
href={item.href}
|
||||||
|
data-section={sectionId}
|
||||||
|
data-nav-active="false"
|
||||||
|
@click="mobile = false"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="relative z-[1] isolate">
|
||||||
|
<DataflowSpine />
|
||||||
|
<section
|
||||||
|
id="intro"
|
||||||
|
class="relative z-10 overflow-hidden px-4 pb-20 pt-16 sm:px-6 sm:pt-24"
|
||||||
|
>
|
||||||
|
<div class="nx-hero-ambient" aria-hidden="true">
|
||||||
|
<div class="nx-hero-ambient__inner">
|
||||||
|
<HeroDataflow />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="relative z-10 mx-auto max-w-6xl">
|
||||||
|
<p
|
||||||
|
class="mb-6 inline-flex items-center gap-2 rounded-full border border-nx-border bg-nx-surface/60 px-3 py-1 text-xs font-medium text-sky-300/90"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="h-1.5 w-1.5 rounded-full bg-sky-400 shadow-[0_0_8px_#38bdf8]"></span>
|
||||||
|
{hero.badge}
|
||||||
|
</p>
|
||||||
|
<h1
|
||||||
|
class="max-w-4xl text-4xl font-semibold leading-[1.1] tracking-tight text-nx-fg sm:text-5xl md:text-6xl"
|
||||||
|
>
|
||||||
|
{hero.titleBefore}
|
||||||
|
<span
|
||||||
|
class="bg-gradient-to-r from-sky-300 via-cyan-200 to-violet-400 bg-clip-text text-transparent"
|
||||||
|
>{hero.titleHighlight}</span
|
||||||
|
>{hero.titleAfter}
|
||||||
|
</h1>
|
||||||
|
<p
|
||||||
|
class="mt-5 max-w-3xl text-lg font-medium leading-relaxed text-sky-100/90 sm:text-xl"
|
||||||
|
>
|
||||||
|
{hero.subtitle}
|
||||||
|
</p>
|
||||||
|
<div class="mt-8 max-w-3xl space-y-4 text-base leading-relaxed text-nx-muted sm:text-lg">
|
||||||
|
{hero.paragraphs.map((p) => <p>{p}</p>)}
|
||||||
|
</div>
|
||||||
|
<div class="mt-10 max-w-3xl">
|
||||||
|
<h2 class="text-base font-semibold text-nx-fg sm:text-lg">
|
||||||
|
{hero.businessUnitsHeading}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-2 text-sm leading-relaxed text-nx-muted sm:text-base">
|
||||||
|
{hero.businessUnitsIntro}
|
||||||
|
</p>
|
||||||
|
<ul class="mt-5 space-y-4 border-l border-nx-border pl-5">
|
||||||
|
{hero.businessUnits.map((u) => (
|
||||||
|
<li>
|
||||||
|
<span class="font-semibold text-sky-300/95">{u.name}</span>
|
||||||
|
<span class="text-nx-muted"> — {u.description}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="relative z-10 px-4 py-6 sm:px-6 sm:py-8">
|
||||||
|
<div
|
||||||
|
class="nx-stats-card mx-auto max-w-6xl rounded-2xl border border-nx-border bg-gradient-to-b from-nx-elevated/78 to-nx-surface/68 px-6 py-10 ring-1 ring-inset ring-white/[0.06] sm:px-10 sm:py-12"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="grid grid-cols-1 gap-8 sm:grid-cols-3 sm:gap-8 lg:gap-10"
|
||||||
|
>
|
||||||
|
{stats.items.map((item) => (
|
||||||
|
<div>
|
||||||
|
<p
|
||||||
|
class="text-3xl font-semibold tracking-tight text-nx-fg sm:text-4xl"
|
||||||
|
>
|
||||||
|
{item.value}
|
||||||
|
</p>
|
||||||
|
<p class="mt-1 text-sm leading-relaxed text-nx-muted">
|
||||||
|
{item.caption}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="prodotti" class="relative z-10 px-4 py-20 sm:px-6">
|
||||||
|
<div class="mx-auto max-w-6xl">
|
||||||
|
<h2
|
||||||
|
class="text-sm font-semibold uppercase tracking-wider text-sky-400/90"
|
||||||
|
>
|
||||||
|
{productsSection.eyebrow}
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
class="mt-2 text-3xl font-semibold tracking-tight text-nx-fg sm:text-4xl"
|
||||||
|
>
|
||||||
|
{productsSection.title}
|
||||||
|
</p>
|
||||||
|
<p class="mt-4 max-w-3xl text-base leading-relaxed text-nx-muted sm:text-lg">
|
||||||
|
{productsSection.lead}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="mt-14 grid gap-8 lg:grid-cols-2 lg:gap-10 lg:items-stretch"
|
||||||
|
>
|
||||||
|
{productsSection.items.map((product, pi) => (
|
||||||
|
<article
|
||||||
|
class:list={[
|
||||||
|
'flex flex-col rounded-2xl border p-6 sm:p-8',
|
||||||
|
pi === 0
|
||||||
|
? 'border-sky-500/25 bg-gradient-to-b from-sky-950/30 to-nx-surface/30'
|
||||||
|
: 'border-emerald-500/20 bg-gradient-to-b from-emerald-950/25 to-nx-surface/30',
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div class="flex flex-wrap items-baseline justify-between gap-2">
|
||||||
|
<h3 class="text-2xl font-semibold tracking-tight text-nx-fg">
|
||||||
|
{product.name}
|
||||||
|
</h3>
|
||||||
|
<span
|
||||||
|
class:list={[
|
||||||
|
'rounded-full px-2.5 py-0.5 text-xs font-medium',
|
||||||
|
pi === 0
|
||||||
|
? 'bg-sky-500/15 text-sky-200/95'
|
||||||
|
: 'bg-emerald-500/15 text-emerald-200/95',
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{product.badge}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-3 text-lg font-medium leading-snug text-nx-fg sm:text-xl">
|
||||||
|
{product.tagline}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
class:list={[
|
||||||
|
'mt-2 text-sm font-medium',
|
||||||
|
pi === 0 ? 'text-sky-200/85' : 'text-emerald-200/85',
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<span class="text-nx-muted font-normal">Focus — </span>
|
||||||
|
{product.focus}
|
||||||
|
</p>
|
||||||
|
<p class="mt-5 text-sm leading-relaxed text-nx-muted sm:text-base">
|
||||||
|
{product.body}
|
||||||
|
</p>
|
||||||
|
<ul class="mt-6 space-y-4 border-t border-white/10 pt-6">
|
||||||
|
{product.highlights.map((h) => (
|
||||||
|
<li>
|
||||||
|
<p class="text-sm leading-relaxed sm:text-[15px]">
|
||||||
|
<span
|
||||||
|
class:list={[
|
||||||
|
'font-semibold',
|
||||||
|
pi === 0 ? 'text-sky-200/95' : 'text-emerald-200/95',
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{h.title}
|
||||||
|
</span>
|
||||||
|
<span class="text-nx-muted">
|
||||||
|
<span class="text-nx-muted/60"> — </span>
|
||||||
|
{h.text}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{'closing' in product && product.closing ? (
|
||||||
|
<p class="mt-6 border-t border-white/10 pt-6 text-sm italic leading-relaxed text-nx-muted">
|
||||||
|
{product.closing}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="servizi" class="relative z-10 px-4 py-20 sm:px-6">
|
||||||
|
<div class="mx-auto max-w-6xl">
|
||||||
|
<h2
|
||||||
|
class="text-sm font-semibold uppercase tracking-wider text-sky-400/90"
|
||||||
|
>
|
||||||
|
{services.eyebrow}
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
class="mt-2 max-w-2xl text-3xl font-semibold tracking-tight text-nx-fg sm:text-4xl"
|
||||||
|
>
|
||||||
|
{services.title}
|
||||||
|
</p>
|
||||||
|
<p class="mt-5 max-w-3xl text-base leading-relaxed text-nx-muted sm:text-lg">
|
||||||
|
{services.lead}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="mt-12 grid gap-4 md:grid-cols-3 md:grid-rows-2 md:gap-5 [&>article]:rounded-2xl [&>article]:border [&>article]:border-nx-border [&>article]:bg-nx-elevated/50 [&>article]:p-6"
|
||||||
|
>
|
||||||
|
{services.cards.map((card) => {
|
||||||
|
const hasGlobe =
|
||||||
|
'decoration' in card &&
|
||||||
|
card.decoration === 'wireframe-globe';
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
class:list={[
|
||||||
|
card.layout === 'wide' && 'md:col-span-2',
|
||||||
|
card.layout === 'tall' && 'md:row-span-2',
|
||||||
|
hasGlobe && 'relative overflow-hidden',
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{hasGlobe && <CardBottomRightTexture />}
|
||||||
|
<div class:list={[hasGlobe && 'relative z-10']}>
|
||||||
|
<h3 class="text-lg font-semibold text-nx-fg">{card.title}</h3>
|
||||||
|
<p class="mt-2 text-sm leading-relaxed text-nx-muted">
|
||||||
|
{card.body}
|
||||||
|
</p>
|
||||||
|
{'callout' in card && card.callout && (
|
||||||
|
<div class="mt-4 rounded-xl border border-sky-500/25 bg-sky-950/20 p-4">
|
||||||
|
<p class="text-sm font-semibold text-sky-200/95">
|
||||||
|
{card.callout.title}
|
||||||
|
</p>
|
||||||
|
<p class="mt-2 text-sm leading-relaxed text-nx-muted">
|
||||||
|
{card.callout.body}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{'bullets' in card && card.bullets && (
|
||||||
|
<ul class="mt-4 space-y-2 text-sm text-sky-300/90">
|
||||||
|
{card.bullets.map((b) => (
|
||||||
|
<li class="flex gap-2">
|
||||||
|
<span class="text-violet-400">▹</span>
|
||||||
|
{b}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
id="stack"
|
||||||
|
class="relative z-10 overflow-hidden border-t border-nx-border bg-nx-surface/30 px-4 py-20 sm:px-6"
|
||||||
|
>
|
||||||
|
<div class="mx-auto max-w-6xl">
|
||||||
|
<h2
|
||||||
|
class="text-sm font-semibold uppercase tracking-wider text-sky-400/90"
|
||||||
|
>
|
||||||
|
{stackSection.eyebrow}
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
class="mt-2 max-w-3xl text-3xl font-semibold tracking-tight text-nx-fg sm:text-4xl"
|
||||||
|
>
|
||||||
|
{stackSection.title}
|
||||||
|
</p>
|
||||||
|
<p class="mt-5 max-w-3xl text-base leading-relaxed text-nx-muted sm:text-lg">
|
||||||
|
{stackSection.lead}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="relative z-[1] mt-12 flex flex-col gap-8 rounded-2xl border border-orange-500/20 bg-gradient-to-br from-orange-950/20 to-nx-elevated/40 p-6 sm:flex-row sm:items-center sm:gap-10 sm:p-8"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={stackSection.cloudflareBadge.href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="group flex shrink-0 items-center gap-4 rounded-xl border border-white/10 bg-nx-bg/60 px-5 py-4 transition hover:border-orange-400/35"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={stackSection.cloudflareBadge.iconSrc}
|
||||||
|
alt={stackSection.cloudflareBadge.iconAlt}
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
class="h-12 w-12 shrink-0 brightness-0 invert"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<p class="text-xs font-medium uppercase tracking-wider text-orange-200/90">
|
||||||
|
Infrastruttura
|
||||||
|
</p>
|
||||||
|
<p class="text-lg font-semibold text-nx-fg group-hover:text-orange-100">
|
||||||
|
{stackSection.cloudflareBadge.label}
|
||||||
|
</p>
|
||||||
|
<p class="mt-0.5 text-xs text-nx-muted">
|
||||||
|
Rete globale, sicurezza e edge
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<h3 class="text-lg font-semibold text-nx-fg">
|
||||||
|
{stackSection.cloudflare.title}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-2 text-sm leading-relaxed text-nx-muted sm:text-base">
|
||||||
|
{stackSection.cloudflare.intro}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="relative z-[1] mt-6 space-y-6">
|
||||||
|
{stackSection.cloudflare.points.map((item) => (
|
||||||
|
<li class="border-l-2 border-orange-400/40 pl-5">
|
||||||
|
<p class="font-semibold text-orange-100/95">{item.title}</p>
|
||||||
|
<p class="mt-1.5 text-sm leading-relaxed text-nx-muted sm:text-[15px]">
|
||||||
|
{item.body}
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="mt-10 rounded-2xl border border-nx-border bg-nx-elevated/50 p-6 sm:p-8"
|
||||||
|
>
|
||||||
|
<h3 class="text-lg font-semibold text-nx-fg">
|
||||||
|
{stackSection.continuity.title}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-3 text-sm leading-relaxed text-nx-muted sm:text-base">
|
||||||
|
{stackSection.continuity.body}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="nx-tech-slider mt-10"
|
||||||
|
role="region"
|
||||||
|
aria-label={stackSection.techSlider.ariaLabel}
|
||||||
|
>
|
||||||
|
<div class="nx-tech-slider__viewport">
|
||||||
|
<div class="nx-tech-slider__track">
|
||||||
|
{techSliderItems.map((item) => (
|
||||||
|
<a
|
||||||
|
class="nx-tech-slider__link"
|
||||||
|
href={item.href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label={item.name}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class:list={[
|
||||||
|
'nx-tech-slider__logo',
|
||||||
|
item.name === 'HL7 FHIR' && 'nx-tech-slider__logo--fhir',
|
||||||
|
]}
|
||||||
|
src={item.iconSrc}
|
||||||
|
alt=""
|
||||||
|
width="168"
|
||||||
|
height="48"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
{techSliderItems.map((item) => (
|
||||||
|
<a
|
||||||
|
class:list={['nx-tech-slider__link', 'nx-tech-slider__link--dup']}
|
||||||
|
href={item.href}
|
||||||
|
tabindex="-1"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class:list={[
|
||||||
|
'nx-tech-slider__logo',
|
||||||
|
item.name === 'HL7 FHIR' && 'nx-tech-slider__logo--fhir',
|
||||||
|
]}
|
||||||
|
src={item.iconSrc}
|
||||||
|
alt=""
|
||||||
|
width="168"
|
||||||
|
height="48"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-10">
|
||||||
|
<h3 class="text-lg font-semibold text-nx-fg">
|
||||||
|
{stackSection.whyThisChoice.title}
|
||||||
|
</h3>
|
||||||
|
<ul class="mt-4 space-y-3 text-sm leading-relaxed text-nx-muted sm:text-[15px]">
|
||||||
|
{stackSection.whyThisChoice.points.map((line) => (
|
||||||
|
<li class="flex gap-3">
|
||||||
|
<span class="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-sky-400/80" />
|
||||||
|
<span>{line}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
id="faq"
|
||||||
|
class="relative z-10 border-t border-nx-border bg-nx-surface/20 px-4 py-20 sm:px-6"
|
||||||
|
x-data="{ q: 0 }"
|
||||||
|
>
|
||||||
|
<div class="mx-auto max-w-2xl">
|
||||||
|
<h2
|
||||||
|
class="text-sm font-semibold uppercase tracking-wider text-sky-400/90"
|
||||||
|
>
|
||||||
|
{faq.eyebrow}
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
class="mt-2 text-3xl font-semibold tracking-tight text-nx-fg sm:text-4xl"
|
||||||
|
>
|
||||||
|
{faq.title}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="mt-10 divide-y divide-nx-border rounded-2xl border border-nx-border bg-nx-elevated/30">
|
||||||
|
{faq.items.map((item, idx) => {
|
||||||
|
const n = idx + 1;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex w-full items-center justify-between px-4 py-4 text-left text-sm font-medium text-nx-fg hover:bg-white/5"
|
||||||
|
@click={`q = q === ${n} ? 0 : ${n}`}
|
||||||
|
:aria-expanded={`q === ${n}`}
|
||||||
|
>
|
||||||
|
{item.question}
|
||||||
|
<span
|
||||||
|
class="ml-3 inline-flex shrink-0 text-nx-muted transition-transform duration-200 ease-out"
|
||||||
|
:class={`q === ${n} && 'rotate-180'`}
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="size-5 sm:size-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="m6 9 6 6 6-6"></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
x-show={`q === ${n}`}
|
||||||
|
x-transition
|
||||||
|
x-cloak
|
||||||
|
class="border-t border-nx-border px-4 py-3 text-sm text-nx-muted"
|
||||||
|
>
|
||||||
|
{item.answer}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="contatti" class="relative z-10 px-4 pb-16 pt-4 sm:px-6 sm:pb-20">
|
||||||
|
<div
|
||||||
|
id="nx-cta-card"
|
||||||
|
class="relative mx-auto max-w-6xl overflow-hidden rounded-3xl border border-nx-border bg-gradient-to-br from-sky-500/15 via-nx-elevated to-violet-600/15 px-5 py-5 sm:px-6 sm:py-6"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="relative z-10 flex flex-col gap-6 lg:flex-row lg:items-start lg:gap-10"
|
||||||
|
>
|
||||||
|
<div class="min-w-0 flex-1 lg:max-w-[min(100%,28rem)] xl:max-w-[min(100%,32rem)]">
|
||||||
|
<h2
|
||||||
|
class="text-2xl font-semibold leading-snug tracking-tight sm:text-3xl"
|
||||||
|
>
|
||||||
|
<span class="block text-nx-fg">{cta.titleLine1}</span>
|
||||||
|
<span class="mt-0.5 block text-sky-200 sm:text-[#bae6fd]">{cta.titleLine2}</span>
|
||||||
|
</h2>
|
||||||
|
<p class="mt-3 max-w-2xl text-nx-muted">
|
||||||
|
{cta.body}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="w-full min-w-0 shrink-0 lg:max-w-md xl:max-w-lg"
|
||||||
|
>
|
||||||
|
<ContactFormInline />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<SiteFooter />
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
import '../scripts/nx-nav-spy.ts';
|
||||||
|
</script>
|
||||||
|
</BaseLayout>
|
||||||
450
src/pages/modello-organizzativo.astro
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
---
|
||||||
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
|
|
||||||
|
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 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';
|
||||||
|
---
|
||||||
|
|
||||||
|
<SubpageLayout
|
||||||
|
title="Modello organizzativo — NexStudio"
|
||||||
|
description="Modello organizzativo NexStudio: ruoli, governance, RACI, controlli lean, KPI e piano operativo a 30 giorni."
|
||||||
|
heading="Modello organizzativo"
|
||||||
|
lead="Schema di ruoli, ritmi di governo e responsabilità per una software company snella (SaaS legal/med). Non sostituisce valutazioni legali (es. D.Lgs. 231/2001): va allineato a società, organi e consulenti."
|
||||||
|
>
|
||||||
|
<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">
|
||||||
|
Documento operativo per <strong class="text-nx-fg">NexStudio</strong> (sede Bangkok). Ruoli
|
||||||
|
numerici indicativi: adattare al team reale e formalizzare nomine e deleghe per iscritto.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="indice" class="!mt-8">Indice</h2>
|
||||||
|
<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>
|
||||||
|
<li><a class={link} href="#responsabilita">Principali responsabilità (sintesi)</a></li>
|
||||||
|
<li><a class={link} href="#raci">RACI per processi critici</a></li>
|
||||||
|
<li><a class={link} href="#flussi">Flussi decisionali rapidi</a></li>
|
||||||
|
<li><a class={link} href="#controlli">Controlli minimi obbligatori (lean)</a></li>
|
||||||
|
<li><a class={link} href="#kpi">KPI essenziali</a></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>
|
||||||
|
|
||||||
|
<h2 id="ruoli">1. Ruoli e perimetro</h2>
|
||||||
|
<p>
|
||||||
|
Elenco sintetico delle funzioni, con attese di impegno (full-time, part-time, frazionale o
|
||||||
|
esternalizzato). I numeri tra parentesi sono orientativi per una fase early-stage.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Founder / CEO (1):</strong> strategia, approvazione delle policy, contatti con board
|
||||||
|
e investitori, accountability complessiva anche verso obblighi di legge e contratti.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>CTO / Head of Product (1):</strong> architettura, roadmap, quality gate di prodotto,
|
||||||
|
responsabilità tecnica end-to-end.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Lead Engineer (1–2):</strong> sviluppo, code review, esecuzione CI/CD e qualità del
|
||||||
|
codice in reparto.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>DevOps / Platform (1, oppure outsourcing):</strong> deploy, KMS, backup e
|
||||||
|
disaster recovery, governo dell’ambiente di produzione.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Security & Privacy Lead (1, ruolo ibrido o contractor):</strong> sicurezza
|
||||||
|
operativa, gestione delle vulnerabilità, coordinamento con il DPO.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>DPO / Privacy Responsible (frazionale o outsourcing):</strong> DPIA, gestione dei
|
||||||
|
diritti degli interessati, coerenza dell’informativa privacy.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Legal & Compliance (frazionale o legale interno):</strong> contratti, NDA,
|
||||||
|
revisione delle normative di settore legale e medico pertinenti ai prodotti.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Product / Clinical Advisor (part-time o consulente):</strong> validazione di
|
||||||
|
funzionalità con impatto medico o legale, limiti d’uso e avvertenze.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Customer Success / Support (1):</strong> onboarding clienti, gestione richieste,
|
||||||
|
escalation verso tecnici e governance.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Operations / HR (1, part-time):</strong> onboarding del personale, formazione,
|
||||||
|
gestione delle segnalazioni interne.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Finance (1, part-time o outsourcing):</strong> contabilità, pagamenti, policy su
|
||||||
|
incassi e fornitori.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="governance">2. Governance essenziale</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Weekly Tactical</strong> (Founder, CTO, Security/Privacy Lead, Customer Success):
|
||||||
|
priorità operative, incidenti aperti, rilasci critici in corso.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Biweekly Product Sync</strong> (CTO, Lead Engineer, Product Advisor): backlog,
|
||||||
|
piano di release, checkpoint di compliance di prodotto.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Monthly Compliance Check</strong> (Founder/CEO, Legal, DPO, Security Lead): revisione
|
||||||
|
DPIA, vendor ad alto rischio, sintesi incidenti e azioni correttive.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Quarterly Board / Founders review:</strong> strategia, budget, rischi maggiori e
|
||||||
|
capacità organizzative.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="responsabilita">3. Principali responsabilità (sintesi)</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Codice etico e policy:</strong> owner <em>Legal & Compliance</em> — approvatore
|
||||||
|
<em>CEO</em>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Sicurezza operativa e incident response:</strong> owner <em>Security Lead</em> —
|
||||||
|
esecutore tecnico <em>CTO</em>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Privacy, trattamenti sensibili e DPIA:</strong> owner <em>DPO</em> — supporto
|
||||||
|
<em>Legal</em>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Rilasci in produzione:</strong> accountable <em>CTO</em> — responsible <em>Lead
|
||||||
|
Engineer</em> — consulted <em>Security</em>, <em>DPO</em>, <em>Product Advisor</em>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Fornitori e sub-processori:</strong> owner <em>Operations</em> + <em>Legal</em> —
|
||||||
|
due diligence <em>Security</em> / <em>DPO</em>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Richieste degli interessati (DSR):</strong> owner <em>DPO</em> — esecuzione operativa
|
||||||
|
<em>Customer Success</em> (ove applicabile).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Segnalazioni e whistleblowing:</strong> owner <em>Operations</em> / <em>HR</em> —
|
||||||
|
supporto investigativo <em>Legal</em>.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="raci">4. RACI sintetico per processi critici</h2>
|
||||||
|
<p>
|
||||||
|
Legenda: <strong>R</strong> = Responsible, <strong>A</strong> = Accountable,
|
||||||
|
<strong>C</strong> = Consulted, <strong>I</strong> = Informed.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Rilascio in produzione</h3>
|
||||||
|
<table class={tbl}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class={th}>Ruolo</th>
|
||||||
|
<th class={th}>R</th>
|
||||||
|
<th class={th}>A</th>
|
||||||
|
<th class={th}>C</th>
|
||||||
|
<th class={th}>I</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class={td}>Lead Engineer</td>
|
||||||
|
<td class={td}>●</td>
|
||||||
|
<td class={td}></td>
|
||||||
|
<td class={td}></td>
|
||||||
|
<td class={td}></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class={td}>CTO</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, Product Advisor</td>
|
||||||
|
<td class={td}></td>
|
||||||
|
<td class={td}></td>
|
||||||
|
<td class={td}>●</td>
|
||||||
|
<td class={td}></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class={td}>CEO, Customer Success</td>
|
||||||
|
<td class={td}></td>
|
||||||
|
<td class={td}></td>
|
||||||
|
<td class={td}></td>
|
||||||
|
<td class={td}>●</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Incident response (violazione dei dati)</h3>
|
||||||
|
<table class={tbl}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class={th}>Ruolo</th>
|
||||||
|
<th class={th}>R</th>
|
||||||
|
<th class={th}>A</th>
|
||||||
|
<th class={th}>C</th>
|
||||||
|
<th class={th}>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 coinvolti, Board (se impatto elevato)</td>
|
||||||
|
<td class={td}></td>
|
||||||
|
<td class={td}></td>
|
||||||
|
<td class={td}></td>
|
||||||
|
<td class={td}>●</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Onboarding vendor (sub-processore)</h3>
|
||||||
|
<table class={tbl}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class={th}>Ruolo</th>
|
||||||
|
<th class={th}>R</th>
|
||||||
|
<th class={th}>A</th>
|
||||||
|
<th class={th}>C</th>
|
||||||
|
<th class={th}>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>
|
||||||
|
|
||||||
|
<h3>DPIA per modulo sanitario o legale</h3>
|
||||||
|
<table class={tbl}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class={th}>Ruolo</th>
|
||||||
|
<th class={th}>R</th>
|
||||||
|
<th class={th}>A</th>
|
||||||
|
<th class={th}>C</th>
|
||||||
|
<th class={th}>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>
|
||||||
|
|
||||||
|
<h2 id="flussi">5. Flussi decisionali rapidi</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Decisione tecnica ordinaria:</strong> Lead Engineer → CTO (documentare su ticket se
|
||||||
|
impatta rischio o contratti).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Rilascio con impatto privacy o sicurezza:</strong> approvazione obbligatoria di
|
||||||
|
<em>Security Lead</em> e <em>DPO</em> (target: entro 48 ore lavorative).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Incident P0:</strong> <em>Security Lead</em> notifica CEO, DPO e Legal entro 4 ore;
|
||||||
|
escalation al Board se l’impatto è elevato (clienti, regolatori, volumi o classi di dati).
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="controlli">6. Controlli minimi obbligatori (lean)</h2>
|
||||||
|
<ul>
|
||||||
|
<li>IAM con MFA per accesso a produzione e a segreti.</li>
|
||||||
|
<li>CI/CD con SAST e scansione automatica delle dipendenze.</li>
|
||||||
|
<li>SBOM generato automaticamente per ogni release.</li>
|
||||||
|
<li>TLS in transito; cifratura a riposo per dati sensibili.</li>
|
||||||
|
<li>Backup giornalieri e test DR trimestrale (ripristino documentato).</li>
|
||||||
|
<li>Log degli accessi e alerting su anomalie (SIEM o servizio gestito).</li>
|
||||||
|
<li>Checklist pre-release (security/privacy) con traccia digitale di approvazione.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="kpi">7. KPI essenziali (core metrics)</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Security:</strong> percentuale di patch critiche entro SLA; MTTD e MTTR per
|
||||||
|
incidenti.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Privacy:</strong> tempo medio di risposta alle DSR; numero di DPIA completate / in
|
||||||
|
corso.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Product:</strong> tempo di ciclo del deploy; copertura test sui moduli critici.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Operations:</strong> uptime rispetto a SLA; tempo medio di risposta del supporto;
|
||||||
|
segnalazioni chiuse nel periodo.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="documentazione">8. Documentazione minima da mantenere</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Codice etico firmato e registro delle adesioni.</li>
|
||||||
|
<li>Informativa privacy e template di DPA (Data Processing Agreement).</li>
|
||||||
|
<li>DPIA per trattamenti critici.</li>
|
||||||
|
<li>Security whitepaper breve (1–2 pagine) per clienti e audit.</li>
|
||||||
|
<li>Incident response playbook (versione esecutiva).</li>
|
||||||
|
<li>SBOM e registro fornitori / sub-processori.</li>
|
||||||
|
<li>Checklist pre-release e log delle approvazioni.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="piano-30">9. Primo piano operativo (30 giorni)</h2>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<strong>Giorni 0–3:</strong> nomina formale dei ruoli chiave (Security Lead, DPO frazionale,
|
||||||
|
Legal frazionale).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Giorni 4–10:</strong> integrare la checklist pre-release in CI/CD; abilitare MFA e
|
||||||
|
policy IAM coerenti con il minimo sopra.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Giorni 11–17:</strong> avviare DPIA sul modulo medico/legale più critico; due
|
||||||
|
diligence sui fornitori ad alto rischio.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Giorni 18–24:</strong> pubblicare una Trust Center di base (link al Codice etico,
|
||||||
|
contatti DPO/security, materiali privacy/DPA se disponibili).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Giorni 25–30:</strong> tabletop su incident response; test di rollback deploy e
|
||||||
|
ripristino backup; formazione iniziale obbligatoria (security e privacy).
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2 id="outsourcing">10. Outsourcing consigliato (per restare snelli)</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Security Ops / SOC:</strong> servizio gestito per log, alerting e penetration test
|
||||||
|
periodici.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>DPO e Legal:</strong> consulenti frazionali con esperienza PDPA, GDPR e contesto
|
||||||
|
medico-legale locale.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>DevOps / Platform:</strong> servizi cloud gestiti (es. KMS, database gestiti) per
|
||||||
|
ridurre carico operativo interno.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="note">11. Note pratiche e raccomandazioni</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Mantenere <strong>separazione dei compiti</strong> sui controlli critici (es. chi sviluppa
|
||||||
|
non approva da solo gli accessi a produzione).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Automatizzare</strong> controlli ripetitivi (SAST, SBOM, scansioni dipendenze) nella
|
||||||
|
pipeline.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Documentare</strong> decisioni su rischio e accettazioni (log nel sistema di
|
||||||
|
ticketing) per audit e post-mortem.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Coinvolgere <strong>advisor clinici e legali</strong> esterni per convalide su funzionalità ad
|
||||||
|
alto rischio.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Prevedere <strong>revisione trimestrale</strong> del modello e aggiornamento delle
|
||||||
|
responsabilità man mano che la società cresce.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Collegamenti</h2>
|
||||||
|
<p>
|
||||||
|
<a class={link} href="/codice-etico">Codice etico</a>
|
||||||
|
<span class="text-nx-muted"> · </span>
|
||||||
|
<a class={link} href="/about">Chi siamo</a>
|
||||||
|
<span class="text-nx-muted"> · </span>
|
||||||
|
<a class={link} href="#indice">Torna all’indice</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</SubpageLayout>
|
||||||
18
src/pages/news.astro
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<SubpageLayout
|
||||||
|
title="Novità — NexStudio"
|
||||||
|
description="Novità, release e articoli da NexStudio."
|
||||||
|
heading="Novità"
|
||||||
|
lead="Hub comunicazioni: collegate qui il vostro blog CMS, oppure elencate manualmente le notizie."
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Quando avrete contenuti da pubblicare (release di prodotto, note di
|
||||||
|
conformità, eventi), potete sostituire questa pagina con una raccolta
|
||||||
|
articoli Astro, un redirect verso Medium/Substack, o un headless CMS.
|
||||||
|
</p>
|
||||||
|
<h2>Archivio</h2>
|
||||||
|
<p class="text-nx-muted">Nessuna news pubblicata per il momento.</p>
|
||||||
|
</SubpageLayout>
|
||||||
18
src/pages/newsletter/grazie.astro
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
import SubpageLayout from '../../layouts/SubpageLayout.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<SubpageLayout
|
||||||
|
title="Grazie — Newsletter NexStudio"
|
||||||
|
description="Iscrizione alla newsletter ricevuta."
|
||||||
|
heading="Grazie"
|
||||||
|
lead="La richiesta di iscrizione è stata inviata. Controlla la posta se è prevista una conferma (double opt-in)."
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Se usi un servizio con conferma via email, apri il messaggio e segui il
|
||||||
|
link per completare l’iscrizione.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a class="text-sky-400/90 hover:text-sky-300" href="/">Torna alla home</a>
|
||||||
|
</p>
|
||||||
|
</SubpageLayout>
|
||||||
53
src/pages/privacy.astro
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<SubpageLayout
|
||||||
|
title="Privacy — NexStudio"
|
||||||
|
description="Informativa sul trattamento dei dati personali NexStudio."
|
||||||
|
heading="Informativa sulla privacy"
|
||||||
|
lead="Documento provvisorio: va adattato alla vostra organizzazione e revisionato da un legale prima della pubblicazione definitiva."
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Questa pagina descrive, in forma generica, come NexStudio intende trattare i
|
||||||
|
dati personali raccolti attraverso il sito web e i canali di contatto. Le
|
||||||
|
finalità, le basi giuridiche e i destinatari vanno definiti in base alle
|
||||||
|
vostre attività reali (es. gestione richieste, newsletter, analytics).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Titolare del trattamento</h2>
|
||||||
|
<p>
|
||||||
|
Indicare ragione sociale, sede, P.IVA e modalità di contatto (es. email
|
||||||
|
dedicata privacy). Per richieste degli interessati potete rimandare alla
|
||||||
|
sezione <a class="text-sky-400/90 hover:text-sky-300" href="/#contatti"
|
||||||
|
>Contatti</a
|
||||||
|
> o a un indirizzo PEC / form strutturato.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Tipologie di dati e finalità</h2>
|
||||||
|
<p>
|
||||||
|
Esempi da personalizzare: dati di navigazione e log tecnici; dati forniti
|
||||||
|
volontariamente tramite moduli o chat; eventuali cookie di profilazione (da
|
||||||
|
collegare alla <a class="text-sky-400/90 hover:text-sky-300" href="/cookies"
|
||||||
|
>informativa cookie</a
|
||||||
|
>). Per ogni trattamento indicare la base giuridica (contratto, legittimo
|
||||||
|
interesse, consenso, obbligo legale).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Conservazione e sicurezza</h2>
|
||||||
|
<p>
|
||||||
|
Specificare tempi di conservazione per categoria di dati e misure di
|
||||||
|
sicurezza adottate (crittografia, controllo accessi, fornitori cloud, sede
|
||||||
|
dei dati).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Diritti degli interessati</h2>
|
||||||
|
<p>
|
||||||
|
Gli interessati possono esercitare i diritti previsti dal Reg. (UE) 2016/679
|
||||||
|
(accesso, rettifica, cancellazione, limitazione, portabilità, opposizione,
|
||||||
|
revoca del consenso). Dettagli operativi nella pagina <a
|
||||||
|
class="text-sky-400/90 hover:text-sky-300"
|
||||||
|
href="/gdpr">GDPR</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
</SubpageLayout>
|
||||||
25
src/pages/terms.astro
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
import SubpageLayout from '../layouts/SubpageLayout.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<SubpageLayout
|
||||||
|
title="Condizioni d’uso — NexStudio"
|
||||||
|
description="Termini e condizioni d’uso dei siti e servizi NexStudio."
|
||||||
|
heading="Condizioni d’uso"
|
||||||
|
lead="Bozza: definire condizioni d’uso del sito, limitazioni di responsabilità, proprietà intellettuale e legge applicabile con supporto legale."
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Questa pagina ospiterà le condizioni d’uso per il sito
|
||||||
|
pubblico e, se del caso, per l’accesso a demo o aree riservate. Struttura
|
||||||
|
tipica: oggetto, utente, servizi offerti, obblighi dell’utente, limitazione
|
||||||
|
di responsabilità, sospensione, legge e foro competente.
|
||||||
|
</p>
|
||||||
|
<h2>Collegamenti</h2>
|
||||||
|
<p>
|
||||||
|
<a class="text-sky-400/90 hover:text-sky-300" href="/privacy">Privacy</a>
|
||||||
|
·
|
||||||
|
<a class="text-sky-400/90 hover:text-sky-300" href="/cookies">Informativa cookie</a>
|
||||||
|
·
|
||||||
|
<a class="text-sky-400/90 hover:text-sky-300" href="/gdpr">Diritti privacy (GDPR)</a>
|
||||||
|
</p>
|
||||||
|
</SubpageLayout>
|
||||||
215
src/scripts/nx-chat-gate-modal.ts
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
interface ChatGateCfg {
|
||||||
|
entryUrl: string;
|
||||||
|
openInNewTab: boolean;
|
||||||
|
actionUrl: string;
|
||||||
|
mailto: string;
|
||||||
|
notifySubject: string;
|
||||||
|
successRedirect: string;
|
||||||
|
fieldName: string;
|
||||||
|
fieldEmail: string;
|
||||||
|
openChatAfterSubmit: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OVERLAY_ID = 'nx-chat-gate-overlay';
|
||||||
|
const PANEL_SEL = '[data-nx-chat-gate-panel]';
|
||||||
|
|
||||||
|
function readCfg(): ChatGateCfg {
|
||||||
|
const el = document.getElementById('nx-chat-gate-model');
|
||||||
|
const raw = el?.textContent?.trim();
|
||||||
|
if (!raw) {
|
||||||
|
return {
|
||||||
|
entryUrl: '/#contatti',
|
||||||
|
openInNewTab: false,
|
||||||
|
actionUrl: '',
|
||||||
|
mailto: '',
|
||||||
|
notifySubject: '',
|
||||||
|
successRedirect: '',
|
||||||
|
fieldName: 'name',
|
||||||
|
fieldEmail: 'email',
|
||||||
|
openChatAfterSubmit: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return JSON.parse(raw) as ChatGateCfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
function overlayEl(): HTMLElement | null {
|
||||||
|
return document.getElementById(OVERLAY_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOpen(): boolean {
|
||||||
|
const el = overlayEl();
|
||||||
|
return Boolean(el && !el.hasAttribute('hidden'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function showFormStep(): void {
|
||||||
|
document.getElementById('nx-chat-gate-step-form')?.classList.remove('hidden');
|
||||||
|
document.getElementById('nx-chat-gate-step-success')?.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSuccessStep(): void {
|
||||||
|
document.getElementById('nx-chat-gate-step-form')?.classList.add('hidden');
|
||||||
|
document.getElementById('nx-chat-gate-step-success')?.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePanel(): void {
|
||||||
|
const el = overlayEl();
|
||||||
|
if (!el) return;
|
||||||
|
el.setAttribute('hidden', '');
|
||||||
|
el.setAttribute('aria-hidden', 'true');
|
||||||
|
document.body.classList.remove('overflow-hidden');
|
||||||
|
const f = document.getElementById('nx-chat-gate-form') as HTMLFormElement | null;
|
||||||
|
f?.reset();
|
||||||
|
const err = document.getElementById('nx-chat-gate-form-error');
|
||||||
|
if (err) {
|
||||||
|
err.textContent = '';
|
||||||
|
err.classList.add('hidden');
|
||||||
|
}
|
||||||
|
showFormStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openChat(): void {
|
||||||
|
const cfg = readCfg();
|
||||||
|
const url = cfg.entryUrl;
|
||||||
|
if (url.startsWith('http')) {
|
||||||
|
window.open(url, '_blank', 'noopener,noreferrer');
|
||||||
|
} else {
|
||||||
|
window.location.assign(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openPanel(): void {
|
||||||
|
const el = overlayEl();
|
||||||
|
if (!el) return;
|
||||||
|
el.removeAttribute('hidden');
|
||||||
|
el.setAttribute('aria-hidden', 'false');
|
||||||
|
document.body.classList.add('overflow-hidden');
|
||||||
|
showFormStep();
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
el.querySelector<HTMLElement>('#nx-cg-email')?.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function eventTargetElement(e: Event): Element | null {
|
||||||
|
const t = e.target;
|
||||||
|
if (t instanceof Element) return t;
|
||||||
|
if (t instanceof Text) return t.parentElement;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind(): void {
|
||||||
|
const root = overlayEl();
|
||||||
|
const form = document.getElementById('nx-chat-gate-form') as HTMLFormElement | null;
|
||||||
|
if (!root || !form) return;
|
||||||
|
|
||||||
|
document.querySelectorAll<HTMLElement>('[data-nx-chat-open]').forEach((btn) => {
|
||||||
|
btn.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
openPanel();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
root.querySelectorAll<HTMLButtonElement>('[data-nx-chat-gate-close]').forEach((b) => {
|
||||||
|
b.addEventListener('click', () => closePanel());
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('nx-chat-gate-open-chat')?.addEventListener('click', () => {
|
||||||
|
openChat();
|
||||||
|
closePanel();
|
||||||
|
});
|
||||||
|
|
||||||
|
const onDocClickCapture = (e: MouseEvent) => {
|
||||||
|
if (!isOpen()) return;
|
||||||
|
const el = eventTargetElement(e);
|
||||||
|
if (!el || !root.contains(el)) return;
|
||||||
|
if (el.closest('[data-nx-chat-gate-close]')) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
closePanel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!el.closest(PANEL_SEL)) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
closePanel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('click', onDocClickCapture, true);
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||||
|
if (e.key !== 'Escape' || !isOpen()) return;
|
||||||
|
e.preventDefault();
|
||||||
|
closePanel();
|
||||||
|
});
|
||||||
|
|
||||||
|
form.addEventListener('submit', async (e) => {
|
||||||
|
const cfg = readCfg();
|
||||||
|
const err = document.getElementById('nx-chat-gate-form-error');
|
||||||
|
if (err) err.classList.add('hidden');
|
||||||
|
|
||||||
|
if (cfg.actionUrl) {
|
||||||
|
e.preventDefault();
|
||||||
|
const fd = new FormData(form);
|
||||||
|
try {
|
||||||
|
const res = await fetch(cfg.actionUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { Accept: 'application/json' },
|
||||||
|
body: fd,
|
||||||
|
});
|
||||||
|
const data = (await res.json().catch(() => null)) as { error?: string } | null;
|
||||||
|
if (!res.ok) {
|
||||||
|
const msg =
|
||||||
|
(data && typeof data.error === 'string' && data.error) ||
|
||||||
|
'Invio non riuscito. Riprova tra poco.';
|
||||||
|
if (err) {
|
||||||
|
err.textContent = msg;
|
||||||
|
err.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cfg.openChatAfterSubmit) {
|
||||||
|
openChat();
|
||||||
|
closePanel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showSuccessStep();
|
||||||
|
document.getElementById('nx-chat-gate-open-chat')?.focus();
|
||||||
|
} catch {
|
||||||
|
if (err) {
|
||||||
|
err.textContent = 'Errore di rete. Controlla la connessione e riprova.';
|
||||||
|
err.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
const fd = new FormData(form);
|
||||||
|
const name = String(fd.get(cfg.fieldName) ?? '').trim();
|
||||||
|
const email = String(fd.get(cfg.fieldEmail) ?? '').trim();
|
||||||
|
if (cfg.mailto) {
|
||||||
|
const subject = encodeURIComponent(cfg.notifySubject || 'Accesso chat');
|
||||||
|
const body = encodeURIComponent(
|
||||||
|
`Richiesta accesso alla chat.\n\nNome: ${name || '—'}\nEmail: ${email}\n`,
|
||||||
|
);
|
||||||
|
window.location.href = `mailto:${cfg.mailto}?subject=${subject}&body=${body}`;
|
||||||
|
closePanel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
err.textContent =
|
||||||
|
'Configura `chat.prechat.actionUrl` (Formspree) o `chat.prechat.mailto` in `src/data/home/chat.ts`.';
|
||||||
|
err.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
window.addEventListener('pageshow', (e) => {
|
||||||
|
if (e.persisted) closePanel();
|
||||||
|
});
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', bind, { once: true });
|
||||||
|
} else {
|
||||||
|
bind();
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/scripts/nx-contact-form.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/** Modulo contatti in pagina (#contatti): mailto o POST Formspree. */
|
||||||
|
interface ContactFormCfg {
|
||||||
|
actionUrl: string;
|
||||||
|
mailto: string;
|
||||||
|
notifySubject: string;
|
||||||
|
successRedirect: string;
|
||||||
|
fieldName: string;
|
||||||
|
fieldCompany: string;
|
||||||
|
fieldCountry: string;
|
||||||
|
fieldEmail: string;
|
||||||
|
fieldMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readCfg(): ContactFormCfg {
|
||||||
|
const el = document.getElementById('nx-contact-model');
|
||||||
|
const raw = el?.textContent?.trim();
|
||||||
|
if (!raw) {
|
||||||
|
return {
|
||||||
|
actionUrl: '',
|
||||||
|
mailto: '',
|
||||||
|
notifySubject: '',
|
||||||
|
successRedirect: '',
|
||||||
|
fieldName: 'name',
|
||||||
|
fieldCompany: 'company',
|
||||||
|
fieldCountry: 'country',
|
||||||
|
fieldEmail: 'email',
|
||||||
|
fieldMessage: 'message',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return JSON.parse(raw) as ContactFormCfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind(): void {
|
||||||
|
const form = document.getElementById('nx-contact-form') as HTMLFormElement | null;
|
||||||
|
if (!form) return;
|
||||||
|
|
||||||
|
form.addEventListener('submit', (e) => {
|
||||||
|
const cfg = readCfg();
|
||||||
|
const err = document.getElementById('nx-contact-form-error');
|
||||||
|
if (err) err.classList.add('hidden');
|
||||||
|
|
||||||
|
if (cfg.actionUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
const fd = new FormData(form);
|
||||||
|
const name = String(fd.get(cfg.fieldName) ?? '').trim();
|
||||||
|
const company = String(fd.get(cfg.fieldCompany) ?? '').trim();
|
||||||
|
const email = String(fd.get(cfg.fieldEmail) ?? '').trim();
|
||||||
|
const country = String(fd.get(cfg.fieldCountry) ?? '').trim();
|
||||||
|
const message = String(fd.get(cfg.fieldMessage) ?? '').trim();
|
||||||
|
if (cfg.mailto) {
|
||||||
|
const subject = encodeURIComponent(cfg.notifySubject || 'Contatto sito');
|
||||||
|
const companyLine = company ? `\nAzienda: ${company}` : '';
|
||||||
|
const body = encodeURIComponent(
|
||||||
|
`Nome: ${name}${companyLine}\nEmail: ${email}\nPaese: ${country}\n\n${message}`,
|
||||||
|
);
|
||||||
|
window.location.href = `mailto:${cfg.mailto}?subject=${subject}&body=${body}`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
err.textContent =
|
||||||
|
'Configura `contactForm.actionUrl` (Formspree) o `contactForm.mailto` in `src/data/home/cta.ts`.';
|
||||||
|
err.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', bind, { once: true });
|
||||||
|
} else {
|
||||||
|
bind();
|
||||||
|
}
|
||||||
|
}
|
||||||
279
src/scripts/nx-cookie-consent.ts
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
type CategoryId = 'necessary' | 'preferences' | 'analytics' | 'marketing';
|
||||||
|
|
||||||
|
interface CcConfig {
|
||||||
|
storageKey: string;
|
||||||
|
policyVersion: string;
|
||||||
|
categories: readonly { id: CategoryId; required: boolean }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConsentPayload {
|
||||||
|
policyVersion: string;
|
||||||
|
updatedAt: string;
|
||||||
|
categories: Record<CategoryId, boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EVENT_NAME = 'nexstudio:cookie-consent';
|
||||||
|
const PANEL_ID = 'nx-cc-dialog';
|
||||||
|
|
||||||
|
/** Chrome può usare come `target` un nodo `#text` dentro al `<button>` */
|
||||||
|
function eventTargetElement(e: Event): Element | null {
|
||||||
|
const t = e.target;
|
||||||
|
if (t instanceof Element) return t;
|
||||||
|
if (t instanceof Text) return t.parentElement;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function panelRoot(): HTMLElement | null {
|
||||||
|
return document.getElementById(PANEL_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPanelOpen(): boolean {
|
||||||
|
const el = panelRoot();
|
||||||
|
return Boolean(el && !el.hasAttribute('hidden'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeConsentPanel(): void {
|
||||||
|
const el = panelRoot();
|
||||||
|
if (!el) return;
|
||||||
|
el.setAttribute('hidden', '');
|
||||||
|
el.setAttribute('aria-hidden', 'true');
|
||||||
|
document.body.classList.remove('overflow-hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function openConsentPanel(): void {
|
||||||
|
const el = panelRoot();
|
||||||
|
if (!el) return;
|
||||||
|
el.removeAttribute('hidden');
|
||||||
|
el.setAttribute('aria-hidden', 'false');
|
||||||
|
document.body.classList.add('overflow-hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultCategories(cfg: CcConfig): Record<CategoryId, boolean> {
|
||||||
|
const out = {} as Record<CategoryId, boolean>;
|
||||||
|
for (const c of cfg.categories) {
|
||||||
|
out[c.id] = c.required ? true : false;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePayload(raw: string | null, cfg: CcConfig): ConsentPayload | null {
|
||||||
|
if (!raw) return null;
|
||||||
|
try {
|
||||||
|
const v = JSON.parse(raw) as Partial<ConsentPayload>;
|
||||||
|
if (!v || typeof v !== 'object') return null;
|
||||||
|
if (v.policyVersion !== cfg.policyVersion) return null;
|
||||||
|
if (typeof v.updatedAt !== 'string') return null;
|
||||||
|
if (!v.categories || typeof v.categories !== 'object') return null;
|
||||||
|
const cats = { ...defaultCategories(cfg) };
|
||||||
|
for (const c of cfg.categories) {
|
||||||
|
if (typeof v.categories[c.id] === 'boolean') {
|
||||||
|
cats[c.id] = c.required ? true : Boolean(v.categories[c.id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { policyVersion: cfg.policyVersion, updatedAt: v.updatedAt, categories: cats };
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readConfig(): CcConfig {
|
||||||
|
const el = document.getElementById('nx-cc-model');
|
||||||
|
const raw = el?.textContent?.trim();
|
||||||
|
if (!raw) {
|
||||||
|
return {
|
||||||
|
storageKey: 'nexstudio.cookieConsent',
|
||||||
|
policyVersion: '1.0',
|
||||||
|
categories: [
|
||||||
|
{ id: 'necessary', required: true },
|
||||||
|
{ id: 'preferences', required: false },
|
||||||
|
{ id: 'analytics', required: false },
|
||||||
|
{ id: 'marketing', required: false },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return JSON.parse(raw) as CcConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
function save(cfg: CcConfig, categories: Record<CategoryId, boolean>): ConsentPayload {
|
||||||
|
const payload: ConsentPayload = {
|
||||||
|
policyVersion: cfg.policyVersion,
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
categories: { ...categories },
|
||||||
|
};
|
||||||
|
for (const c of cfg.categories) {
|
||||||
|
if (c.required) payload.categories[c.id] = true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
localStorage.setItem(cfg.storageKey, JSON.stringify(payload));
|
||||||
|
} catch {
|
||||||
|
/* ignore quota / private mode */
|
||||||
|
}
|
||||||
|
window.dispatchEvent(new CustomEvent(EVENT_NAME, { detail: payload }));
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStored(cfg: CcConfig): ConsentPayload | null {
|
||||||
|
try {
|
||||||
|
return parsePayload(localStorage.getItem(cfg.storageKey), cfg);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let nxCcDocCapture: ((e: MouseEvent) => void) | undefined;
|
||||||
|
|
||||||
|
function bind(): void {
|
||||||
|
const cfg = readConfig();
|
||||||
|
const banner = document.getElementById('nx-cc-banner');
|
||||||
|
const root = panelRoot();
|
||||||
|
if (!banner || !root) return;
|
||||||
|
|
||||||
|
closeConsentPanel();
|
||||||
|
|
||||||
|
function readForm(): Record<CategoryId, boolean> {
|
||||||
|
const d = panelRoot();
|
||||||
|
const base = defaultCategories(cfg);
|
||||||
|
base.preferences = Boolean(d?.querySelector<HTMLInputElement>('[data-nx-cc-cat="preferences"]')?.checked);
|
||||||
|
base.analytics = Boolean(d?.querySelector<HTMLInputElement>('[data-nx-cc-cat="analytics"]')?.checked);
|
||||||
|
base.marketing = Boolean(d?.querySelector<HTMLInputElement>('[data-nx-cc-cat="marketing"]')?.checked);
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeForm(cats: Record<CategoryId, boolean>) {
|
||||||
|
const d = panelRoot();
|
||||||
|
const p = d?.querySelector<HTMLInputElement>('[data-nx-cc-cat="preferences"]');
|
||||||
|
const a = d?.querySelector<HTMLInputElement>('[data-nx-cc-cat="analytics"]');
|
||||||
|
const m = d?.querySelector<HTMLInputElement>('[data-nx-cc-cat="marketing"]');
|
||||||
|
if (p) p.checked = cats.preferences;
|
||||||
|
if (a) a.checked = cats.analytics;
|
||||||
|
if (m) m.checked = cats.marketing;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideBanner() {
|
||||||
|
banner.setAttribute('hidden', '');
|
||||||
|
banner.setAttribute('aria-hidden', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showBanner() {
|
||||||
|
banner.removeAttribute('hidden');
|
||||||
|
banner.setAttribute('aria-hidden', 'false');
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFromStorage() {
|
||||||
|
const stored = getStored(cfg);
|
||||||
|
if (stored) {
|
||||||
|
writeForm(stored.categories);
|
||||||
|
hideBanner();
|
||||||
|
window.dispatchEvent(new CustomEvent(EVENT_NAME, { detail: stored }));
|
||||||
|
} else {
|
||||||
|
writeForm(defaultCategories(cfg));
|
||||||
|
showBanner();
|
||||||
|
}
|
||||||
|
closeConsentPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistAndClose(cats: Record<CategoryId, boolean>) {
|
||||||
|
save(cfg, cats);
|
||||||
|
hideBanner();
|
||||||
|
closeConsentPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
banner.querySelector('[data-nx-cc-reject]')?.addEventListener('click', () => {
|
||||||
|
persistAndClose(defaultCategories(cfg));
|
||||||
|
});
|
||||||
|
|
||||||
|
banner.querySelector('[data-nx-cc-accept]')?.addEventListener('click', () => {
|
||||||
|
const cats = defaultCategories(cfg);
|
||||||
|
for (const c of cfg.categories) {
|
||||||
|
if (!c.required) cats[c.id] = true;
|
||||||
|
}
|
||||||
|
persistAndClose(cats);
|
||||||
|
});
|
||||||
|
|
||||||
|
banner.querySelector('[data-nx-cc-customize]')?.addEventListener('click', () => {
|
||||||
|
const stored = getStored(cfg);
|
||||||
|
writeForm(stored?.categories ?? defaultCategories(cfg));
|
||||||
|
openConsentPanel();
|
||||||
|
});
|
||||||
|
|
||||||
|
const onDocClickCapture = (e: MouseEvent) => {
|
||||||
|
if (!isPanelOpen()) return;
|
||||||
|
const d = panelRoot();
|
||||||
|
if (!d) return;
|
||||||
|
const el = eventTargetElement(e);
|
||||||
|
if (!el || !d.contains(el)) return;
|
||||||
|
|
||||||
|
if (el.closest('[data-nx-cc-save]')) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
persistAndClose(readForm());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (el.closest('[data-nx-cc-cancel]')) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
closeConsentPanel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!el.closest('[data-nx-cc-panel]')) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
closeConsentPanel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (nxCcDocCapture) {
|
||||||
|
document.removeEventListener('click', nxCcDocCapture, true);
|
||||||
|
}
|
||||||
|
nxCcDocCapture = onDocClickCapture;
|
||||||
|
document.addEventListener('click', nxCcDocCapture, true);
|
||||||
|
|
||||||
|
document.querySelectorAll<HTMLElement>('[data-nx-cc-open]').forEach((el) => {
|
||||||
|
el.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const stored = getStored(cfg);
|
||||||
|
writeForm(stored?.categories ?? defaultCategories(cfg));
|
||||||
|
openConsentPanel();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
applyFromStorage();
|
||||||
|
|
||||||
|
(window as unknown as { NexStudioCookieConsent?: unknown }).NexStudioCookieConsent = {
|
||||||
|
getConsent: () => getStored(cfg),
|
||||||
|
openPreferences: () => {
|
||||||
|
const stored = getStored(cfg);
|
||||||
|
writeForm(stored?.categories ?? defaultCategories(cfg));
|
||||||
|
openConsentPanel();
|
||||||
|
},
|
||||||
|
acceptAll: () => {
|
||||||
|
const cats = defaultCategories(cfg);
|
||||||
|
for (const c of cfg.categories) {
|
||||||
|
if (!c.required) cats[c.id] = true;
|
||||||
|
}
|
||||||
|
persistAndClose(cats);
|
||||||
|
},
|
||||||
|
rejectOptional: () => {
|
||||||
|
persistAndClose(defaultCategories(cfg));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
window.addEventListener('pageshow', (e) => {
|
||||||
|
if (e.persisted) closeConsentPanel();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||||
|
if (e.key !== 'Escape') return;
|
||||||
|
if (!isPanelOpen()) return;
|
||||||
|
e.preventDefault();
|
||||||
|
closeConsentPanel();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', bind, { once: true });
|
||||||
|
} else {
|
||||||
|
bind();
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/scripts/nx-nav-spy.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Home: evidenzia in bianco il link del menu che corrisponde alla sezione in vista.
|
||||||
|
*/
|
||||||
|
const SECTION_IDS = ['prodotti', 'servizi', 'stack', 'faq', 'contatti'] as const;
|
||||||
|
const HEADER_OFFSET = 88;
|
||||||
|
|
||||||
|
function getActiveSectionId(): string | null {
|
||||||
|
const y = window.scrollY + HEADER_OFFSET;
|
||||||
|
let active: string | null = null;
|
||||||
|
for (const id of SECTION_IDS) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (!el) continue;
|
||||||
|
if (el.offsetTop <= y) active = id;
|
||||||
|
}
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyActive(): void {
|
||||||
|
const active = getActiveSectionId();
|
||||||
|
document.querySelectorAll<HTMLAnchorElement>('a[data-section]').forEach((a) => {
|
||||||
|
const id = a.dataset.section;
|
||||||
|
const on = Boolean(id && id === active);
|
||||||
|
a.dataset.navActive = on ? 'true' : 'false';
|
||||||
|
if (on) a.setAttribute('aria-current', 'true');
|
||||||
|
else a.removeAttribute('aria-current');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let scheduled = false;
|
||||||
|
function onScroll(): void {
|
||||||
|
if (scheduled) return;
|
||||||
|
scheduled = true;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
scheduled = false;
|
||||||
|
applyActive();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind(): void {
|
||||||
|
const path = window.location.pathname;
|
||||||
|
if (path !== '/' && path !== '/index.html') return;
|
||||||
|
|
||||||
|
applyActive();
|
||||||
|
window.addEventListener('scroll', onScroll, { passive: true });
|
||||||
|
window.addEventListener('resize', onScroll, { passive: true });
|
||||||
|
window.addEventListener('hashchange', applyActive);
|
||||||
|
window.addEventListener('load', applyActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', bind, { once: true });
|
||||||
|
} else {
|
||||||
|
bind();
|
||||||
|
}
|
||||||
|
}
|
||||||
476
src/styles/global.css
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
||||||
|
--font-mono: "JetBrains Mono", ui-monospace, monospace;
|
||||||
|
|
||||||
|
--color-nx-bg: #030712;
|
||||||
|
--color-nx-surface: #0b1220;
|
||||||
|
--color-nx-elevated: #111c2e;
|
||||||
|
--color-nx-border: rgba(148, 163, 184, 0.12);
|
||||||
|
--color-nx-muted: #94a3b8;
|
||||||
|
--color-nx-fg: #f1f5f9;
|
||||||
|
--color-nx-accent: #38bdf8;
|
||||||
|
--color-nx-accent-2: #a78bfa;
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-nx-bg text-nx-fg antialiased;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.nx-nav-link {
|
||||||
|
@apply inline-block transition-colors duration-200;
|
||||||
|
transition-property: color, filter, text-shadow, opacity;
|
||||||
|
}
|
||||||
|
.nx-nav-link[data-nav-active='false'] {
|
||||||
|
@apply text-nx-muted hover:text-nx-fg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Canvas particelle: dentro .nx-grid-bg, sopra lo sfondo della sezione */
|
||||||
|
.nx-particle-layer {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 0;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subtle grid (Vercel-like) + vignette */
|
||||||
|
.nx-grid-bg {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(15, 23, 42, 0.85), rgba(3, 7, 18, 0.95)),
|
||||||
|
linear-gradient(rgba(56, 189, 248, 0.03) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(56, 189, 248, 0.03) 1px, transparent 1px);
|
||||||
|
background-size: 100% 100%, 64px 64px, 64px 64px;
|
||||||
|
background-position: 0 0, -1px -1px, -1px -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code window title bar dots */
|
||||||
|
.nx-dot {
|
||||||
|
@apply size-2.5 rounded-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
[x-cloak] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hero: flusso dati a destra, altezza piena sezione — maschere morbide (no tagli netti) */
|
||||||
|
.nx-hero-ambient {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-hero-ambient__inner {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: -14%;
|
||||||
|
bottom: 0;
|
||||||
|
width: min(56vw, 560px);
|
||||||
|
height: 100%;
|
||||||
|
opacity: 0.62;
|
||||||
|
mask-image:
|
||||||
|
linear-gradient(to bottom, transparent 0%, black 6%, black 94%, transparent 100%),
|
||||||
|
linear-gradient(to left, black 0%, black 42%, transparent 88%);
|
||||||
|
-webkit-mask-image:
|
||||||
|
linear-gradient(to bottom, transparent 0%, black 6%, black 94%, transparent 100%),
|
||||||
|
linear-gradient(to left, black 0%, black 42%, transparent 88%);
|
||||||
|
-webkit-mask-composite: source-in;
|
||||||
|
mask-composite: intersect;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-hero-dataflow-svg {
|
||||||
|
display: block;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-hero-dataflow-svg .nx-flow-line--a {
|
||||||
|
animation: nx-dataflow-dash 20s linear infinite;
|
||||||
|
}
|
||||||
|
.nx-hero-dataflow-svg .nx-flow-line--b {
|
||||||
|
animation: nx-dataflow-dash 28s linear infinite;
|
||||||
|
animation-direction: reverse;
|
||||||
|
}
|
||||||
|
.nx-hero-dataflow-svg .nx-flow-line--c {
|
||||||
|
animation: nx-dataflow-dash 24s linear infinite;
|
||||||
|
}
|
||||||
|
.nx-hero-dataflow-svg .nx-flow-line--d {
|
||||||
|
animation: nx-dataflow-dash 32s linear infinite;
|
||||||
|
animation-direction: reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes nx-dataflow-dash {
|
||||||
|
to {
|
||||||
|
stroke-dashoffset: -120;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.nx-hero-ambient__inner {
|
||||||
|
opacity: 0.42;
|
||||||
|
width: min(78vw, 420px);
|
||||||
|
right: -18%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spine: prolunga i flussi hero verso la fascia “Prodotti” (dietro al contenuto) */
|
||||||
|
.nx-dataflow-spine {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0.46;
|
||||||
|
mask-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
transparent 0,
|
||||||
|
transparent max(9vh, 64px),
|
||||||
|
rgba(0, 0, 0, 0.28) min(20vh, 180px),
|
||||||
|
rgba(0, 0, 0, 0.88) min(36vh, 400px),
|
||||||
|
black min(72%, 100%),
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
-webkit-mask-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
transparent 0,
|
||||||
|
transparent max(9vh, 64px),
|
||||||
|
rgba(0, 0, 0, 0.28) min(20vh, 180px),
|
||||||
|
rgba(0, 0, 0, 0.88) min(36vh, 400px),
|
||||||
|
black min(72%, 100%),
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-dataflow-spine-svg {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-dataflow-spine-svg .nx-flow-line--a {
|
||||||
|
animation: nx-dataflow-dash 24s linear infinite;
|
||||||
|
}
|
||||||
|
.nx-dataflow-spine-svg .nx-flow-line--b {
|
||||||
|
animation: nx-dataflow-dash 34s linear infinite;
|
||||||
|
animation-direction: reverse;
|
||||||
|
}
|
||||||
|
.nx-dataflow-spine-svg .nx-flow-line--c {
|
||||||
|
animation: nx-dataflow-dash 26s linear infinite;
|
||||||
|
}
|
||||||
|
.nx-dataflow-spine-svg .nx-flow-line--d {
|
||||||
|
animation: nx-dataflow-dash 36s linear infinite;
|
||||||
|
animation-direction: reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.nx-dataflow-spine {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.nx-hero-dataflow-svg .nx-flow-line--a,
|
||||||
|
.nx-hero-dataflow-svg .nx-flow-line--b,
|
||||||
|
.nx-hero-dataflow-svg .nx-flow-line--c,
|
||||||
|
.nx-hero-dataflow-svg .nx-flow-line--d {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
.nx-dataflow-spine-svg .nx-flow-line--a,
|
||||||
|
.nx-dataflow-spine-svg .nx-flow-line--b,
|
||||||
|
.nx-dataflow-spine-svg .nx-flow-line--c,
|
||||||
|
.nx-dataflow-spine-svg .nx-flow-line--d {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stats: alone morbido attorno alla card (sky / violet) */
|
||||||
|
.nx-stats-card {
|
||||||
|
box-shadow:
|
||||||
|
0 24px 48px -12px rgba(0, 0, 0, 0.55),
|
||||||
|
0 0 56px -12px rgba(56, 189, 248, 0.24),
|
||||||
|
0 0 96px -24px rgba(167, 139, 250, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stack: carosello loghi (marquee), fade ai bordi */
|
||||||
|
.nx-tech-slider__viewport {
|
||||||
|
overflow: hidden;
|
||||||
|
mask-image: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent 0%,
|
||||||
|
#000 5%,
|
||||||
|
#000 95%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
-webkit-mask-image: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent 0%,
|
||||||
|
#000 5%,
|
||||||
|
#000 95%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Un’unica fila di link: lo stesso gap vale anche tra ultima e prima copia (niente OpenAI “incollato” a Cloudflare) */
|
||||||
|
.nx-tech-slider__track {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
width: max-content;
|
||||||
|
gap: clamp(2.75rem, 8vw, 5rem);
|
||||||
|
animation: nx-tech-marquee 60s linear infinite;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-tech-slider__link {
|
||||||
|
display: flex;
|
||||||
|
flex: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 3rem;
|
||||||
|
opacity: 0.72;
|
||||||
|
transition: opacity 0.2s ease, filter 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-tech-slider__link:hover {
|
||||||
|
opacity: 1;
|
||||||
|
filter: brightness(1.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-tech-slider__link:focus-visible {
|
||||||
|
outline: 2px solid rgba(56, 189, 248, 0.45);
|
||||||
|
outline-offset: 4px;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-tech-slider__logo {
|
||||||
|
height: 2.5rem;
|
||||||
|
width: auto;
|
||||||
|
max-width: 8.5rem;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.nx-tech-slider__logo {
|
||||||
|
height: 2.875rem;
|
||||||
|
max-width: 9.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badge testuale HL7 FHIR: più largo ma stessa altezza riga */
|
||||||
|
.nx-tech-slider__logo--fhir {
|
||||||
|
max-width: 11.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.nx-tech-slider__logo--fhir {
|
||||||
|
max-width: 12.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes nx-tech-marquee {
|
||||||
|
from {
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate3d(-50%, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.nx-tech-slider__link--dup {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-tech-slider__viewport {
|
||||||
|
overflow: visible;
|
||||||
|
mask-image: none;
|
||||||
|
-webkit-mask-image: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-tech-slider__track {
|
||||||
|
animation: none;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 52rem;
|
||||||
|
margin-inline: auto;
|
||||||
|
row-gap: 1.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Stampa: pagine con `nx-print-sheet` (SubpageLayout, `printFriendly` predefinito true) --- */
|
||||||
|
@media print {
|
||||||
|
@page {
|
||||||
|
margin: 14mm 16mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nx-chat-gate-overlay,
|
||||||
|
#nx-cc-banner,
|
||||||
|
#nx-cc-dialog,
|
||||||
|
#particleCanvas {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet {
|
||||||
|
min-height: 0 !important;
|
||||||
|
background: #fff !important;
|
||||||
|
background-image: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet header {
|
||||||
|
position: static !important;
|
||||||
|
border-bottom: 1px solid #cbd5e1 !important;
|
||||||
|
background: #fff !important;
|
||||||
|
backdrop-filter: none !important;
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet header a {
|
||||||
|
color: #0f172a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet main {
|
||||||
|
padding-top: 0.75rem !important;
|
||||||
|
padding-bottom: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet .nx-print-meta {
|
||||||
|
display: block !important;
|
||||||
|
border-color: #cbd5e1 !important;
|
||||||
|
color: #475569 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet h1,
|
||||||
|
.nx-print-sheet .text-nx-fg {
|
||||||
|
color: #0f172a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet h2 {
|
||||||
|
color: #0f172a !important;
|
||||||
|
break-after: avoid;
|
||||||
|
page-break-after: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet p,
|
||||||
|
.nx-print-sheet li,
|
||||||
|
.nx-print-sheet .text-nx-muted {
|
||||||
|
color: #334155 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet a {
|
||||||
|
color: #0369a1 !important;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet a[href^="http"]::after {
|
||||||
|
content: " (" attr(href) ")";
|
||||||
|
font-size: 0.78em;
|
||||||
|
font-weight: 400;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet footer {
|
||||||
|
border-color: #cbd5e1 !important;
|
||||||
|
background: #fff !important;
|
||||||
|
break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet footer [data-nx-footer-print-collapsible] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet article {
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet table,
|
||||||
|
.nx-print-sheet th,
|
||||||
|
.nx-print-sheet td {
|
||||||
|
border-color: #94a3b8 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet th {
|
||||||
|
background: #f1f5f9 !important;
|
||||||
|
color: #0f172a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nx-print-sheet td {
|
||||||
|
color: #334155 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stato attivo menu home: fuori da @layer così vince su utilities Tailwind.
|
||||||
|
* Sottolineatura luminosa + ombre (testo + filtro) ben visibili su header nero.
|
||||||
|
*/
|
||||||
|
header a.nx-nav-link[data-nav-active='true'] {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
color: #f8fafc;
|
||||||
|
font-weight: 500;
|
||||||
|
text-shadow:
|
||||||
|
0 0 1px #fff,
|
||||||
|
0 0 14px rgba(255, 255, 255, 0.5),
|
||||||
|
0 0 24px rgba(56, 189, 248, 0.55);
|
||||||
|
filter: drop-shadow(0 0 3px rgba(255, 255, 255, 0.55)) drop-shadow(0 0 10px rgba(56, 189, 248, 0.45));
|
||||||
|
}
|
||||||
|
|
||||||
|
header a.nx-nav-link[data-nav-active='true']::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: -0.5rem;
|
||||||
|
height: 2px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent 0%,
|
||||||
|
rgba(255, 255, 255, 0.2) 15%,
|
||||||
|
#e0f2fe 50%,
|
||||||
|
rgba(255, 255, 255, 0.2) 85%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
box-shadow:
|
||||||
|
0 0 8px 1px rgba(255, 255, 255, 0.4),
|
||||||
|
0 0 16px 3px rgba(56, 189, 248, 0.4);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a.nx-nav-link[data-nav-active='false'] {
|
||||||
|
filter: none;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a.nx-nav-link[data-nav-active='false']::after {
|
||||||
|
content: none;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
header a.nx-nav-link[data-nav-active='true'] {
|
||||||
|
text-shadow: 0 0 8px rgba(255, 255, 255, 0.4);
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
header a.nx-nav-link[data-nav-active='true']::after {
|
||||||
|
box-shadow: 0 0 6px rgba(56, 189, 248, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict",
|
||||||
|
"include": [
|
||||||
|
".astro/types.d.ts",
|
||||||
|
"**/*",
|
||||||
|
"./worker-configuration.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
|
}
|
||||||
13022
worker-configuration.d.ts
vendored
Normal file
13
wrangler.jsonc
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compatibility_date": "2026-04-18",
|
||||||
|
"compatibility_flags": ["global_fetch_strictly_public"],
|
||||||
|
"name": "nexstudio",
|
||||||
|
"main": "@astrojs/cloudflare/entrypoints/server",
|
||||||
|
"assets": {
|
||||||
|
"directory": "./dist",
|
||||||
|
"binding": "ASSETS"
|
||||||
|
},
|
||||||
|
"observability": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||