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
This commit is contained in:
Javaxman
2026-04-22 13:07:40 +02:00
commit 16c380dfda
64 changed files with 25028 additions and 0 deletions

618
src/pages/index.astro Normal file
View 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>