/* Route announcer */
	.astro-route-announcer {
		position: absolute;
		left: 0;
		top: 0;
		clip: rect(0 0 0 0);
		clip-path: inset(50%);
		overflow: hidden;
		white-space: nowrap;
		width: 1px;
		height: 1px;
	}.topo[data-astro-cid-q6p5o223] {
  opacity: var(--topo-opacity, .07);
}
.backdrop[data-astro-cid-xzqoch3y] {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}

.backdrop[data-astro-cid-xzqoch3y] .backdrop__lines {
  --drift: 18px;
  position: absolute;
  inset-block-start: calc(var(--drift) * -2);
  inset-inline-start: calc(var(--drift) * -2);
  inline-size: calc(100% + var(--drift) * 4);
  block-size: calc(100% + var(--drift) * 4);
  max-inline-size: none;
  color: var(--color-paper);
  translate: calc(var(--n46-px, 0) * var(--drift) * -1) calc(var(--n46-py, 0) * var(--drift) * -1);
  transition: translate .8s var(--ease-display);
}

@media (prefers-reduced-motion: reduce) {
  .backdrop[data-astro-cid-xzqoch3y] .backdrop__lines {
    translate: none;
    transition: none;
  }
}
.logo[data-astro-cid-5b3tql3r] {
  inline-size: auto;
  block-size: var(--logo-height, 2.25rem);
}
.site-header[data-astro-cid-blryxaq5]:has([data-menu-toggle][aria-expanded="true"]) {
  z-index: calc(var(--z-menu) + 1);
}

.site-header[data-astro-cid-blryxaq5] {
  position: sticky;
  inset-block-start: 0;
  z-index: var(--z-header);
  background-color: #0000;
  border-block-end: var(--border-hairline) solid transparent;
  transition: background-color var(--duration-short) var(--ease-out),
			border-color var(--duration-short) var(--ease-out);
}

.site-header[data-astro-cid-blryxaq5][data-stuck] {
  background-color: var(--color-ink);
  border-block-end-color: var(--rule);
}

@supports (backdrop-filter: blur(1px)) {
  .site-header[data-astro-cid-blryxaq5][data-stuck] {
    background-color: color-mix(in srgb, var(--color-ink) 90%, transparent);
    backdrop-filter: blur(12px) saturate(140%);
  }
}

#header-sentinel[data-astro-cid-blryxaq5] {
  position: absolute;
  inset-block-start: 0;
  block-size: 1px;
  inline-size: 100%;
}

.site-header__inner[data-astro-cid-blryxaq5] {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  min-block-size: var(--header-height);
}

.site-header__brand[data-astro-cid-blryxaq5] {
  display: inline-flex;
  align-items: center;
  margin-inline-end: auto;
  min-block-size: 2.75rem;
}

.site-header__nav[data-astro-cid-blryxaq5] {
  display: none;
}

.site-header__list[data-astro-cid-blryxaq5] {
  display: flex;
  align-items: center;
  gap: clamp(1.25rem, 2.5vw, 2.5rem);
  margin: 0;
}

.site-header__link[data-astro-cid-blryxaq5] {
  font-size: var(--text-small);
  font-weight: var(--weight-semibold);
  letter-spacing: .04em;
}

.site-header__link[data-astro-cid-blryxaq5][aria-current="page"] {
  color: var(--accent-text);
}

.site-header__link[data-astro-cid-blryxaq5][aria-current="page"]:after {
  background-color: var(--accent-mark);
}

.site-header__cta[data-astro-cid-blryxaq5], .site-header__shop[data-astro-cid-blryxaq5] {
  display: none;
}

.site-header__cta[data-astro-cid-blryxaq5], .site-header__shop[data-astro-cid-blryxaq5] {
  min-height: 2.75rem;
  padding: var(--space-2xs) var(--space-sm);
  font-size: var(--text-eyebrow);
}

.site-header__toggle[data-astro-cid-blryxaq5] {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  min-block-size: 2.75rem;
  min-inline-size: 2.75rem;
  padding-inline: var(--space-2xs);
  font-size: var(--text-small);
  font-weight: var(--weight-semibold);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--on-surface);
}

.site-header__toggle-label[data-astro-cid-blryxaq5] {
  display: grid;
  justify-items: end;
}

.site-header__word[data-astro-cid-blryxaq5] {
  grid-area: 1 / 1;
}

.site-header__word--close[data-astro-cid-blryxaq5] {
  opacity: 0;
}

[data-astro-cid-blryxaq5][aria-expanded="true"] .site-header__word--open[data-astro-cid-blryxaq5] {
  opacity: 0;
}

[data-astro-cid-blryxaq5][aria-expanded="true"] .site-header__word--close[data-astro-cid-blryxaq5] {
  opacity: 1;
}

.site-header__burger[data-astro-cid-blryxaq5] {
  display: grid;
  gap: 5px;
  inline-size: 1.25rem;
}

.site-header__burger[data-astro-cid-blryxaq5] > span[data-astro-cid-blryxaq5] {
  display: block;
  block-size: 2px;
  background-color: currentColor;
}

[data-astro-cid-blryxaq5][aria-expanded="true"] .site-header__burger[data-astro-cid-blryxaq5] > span[data-astro-cid-blryxaq5]:first-child {
  translate: 0 3.5px;
  rotate: 45deg;
}

[data-astro-cid-blryxaq5][aria-expanded="true"] .site-header__burger[data-astro-cid-blryxaq5] > span[data-astro-cid-blryxaq5]:last-child {
  translate: 0 -3.5px;
  rotate: -45deg;
}

@media (prefers-reduced-motion: no-preference) {
  .site-header__word[data-astro-cid-blryxaq5] {
    transition: opacity var(--duration-short) var(--ease-out);
  }

  .site-header__burger[data-astro-cid-blryxaq5] > span[data-astro-cid-blryxaq5] {
    transition: translate var(--duration-short) var(--ease-out),
				rotate var(--duration-short) var(--ease-out);
  }
}

@media (width >= 60em) {
  .site-header__nav[data-astro-cid-blryxaq5], .site-header__cta[data-astro-cid-blryxaq5], .site-header__shop[data-astro-cid-blryxaq5] {
    display: flex;
  }

  .site-header__toggle[data-astro-cid-blryxaq5] {
    display: none;
  }
}

.mobile-menu[data-astro-cid-blryxaq5] {
  --menu-duration: .54s;
  position: fixed;
  inset: 0;
  z-index: var(--z-menu);
  background-color: var(--color-ink);
  overflow-y: auto;
  overscroll-behavior: contain;
  translate: 100%;
  opacity: 0;
}

.mobile-menu[data-astro-cid-blryxaq5].is-open {
  translate: 0;
  opacity: 1;
}

@media (prefers-reduced-motion: no-preference) {
  .mobile-menu[data-astro-cid-blryxaq5] {
    transition: translate var(--menu-duration) var(--ease-out),
				opacity var(--menu-duration) var(--ease-out);
  }
}

@media (prefers-reduced-motion: reduce) {
  .mobile-menu[data-astro-cid-blryxaq5]:not([hidden]) {
    translate: 0;
    opacity: 1;
  }
}

.mobile-menu[data-astro-cid-blryxaq5][hidden] {
  display: none;
}

.mobile-menu__inner[data-astro-cid-blryxaq5] {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
  min-block-size: 100svh;
  padding-block: calc(var(--header-height) + var(--space-sm)) var(--space-lg);
}

.mobile-menu__home[data-astro-cid-blryxaq5] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: flex-start;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  margin-inline-start: calc(-1 * var(--space-2xs));
  margin-block-end: var(--space-xs);
  border-radius: var(--radius-sm);
  color: var(--on-surface);
}

.mobile-menu__home[data-astro-cid-blryxaq5][aria-current="page"] {
  color: var(--accent-text);
}

.mobile-menu__list[data-astro-cid-blryxaq5] {
  display: flex;
  flex-direction: column;
  margin: 0;
}

.mobile-menu__item[data-astro-cid-blryxaq5] + .mobile-menu__item[data-astro-cid-blryxaq5] {
  border-block-start: var(--border-hairline) solid var(--rule);
}

.mobile-menu__link[data-astro-cid-blryxaq5] {
  display: grid;
  gap: var(--space-2xs);
  padding-block: var(--space-md);
  text-decoration: none;
}

.mobile-menu__label[data-astro-cid-blryxaq5] {
  font-size: clamp(2rem, 9vw, 3rem);
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
}

.mobile-menu__hint[data-astro-cid-blryxaq5] {
  font-size: var(--text-small);
  color: var(--on-surface-muted);
}

.mobile-menu__link[data-astro-cid-blryxaq5][aria-current="page"] .mobile-menu__label[data-astro-cid-blryxaq5] {
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: .15em;
}

.mobile-menu__footer[data-astro-cid-blryxaq5] {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  margin-block-start: auto;
  padding-block-start: var(--space-lg);
  border-block-start: var(--border-hairline) solid var(--rule);
}

@media (height <= 56em) {
  .mobile-menu__inner[data-astro-cid-blryxaq5] {
    gap: var(--space-sm);
    padding-block: calc(var(--header-height) + var(--space-2xs)) var(--space-sm);
  }

  .mobile-menu__link[data-astro-cid-blryxaq5] {
    padding-block: var(--space-xs);
  }

  .mobile-menu__label[data-astro-cid-blryxaq5] {
    font-size: clamp(1.5rem, 6.5vw, 2rem);
  }

  .mobile-menu__footer[data-astro-cid-blryxaq5] {
    padding-block-start: var(--space-md);
  }
}

@media (height <= 46em) {
  .mobile-menu__inner[data-astro-cid-blryxaq5] {
    gap: var(--space-xs);
  }

  .mobile-menu__link[data-astro-cid-blryxaq5] {
    padding-block: var(--space-2xs);
  }

  .mobile-menu__footer[data-astro-cid-blryxaq5] {
    padding-block-start: var(--space-sm);
  }
}

@media (width >= 60em) {
  .mobile-menu[data-astro-cid-blryxaq5] {
    display: none;
  }
}
.contact-icon[data-astro-cid-xpy25ri3] {
  flex: none;
  margin-block-start: .15em;
  color: var(--on-surface-muted);
}
.site-footer[data-astro-cid-ui5ps2i3] {
  padding-block: var(--space-3xl) var(--space-xl);
}

.site-footer__top[data-astro-cid-ui5ps2i3] {
  display: grid;
  gap: var(--space-2xl);
  padding-block-end: var(--space-2xl);
}

@media (width >= 48em) {
  .site-footer__top[data-astro-cid-ui5ps2i3] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (width >= 68em) {
  .site-footer__top[data-astro-cid-ui5ps2i3] {
    grid-template-columns: 2fr 1fr 1fr 1.4fr;
    gap: var(--space-xl);
  }
}

.site-footer__brand[data-astro-cid-ui5ps2i3] {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-md);
}

.site-footer__slogan[data-astro-cid-ui5ps2i3] {
  max-width: 22ch;
  font-weight: var(--weight-semibold);
}

.site-footer__nav[data-astro-cid-ui5ps2i3] h2[data-astro-cid-ui5ps2i3], .site-footer__block[data-astro-cid-ui5ps2i3] h2[data-astro-cid-ui5ps2i3] {
  margin-block-end: var(--space-md);
}

.site-footer__list[data-astro-cid-ui5ps2i3], .site-footer__social[data-astro-cid-ui5ps2i3] {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  margin: 0;
}

.site-footer__list[data-astro-cid-ui5ps2i3] a[data-astro-cid-ui5ps2i3], .site-footer__social[data-astro-cid-ui5ps2i3] a[data-astro-cid-ui5ps2i3], .site-footer__block[data-astro-cid-ui5ps2i3] a[data-astro-cid-ui5ps2i3] {
  font-size: var(--text-small);
  text-decoration: none;
}

.site-footer__territory[data-astro-cid-ui5ps2i3], .site-footer__pending[data-astro-cid-ui5ps2i3] {
  font-size: var(--text-small);
  line-height: var(--leading-normal);
  color: var(--on-surface-muted);
  max-width: 34ch;
}

.site-footer__territory[data-astro-cid-ui5ps2i3] {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs, .25rem);
  margin: 0;
  padding: 0;
}

.site-footer__contact[data-astro-cid-ui5ps2i3] {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
}

.site-footer__contact-link[data-astro-cid-ui5ps2i3] {
  display: inline-flex;
  align-items: flex-start;
  gap: .5rem;
  text-decoration: none;
  transition: color var(--duration-micro) var(--ease-out);
}

.site-footer__contact-link[data-astro-cid-ui5ps2i3]:hover {
  color: var(--accent-text);
}

.site-footer__pending[data-astro-cid-ui5ps2i3] a[data-astro-cid-ui5ps2i3] {
  color: var(--on-surface);
}

.site-footer__address[data-astro-cid-ui5ps2i3] {
  font-size: var(--text-small);
  line-height: var(--leading-normal);
  color: var(--on-surface-muted);
}

.site-footer__social[data-astro-cid-ui5ps2i3] {
  margin-block-start: var(--space-md);
}

.site-footer__bottom[data-astro-cid-ui5ps2i3] {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding-block-start: var(--space-lg);
  border-block-start: var(--border-hairline) solid var(--rule);
}
@keyframes astroFadeInOut {
	from {
		opacity: 1;
	}
	to {
		opacity: 0;
	}
}

@keyframes astroFadeIn {
	from {
		opacity: 0;
		mix-blend-mode: plus-lighter;
	}
	to {
		opacity: 1;
		mix-blend-mode: plus-lighter;
	}
}

@keyframes astroFadeOut {
	from {
		opacity: 1;
		mix-blend-mode: plus-lighter;
	}
	to {
		opacity: 0;
		mix-blend-mode: plus-lighter;
	}
}

@keyframes astroSlideFromRight {
	from {
		transform: translateX(100%);
	}
}

@keyframes astroSlideFromLeft {
	from {
		transform: translateX(-100%);
	}
}

@keyframes astroSlideToRight {
	to {
		transform: translateX(100%);
	}
}

@keyframes astroSlideToLeft {
	to {
		transform: translateX(-100%);
	}
}

@media (prefers-reduced-motion) {
	::view-transition-group(*),
	::view-transition-old(*),
	::view-transition-new(*) {
		animation: none !important;
	}

	[data-astro-transition-scope] {
		animation: none !important;
	}
}
:root {
  --fab-size: 3.5rem;
}

.wa-fab[data-astro-cid-esatai4s] {
  position: fixed;
  inset-block-end: calc(var(--space-lg) + env(safe-area-inset-bottom, 0px));
  inset-inline-end: calc(var(--gutter) + env(safe-area-inset-right, 0px));
  z-index: calc(var(--z-menu) - 1);
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--fab-size);
  block-size: var(--fab-size);
  border-radius: 50%;
  background-color: var(--color-accent);
  color: var(--color-ink);
  text-decoration: none;
  box-shadow: 0 .5rem 1.5rem #00000059;
}

.wa-fab[data-astro-cid-esatai4s]:hover {
  translate: 0 -2px;
}

@media (width >= 62em) {
  .wa-fab[data-astro-cid-esatai4s] {
    display: none;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .wa-fab[data-astro-cid-esatai4s] {
    transition: translate var(--duration-micro) var(--ease-out);
  }
}
/*
 * N46° Digital Studio — Design tokens
 * Fonte: docs/DESIGN_SYSTEM.md. Nessun valore cromatico o dimensionale
 * deve essere scritto direttamente nei componenti.
 */

:root {
	/* ---------------------------------------------------------------
	 * 1. Palette brand — i cinque colori ufficiali, non modificabili.
	 * --------------------------------------------------------------- */
	--color-ink: #0e141b;
	--color-surface-dark: #1e2329;
	--color-muted: #6b7176;
	--color-paper: #f4f6f8;
	--color-accent: #f26a21;

	/*
	 * Varianti tecniche derivate, necessarie per raggiungere WCAG 2.2 AA.
	 * Non sostituiscono la palette: servono solo dove il colore brand puro
	 * non raggiunge il rapporto di contrasto richiesto su quella superficie.
	 * Misure documentate in docs/DESIGN_SYSTEM.md §Nota tecnica sul contrasto.
	 *   --color-accent-strong : 4.97:1 su --color-paper (testo/UI su chiaro)
	 *   --color-muted-on-light: 5.87:1 su --color-paper
	 *   --color-muted-on-dark : 7.08:1 su --color-ink, 6.05:1 su --color-surface-dark
	 */
	--color-accent-strong: #b34a0d;
	--color-muted-on-light: #5a6066;
	--color-muted-on-dark: #9aa1a7;

	/* Superfici ausiliarie ricavate dai colori brand. */
	--color-paper-sunken: #e8ecf0;
	--color-ink-raised: #161d25;

	/* ---------------------------------------------------------------
	 * 2. Token semantici di superficie.
	 *
	 * Il tema è integralmente scuro e il fondo è UNO SOLO per tutto il sito:
	 * l'inchiostro dipinto da `body` più lo sfondo unico di Backdrop.astro.
	 * Le sezioni non dipingono più il proprio riquadro, quindi il ritmo
	 * editoriale non lo dà più l'alternanza di due grigi ma la spaziatura,
	 * i filetti e la scala tipografica.
	 *
	 * Restano due tonalità, ma per i PANNELLI dentro le sezioni, non per le
	 * sezioni stesse:
	 *   --surface        → il pannello che stacca dal fondo (campi del form)
	 *   --surface-sunken → il blocco incassato (note, segnaposto immagine)
	 *
	 * Sopra l'inchiostro nessuna superficie può essere più scura del fondo:
	 * entrambe salgono, non scendono.
	 * --------------------------------------------------------------- */
	--surface: var(--color-surface-dark);
	--surface-sunken: var(--color-surface-dark);
	--on-surface: var(--color-paper);
	--on-surface-muted: var(--color-muted-on-dark);
	--rule: color-mix(in srgb, var(--color-paper) 16%, transparent);
	--rule-strong: var(--color-muted);
	--accent-text: var(--color-accent);
	--accent-mark: var(--color-accent);
	--focus-ring: var(--color-accent);
	--on-accent: var(--color-ink);

	/* ---------------------------------------------------------------
	 * 3. Tipografia
	 * --------------------------------------------------------------- */
	--font-sans:
		'Montserrat Variable', 'Montserrat', ui-sans-serif, system-ui, 'Segoe UI', Roboto,
		'Helvetica Neue', Arial, sans-serif;

	--weight-regular: 400;
	--weight-semibold: 600;
	--weight-bold: 700;

	/* Scala fluida — docs/DESIGN_SYSTEM.md §Tipografia
	 *
	 * La display è volutamente grande: nel riferimento editoriale il primo
	 * schermo è quasi solo tipografia. Il problema della piega non si risolve
	 * rimpicciolendo il titolo ma distribuendo l'hero su 100svh (vedi Hero.astro).
	 *
	 * `--text-mega` non c'è più: serviva ai numeri d'indice contornati delle
	 * schede progetto, tolti da DEC-037 insieme al resto dei metadati. */
	/* Lo slogan va a capo liberamente (Hero.astro non spezza più le righe a
	   mano): il tetto tiene la frase su due righe da 1440px in giù e la
	   soglia minima la mantiene leggibile sui telefoni. */
	--text-display: clamp(2.75rem, 7.4vw, 6.5rem);
	--text-h1: clamp(2.75rem, 7vw, 6.5rem);
	--text-h2: clamp(2.25rem, 5.4vw, 5rem);
	--text-h3: clamp(1.5rem, 2.8vw, 2.75rem);
	/* Tetto alzato di due punti: `.t-h4` è l'intestazione strutturale più usata
	   del sito — passi del metodo, valori, titoli di blocco e di galleria, h2
	   del case study, slogan del piè di pagina — e contro un corpo che ora
	   arriva davvero a 18px stava a 1,44×, schiacciata fra quello e un t-h2 da
	   78px. Il minimo non cambia. */
	--text-h4: clamp(1.25rem, 2vw, 1.75rem);
	--text-body-lg: clamp(1.125rem, 1.6vw, 1.5rem);
	/*
	 * Il termine fluido era `1.05vw`, che supera 1rem solo sopra i 1523px di
	 * viewport e tocca 1.125rem a 1714px: su ogni schermo reale il corpo restava
	 * inchiodato a 16px e la fascia 1–1.125rem documentata in DESIGN_SYSTEM.md
	 * non era raggiungibile da nessuno. Gli estremi sono gli stessi; cambia solo
	 * la pendenza, che ora attraversa davvero l'intervallo dichiarato.
	 */
	--text-body: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
	--text-small: 0.9375rem;
	--text-eyebrow: 0.75rem;

	--leading-display: 0.88;
	--leading-tight: 1.02;
	--leading-snug: 1.2;
	--leading-normal: 1.6;
	--leading-relaxed: 1.72;

	--tracking-display: -0.04em;
	--tracking-tight: -0.025em;
	--tracking-normal: 0;
	--tracking-eyebrow: 0.18em;

	/* ---------------------------------------------------------------
	 * 4. Spazio e griglia
	 * --------------------------------------------------------------- */
	--space-3xs: 0.25rem;
	--space-2xs: 0.5rem;
	--space-xs: 0.75rem;
	--space-sm: 1rem;
	--space-md: 1.5rem;
	--space-lg: 2rem;
	--space-xl: 3rem;
	--space-2xl: clamp(3rem, 6vw, 5rem);
	--space-3xl: clamp(4rem, 9vw, 8rem);
	--space-section: clamp(5rem, 12vw, 11rem);

	--gutter: clamp(1.25rem, 5vw, 4rem);
	--container-max: 90rem; /* 1440px */
	--measure: 62ch;
	--measure-tight: 46ch;

	/* ---------------------------------------------------------------
	 * 5. Forma, bordi, elevazione
	 * --------------------------------------------------------------- */
	--radius-sm: 2px;
	--radius-md: 4px;
	--radius-pill: 999px;
	--border-hairline: 1px;

	/* ---------------------------------------------------------------
	 * 6. Movimento — docs/DESIGN_SYSTEM.md §Movimento
	 * --------------------------------------------------------------- */
	--duration-micro: 180ms;
	--duration-short: 260ms;
	--duration-medium: 420ms;
	--duration-reveal: 720ms;
	/* L'ingresso di pagina è più lento del reveal: è una sequenza, non un
	   effetto. Deve leggersi come una cosa sola che si compone. */
	--duration-enter: 980ms;
	--ease-out: cubic-bezier(0.2, 0.7, 0.2, 1);
	--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
	/* Uscita decisa poi arresto morbido: dà peso ai titoli grandi. */
	--ease-display: cubic-bezier(0.16, 1, 0.3, 1);

	/*
	 * Scaglionamento dei reveal legati allo scroll.
	 *
	 * Il passo è una PERCENTUALE di range, non un tempo: con una timeline di
	 * scroll `animation-delay` non ha alcun effetto (vedi motion.css).
	 *
	 * Il tetto non è prudenza generica. Ogni soggetto misura la PROPRIA
	 * timeline, quindi due elementi impilati sono già scaglionati dalla loro
	 * posizione: l'indice aggiunge un secondo ritardo a uno che esiste di suo.
	 * Senza tetto, la sesta voce di un elenco arrivava a metà schermo ancora
	 * al 53% di opacità mentre la prima era opaca da un pezzo. Tre passi sono
	 * quanto basta a far leggere una cascata fra elementi affiancati.
	 */
	--reveal-step: 3%;
	--reveal-cap: 3;

	/* ---------------------------------------------------------------
	 * 7. Livelli
	 * --------------------------------------------------------------- */
	--z-base: 1;
	--z-sticky: 100;
	--z-header: 200;
	--z-menu: 300;
	--z-skip-link: 400;

	--header-height: 4.5rem;
}

@media (min-width: 60em) {
	:root {
		--header-height: 5.5rem;
	}
}

/* -------------------------------------------------------------------
 * Classi di superficie: unico punto in cui i token semantici cambiano.
 *
 * NESSUNA DIPINGE UN FONDO. Il fondo del sito è uno solo — l'inchiostro di
 * `body` più lo sfondo unico di Backdrop.astro — e una sezione opaca lo
 * coprirebbe, riportando lo stacco fra hero e resto del sito che si voleva
 * togliere. Chi ha davvero bisogno di un fondo opaco (il menu mobile,
 * l'header quando è agganciato) se lo dichiara nel proprio componente.
 *
 * Restano perché definiscono i token di testo, filetto e accento, e perché
 * marcano il ruolo editoriale della sezione: rimetterle a dipingere è una
 * riga sola, se in futuro si volesse tornare all'alternanza.
 * ------------------------------------------------------------------- */
/* Apertura e chiusura di ogni pagina. */
.surface-base {
	--surface: var(--color-surface-dark);
	--surface-sunken: var(--color-surface-dark);
	--on-surface: var(--color-paper);
	--on-surface-muted: var(--color-muted-on-dark);
	--rule: color-mix(in srgb, var(--color-paper) 16%, transparent);
	--rule-strong: var(--color-muted);
	--accent-text: var(--color-accent);
	--accent-mark: var(--color-accent);
	--focus-ring: var(--color-accent);
	--on-accent: var(--color-ink);

	background-color: transparent;
	color: var(--on-surface);
}

/*
 * Sezione di corpo. Sul fondo unico i contrasti si calcolano su #0E141B,
 * dove sono più larghi di prima: testo 15.9:1, secondario 7.08:1,
 * accento 5.95:1 — tutti sopra la soglia AA.
 */
.surface-raised {
	--surface: var(--color-surface-dark);
	--surface-sunken: var(--color-surface-dark);
	--on-surface: var(--color-paper);
	--on-surface-muted: var(--color-muted-on-dark);
	--rule: color-mix(in srgb, var(--color-paper) 14%, transparent);
	--rule-strong: var(--color-muted);
	--accent-text: var(--color-accent);
	--accent-mark: var(--color-accent);
	--focus-ring: var(--color-accent);
	--on-accent: var(--color-ink);

	background-color: transparent;
	color: var(--on-surface);
}

/*
 * Superficie chiara: NON usata dal tema corrente, che è integralmente scuro.
 * Resta definita perché il sistema di token la prevede e perché serve a
 * qualunque contesto su fondo chiaro (stampa, futura pagina stampa/press).
 * I token --color-accent-strong e --color-muted-on-light esistono solo per
 * questa superficie: vedi docs/DESIGN_SYSTEM.md §Nota tecnica sul contrasto.
 */
.surface-light {
	--surface: var(--color-paper);
	--surface-sunken: var(--color-paper-sunken);
	--on-surface: var(--color-ink);
	--on-surface-muted: var(--color-muted-on-light);
	--rule: color-mix(in srgb, var(--color-ink) 14%, transparent);
	--rule-strong: color-mix(in srgb, var(--color-ink) 28%, transparent);
	--accent-text: var(--color-accent-strong);
	--accent-mark: var(--color-accent);
	--focus-ring: var(--color-accent-strong);
	--on-accent: var(--color-ink);

	background-color: var(--surface);
	color: var(--on-surface);
}
/*
 * Tipografia — Montserrat Variable self-hosted (DEC-024).
 * I file WOFF2 stanno in src/assets/fonts/ e vengono versionati/hashati da Astro.
 * Licenza SIL OFL: src/assets/fonts/LICENSE-Montserrat.txt
 */

@font-face {
	font-family: 'Montserrat Variable';
	font-style: normal;
	font-display: swap;
	font-weight: 100 900;
	src: url('/_astro/montserrat-latin-wght-normal.l_AIctKy.woff2') format('woff2-variations');
	unicode-range:
		U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
		U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: 'Montserrat Variable';
	font-style: normal;
	font-display: swap;
	font-weight: 100 900;
	src: url('/_astro/montserrat-latin-ext-wght-normal.BsZE-iaG.woff2') format('woff2-variations');
	unicode-range:
		U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
		U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
		U+A720-A7FF;
}

/* -------------------------------------------------------------------
 * Stili tipografici riutilizzabili
 * ------------------------------------------------------------------- */

.t-display,
.t-h1,
.t-h2,
.t-h3,
.t-h4 {
	font-weight: var(--weight-bold);
	text-wrap: balance;
	/*
	 * Sillabazione automatica, non `manual`.
	 *
	 * Gli heading ereditano `overflow-wrap: break-word` da global.css. Con
	 * `manual` una parola più larga della sua colonna veniva spezzata in un
	 * punto qualsiasi e SENZA trattino: in /servizi si leggeva
	 * «Rinnovame / nto dell'identità», a ogni viewport oltre i 992px.
	 *
	 * `auto` con `lang="it"` usa il dizionario italiano e rompe dove si deve,
	 * col trattino. `break-word` resta dov'è e non è una svista: per specifica
	 * interviene solo quando non esiste ALCUN altro punto di rottura
	 * accettabile, e una sillabazione è un punto accettabile. Continua quindi a
	 * fare da rete per ciò che non si può sillabare — un indirizzo, un codice —
	 * senza più intercettare le parole normali. Toglierlo sostituirebbe una
	 * spezzatura brutta con un overflow orizzontale, che è peggio.
	 *
	 * Il prefisso serve alle versioni di Safari che ancora lo richiedono: il
	 * progetto non ha autoprefixer.
	 */
	-webkit-hyphens: auto;
	hyphens: auto;
}

/*
 * Su telefono i titoli NON si sillabano.
 *
 * La sillabazione qui sopra nasce da un guasto reale — «Rinnovame / nto»
 * spezzato senza trattino oltre i 992px — ed è giusta lì. Su schermo stretto
 * però il titolo è già corto e la colonna è già larga quanto lo schermo: quello
 * che resta è una parola tagliata con un trattino a metà di ogni intestazione,
 * che Gioele legge come un difetto e non come tipografia.
 *
 * Si disattiva SOLO sotto la soglia mobile, non ovunque: sopra i 48em il motivo
 * per cui esiste è ancora valido.
 *
 * `overflow-wrap: break-word` da global.css resta, e resta apposta: è la rete
 * per ciò che non si può spezzare altrimenti — un indirizzo, un codice. Senza,
 * una parola più larga dello schermo produrrebbe scorrimento orizzontale, che è
 * peggio di una spezzatura.
 */
@media (max-width: 47.999em) {
	h1,
	h2,
	h3,
	h4,
	h5,
	h6,
	.t-display,
	.t-h1,
	.t-h2,
	.t-h3,
	.t-h4 {
		-webkit-hyphens: manual;
		hyphens: manual;
	}
}

.t-display {
	font-size: var(--text-display);
	line-height: var(--leading-display);
	letter-spacing: var(--tracking-display);
	text-transform: uppercase;
}

.t-h1 {
	font-size: var(--text-h1);
	line-height: var(--leading-tight);
	letter-spacing: var(--tracking-display);
}

.t-h2 {
	font-size: var(--text-h2);
	line-height: var(--leading-tight);
	letter-spacing: var(--tracking-tight);
}

.t-h3 {
	font-size: var(--text-h3);
	line-height: var(--leading-snug);
	letter-spacing: var(--tracking-tight);
}

.t-h4 {
	font-size: var(--text-h4);
	line-height: var(--leading-snug);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-tight);
}

.t-lead {
	font-size: var(--text-body-lg);
	line-height: var(--leading-normal);
	font-weight: var(--weight-regular);
	max-width: var(--measure);
	text-wrap: pretty;
}

.t-body {
	font-size: var(--text-body);
	line-height: var(--leading-relaxed);
	max-width: var(--measure);
	text-wrap: pretty;
}

.t-small {
	font-size: var(--text-small);
	line-height: var(--leading-normal);
}

/* Occhiello: etichetta di sezione in maiuscoletto spaziato. */
.t-eyebrow {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2xs);
	font-size: var(--text-eyebrow);
	font-weight: var(--weight-semibold);
	line-height: 1;
	letter-spacing: var(--tracking-eyebrow);
	text-transform: uppercase;
	color: var(--on-surface-muted);
}

/* Metadati tecnici: numerazione progetti, anni, contatori. */
.t-meta {
	font-size: var(--text-small);
	font-weight: var(--weight-semibold);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--on-surface-muted);
	font-variant-numeric: tabular-nums;
}

.t-index {
	font-size: var(--text-small);
	font-weight: var(--weight-semibold);
	letter-spacing: 0.1em;
	font-variant-numeric: tabular-nums;
	color: var(--accent-text);
}

/* Parola evidenziata dentro un titolo. Il colore non è mai l'unico segnale:
   la parola resta leggibile e conserva il peso del titolo. */
.t-accent {
	color: var(--accent-text);
}

/* -------------------------------------------------------------------
 * Split in parole (src/scripts/reveal.ts).
 * La maschera esiste sempre; è il movimento a essere condizionale.
 * `padding-block` evita che discendenti e accenti vengano tagliati.
 * ------------------------------------------------------------------- */
.split__mask {
	display: inline-block;
	overflow: hidden;
	vertical-align: top;
	padding-block: 0.06em 0.14em;
	margin-block: -0.06em -0.14em;
}

.split__word {
	display: inline-block;
	white-space: pre;
}

/* -------------------------------------------------------------------
 * Case study lungo: il corpo Markdown di un file di progetto (DEC-058).
 *
 * Vive nel foglio globale e non negli stili scoped della pagina, perché
 * `<Content />` produce markup che non porta l'attributo di scope di Astro:
 * una regola scoped non lo raggiungerebbe mai.
 *
 * Nessun token nuovo: la scala è quella dei blocchi narrativi che precedono
 * questo corpo, così il passaggio dai quattro paragrafi al testo lungo non
 * si legge come un cambio di documento.
 * ------------------------------------------------------------------- */
.case-prose {
	display: flow-root;
	padding-block-start: var(--space-md);
	border-block-start: var(--border-hairline) solid var(--rule);
}

.case-prose > * {
	max-width: var(--measure);
}

/* Titolo di capitolo: stessa misura dei titoli dei blocchi narrativi
   (`.case-block__title`), così i due livelli restano un solo indice. */
.case-prose h2 {
	font-size: var(--text-h4);
	line-height: var(--leading-snug);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-tight);
	text-wrap: balance;
	margin-block: var(--space-xl) var(--space-sm);
	/* Niente attenuazione: un titolo di sezione non deve pesare meno del testo
	   che introduce. A distinguerlo bastano corpo, peso e spaziatura. */
}

.case-prose > h2:first-child {
	margin-block-start: 0;
}

/* Sottotitolo dentro un capitolo: scende alla misura del corpo, ma resta
   distinguibile per peso e colore pieno. */
.case-prose h3 {
	font-size: var(--text-body-lg);
	line-height: var(--leading-normal);
	font-weight: var(--weight-semibold);
	margin-block: var(--space-lg) var(--space-2xs);
	color: var(--on-surface);
}

.case-prose p {
	font-size: var(--text-body);
	line-height: var(--leading-relaxed);
	text-wrap: pretty;
	margin-block: 0 var(--space-sm);
}

.case-prose ul,
.case-prose ol {
	display: flex;
	flex-direction: column;
	gap: var(--space-2xs);
	margin-block: 0 var(--space-md);
	padding: 0;
	font-size: var(--text-body);
	line-height: var(--leading-relaxed);
}

.case-prose li {
	list-style: none;
	padding-inline-start: 1.6em;
	text-indent: -1.6em;
}

/* Stesso segno di `.case-outcomes`: un elenco è un elenco in tutta la pagina. */
.case-prose ul > li::before {
	content: '↗';
	display: inline-block;
	inline-size: 1.6em;
	text-indent: 0;
	color: var(--accent-text);
}

/* Un elenco numerato numera davvero — è una sequenza da percorrere in ordine
   (il flusso operativo), non un elenco di voci equivalenti. */
.case-prose ol {
	counter-reset: case-step;
}

.case-prose ol > li {
	counter-increment: case-step;
}

.case-prose ol > li::before {
	content: counter(case-step, decimal-leading-zero);
	display: inline-block;
	inline-size: 1.6em;
	text-indent: 0;
	font-size: var(--text-small);
	font-weight: var(--weight-semibold);
	font-variant-numeric: tabular-nums;
	color: var(--accent-text);
}

/*
 * Citazione in evidenza: filetto a sinistra, mai virgolette decorative.
 *
 * Sale alla misura dei titoli invece di restare al corpo del testo. A
 * `--text-body-lg` con il colore muto si leggeva come un paragrafo rientrato,
 * cioè come un inciso — mentre nel case study è la frase che riassume tutto.
 * Il filetto resta grigio di marca: l'arancione segnala il passo verso il
 * contatto e nient'altro (DEC-049, DEC-050).
 */
.case-prose blockquote {
	/* `ch` scala col corpo del carattere: a misura di titolo, 62ch supererebbero
	   la colonna dei paragrafi e la citazione sporgerebbe a destra. */
	max-width: var(--measure-tight);
	margin-block: var(--space-xl);
	margin-inline: 0;
	padding-inline-start: var(--space-md);
	border-inline-start: 2px solid var(--rule-strong);
	font-size: var(--text-h4);
	line-height: var(--leading-snug);
	letter-spacing: var(--tracking-tight);
	text-wrap: balance;
	color: var(--on-surface);
}

.case-prose blockquote p {
	font-size: inherit;
	line-height: inherit;
}

.case-prose blockquote p:last-child {
	margin-block-end: 0;
}

.case-prose strong {
	font-weight: var(--weight-semibold);
	color: var(--on-surface);
}

.case-prose > :last-child {
	margin-block-end: 0;
}
/*
 * Reset ragionato + stili di base.
 * Caricato dopo tokens.css e typography.css (vedi BaseLayout.astro).
 */

*,
*::before,
*::after {
	box-sizing: border-box;
}

* {
	margin: 0;
}

html {
	-webkit-text-size-adjust: 100%;
	text-size-adjust: 100%;
	/* Nessuno scroll-behavior globale: lo smooth scroll è opt-in e viene
	   comunque disattivato da prefers-reduced-motion (motion.css).

	   Niente `scrollbar-gutter: stable`: con il fondo scuro del documento
	   riserverebbe una striscia scura visibile a destra delle sezioni chiare
	   sui browser con scrollbar in overlay. */
}

body {
	min-height: 100svh;
	font-family: var(--font-sans);
	font-size: var(--text-body);
	font-weight: var(--weight-regular);
	line-height: var(--leading-relaxed);
	/*
	 * Il fondo del documento è scuro perché ogni pagina apre su una sezione
	 * scura e chiude sul footer scuro. Non è una scelta estetica marginale:
	 * l'header è sticky e trasparente a riposo, quindi il colore ereditato dal
	 * body è quello su cui va calcolato il contrasto del testo dell'header.
	 */
	background-color: var(--color-ink);
	color: var(--color-paper);
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
	/* Difesa strutturale contro lo scroll orizzontale richiesto dalla QA. */
	overflow-x: clip;
}

/* Blocco dello scroll quando il menu mobile è aperto. */
body[data-scroll-locked] {
	overflow: hidden;
}

img,
picture,
video,
canvas,
svg {
	display: block;
	max-width: 100%;
}

img {
	height: auto;
}

input,
button,
textarea,
select {
	font: inherit;
	color: inherit;
}

button {
	background: none;
	border: none;
	padding: 0;
	cursor: pointer;
}

h1,
h2,
h3,
h4,
h5,
h6 {
	font-weight: var(--weight-bold);
	line-height: var(--leading-tight);
	letter-spacing: var(--tracking-tight);
	overflow-wrap: break-word;
}

p,
li {
	overflow-wrap: break-word;
}

ul[role='list'],
ol[role='list'] {
	list-style: none;
	padding: 0;
}

a {
	color: inherit;
	text-decoration-color: var(--rule-strong);
	text-underline-offset: 0.22em;
	text-decoration-thickness: 1px;
}

hr {
	border: 0;
	border-top: var(--border-hairline) solid var(--rule);
	margin: 0;
}

:target {
	scroll-margin-top: calc(var(--header-height) + var(--space-lg));
}

/* -------------------------------------------------------------------
 * Focus: sempre visibile, colore adattato alla superficie.
 * ------------------------------------------------------------------- */
:focus-visible {
	outline: 3px solid var(--focus-ring);
	outline-offset: 3px;
	border-radius: var(--radius-sm);
}

/* Rimuove l'outline solo quando il focus arriva dal puntatore, mai da tastiera. */
:focus:not(:focus-visible) {
	outline: none;
}

/* -------------------------------------------------------------------
 * Utility strutturali
 * ------------------------------------------------------------------- */
.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

.container {
	width: 100%;
	max-width: calc(var(--container-max) + var(--gutter) * 2);
	margin-inline: auto;
	padding-inline: var(--gutter);
}

.container--narrow {
	max-width: calc(64rem + var(--gutter) * 2);
}

.section {
	padding-block: var(--space-section);
}

/*
 * Toglie il padding superiore dove il blocco precedente ha già dato aria.
 *
 * `.section` mette `--space-section` su ENTRAMBI i lati, quindi due sezioni
 * adiacenti sommano i due padding: a 1440px sono 346px di nulla, e /studio ne
 * ha quattro di fila. Il difetto non è il valore, è che nessuno dei due lati
 * sa dell'altro. Questa classe fa valere il confine una spaziatura sola.
 *
 * Va sulla sezione SEGUENTE, non sulla precedente: così la prima sezione di
 * una pagina conserva la sua aria sotto l'apertura, e l'ultima la sua sopra la
 * chiamata all'azione. È lo stesso ragionamento con cui DEC-114 ha tolto
 * PageHero da /contatti, applicato senza rimuovere nulla.
 */
.section--flush {
	padding-block-start: 0;
}

/*
 * `.section--tight` non c'è più: era definita, non usata da nessuno, e il
 * bisogno reale — il confine fra due sezioni, non la sezione — lo copre
 * `.section--flush`. Un blocco che voglia davvero un ritmo proprio se lo
 * dichiara, come fa `.case-body--compact`.
 */

/* Griglia editoriale a 12 colonne: base delle composizioni asimmetriche. */
.grid-12 {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	column-gap: var(--space-md);
	row-gap: var(--space-xl);
}

@media (min-width: 48em) {
	.grid-12 {
		grid-template-columns: repeat(12, minmax(0, 1fr));
		column-gap: var(--space-lg);
	}
}

/* Filetto orizzontale usato come separatore editoriale. */
.rule {
	border-top: var(--border-hairline) solid var(--rule);
}

/* Contenitore immagine a proporzione fissa: evita CLS e dà controllo
   sul ritaglio senza dipendere dagli stili responsive automatici di Astro. */
.media {
	position: relative;
	overflow: hidden;
	background-color: var(--surface-sunken);
}

.media > img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
}

.media--wide {
	aspect-ratio: 16 / 10;
}

.media--tall {
	aspect-ratio: 4 / 3;
}

/* -------------------------------------------------------------------
 * Cover che partecipano al morphing fra indice e case study.
 * `view-transition-class` permette di dare una regola unica a tutte le
 * cover senza elencare i nomi generati progetto per progetto.
 *
 * `.pc__media` resta qui anche se `/progetti` non usa più `ProjectCard`
 * (N46-080, `.q46__zoom` l'ha sostituita): il selettore è innocuo finché
 * `ProjectCard.astro` resta nel repository come codice orfano, e toglierlo
 * ora significherebbe correggere un file estraneo a questo incarico.
 * `.q46__zoom` è la cover della scheda oggi in uso, sia sull'indice
 * pubblico sia sull'anteprima di confronto.
 * ------------------------------------------------------------------- */
.pc__media img,
.q46__zoom img,
.case-cover__media img {
	view-transition-class: cover;
}
/*
 * Componenti trasversali che vivono su qualunque superficie:
 * pulsanti, link editoriali, badge, segni topografici.
 * Tutti leggono i token semantici, quindi si adattano da soli a chiaro/scuro.
 */

/* -------------------------------------------------------------------
 * Pulsanti
 * ------------------------------------------------------------------- */
.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-2xs);
	/*
	 * 48px: scelta di comodità, non un obbligo di conformità.
	 *
	 * Il criterio che il sito dichiara di rispettare è WCAG 2.5.8 (AA, 2.2), e
	 * chiede 24×24. I 44×44 che si citano di solito sono 2.5.5, che è AAA e non
	 * è fra i tag verificati da axe. Qui si sta sopra a entrambi perché su un
	 * telefono un bersaglio comodo si sente, non perché una regola lo imponga.
	 *
	 * Serve saperlo per poter derogare con cognizione: la barra desktop scende a
	 * 2.75rem in `Header.astro`, dove si punta col mouse.
	 */
	min-height: 3rem;
	padding: 0.75rem 1.5rem;
	font-size: var(--text-small);
	font-weight: var(--weight-semibold);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	text-decoration: none;
	border-radius: var(--radius-pill);
	border: var(--border-hairline) solid transparent;
	transition:
		background-color var(--duration-micro) var(--ease-out),
		color var(--duration-micro) var(--ease-out),
		border-color var(--duration-micro) var(--ease-out);
}

/*
 * Hover a riempimento: una superficie sale dal basso e inverte il pulsante.
 * `isolation: isolate` tiene lo pseudo-elemento a z-index -1 dentro il
 * pulsante, così non finisce dietro alla sezione.
 */
.btn {
	position: relative;
	overflow: hidden;
	isolation: isolate;
}

.btn::before {
	content: '';
	position: absolute;
	inset: 0;
	z-index: -1;
	translate: 0 101%;
}

@media (prefers-reduced-motion: no-preference) {
	.btn::before {
		transition: translate var(--duration-short) var(--ease-out);
	}
}

.btn:is(:hover, :focus-visible)::before {
	translate: 0 0;
}

/*
 * Gerarchia dei pulsanti (DEC-049).
 *
 * L'arancio segnala il passo che porta al contatto, e nient'altro: la CTA
 * della barra di navigazione e il pulsante che invia il modulo. Sono i due
 * punti in cui una persona decide di scriverci. Ogni altro pulsante pieno usa
 * il grigio di marca — un accento che compare ovunque smette di essere un
 * accento (DEC-049, DEC-050).
 *
 * Contrasti verificati sul fondo #0E141B:
 *   grigio #6B7176 contro il fondo ....... 3.74:1 (soglia 3:1 per un elemento)
 *   testo #F4F6F8 dentro il grigio ....... 4.56:1 (soglia 4.5:1 per il testo)
 *   testo #0E141B dentro l'arancio ....... 6.05:1
 * Il testo chiaro dentro il grigio passa senza margine: se un giorno il
 * grigio dovesse schiarirsi, quel 4.56 va ricontrollato per primo.
 */
.btn--accent {
	background-color: var(--color-accent);
	color: var(--on-accent);
	border-color: var(--color-accent);
}

/* L'accento si inverte in chiaro: ink su paper, 14.6:1. */
.btn--accent::before {
	background-color: var(--color-paper);
}

.btn--accent:is(:hover, :focus-visible) {
	color: var(--color-ink);
	border-color: var(--color-paper);
}

.btn--primary {
	background-color: var(--color-muted);
	color: var(--color-paper);
	border-color: var(--color-muted);
}

/* Come per l'accento, il riempimento porta il pulsante in chiaro: 17.1:1. */
.btn--primary::before {
	background-color: var(--color-paper);
}

.btn--primary:is(:hover, :focus-visible) {
	color: var(--color-ink);
	border-color: var(--color-paper);
}

.btn--ghost {
	background-color: transparent;
	color: var(--on-surface);
	border-color: var(--rule-strong);
}

/* Il contorno si riempie di grigio brand: paper su grigio, 4.56:1.
   Prima si riempiva d'arancio, che ora appartiene al solo menu. */
.btn--ghost::before {
	background-color: var(--color-muted);
}

.btn--ghost:is(:hover, :focus-visible) {
	color: var(--color-paper);
	border-color: var(--color-muted);
}

.btn[aria-disabled='true'],
.btn:disabled {
	cursor: not-allowed;
	background-color: transparent;
	color: var(--on-surface-muted);
	border-color: var(--rule);
}

/* -------------------------------------------------------------------
 * Link editoriale con sottolineatura animata
 * ------------------------------------------------------------------- */
.link-underline {
	position: relative;
	display: inline-block;
	text-decoration: none;
	padding-block-end: 0.15em;
}

/*
 * Sottolineatura che si disegna da sinistra all'ingresso del puntatore e si
 * ritira verso destra all'uscita: il tratto segue la direzione del gesto
 * invece di comparire e sparire.
 */
.link-underline::after {
	content: '';
	position: absolute;
	inset-inline: 0;
	inset-block-end: 0;
	height: 1px;
	background-color: currentColor;
	scale: 0 1;
	transform-origin: right;
}

.link-underline:is(:hover, :focus-visible)::after {
	scale: 1 1;
	transform-origin: left;
}

/* La voce corrente resta segnata anche senza puntatore: il colore non è mai
   l'unico indizio dello stato attivo. */
.link-underline[aria-current='page']::after {
	scale: 1 1;
	transform-origin: left;
	background-color: var(--accent-mark);
}

/* Link "freccia" per CTA testuali. */
.link-arrow {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2xs);
	font-weight: var(--weight-semibold);
	text-decoration: none;
	color: var(--accent-text);
}

.link-arrow:hover {
	text-decoration: underline;
	text-underline-offset: 0.25em;
}

.cta-arrow {
	flex: none;
}

/* -------------------------------------------------------------------
 * Badge di stato progetto — sempre visibile, mai solo colore.
 * Il testo è l'informazione; il pallino è un rinforzo visivo.
 * ------------------------------------------------------------------- */
.status-badge {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2xs);
	padding: 0.375rem 0.75rem;
	border: var(--border-hairline) solid var(--rule-strong);
	border-radius: var(--radius-pill);
	font-size: 0.75rem;
	font-weight: var(--weight-semibold);
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--on-surface);
	white-space: nowrap;
}

.status-badge__dot {
	inline-size: 0.5rem;
	block-size: 0.5rem;
	border-radius: 50%;
	flex: none;
}

.status-badge[data-status='live'] .status-badge__dot {
	background-color: var(--accent-mark);
}

.status-badge[data-status='in-progress'] .status-badge__dot {
	background-color: var(--on-surface);
	/* Semicerchio: distingue lo stato anche senza percepire il colore. */
	clip-path: polygon(0 0, 50% 0, 50% 100%, 0 100%);
}

.status-badge[data-status='concept'] .status-badge__dot {
	background-color: transparent;
	border: 1.5px solid var(--on-surface);
}

/* -------------------------------------------------------------------
 * Segni topografici / territoriali
 * ------------------------------------------------------------------- */
.topo {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	pointer-events: none;
	color: var(--on-surface);
	opacity: 0.07;
}

/*
 * Separatore editoriale con il profilo di montagna N46°.
 *
 * ATTENZIONE, lo spazio attorno si regola DA QUI o con la variabile, mai con
 * una regola `.qualcosa__rule` negli stili scoped di una pagina: Astro scopa i
 * selettori sugli elementi del proprio template, il componente non riceve
 * l'attributo di scope, e il compilatore eliminava quelle regole come
 * inutilizzate. Sono rimaste morte a lungo e il filetto è stato senza margine
 * su tutte e tre le pagine che lo usano (DEC-062).
 *
 * `--mountain-rule-space` si imposta su un antenato — quello sì nel template
 * della pagina, quindi correttamente scopato — e scende per ereditarietà.
 *
 * Il disegno non è simmetrico: la linea sta in basso nel viewBox e la montagna
 * sale sopra, quindi sotto la linea restano appena 4 px di riquadro. È il
 * margine inferiore a dover reggere tutto lo stacco dal testo che segue.
 */
.mountain-rule {
	display: block;
	inline-size: 100%;
	block-size: auto;
	margin-block: var(--mountain-rule-space, var(--space-2xl));
	color: var(--rule-strong);
}

/* Numerazione di sezione in stile indice editoriale. */
.section-index {
	display: inline-block;
	font-size: var(--text-small);
	font-weight: var(--weight-semibold);
	font-variant-numeric: tabular-nums;
	letter-spacing: 0.1em;
	color: var(--accent-text);
}
/*
 * Movimento — docs/DESIGN_SYSTEM.md §Movimento, PROMPT §10.
 *
 * Due sistemi distinti, perché risolvono due problemi diversi:
 *
 *   [data-enter]  → contenuto già visibile al caricamento (hero). Animazione
 *                   a tempo, con scaglionamento via --i. Le animazioni legate
 *                   allo scroll qui NON funzionerebbero: l'elemento è già in
 *                   viewport e non attraversa mai il proprio range.
 *
 *   [data-reveal] → contenuto sotto la piega. Animazione ancorata alla
 *                   posizione di scroll, quindi legata al gesto dell'utente e
 *                   non a un timer. È la differenza percepita fra
 *                   "dissolvenza da template" e movimento che risponde.
 *
 * Principio invariato: lo stato di riposo è quello FINALE e visibile.
 * Il movimento è additivo e richiede `js-motion` sull'<html>, aggiunto da uno
 * script inline solo se l'utente non ha chiesto motion ridotto.
 * Senza JS o con motion ridotto il sito è identico, solo statico.
 *
 * ─────────────────────────────────────────────────────────────────────
 * PERCHÉ `--rv` E NON `view()`
 *
 * Il minificatore CSS fonde le longhand `animation-*` nella shorthand
 * `animation`. Con una timeline anonima produce `animation: … view()`, che
 * NON è sintassi valida per la shorthand: il browser scarta la dichiarazione
 * e il movimento sparisce senza alcun errore visibile.
 * Una timeline con nome (`--rv`) è invece un valore legale anche dentro la
 * shorthand, quindi sopravvive alla minificazione.
 * La regressione è coperta da tests/e2e/motion.spec.ts.
 * ─────────────────────────────────────────────────────────────────────
 */

@media (prefers-reduced-motion: no-preference) {
	/* =================================================================
	 * 1. Ingresso a tempo — contenuto sopra la piega
	 * ================================================================= */

	.js-motion [data-enter] {
		--enter-delay: calc(var(--i, 0) * 90ms + var(--enter-offset, 0ms));
	}

	/*
	 * Come per i reveal, l'ingresso generico vale solo per i valori non
	 * specializzati: gli altri hanno un gesto proprio e non devono ricevere
	 * anche opacità e translate, che altrimenti si sommerebbero a quello dei
	 * figli e renderebbero l'elemento blocco contenitore dei suoi assoluti.
	 */
	.js-motion
		[data-enter]:not(
			[data-enter='line'],
			[data-enter='mask'],
			[data-enter='rule'],
			[data-enter='split']
		) {
		opacity: 0;
		animation: enter-up var(--duration-reveal) var(--ease-out) var(--enter-delay) both;
	}

	/*
	 * Riga di display: sale da sotto una maschera, con un filo di sfocatura
	 * che si posa. È il gesto portante della sequenza d'apertura, quindi ha
	 * una durata e una curva proprie, più lente del resto.
	 */
	.js-motion [data-enter='line'] {
		display: block;
		opacity: 1;
		animation: enter-line var(--duration-enter) var(--ease-display) var(--enter-delay) both;
	}

	/* Filetto che si disegna da sinistra: chiude la sequenza dell'hero. */
	.js-motion [data-enter='rule']::before {
		animation: enter-rule var(--duration-enter) var(--ease-display) var(--enter-delay) both;
	}

	.js-motion [data-enter='mask'] {
		opacity: 1;
		animation: enter-mask calc(var(--duration-reveal) * 1.2) var(--ease-out) var(--enter-delay) both;
	}

	.js-motion [data-enter='split'] .split__word {
		animation: enter-word var(--duration-reveal) var(--ease-out) both;
		animation-delay: calc(var(--i, 0) * 55ms + var(--enter-offset, 0ms));
	}

	@keyframes enter-up {
		from {
			opacity: 0;
			translate: 0 1.5rem;
		}
		to {
			opacity: 1;
			translate: none;
		}
	}

	@keyframes enter-line {
		from {
			clip-path: inset(0 0 105% 0);
			translate: 0 0.16em;
			filter: blur(6px);
		}
		60% {
			filter: blur(0);
		}
		to {
			clip-path: inset(-0.25em 0 -0.25em 0);
			translate: none;
			filter: blur(0);
		}
	}

	@keyframes enter-rule {
		from {
			scale: 0 1;
		}
		to {
			scale: 1 1;
		}
	}

	@keyframes enter-mask {
		from {
			clip-path: inset(0 0 100% 0);
		}
		to {
			clip-path: inset(0 0 0 0);
		}
	}

	@keyframes enter-word {
		from {
			translate: 0 105%;
		}
		to {
			translate: none;
		}
	}

	@keyframes unveil-scale {
		from {
			scale: 1.16;
		}
		to {
			scale: 1;
		}
	}

	@keyframes parallax-y {
		from {
			translate: 0 -9%;
		}
		to {
			translate: 0 9%;
		}
	}

	/* Uscita per dissolvenza: il valore di partenza è quello che l'elemento
	   ha già, così la regola non impone un'opacità iniziale a nessuno. */
	@keyframes fade-out {
		to {
			opacity: 0;
		}
	}

	/* =================================================================
	 * 2. Movimento legato allo scroll
	 * ================================================================= */

	@supports (animation-timeline: --rv) {
		/*
		 * Ogni soggetto pubblica la propria timeline di avanzamento nel
		 * viewport. I discendenti possono agganciarcisi per nome, quindi
		 * l'immagine dentro un'inquadratura usa il progresso del contenitore.
		 */
		.js-motion [data-reveal],
		.js-motion .media--parallax {
			view-timeline-name: --rv;
		}

		/*
		 * Il reveal generico vale SOLO per i valori non specializzati.
		 * Applicarlo anche a line/media/split significherebbe imporre un
		 * `translate` a quegli elementi — e un translate rende l'elemento
		 * blocco contenitore dei discendenti assoluti, il che spezza lo
		 * stretched link delle schede progetto riducendone l'area cliccabile
		 * al solo testo del titolo.
		 */
		.js-motion
			[data-reveal]:not([data-reveal='line'], [data-reveal='media'], [data-reveal='split']) {
			animation: enter-up linear both;
			animation-timeline: --rv;
			/*
			 * Lo scaglionamento sposta il RANGE, non il tempo: con una timeline di
			 * scroll `animation-delay` non ha alcun effetto. Il passo è quindi una
			 * percentuale, ed è la ragione per cui nel markup l'indice è un numero
			 * puro (`--i`) e non i millisecondi che vi stavano scritti prima.
			 *
			 * Entrambi gli estremi stanno nella STESSA fase. Prima l'inizio era in
			 * `entry` e la fine in `cover`, che su un elemento alto ~190px durano
			 * rispettivamente 190px e 1090px di scorrimento: il passo del 4%
			 * distribuiva gli inizi su 23px e le fini su 131px, quindi non una
			 * cascata ma quattro elementi che finivano a velocità diverse.
			 *
			 * L'indice è LIMITATO da `--reveal-cap`, e non per prudenza: ogni
			 * soggetto misura la propria timeline, quindi chi sta più in basso è
			 * già in ritardo di suo e l'indice glielo raddoppia. Senza tetto la
			 * sesta voce di un elenco arrivava a metà schermo ancora al 53% di
			 * opacità, mentre la prima era opaca da un pezzo. I token stanno in
			 * tokens.css §6: qui non si scrivono numeri a mano.
			 */
			animation-range: cover calc(6% + min(var(--i, 0), var(--reveal-cap)) * var(--reveal-step))
				cover calc(34% + min(var(--i, 0), var(--reveal-cap)) * var(--reveal-step));
		}

		/*
		 * `data-reveal="line"` non ha oggi utilizzatori: le aperture di pagina che
		 * lo usavano sono passate a `data-enter="line"`, perché stanno sopra la
		 * piega e un reveal legato allo scroll lì non parte mai. La variante resta
		 * definita, come le `data-enter` senza consumatori: è il gesto per una
		 * riga di display che entri DA SOTTO la piega, e serve il giorno che ce ne
		 * sia una. Il suo range non ha tetto perché non ha elenchi da scaglionare.
		 */
		.js-motion [data-reveal='line'] {
			display: block;
			animation: enter-line linear both;
			animation-timeline: --rv;
			animation-range: entry calc(6% + var(--i, 0) * 5%) cover calc(26% + var(--i, 0) * 5%);
		}

		/* Il titolo si compone parola per parola man mano che sale. */
		.js-motion [data-reveal='split'] .split__word {
			animation: enter-word linear both;
			animation-timeline: --rv;
			animation-range: entry calc(10% + var(--i, 0) * 3.5%) cover calc(26% + var(--i, 0) * 3.5%);
		}

		/*
		 * Il contenitore di un reveal a maschera DEVE ritagliare davvero.
		 * `clip-path` ritaglia solo in pittura: l'immagine controscalata a 1.16
		 * resta fuori dai bordi ai fini dello scroll del documento e produce
		 * scroll orizzontale sui viewport stretti. `.media` ha già
		 * `overflow: hidden`; questa regola copre gli altri contenitori.
		 */
		.js-motion [data-reveal='media'] {
			overflow: clip;
		}

		/* Immagine che si scopre mentre l'inquadratura si apre. */
		.js-motion [data-reveal='media'] {
			animation: enter-mask linear both;
			animation-timeline: --rv;
			animation-range: entry 0% entry 95%;
		}

		.js-motion [data-reveal='media'] > img {
			animation: unveil-scale linear both;
			animation-timeline: --rv;
			animation-range: entry 0% entry 100%;
		}

		/*
		 * Parallasse: l'immagine è più alta della propria inquadratura e scorre
		 * in controtempo rispetto alla pagina. È il segnale di profondità che
		 * distingue una composizione editoriale da una griglia statica.
		 */
		.js-motion .media--parallax > img {
			position: absolute;
			block-size: 124%;
			inset-block-start: -12%;
			animation: parallax-y linear both;
			animation-timeline: --rv;
			animation-range: cover 0% cover 100%;
		}

		/* Dove convivono maschera e parallasse servono due animazioni sullo
		   stesso elemento, con range diversi sulla medesima timeline. */
		.js-motion [data-reveal='media'].media--parallax > img {
			animation:
				unveil-scale linear both,
				parallax-y linear both;
			animation-timeline: --rv, --rv;
			animation-range:
				entry 0% entry 100%,
				cover 0% cover 100%;
		}

		/*
		 * La deriva orizzontale dei numeri d'indice fuori misura non c'è più:
		 * quei numeri sono stati tolti da DEC-037 e la classe non aveva più
		 * utilizzatori. Vale la pena ricordare perché non va rimessa a cuor
		 * leggero: muovendo un elemento oltre il proprio riquadro produceva
		 * scroll orizzontale vero, ed è per questo che `motion.spec.ts` lo
		 * verifica ancora a 390px.
		 */

		/*
		 * L'indicatore "Scorri" si dissolve mentre il primo schermo se ne va.
		 * Dice di scorrere: appena lo si fa ha smesso di essere un invito ed è
		 * diventato un residuo. La dissolvenza è legata alla posizione e non a
		 * un tempo, quindi chi scorre piano lo vede sbiadire piano.
		 *
		 * La timeline è quella della HERO, non dell'indicatore: l'indicatore è
		 * alto poche decine di pixel e la sua uscita dal viewport sarebbe un
		 * lampo. Sull'`exit` della hero lo 0% cade quando il primo schermo
		 * comincia a scorrere via, e a metà uscita — mezzo viewport — è già
		 * sparito, molto prima che la sezione successiva prenda la scena.
		 *
		 * `:not(.intro-pending)` tiene la regola fuori dall'intro: in cascata
		 * un'animazione CSS batte la dichiarazione `opacity: 0` degli stati
		 * iniziali, e l'indicatore lampeggerebbe prima del proprio ingresso.
		 */
		.js-motion .hero {
			view-timeline-name: --hero;
		}

		.js-motion:not(.intro-pending) .hero__scroll {
			animation: fade-out linear both;
			animation-timeline: --hero;
			animation-range: exit 0% exit 50%;
		}

		/* Forzatura usata da test e screenshot per fotografare lo stato finale. */
		.js-motion [data-reveal].is-revealed,
		.js-motion [data-reveal].is-revealed > img,
		.js-motion [data-reveal].is-revealed .split__word {
			animation: none;
			opacity: 1;
			translate: none;
			scale: none;
			clip-path: none;
		}
	}

	/* Ripiego per browser senza timeline di scroll: transizione su classe,
	   applicata dall'IntersectionObserver di src/scripts/reveal.ts. */
	@supports not (animation-timeline: --rv) {
		/* Stessa esclusione della variante con timeline di scroll: il translate
		   non deve toccare line/media/split. */
		.js-motion
			[data-reveal]:not([data-reveal='line'], [data-reveal='media'], [data-reveal='split']) {
			opacity: 0;
			translate: 0 1.5rem;
			transition:
				opacity var(--duration-reveal) var(--ease-out),
				translate var(--duration-reveal) var(--ease-out);
			/* 70ms e non 90: questo ritardo diventa effettivo solo ora che il markup
			   scrive `--i`, e su sei voci 90ms sommavano 540ms sopra una durata di
			   720 — l'ultima voce arrivava quando la prima era già ferma da un pezzo. */
			transition-delay: calc(var(--i, 0) * 70ms);
		}

		.js-motion [data-reveal].is-revealed {
			opacity: 1;
			translate: none;
		}

		.js-motion [data-reveal='line'],
		.js-motion [data-reveal='media'] {
			opacity: 1;
			translate: none;
			clip-path: inset(0 0 100% 0);
			transition: clip-path calc(var(--duration-reveal) * 1.15) var(--ease-out);
			/* Stesso passo, stessa ragione del reveal generico qui sopra. */
			transition-delay: calc(var(--i, 0) * 70ms);
		}

		.js-motion [data-reveal='line'].is-revealed,
		.js-motion [data-reveal='media'].is-revealed {
			clip-path: inset(0 0 0 0);
		}

		.js-motion [data-reveal='split'] .split__word {
			translate: 0 105%;
			transition: translate var(--duration-reveal) var(--ease-out);
			transition-delay: calc(var(--i, 0) * 55ms);
		}

		.js-motion [data-reveal='split'].is-revealed .split__word {
			translate: none;
		}
	}

	/* =================================================================
	 * 3. Microinterazioni
	 * ================================================================= */

	.hover-media img {
		transition: scale var(--duration-medium) var(--ease-out);
	}

	/* `hover-target` è l'antenato comune di link e immagine: l'effetto scatta
	   anche col puntatore sullo stretched link o col focus da tastiera. */
	.hover-target:is(:hover, :focus-within) .hover-media img,
	.hover-media:is(:hover, :focus-within) img {
		scale: 1.04;
	}

	.link-underline::after {
		transition: scale var(--duration-short) var(--ease-out);
	}

	.cta-arrow {
		transition: translate var(--duration-short) var(--ease-out);
	}

	:is(a, button):is(:hover, :focus-visible) .cta-arrow {
		translate: 0.25rem 0;
	}
}

/*
 * Motion ridotto: nessuna transizione, nessuna animazione, nessuno smooth
 * scroll. Copre anche il markup iniettato dalle view transitions.
 */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}

	::view-transition-group(*),
	::view-transition-old(*),
	::view-transition-new(*) {
		animation: none !important;
	}
}

/* -------------------------------------------------------------------
 * 4. View transitions: continuità fra indice progetti e case study.
 * La cover scelta nell'indice diventa la cover del progetto invece di
 * sparire e riapparire.
 * ------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
	::view-transition-old(root),
	::view-transition-new(root) {
		animation-duration: var(--duration-short);
		animation-timing-function: var(--ease-out);
	}

	::view-transition-group(.cover) {
		animation-duration: var(--duration-medium);
		animation-timing-function: var(--ease-in-out);
	}

	::view-transition-old(.cover),
	::view-transition-new(.cover) {
		mix-blend-mode: normal;
		block-size: 100%;
		object-fit: cover;
	}

	/*
	 * Nessuna dissolvenza incrociata SOPRA IL MORPHING: si vedrebbero due
	 * immagini sovrapposte durante il volo.
	 *
	 * Ma solo lì, ed è tutta qui la ragione del `:only-child`. Ogni cover ha un
	 * nome proprio, quindi uscendo dall'indice verso una pagina che di cover non
	 * ne ha — servizi, studio, contatti — e per le schede NON cliccate quando si
	 * apre un progetto, lo scatto vecchio resta senza controparte: è figlio unico
	 * della propria coppia. Lì la dissolvenza del browser è l'unica cosa che lo
	 * toglie di mezzo, e azzerarla lo lasciava pienamente opaco sopra la pagina
	 * già caricata per tutti i 420 ms del gruppo. Era l'immagine che restava
	 * sospesa in sovraimpressione.
	 */
	::view-transition-old(.cover):not(:only-child),
	::view-transition-new(.cover):not(:only-child) {
		animation: none;
	}

	/*
	 * Lo scatto spaiato deve essere via PRIMA che la pagina nuova sia al pieno,
	 * non insieme a lei. La dissolvenza del browser dura ~250 ms e parte con una
	 * curva lenta: a metà strada l'immagine è ancora mezza opaca sopra una pagina
	 * ormai completa, ed è la sovrapposizione che restava da togliere.
	 *
	 * 120 ms è più corto di qualunque token di durata, e non è una svista: negli
	 * altri casi la durata serve a far leggere un gesto, qui c'è solo un residuo
	 * da mandare via. La curva scende subito, quindi a un terzo del tempo
	 * l'immagine è già oltre metà strada.
	 *
	 * Solo `old`. La cover che ENTRA, atterrando sull'indice da un'altra pagina,
	 * tiene la dissolvenza del browser: lì l'immagine è ciò che si sta guardando,
	 * non ciò che se ne va.
	 */
	::view-transition-old(.cover):only-child {
		animation-duration: 120ms;
		animation-timing-function: var(--ease-out);
	}
}
/*
 * Intro della hero.
 *
 * Registro richiesto da Gioele (21.08.2026):
 *   1. il titolo si scrive lettera per lettera, da sinistra a destra;
 *   2. logo e menu entrano con un leggero slittamento DA SINISTRA + dissolvenza;
 *   3. il piede della hero entra allo stesso modo ma DA DESTRA;
 *   4. l'indicatore "Scorri" sale dal basso.
 *
 * Ogni passo anima soltanto `opacity` e `translate`: nessuna proprietà che
 * provochi layout, nessuna maschera, nessun ridimensionamento. Il titolo non
 * si sposta mai — le lettere sono già al loro posto e si limitano a comparire.
 *
 * ─────────────────────────────────────────────────────────────────────
 * SICUREZZA
 *
 * Tutti gli stati iniziali vivono sotto `.intro-pending`, classe aggiunta
 * dallo script inline nel <head> e rimossa sia dalla timeline sia da un
 * timeout di emergenza. Senza JavaScript la classe non compare mai e il
 * contenuto è visibile; con `prefers-reduced-motion: reduce` nemmeno.
 * Vedi src/layouts/BaseLayout.astro e src/scripts/intro.ts.
 * ─────────────────────────────────────────────────────────────────────
 */

/*
 * Il titolo è escluso dallo stato iniziale generico: a nascondersi sono le
 * singole lettere, non l'h1. Se il contenitore partisse trasparente, la
 * scrittura carattere per carattere avverrebbe dentro un blocco invisibile.
 */
/*
 * La CTA è esclusa anche da qui, non solo dalla traslazione: sta dentro il
 * piede e deve comparire INSIEME alle parole chiave che le stanno a fianco
 * (DEC-047). Nasconderla con questa regola significava non scoprirla mai —
 * nessuna animazione la prende di mira — e vederla apparire di colpo a
 * sequenza finita, quando cade `intro-pending`. Il commento qui sotto
 * dichiarava l'esclusione dal 21.08; il selettore non la applicava (DEC-066).
 */
/*
 * `[data-intro-done]` è l'uscita anticipata, e non è un dettaglio di pulizia.
 *
 * Lo script lo mette sul singolo elemento appena il SUO ingresso è finito.
 * Serve perché finché la regola qui sotto lo riguarda, l'elemento resta
 * visibile solo grazie al `fill` dell'animazione: la sua opacità arriva dal
 * compositore, non da un paint. Misurato il 16.09.2026 a 390 px: il sommario
 * era leggibile a 512 ms, ma Chrome registrava il suo primo candidato LCP a
 * 1 992 ms — sette millisecondi dopo `settle()`, cioè dopo la cancellazione
 * delle animazioni. Il numero che finisce nei Core Web Vitals era quello.
 *
 * Con l'attributo, ogni blocco esce dallo stato iniziale per conto suo e
 * ridipinge quando ha finito, senza aspettare la fine dell'intera sequenza.
 * Senza JavaScript l'attributo non compare mai, e nemmeno `.intro-pending`:
 * lo stato di riposo resta quello di sempre.
 */
.intro-pending [data-intro]:not([data-intro-type], [data-intro='cta'], [data-intro-done]) {
	opacity: 0;
}

.intro-pending [data-char] {
	opacity: 0;
}

/* Logo e menu: entrano da sinistra. */
.intro-pending [data-intro='header']:not([data-intro-done]) {
	translate: -28px 0;
}

/* Testo, pulsante e punti chiave: entrano dal lato opposto, a scalare. */
/* La CTA non compare più qui: sta dentro [data-intro='foot'], che la
   nasconde e la rivela insieme a sé (DEC-047). Lasciarcela significherebbe
   nasconderla due volte e non scoprirla mai, perché nessuna animazione la
   prende più di mira. */
.intro-pending [data-intro='lead']:not([data-intro-done]),
.intro-pending [data-intro='foot']:not([data-intro-done]) {
	translate: 28px 0;
}

/* "Scorri": sale dal basso. */
.intro-pending [data-intro='scroll']:not([data-intro-done]) {
	translate: 0 22px;
}

/*
 * B01-R2 (N46-079/N46-082) — focus percepibile durante l'avvio, prima ancora
 * che il modulo carichi.
 *
 * Riprodotto con JavaScript rallentato di 800 ms: dopo il salto al contenuto,
 * tre Tab reali portano il fuoco su logo e pulsante Menu — entrambi dentro
 * `[data-intro="header"]` — mentre quel contenitore è ancora a `opacity: 0`
 * per la regola qui sopra. Il fuoco esiste (l'elemento è cliccabile e
 * attivabile), ma non si vede: un controllo attivo invisibile non supera il
 * requisito, a prescindere da quanto dura la finestra.
 *
 * Stessa condizione per la CTA della hero: vive dentro `[data-intro="foot"]`
 * (DEC-047) e ne eredita l'opacità del contenitore, quindi lo stesso
 * problema si presenterebbe lì se il fuoco arrivasse prima della
 * rivelazione.
 *
 * Correzione minima e solo CSS, nessuna dipendenza da JavaScript: quando un
 * discendente focalizzabile di un blocco `[data-intro]` riceve il fuoco da
 * tastiera, quel blocco torna a piena opacità e posizione naturale — non
 * serve `data-intro-done`, perché lo stesso `:has()` copre anche il caso in
 * cui lo script non sia ancora arrivato affatto. Non è una trappola: appena
 * il fuoco si sposta altrove — o la timeline rivela il blocco per conto suo —
 * la regola smette di applicarsi. Nessun controllo viene disabilitato: resta
 * sempre nel DOM, nell'ordine di tabulazione e attivabile; cambia solo la sua
 * visibilità mentre ha il fuoco. `:has()` è già in uso nel progetto
 * (`Header.astro`, stato del pannello mobile), non è una tecnica nuova.
 *
 * Specificità pari alle regole di opacità/traslazione qui sopra (una classe,
 * un attributo, una pseudo-classe cadauna): questa regola vince perché viene
 * DOPO nel foglio, non perché è più specifica — spostarla prima delle altre
 * la renderebbe inefficace.
 */
.intro-pending [data-intro]:has(:focus-visible) {
	opacity: 1;
	translate: none;
}

/*
 * Con motion ridotto lo script inline non aggiunge `intro-pending`, quindi
 * queste regole non si applicano mai. La dichiarazione esplicita resta come
 * rete: se in futuro la classe arrivasse per altra via, il contenuto
 * resterebbe comunque visibile e fermo.
 */
@media (prefers-reduced-motion: reduce) {
	.intro-pending [data-intro],
	.intro-pending [data-char] {
		opacity: 1;
		translate: none;
	}
}
.skip-link[data-astro-cid-z4jru4n3] {
  position: absolute;
  inset-inline-start: var(--gutter);
  inset-block-start: 0;
  z-index: var(--z-skip-link);
  translate: 0 -150%;
  padding: .75rem 1.25rem;
  background-color: var(--color-accent);
  color: var(--color-ink);
  font-size: var(--text-small);
  font-weight: var(--weight-semibold);
  text-decoration: none;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
}

.skip-link[data-astro-cid-z4jru4n3]:focus-visible {
  translate: 0;
}
