Aggiunge menu lingua dropdown e base i18n IT/EN/TH.
Introduce routing multilingua con locale tailandese, selettore lingua a tendina con icone bandiera SVG e gestione chiusura click-outside/Esc, oltre a integrazione Turnstile lato form contatti. Made-with: Cursor
This commit is contained in:
@ -37,4 +37,11 @@ export default defineConfig({
|
|||||||
|
|
||||||
adapter: cloudflare(),
|
adapter: cloudflare(),
|
||||||
integrations: [alpinejs()],
|
integrations: [alpinejs()],
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: 'it',
|
||||||
|
locales: ['it', 'en', 'th'],
|
||||||
|
routing: {
|
||||||
|
prefixDefaultLocale: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
6
public/images/flags/en.svg
Normal file
6
public/images/flags/en.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" aria-hidden="true">
|
||||||
|
<rect width="48" height="32" fill="#012169" />
|
||||||
|
<path d="M0 0l48 32M48 0L0 32" stroke="#fff" stroke-width="6" />
|
||||||
|
<path d="M24 0v32M0 16h48" stroke="#fff" stroke-width="10" />
|
||||||
|
<path d="M24 0v32M0 16h48" stroke="#c8102e" stroke-width="6" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 333 B |
5
public/images/flags/it.svg
Normal file
5
public/images/flags/it.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" aria-hidden="true">
|
||||||
|
<rect width="16" height="32" x="0" y="0" fill="#009246" />
|
||||||
|
<rect width="16" height="32" x="16" y="0" fill="#ffffff" />
|
||||||
|
<rect width="16" height="32" x="32" y="0" fill="#ce2b37" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 272 B |
5
public/images/flags/th.svg
Normal file
5
public/images/flags/th.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" aria-hidden="true">
|
||||||
|
<rect width="48" height="32" x="0" y="0" fill="#a51931" />
|
||||||
|
<rect width="48" height="24" x="0" y="4" fill="#ffffff" />
|
||||||
|
<rect width="48" height="16" x="0" y="8" fill="#2d2a4a" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 270 B |
@ -5,6 +5,7 @@ const cf = cta.contactForm;
|
|||||||
const model = {
|
const model = {
|
||||||
actionUrl: cf.actionUrl,
|
actionUrl: cf.actionUrl,
|
||||||
mailto: cf.mailto,
|
mailto: cf.mailto,
|
||||||
|
turnstileSiteKey: cf.turnstileSiteKey,
|
||||||
notifySubject: cf.notifySubject,
|
notifySubject: cf.notifySubject,
|
||||||
successRedirect: cf.successRedirect,
|
successRedirect: cf.successRedirect,
|
||||||
fieldName: cf.fieldName,
|
fieldName: cf.fieldName,
|
||||||
@ -15,6 +16,8 @@ const model = {
|
|||||||
};
|
};
|
||||||
const hasFormspree =
|
const hasFormspree =
|
||||||
typeof cf.actionUrl === 'string' && cf.actionUrl.length > 0;
|
typeof cf.actionUrl === 'string' && cf.actionUrl.length > 0;
|
||||||
|
const hasTurnstile =
|
||||||
|
typeof cf.turnstileSiteKey === 'string' && cf.turnstileSiteKey.length > 0;
|
||||||
const nextUrl =
|
const nextUrl =
|
||||||
typeof cf.successRedirect === 'string' && cf.successRedirect.length > 0
|
typeof cf.successRedirect === 'string' && cf.successRedirect.length > 0
|
||||||
? cf.successRedirect
|
? cf.successRedirect
|
||||||
@ -178,6 +181,23 @@ const fieldClass =
|
|||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{
|
||||||
|
hasFormspree && hasTurnstile ? (
|
||||||
|
<div class="pt-1">
|
||||||
|
<div
|
||||||
|
class="cf-turnstile"
|
||||||
|
data-sitekey={cf.turnstileSiteKey}
|
||||||
|
data-theme="dark"
|
||||||
|
data-language="it"
|
||||||
|
data-size="normal"
|
||||||
|
></div>
|
||||||
|
<p class="mt-1.5 text-xs leading-relaxed text-nx-muted">
|
||||||
|
Verifica anti-spam: completa il controllo prima di inviare il messaggio.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
<div class="pt-1">
|
<div class="pt-1">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
@ -191,6 +211,16 @@ const fieldClass =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
hasFormspree && hasTurnstile ? (
|
||||||
|
<script
|
||||||
|
src="https://challenges.cloudflare.com/turnstile/v0/api.js"
|
||||||
|
async
|
||||||
|
defer
|
||||||
|
></script>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import '../scripts/nx-contact-form.ts';
|
import '../scripts/nx-contact-form.ts';
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -17,6 +17,16 @@ const notifySubject =
|
|||||||
|
|
||||||
const linkClass =
|
const linkClass =
|
||||||
'text-sm text-sky-400/90 transition-colors hover:text-sky-300';
|
'text-sm text-sky-400/90 transition-colors hover:text-sky-300';
|
||||||
|
const path = Astro.url.pathname;
|
||||||
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
|
const currentLocale = localeMatch?.[1] ?? 'it';
|
||||||
|
const withLocale = (href: string) => {
|
||||||
|
if (currentLocale === 'it') return href;
|
||||||
|
if (href.startsWith(`/${currentLocale}`)) return href;
|
||||||
|
if (href.startsWith('/#')) return `/${currentLocale}${href}`;
|
||||||
|
if (href.startsWith('/')) return `/${currentLocale}${href}`;
|
||||||
|
return href;
|
||||||
|
};
|
||||||
---
|
---
|
||||||
|
|
||||||
<footer id="site-footer" class="relative z-[1] border-t border-nx-border px-4 py-12 sm:px-6">
|
<footer id="site-footer" class="relative z-[1] border-t border-nx-border px-4 py-12 sm:px-6">
|
||||||
@ -35,7 +45,7 @@ const linkClass =
|
|||||||
<ul class="space-y-2.5">
|
<ul class="space-y-2.5">
|
||||||
{footer.homeNav.map((link) => (
|
{footer.homeNav.map((link) => (
|
||||||
<li>
|
<li>
|
||||||
<a class={linkClass} href={link.href}>
|
<a class={linkClass} href={withLocale(link.href)}>
|
||||||
{link.label}
|
{link.label}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@ -52,7 +62,7 @@ const linkClass =
|
|||||||
<ul class="space-y-2.5">
|
<ul class="space-y-2.5">
|
||||||
{footer.legalColumnNav.map((link) => (
|
{footer.legalColumnNav.map((link) => (
|
||||||
<li>
|
<li>
|
||||||
<a class={linkClass} href={link.href}>
|
<a class={linkClass} href={withLocale(link.href)}>
|
||||||
{link.label}
|
{link.label}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@ -101,7 +111,7 @@ const linkClass =
|
|||||||
{footer.companyNav.map((link) => (
|
{footer.companyNav.map((link) => (
|
||||||
<li>
|
<li>
|
||||||
{link.href ? (
|
{link.href ? (
|
||||||
<a class={linkClass} href={link.href}>
|
<a class={linkClass} href={withLocale(link.href)}>
|
||||||
{link.label}
|
{link.label}
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -30,6 +30,11 @@ export const cta = {
|
|||||||
actionUrl: '',
|
actionUrl: '',
|
||||||
/** Formspree: redirect dopo invio (`_next`) — URL **assoluta** in produzione */
|
/** Formspree: redirect dopo invio (`_next`) — URL **assoluta** in produzione */
|
||||||
successRedirect: '',
|
successRedirect: '',
|
||||||
|
/**
|
||||||
|
* Cloudflare Turnstile (anti-spam): inserisci qui la site key pubblica.
|
||||||
|
* Se vuota, il CAPTCHA non viene mostrato.
|
||||||
|
*/
|
||||||
|
turnstileSiteKey: '',
|
||||||
/** Formspree `_subject` e oggetto mailto */
|
/** Formspree `_subject` e oggetto mailto */
|
||||||
notifySubject: 'Richiesta contatto — sito NexStudio',
|
notifySubject: 'Richiesta contatto — sito NexStudio',
|
||||||
/** Se `actionUrl` è vuoto: email per `mailto:` (es. `info@tuodominio.com`) */
|
/** Se `actionUrl` è vuoto: email per `mailto:` (es. `info@tuodominio.com`) */
|
||||||
|
|||||||
@ -12,10 +12,13 @@ const {
|
|||||||
title = 'NexStudio — Software su misura',
|
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.',
|
description = 'Studio di sviluppo software: web app, API, automazioni e prodotti digitali, con focus su qualità e time-to-market.',
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
|
const pathname = Astro.url.pathname;
|
||||||
|
const localeMatch = pathname.match(/^\/(en|th)(\/|$)/);
|
||||||
|
const htmlLang = localeMatch?.[1] ?? 'it';
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="it" class="bg-nx-bg">
|
<html lang={htmlLang} class="bg-nx-bg">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|||||||
@ -27,6 +27,26 @@ const printDate = new Intl.DateTimeFormat('it-IT', {
|
|||||||
dateStyle: 'long',
|
dateStyle: 'long',
|
||||||
}).format(new Date());
|
}).format(new Date());
|
||||||
const printSource = Astro.url.href;
|
const printSource = Astro.url.href;
|
||||||
|
const path = Astro.url.pathname;
|
||||||
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
|
const currentLocale = localeMatch?.[1] ?? 'it';
|
||||||
|
const strippedPath = path.replace(/^\/(en|th)(?=\/|$)/, '') || '/';
|
||||||
|
const localeHref = (locale: 'it' | 'en' | 'th') => {
|
||||||
|
if (locale === 'it') return strippedPath;
|
||||||
|
return `/${locale}${strippedPath === '/' ? '' : strippedPath}`;
|
||||||
|
};
|
||||||
|
const localize = (href: string) => {
|
||||||
|
if (currentLocale === 'it') return href;
|
||||||
|
if (href.startsWith(`/${currentLocale}`)) return href;
|
||||||
|
if (href.startsWith('/#')) return `/${currentLocale}${href}`;
|
||||||
|
if (href.startsWith('/')) return `/${currentLocale}${href}`;
|
||||||
|
return href;
|
||||||
|
};
|
||||||
|
const langMeta = {
|
||||||
|
it: { code: 'IT', label: 'Italiano', flagSrc: '/images/flags/it.svg' },
|
||||||
|
en: { code: 'EN', label: 'English', flagSrc: '/images/flags/en.svg' },
|
||||||
|
th: { code: 'TH', label: 'ไทย', flagSrc: '/images/flags/th.svg' },
|
||||||
|
} as const;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={title} description={description}>
|
<BaseLayout title={title} description={description}>
|
||||||
@ -43,14 +63,75 @@ const printSource = Astro.url.href;
|
|||||||
class="mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-3 sm:px-6"
|
class="mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-3 sm:px-6"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="/"
|
href={localize('/')}
|
||||||
class="text-lg font-semibold tracking-tight text-nx-fg hover:text-white sm:text-xl"
|
class="text-lg font-semibold tracking-tight text-nx-fg hover:text-white sm:text-xl"
|
||||||
>NexStudio</a
|
>NexStudio</a
|
||||||
>
|
>
|
||||||
<a
|
<div class="flex items-center gap-2">
|
||||||
class="text-sm font-medium text-sky-400/90 hover:text-sky-300"
|
<details class="relative" data-lang-dropdown>
|
||||||
href="/#contatti">Contattaci</a
|
<summary
|
||||||
>
|
class="inline-flex h-10 cursor-pointer list-none items-center gap-2 rounded-lg border border-nx-border/80 bg-nx-bg/50 px-3 text-xs font-medium text-nx-fg transition-colors hover:border-sky-500/35 hover:text-white"
|
||||||
|
aria-label="Seleziona lingua"
|
||||||
|
>
|
||||||
|
<img src={langMeta[currentLocale].flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||||
|
<span>{langMeta[currentLocale].code}</span>
|
||||||
|
<svg class="h-3.5 w-3.5 text-nx-muted" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
|
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 0 1 1.06.02L10 11.168l3.71-3.938a.75.75 0 1 1 1.08 1.04l-4.25 4.5a.75.75 0 0 1-1.08 0l-4.25-4.5a.75.75 0 0 1 .02-1.06Z" clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
</summary>
|
||||||
|
<div
|
||||||
|
class="absolute right-0 top-12 z-50 min-w-44 overflow-hidden rounded-lg border border-nx-border bg-nx-elevated/95 p-1 shadow-xl backdrop-blur"
|
||||||
|
role="menu"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={localeHref('it')}
|
||||||
|
class:list={[
|
||||||
|
'flex items-center justify-between rounded-md px-3 py-2 text-sm text-nx-muted transition-colors hover:bg-white/5 hover:text-nx-fg',
|
||||||
|
currentLocale === 'it' && 'bg-white/5 text-nx-fg',
|
||||||
|
]}
|
||||||
|
aria-current={currentLocale === 'it' ? 'page' : undefined}
|
||||||
|
>
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<img src={langMeta.it.flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||||
|
{langMeta.it.label}
|
||||||
|
</span>
|
||||||
|
<span class="text-xs">{langMeta.it.code}</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={localeHref('en')}
|
||||||
|
class:list={[
|
||||||
|
'flex items-center justify-between rounded-md px-3 py-2 text-sm text-nx-muted transition-colors hover:bg-white/5 hover:text-nx-fg',
|
||||||
|
currentLocale === 'en' && 'bg-white/5 text-nx-fg',
|
||||||
|
]}
|
||||||
|
aria-current={currentLocale === 'en' ? 'page' : undefined}
|
||||||
|
>
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<img src={langMeta.en.flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||||
|
{langMeta.en.label}
|
||||||
|
</span>
|
||||||
|
<span class="text-xs">{langMeta.en.code}</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={localeHref('th')}
|
||||||
|
class:list={[
|
||||||
|
'flex items-center justify-between rounded-md px-3 py-2 text-sm text-nx-muted transition-colors hover:bg-white/5 hover:text-nx-fg',
|
||||||
|
currentLocale === 'th' && 'bg-white/5 text-nx-fg',
|
||||||
|
]}
|
||||||
|
aria-current={currentLocale === 'th' ? 'page' : undefined}
|
||||||
|
>
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<img src={langMeta.th.flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||||
|
{langMeta.th.label}
|
||||||
|
</span>
|
||||||
|
<span class="text-xs">{langMeta.th.code}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<a
|
||||||
|
class="text-sm font-medium text-sky-400/90 hover:text-sky-300"
|
||||||
|
href={localize('/#contatti')}>Contattaci</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main class="relative z-[1] flex-1 px-4 py-12 sm:px-6 sm:py-16">
|
<main class="relative z-[1] flex-1 px-4 py-12 sm:px-6 sm:py-16">
|
||||||
@ -86,3 +167,7 @@ const printSource = Astro.url.href;
|
|||||||
<SiteFooter />
|
<SiteFooter />
|
||||||
</div>
|
</div>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import '../scripts/nx-lang-dropdown.ts';
|
||||||
|
</script>
|
||||||
|
|||||||
5
src/pages/en/about.astro
Normal file
5
src/pages/en/about.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../about.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/en/codice-etico.astro
Normal file
5
src/pages/en/codice-etico.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../codice-etico.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/en/cookies.astro
Normal file
5
src/pages/en/cookies.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../cookies.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/en/dove-siamo.astro
Normal file
5
src/pages/en/dove-siamo.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../dove-siamo.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/en/gdpr.astro
Normal file
5
src/pages/en/gdpr.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../gdpr.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/en/index.astro
Normal file
5
src/pages/en/index.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../index.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/en/modello-organizzativo.astro
Normal file
5
src/pages/en/modello-organizzativo.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../modello-organizzativo.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/en/news.astro
Normal file
5
src/pages/en/news.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../news.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/en/newsletter/grazie.astro
Normal file
5
src/pages/en/newsletter/grazie.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../../newsletter/grazie.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/en/privacy.astro
Normal file
5
src/pages/en/privacy.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../privacy.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/en/terms.astro
Normal file
5
src/pages/en/terms.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../terms.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
@ -18,6 +18,24 @@ import {
|
|||||||
} from '../data/home';
|
} from '../data/home';
|
||||||
|
|
||||||
const techSliderItems = stackSection.techSlider.items;
|
const techSliderItems = stackSection.techSlider.items;
|
||||||
|
const path = Astro.url.pathname;
|
||||||
|
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||||
|
const currentLocale = localeMatch?.[1] ?? 'it';
|
||||||
|
const localize = (href: string) => {
|
||||||
|
if (currentLocale === 'it') return href;
|
||||||
|
if (href.startsWith(`/${currentLocale}`)) return href;
|
||||||
|
if (href.startsWith('/#')) return `/${currentLocale}${href}`;
|
||||||
|
if (href.startsWith('/')) return `/${currentLocale}${href}`;
|
||||||
|
return href;
|
||||||
|
};
|
||||||
|
const langItHref = '/';
|
||||||
|
const langEnHref = '/en';
|
||||||
|
const langThHref = '/th';
|
||||||
|
const langMeta = {
|
||||||
|
it: { code: 'IT', label: 'Italiano', flagSrc: '/images/flags/it.svg' },
|
||||||
|
en: { code: 'EN', label: 'English', flagSrc: '/images/flags/en.svg' },
|
||||||
|
th: { code: 'TH', label: 'ไทย', flagSrc: '/images/flags/th.svg' },
|
||||||
|
} as const;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={siteMeta.title} description={siteMeta.description}>
|
<BaseLayout title={siteMeta.title} description={siteMeta.description}>
|
||||||
@ -37,7 +55,7 @@ const techSliderItems = stackSection.techSlider.items;
|
|||||||
<div
|
<div
|
||||||
class="mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-3 sm:px-6"
|
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">
|
<a href={localize('/')} class="group no-underline">
|
||||||
<span
|
<span
|
||||||
class="text-2xl font-semibold tracking-tight text-nx-fg group-hover:text-white sm:text-3xl"
|
class="text-2xl font-semibold tracking-tight text-nx-fg group-hover:text-white sm:text-3xl"
|
||||||
>{navigation.brandName}</span
|
>{navigation.brandName}</span
|
||||||
@ -55,7 +73,7 @@ const techSliderItems = stackSection.techSlider.items;
|
|||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
class="nx-nav-link"
|
class="nx-nav-link"
|
||||||
href={item.href}
|
href={localize(item.href)}
|
||||||
data-section={sectionId}
|
data-section={sectionId}
|
||||||
data-nav-active="false"
|
data-nav-active="false"
|
||||||
>
|
>
|
||||||
@ -66,6 +84,65 @@ const techSliderItems = stackSection.techSlider.items;
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="flex items-center gap-2 sm:gap-3">
|
<div class="flex items-center gap-2 sm:gap-3">
|
||||||
|
<details class="relative hidden sm:block" data-lang-dropdown>
|
||||||
|
<summary
|
||||||
|
class="inline-flex h-10 cursor-pointer list-none items-center gap-2 rounded-lg border border-nx-border/80 bg-nx-bg/50 px-3 text-xs font-medium text-nx-fg transition-colors hover:border-sky-500/35 hover:text-white"
|
||||||
|
aria-label="Seleziona lingua"
|
||||||
|
>
|
||||||
|
<img src={langMeta[currentLocale].flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||||
|
<span>{langMeta[currentLocale].code}</span>
|
||||||
|
<svg class="h-3.5 w-3.5 text-nx-muted" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
|
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 0 1 1.06.02L10 11.168l3.71-3.938a.75.75 0 1 1 1.08 1.04l-4.25 4.5a.75.75 0 0 1-1.08 0l-4.25-4.5a.75.75 0 0 1 .02-1.06Z" clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
</summary>
|
||||||
|
<div
|
||||||
|
class="absolute right-0 top-12 z-50 min-w-44 overflow-hidden rounded-lg border border-nx-border bg-nx-elevated/95 p-1 shadow-xl backdrop-blur"
|
||||||
|
role="menu"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={langItHref}
|
||||||
|
class:list={[
|
||||||
|
'flex items-center justify-between rounded-md px-3 py-2 text-sm text-nx-muted transition-colors hover:bg-white/5 hover:text-nx-fg',
|
||||||
|
currentLocale === 'it' && 'bg-white/5 text-nx-fg',
|
||||||
|
]}
|
||||||
|
aria-current={currentLocale === 'it' ? 'page' : undefined}
|
||||||
|
>
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<img src={langMeta.it.flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||||
|
{langMeta.it.label}
|
||||||
|
</span>
|
||||||
|
<span class="text-xs">{langMeta.it.code}</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={langEnHref}
|
||||||
|
class:list={[
|
||||||
|
'flex items-center justify-between rounded-md px-3 py-2 text-sm text-nx-muted transition-colors hover:bg-white/5 hover:text-nx-fg',
|
||||||
|
currentLocale === 'en' && 'bg-white/5 text-nx-fg',
|
||||||
|
]}
|
||||||
|
aria-current={currentLocale === 'en' ? 'page' : undefined}
|
||||||
|
>
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<img src={langMeta.en.flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||||
|
{langMeta.en.label}
|
||||||
|
</span>
|
||||||
|
<span class="text-xs">{langMeta.en.code}</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={langThHref}
|
||||||
|
class:list={[
|
||||||
|
'flex items-center justify-between rounded-md px-3 py-2 text-sm text-nx-muted transition-colors hover:bg-white/5 hover:text-nx-fg',
|
||||||
|
currentLocale === 'th' && 'bg-white/5 text-nx-fg',
|
||||||
|
]}
|
||||||
|
aria-current={currentLocale === 'th' ? 'page' : undefined}
|
||||||
|
>
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<img src={langMeta.th.flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||||
|
{langMeta.th.label}
|
||||||
|
</span>
|
||||||
|
<span class="text-xs">{langMeta.th.code}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-nx-chat-open
|
data-nx-chat-open
|
||||||
@ -122,7 +199,7 @@ const techSliderItems = stackSection.techSlider.items;
|
|||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
class="nx-nav-link block py-1"
|
class="nx-nav-link block py-1"
|
||||||
href={item.href}
|
href={localize(item.href)}
|
||||||
data-section={sectionId}
|
data-section={sectionId}
|
||||||
data-nav-active="false"
|
data-nav-active="false"
|
||||||
@click="mobile = false"
|
@click="mobile = false"
|
||||||
@ -663,5 +740,6 @@ const techSliderItems = stackSection.techSlider.items;
|
|||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
import '../scripts/nx-nav-spy.ts';
|
import '../scripts/nx-nav-spy.ts';
|
||||||
|
import '../scripts/nx-lang-dropdown.ts';
|
||||||
</script>
|
</script>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
5
src/pages/th/about.astro
Normal file
5
src/pages/th/about.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../about.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/th/codice-etico.astro
Normal file
5
src/pages/th/codice-etico.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../codice-etico.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/th/cookies.astro
Normal file
5
src/pages/th/cookies.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../cookies.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/th/dove-siamo.astro
Normal file
5
src/pages/th/dove-siamo.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../dove-siamo.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/th/gdpr.astro
Normal file
5
src/pages/th/gdpr.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../gdpr.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/th/index.astro
Normal file
5
src/pages/th/index.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../index.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/th/modello-organizzativo.astro
Normal file
5
src/pages/th/modello-organizzativo.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../modello-organizzativo.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/th/news.astro
Normal file
5
src/pages/th/news.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../news.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/th/newsletter/grazie.astro
Normal file
5
src/pages/th/newsletter/grazie.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../../newsletter/grazie.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/th/privacy.astro
Normal file
5
src/pages/th/privacy.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../privacy.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
5
src/pages/th/terms.astro
Normal file
5
src/pages/th/terms.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Page from '../terms.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Page />
|
||||||
@ -2,6 +2,7 @@
|
|||||||
interface ContactFormCfg {
|
interface ContactFormCfg {
|
||||||
actionUrl: string;
|
actionUrl: string;
|
||||||
mailto: string;
|
mailto: string;
|
||||||
|
turnstileSiteKey: string;
|
||||||
notifySubject: string;
|
notifySubject: string;
|
||||||
successRedirect: string;
|
successRedirect: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
@ -18,6 +19,7 @@ function readCfg(): ContactFormCfg {
|
|||||||
return {
|
return {
|
||||||
actionUrl: '',
|
actionUrl: '',
|
||||||
mailto: '',
|
mailto: '',
|
||||||
|
turnstileSiteKey: '',
|
||||||
notifySubject: '',
|
notifySubject: '',
|
||||||
successRedirect: '',
|
successRedirect: '',
|
||||||
fieldName: 'name',
|
fieldName: 'name',
|
||||||
@ -39,6 +41,23 @@ function bind(): void {
|
|||||||
const err = document.getElementById('nx-contact-form-error');
|
const err = document.getElementById('nx-contact-form-error');
|
||||||
if (err) err.classList.add('hidden');
|
if (err) err.classList.add('hidden');
|
||||||
|
|
||||||
|
const hasTurnstile =
|
||||||
|
typeof cfg.turnstileSiteKey === 'string' && cfg.turnstileSiteKey.length > 0;
|
||||||
|
if (cfg.actionUrl && hasTurnstile) {
|
||||||
|
const token = form.querySelector<HTMLInputElement>(
|
||||||
|
'input[name="cf-turnstile-response"]',
|
||||||
|
)?.value;
|
||||||
|
if (!token) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (err) {
|
||||||
|
err.textContent =
|
||||||
|
'Completa il controllo anti-spam prima di inviare il messaggio.';
|
||||||
|
err.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (cfg.actionUrl) {
|
if (cfg.actionUrl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/scripts/nx-lang-dropdown.ts
Normal file
32
src/scripts/nx-lang-dropdown.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
function bindLanguageDropdown(): void {
|
||||||
|
const dropdowns = Array.from(
|
||||||
|
document.querySelectorAll<HTMLDetailsElement>('details[data-lang-dropdown]'),
|
||||||
|
);
|
||||||
|
if (!dropdowns.length) return;
|
||||||
|
|
||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
const target = event.target as Node | null;
|
||||||
|
dropdowns.forEach((dd) => {
|
||||||
|
if (!dd.open) return;
|
||||||
|
if (target && dd.contains(target)) return;
|
||||||
|
dd.open = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key !== 'Escape') return;
|
||||||
|
dropdowns.forEach((dd) => {
|
||||||
|
dd.open = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', bindLanguageDropdown, {
|
||||||
|
once: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
bindLanguageDropdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user