/* ==========================================================================
   AND ITALIA — design system
   Ported verbatim (tokens + components + behaviors) from the approved
   mockup: mockup-c3-modern-purple.html ("C3 — Modern Purple").
   Additional components required for the multi-page site (dropdown nav,
   compact inner-page header, article typography, forms, avatar-initials,
   consent banner) are new but built with the same tokens/spring/shadows.
   ========================================================================== */

:root{
  --violet:#9333ea;
  --violet-deep:#7c3aed;
  --pink:#ec4899;
  --magenta-deep:#a3186b;
  /* --magenta-bright: brighter/more vivid stand-in for --pink, used only by
     the assistenti-virtuali hero triad label (round: triad2-news) — the
     old site's reference used a pure #ff00ff pop for that label, notably
     more saturated than --pink; this is the closest AA-safe match against
     the triad's near-black scrim (see docs/.sdd/triad2-news.md for the
     per-sample contrast table: 5.08-5.75:1 across the triad's realistic
     footprint, clearing 4.5:1 with more margin than --pink did there). */
  --magenta-bright:#ff2ea6;
  --amber:#f5a524;
  /* --amber-safe: round-11 QA fix (task-7-a11y I-2) — --amber (#f5a524)
     computes only 2.00:1 against the light --bg (#fdfcff), failing even the
     3:1 large-text floor. This darker/more saturated amber computes 3.69:1
     against --bg, so any gradient stop that can land on a LIGHT background
     (.section-title-big) uses this instead; --amber itself is left
     untouched because it's also used on the hero's dark navy background
     (9.63:1 there, already safe) and other dark-surface contexts. */
  --amber-safe:#c26f00;
  --coral:#ff8a5c;
  --ink:#150d24;
  --ink-soft:#3d3350;
  --muted:#5c5270;
  --bg:#fdfcff;
  --card:#ffffff;
  --border:rgba(124,58,237,0.14);
  --border-soft:rgba(124,58,237,0.09);
  --dark-bg:#150c26;
  --dark-border:rgba(255,255,255,0.12);
  --dark-text:#f5f2fb;
  --dark-muted:#b6abc9;
  --shadow-violet:0 20px 45px -20px rgba(124,58,237,0.45);
  --shadow-pink:0 20px 45px -20px rgba(236,72,153,0.4);
  --radius-lg:24px;
  --radius-xl:28px;
  --radius-pill:999px;
  --maxw:1180px;
  --spring:cubic-bezier(.34,1.56,.64,1);
  --error:#c62855;
}

*,*::before,*::after{box-sizing:border-box;}
html{scroll-behavior:smooth;}
[id]{scroll-margin-top:112px;}

body{
  margin:0;
  font-family:'Plus Jakarta Sans', 'Sora', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  color:var(--ink);
  background:var(--bg);
  font-size:16px;
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}

h1,h2,h3,h4{
  font-family:'Sora', 'Plus Jakarta Sans', sans-serif;
  line-height:1.15;
  margin:0 0 0.5em 0;
  letter-spacing:-0.01em;
}
p{margin:0 0 1em 0;}
img,svg{max-width:100%;display:block;}
a{color:inherit;}
ul,ol{margin:0 0 1em 0; padding-left:1.3em;}

::selection{background:var(--violet-deep); color:#fff;}

:focus-visible{
  outline:3px solid var(--violet-deep);
  outline-offset:3px;
  border-radius:6px;
}

.wrap{
  max-width:var(--maxw);
  margin:0 auto;
  padding:0 24px;
}

.sr-only{
  position:absolute;
  width:1px;height:1px;
  padding:0;margin:-1px;
  overflow:hidden;
  clip:rect(0,0,0,0);
  white-space:nowrap;border:0;
}

/* ---------- Skip link ---------- */
.skip-link{
  position:absolute;
  left:16px;
  top:-60px;
  z-index:1000;
  background:var(--dark-bg);
  color:#fff;
  padding:12px 20px;
  border-radius:var(--radius-pill);
  text-decoration:none;
  font-weight:700;
  font-size:14.5px;
  transition:top 0.2s ease;
}
.skip-link:focus{top:16px;}

/* ---------- Scroll progress ---------- */
.scroll-progress{
  position:fixed; top:0; left:0; height:3px; width:0%;
  background:linear-gradient(90deg, var(--violet-deep), var(--pink), var(--amber));
  z-index:300; pointer-events:none;
}

/* ---------- Mesh blobs ---------- */
.blob{
  position:absolute;
  border-radius:50%;
  filter:blur(80px);
  opacity:0.6;
  z-index:0;
  pointer-events:none;
}
@media (prefers-reduced-motion: no-preference){
  .blob{animation:float 16s ease-in-out infinite;}
  .blob.b2{animation-duration:20s; animation-delay:-4s;}
  .blob.b3{animation-duration:24s; animation-delay:-9s;}
}
@keyframes float{
  0%,100%{transform:translate(0,0) scale(1);}
  33%{transform:translate(30px,-25px) scale(1.05);}
  66%{transform:translate(-20px,20px) scale(0.97);}
}

/* ---------- Buttons ---------- */
.btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:8px;
  padding:14px 28px;
  border-radius:var(--radius-pill);
  font-weight:700;
  font-size:16px;
  font-family:inherit;
  text-decoration:none;
  cursor:pointer;
  border:2px solid transparent;
  transition:transform 0.3s var(--spring), box-shadow 0.3s ease, background 0.2s ease, color 0.2s ease;
  white-space:nowrap;
}
.btn-primary{
  background:linear-gradient(135deg, var(--violet), var(--pink));
  color:#fff;
  box-shadow:var(--shadow-violet);
}
.btn-primary:hover{transform:translateY(-2px) scale(1.035); box-shadow:0 24px 50px -16px rgba(124,58,237,0.55);}
.btn-secondary{
  background:rgba(255,255,255,0.08);
  color:#fff;
  border-color:rgba(255,255,255,0.55);
  backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px);
}
.btn-secondary:hover{background:rgba(255,255,255,0.18); border-color:#fff; color:#fff; transform:translateY(-2px) scale(1.02);}
.btn-white{
  background:#fff;
  color:var(--violet-deep);
}
.btn-white:hover{transform:translateY(-2px) scale(1.035); box-shadow:0 20px 45px -18px rgba(0,0,0,0.35);}
.btn-outline{
  background:transparent;
  color:var(--violet-deep);
  border-color:var(--border);
}
/* Round 6-7: lift added so btn-outline's hover matches the other three
   variants' consistent transform+timing (previously color-only). */
.btn-outline:hover{background:rgba(124,58,237,0.08); border-color:var(--violet-deep); transform:translateY(-2px) scale(1.02);}
.btn-lg{padding:17px 36px; font-size:17px;}
.btn-sm{padding:10px 20px; font-size:14px;}
.btn:disabled{opacity:0.55; cursor:not-allowed; transform:none !important;}
/* Home hero "Prova il nostro assistente" busy state (assets/js/main.js
   #heroChatCta): shown while polling for the ChatAnd embed's launcher
   button (#chatbot-open-btn, built by and-italia-chatbot-embed.js) to
   appear before clicking it. aria-busy is set alongside
   :disabled, so this needs its own (lighter) opacity rather than inheriting
   .btn:disabled's 0.55 dimming, which would make the "Un attimo…" text hard
   to read while it's the whole point of the state. */
.btn[aria-busy="true"]{opacity:1; cursor:wait;}
.btn-spinner{
  width:14px; height:14px; flex:none;
  border-radius:50%;
  border:2px solid rgba(255,255,255,.35); border-top-color:#fff;
  animation:btnSpin .7s linear infinite;
}
@keyframes btnSpin{to{transform:rotate(360deg);}}
@media (prefers-reduced-motion:reduce){
  .btn-spinner{animation-duration:1.4s;}
}

/* ---------- Floating glass nav ---------- */
header.site{
  position:fixed;
  top:16px;
  left:50%;
  transform:translateX(-50%);
  z-index:200;
  /* No fixed width: with only `left` set, a fixed-position box with no
     explicit width shrink-wraps to its child's content (bounded by the
     max-width safety cap below). This lets the pill itself size to
     whatever the nav actually needs at a given breakpoint/font-compaction
     tier, instead of stretching to a flat 1120px that the full desktop nav
     (logo + 7 links + CTA) no longer fits inside — that mismatch was why
     the CTA poked out past the pill's right edge. The >900px→hamburger
     media query below restores the old full-bleed-bar width for the
     collapsed/mobile tier, where .mobile-panel still needs a wide
     positioning box. */
  max-width:calc(100vw - 32px);
  /* .nav-pill + .nav-cluster (search/social icon cluster, owner-requested
     parity feature: a SEPARATE glass pill to the right of the nav, not
     merged into it — see docs/.sdd/search-social.md) sit side by side as
     flex siblings here, vertically centered together, so the pair reads
     as one floating group while .nav-pill's own width/padding/shadow stay
     completely untouched. */
  display:flex;
  align-items:center;
  gap:14px;
}
.nav-pill{
  position:relative;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:20px;
  padding:10px 10px 10px 22px;
  width:fit-content;
  max-width:100%;
  /* min-width:0 (final-fixes.md): default flex auto-min floors this at
     content width, so it can't shrink at all — this is the safety valve
     if the .nav-cluster sibling ever gets tight against the cap. */
  min-width:0;
  border-radius:var(--radius-pill);
  background:rgba(253,252,255,0.88);
  backdrop-filter:blur(20px) saturate(180%);
  -webkit-backdrop-filter:blur(20px) saturate(180%);
  border:1px solid rgba(124,58,237,0.14);
  box-shadow:0 12px 40px -16px rgba(124,58,237,0.28), 0 2px 10px rgba(21,12,38,0.05);
  transition:padding 0.35s ease, box-shadow 0.35s ease, border-radius 0.35s ease, gap 0.35s ease;
}
header.site.scrolled .nav-pill{
  padding:6px 8px 6px 18px;
  box-shadow:0 10px 30px -12px rgba(124,58,237,0.38), 0 2px 10px rgba(21,12,38,0.08);
}
.logo{text-decoration:none; display:flex; align-items:center; flex-shrink:0;}
.logo-img{display:block; height:38px; width:auto; max-width:none; transition:height 0.35s ease;}
header.site.scrolled .logo-img{height:30px; width:auto; max-width:none;}
nav.links{display:flex; align-items:center; gap:6px;}
.site-nav-item{position:relative; display:flex; align-items:center;}
nav.links a.nav-link{
  font-weight:600;
  font-size:14.5px;
  color:var(--ink-soft);
  text-decoration:none;
  position:relative;
  padding:8px 6px;
  white-space:nowrap;
}
nav.links a.nav-link::after{
  content:"";
  position:absolute;
  left:6px;right:6px;bottom:2px;
  height:2px;
  transform:scaleX(0);
  transform-origin:left;
  background:linear-gradient(90deg, var(--violet), var(--pink));
  transition:transform 0.25s ease;
}
nav.links a.nav-link:hover::after,
nav.links a.nav-link:focus-visible::after{transform:scaleX(1);}

.nav-caret{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:22px;height:22px;
  margin-left:-2px;
  border:none;
  background:transparent;
  color:var(--ink-soft);
  cursor:pointer;
  border-radius:50%;
}
.nav-caret svg{width:10px;height:10px; transition:transform 0.2s ease;}
.site-nav-item.open .nav-caret svg{transform:rotate(180deg);}

.nav-dropdown{
  /* Positioning shell only — no background/border/shadow here. The visible
     14px gap between the nav pill and the panel is rendered as padding-top
     on THIS element (not a top offset), so the gap is part of the same
     hoverable box tree as the panel: the pointer never crosses empty space
     that isn't a descendant of .site-nav-item, so :hover survives the trip down
     from the nav-link into the dropdown. All visuals live on
     .nav-dropdown-inner instead. */
  display:none;
  position:absolute;
  top:100%;
  left:0;
  padding-top:14px;
  min-width:240px;
  z-index:210;
}
.site-nav-item:hover .nav-dropdown,
.site-nav-item.open .nav-dropdown{display:block;}
.nav-dropdown-inner{
  padding:10px;
  border-radius:18px;
  background:rgba(253,252,255,0.98);
  backdrop-filter:blur(20px);
  -webkit-backdrop-filter:blur(20px);
  border:1px solid rgba(124,58,237,0.14);
  box-shadow:0 24px 55px -20px rgba(124,58,237,0.4);
}
.nav-dropdown a{
  display:block;
  padding:10px 12px;
  border-radius:10px;
  text-decoration:none;
  font-weight:600;
  font-size:14px;
  color:var(--ink-soft);
}
.nav-dropdown a:hover,
.nav-dropdown a:focus-visible{background:rgba(124,58,237,0.08); color:var(--violet-deep);}

.nav-right{display:flex; align-items:center; gap:14px;}
.nav-right .btn-primary{padding:11px 22px; font-size:14.5px;}
.hamburger{
  display:none;
  flex-direction:column;
  justify-content:center;
  gap:5px;
  width:42px;height:42px;
  border-radius:50%;
  border:1px solid var(--border);
  background:#fff;
  cursor:pointer;
  flex:none;
}
.hamburger span{
  display:block;
  height:2px;
  width:20px;
  margin:0 auto;
  background:var(--ink);
  border-radius:2px;
  transition:transform 0.25s ease, opacity 0.25s ease;
}
.hamburger[aria-expanded="true"] span:nth-child(1){transform:translateY(7px) rotate(45deg);}
.hamburger[aria-expanded="true"] span:nth-child(2){opacity:0;}
.hamburger[aria-expanded="true"] span:nth-child(3){transform:translateY(-7px) rotate(-45deg);}

.mobile-panel{
  display:none;
  position:absolute;
  top:calc(100% + 10px);
  left:0; right:0;
  flex-direction:column;
  gap:2px;
  padding:14px 20px 20px;
  border-radius:26px;
  background:rgba(253,252,255,0.98);
  backdrop-filter:blur(20px);
  -webkit-backdrop-filter:blur(20px);
  border:1px solid rgba(124,58,237,0.14);
  box-shadow:0 24px 60px -20px rgba(124,58,237,0.4);
  max-height:calc(100vh - 120px);
  overflow-y:auto;
}
.mobile-panel.open{display:flex;}
.mobile-panel > a,
.mobile-panel .mobile-item > .mobile-link{
  padding:12px 4px;
  font-weight:600;
  text-decoration:none;
  color:var(--ink-soft);
  border-bottom:1px solid var(--border);
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:8px;
}
.mobile-item{border-bottom:1px solid transparent;}
.mobile-sub{
  display:none;
  flex-direction:column;
  padding:0 0 8px 14px;
}
.mobile-item.open .mobile-sub{display:flex;}
.mobile-item.open .mobile-toggle svg{transform:rotate(180deg);}
.mobile-sub a{
  padding:9px 4px;
  font-weight:600;
  font-size:14.5px;
  text-decoration:none;
  color:var(--muted);
}
.mobile-toggle{
  background:none; border:none; cursor:pointer;
  display:flex; align-items:center; justify-content:center;
  width:28px; height:28px; color:var(--ink-soft); flex:none;
}
.mobile-toggle svg{width:12px;height:12px; transition:transform 0.2s ease;}
.mobile-panel .btn{margin-top:12px; width:100%;}

/* Mobile panel bottom section: the search entry + 3 social links that live
   in .nav-cluster on desktop move in here below >1280px (see .nav-cluster
   display:none in the hamburger-tier media query below) — owner-requested
   parity feature, docs/.sdd/search-social.md. Purely additive; no other
   mobile-panel row is touched. */
.mobile-bottom{
  margin-top:14px;
  padding-top:14px;
  border-top:1px solid var(--border);
  display:flex;
  flex-direction:column;
  gap:12px;
}
.mobile-search-btn{
  display:flex;
  align-items:center;
  gap:10px;
  width:100%;
  border:none;
  background:none;
  padding:10px 4px;
  font:inherit;
  font-weight:600;
  font-size:14.5px;
  color:var(--ink-soft);
  text-align:left;
  cursor:pointer;
}
.mobile-search-btn svg{width:18px; height:18px; flex:none; color:var(--violet-deep);}
.mobile-socials{display:flex; gap:10px;}
.mobile-socials a{
  width:38px;height:38px;
  border-radius:50%;
  border:1px solid var(--border);
  display:flex;align-items:center;justify-content:center;
  color:var(--ink-soft);
  transition:background 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
.mobile-socials a:hover{background:linear-gradient(135deg, var(--violet), var(--pink)); color:#fff; border-color:transparent; transform:translateY(-2px);}

/* ---------- Nav icon cluster (search + socials, desktop) ----------
   Separate small glass cluster to the RIGHT of the floating pill (owner
   explicit: NOT inside the pill — old-site parity). Same glass treatment
   as .nav-pill (background/border/shadow copied verbatim) so the two read
   as a matched pair, but it's its own box: .nav-pill's width/padding are
   never touched by this. Hidden below the hamburger breakpoint, where its
   3 socials + search entry move into .mobile-bottom instead. */
.nav-cluster{
  display:flex;
  align-items:center;
  gap:6px;
  padding:6px;
  flex:none;
  border-radius:var(--radius-pill);
  background:rgba(253,252,255,0.88);
  backdrop-filter:blur(20px) saturate(180%);
  -webkit-backdrop-filter:blur(20px) saturate(180%);
  border:1px solid rgba(124,58,237,0.14);
  box-shadow:0 12px 40px -16px rgba(124,58,237,0.28), 0 2px 10px rgba(21,12,38,0.05);
  transition:padding 0.35s ease, gap 0.35s ease, box-shadow 0.35s ease;
}
/* Header scroll shrink (assets/js/main.js toggles header.site.scrolled)
   applies to the cluster too — same subtle shrink language as
   header.site.scrolled .nav-pill above, just scaled to this component. */
header.site.scrolled .nav-cluster{
  padding:4px;
  box-shadow:0 10px 30px -12px rgba(124,58,237,0.38), 0 2px 10px rgba(21,12,38,0.08);
}
.cluster-btn{
  display:flex;
  align-items:center;
  justify-content:center;
  width:40px;height:40px;
  flex:none;
  border:none;
  border-radius:50%;
  background:transparent;
  color:var(--ink-soft);
  cursor:pointer;
  transition:background 0.2s ease, color 0.2s ease, transform 0.2s ease, width 0.35s ease, height 0.35s ease;
}
.cluster-btn svg{width:18px;height:18px;}
.cluster-btn:hover,
.cluster-btn:focus-visible{background:linear-gradient(135deg, var(--violet), var(--pink)); color:#fff; transform:translateY(-2px);}
header.site.scrolled .cluster-btn{width:34px;height:34px;}

/* Middle tier: the full 7-link nav + CTA still fits (fit-content pill),
   but not with room to spare — tighten gaps/padding/type-size a notch so
   there's breathing room before the hamburger tier below. Range EXTENDED
   1400px -> 1440px (final-fixes.md): with .nav-cluster now beside the
   pill, the comfortable-tier pairing (≈1399px) overflows the
   `100vw-32px` cap at 1401-1431px; staying in this tighter tier (≈1248px)
   through 1440px closes that gap (see final-fixes.md for the fit table). */
@media (max-width: 1440px){
  .nav-pill{gap:12px; padding:10px 10px 10px 18px;}
  nav.links{gap:4px;}
  nav.links a.nav-link{font-size:13.5px; padding:8px 5px;}
  .nav-caret{width:20px; height:20px;}
  .nav-right .btn-primary{padding:9px 16px; font-size:13px;}
  header.site{gap:10px;}
  .nav-cluster{gap:4px; padding:5px;}
  .cluster-btn{width:36px; height:36px;}
}

/* Hamburger/mobile collapse. Raised again, 1200px -> 1280px
   (final-fixes.md): the cluster+pill pair now needs ≈1248px, which only
   clears the `100vw-32px` cap from 1280px up (at 1200px it overflowed by
   up to -79px in 1201-1280px). max-width:1279px keeps 1280px+ as the
   first desktop-pair width. Below this, header.site/.nav-pill revert to
   full-bleed sizing so .mobile-panel keeps its full-width dropdown. */
@media (max-width: 1279px){
  nav.links, .nav-right .btn-primary{display:none;}
  .hamburger{display:flex;}
  header.site{width:min(1120px, calc(100vw - 32px)); max-width:none;}
  .nav-pill{width:100%; max-width:none;}
  /* Cluster moves into .mobile-bottom (inside .mobile-panel) at this tier —
     see search-social.md. */
  .nav-cluster{display:none;}
}

/* ---------- Search overlay ----------
   Moved to assets/css/search.css, lazily <link>-injected by
   assets/js/main.js the first time the search button is clicked (same
   moment it dynamic-import()s assets/js/search.js) — the overlay is
   hidden by default and needs neither its styles nor its behavior
   before that first interaction, so keeping both out of the eagerly-
   loaded style.css/main.js keeps every page's first-load weight down
   (same technique already used for the brochure page's flipbook.js).
   .nav-cluster/.mobile-bottom above stay HERE: unlike the overlay,
   they're visible immediately on page load, not gated on interaction. */

/* ---------- Hero (home) ---------- */
.hero{
  position:relative;
  padding:clamp(120px,20vw,168px) 0 112px;
  overflow:hidden;
  min-height:100vh;
  display:flex;
  flex-direction:column;
  justify-content:center;
  background-color:#050a1e;
  /* background-image intentionally NOT set here: it is emitted as a
     page-scoped inline <style> only on template:"home" by build.py, so
     check.py's page-weight accounting doesn't charge every other page
     for the 488KB hero PNG via this shared stylesheet's url(...) ref. */
  background-repeat:no-repeat;
  background-size:cover;
  background-position:18% 34%;
}
.hero::before{
  content:"";
  position:absolute; inset:0;
  background-image:url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='300'%20height='300'%3E%3Cfilter%20id='n'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.85'%20numOctaves='3'%20stitchTiles='stitch'/%3E%3C/filter%3E%3Crect%20width='100%25'%20height='100%25'%20filter='url(%23n)'/%3E%3C/svg%3E");
  opacity:0.045;
  mix-blend-mode:overlay;
  pointer-events:none;
  z-index:1;
}
.hero::after{
  content:"";
  position:absolute; inset:0;
  /* Round-1 redesign: hero copy is centered over the face/waveform image,
     so the scrim is a centered radial (was a left-to-right linear gradient
     tuned for the old left-aligned layout) — keeps white title/subtitle at
     AA contrast wherever the underlying photo is brightest, center-most. */
  background:radial-gradient(62% 62% at 50% 42%, rgba(5,10,30,.90) 0%, rgba(5,10,30,.80) 40%, rgba(5,10,30,.58) 68%, rgba(5,10,30,.32) 100%);
  pointer-events:none;
  z-index:1;
}
.hero-inner{
  position:relative; z-index:2;
  max-width:900px;
  margin:0 auto;
  display:flex;
  flex-direction:column;
  align-items:center;
  text-align:center;
}
.eyebrow{
  /* No longer rendered on the home hero (round-1 centered redesign removed
     the eyebrow pill) — rule kept in case a future template reuses it. */
  display:inline-flex;
  align-items:center;
  gap:8px;
  padding:8px 18px;
  border-radius:var(--radius-pill);
  background:linear-gradient(135deg, rgba(147,51,234,0.45), rgba(236,72,153,0.4));
  color:#fdf1ff;
  font-weight:700;
  font-size:14px;
  margin-bottom:24px;
  border:1px solid rgba(255,255,255,0.35);
  backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px);
}
.eyebrow .spark{width:8px;height:8px;border-radius:2px; background:#fff; transform:rotate(45deg);}
.hero h1{margin:0;}
.hero-brand{
  display:block;
  font-size:clamp(2.8rem, 9vw, 6rem);
  font-weight:800;
  line-height:0.98;
  letter-spacing:0.01em;
  text-transform:uppercase;
  color:var(--dark-text);
}
.hero-tagline{
  display:block;
  margin-top:16px;
  font-size:clamp(1.1rem, 2.6vw, 1.6rem);
  font-weight:600;
  letter-spacing:0.12em;
  text-transform:uppercase;
  color:rgba(255,255,255,.92);
}
/* Solid fallback FIRST (M-1, same pattern as .grad-text/.section-title-big
   above): --dark-text reads correctly against the hero's dark navy
   background for browsers/forced-colors modes without background-clip:text
   support. Gradient itself is unchanged (--amber, not --amber-safe) — this
   text always sits on the dark hero background, where --amber already
   clears 9.63:1, unlike .section-title-big's light-background case. */
.hero h1 .grad{color:var(--dark-text);}
@supports (background-clip:text) or (-webkit-background-clip:text){
  .hero h1 .grad{
    background:linear-gradient(120deg, var(--violet-deep), var(--pink) 45%, var(--amber) 75%, var(--violet-deep));
    background-size:300% 100%;
    -webkit-background-clip:text; background-clip:text;
    -webkit-text-fill-color:transparent; color:transparent;
  }
}
@media (prefers-reduced-motion: no-preference){
  .hero h1 .grad{animation:gradFlow 7s ease infinite;}
}
@keyframes gradFlow{0%{background-position:0% 50%;}50%{background-position:100% 50%;}100%{background-position:0% 50%;}}
.hero-partner{display:flex; align-items:center; justify-content:center; gap:12px; margin-top:34px; opacity:.85;}
.hero-partner span{font-size:.78rem; letter-spacing:.08em; text-transform:uppercase; color:rgba(255,255,255,.75);}
.hero-partner img{height:44px; width:auto; border-radius:8px;}
.hero-cta{display:flex; align-items:center; justify-content:center; gap:16px; flex-wrap:wrap; margin-top:30px;}

/* ---------- Home lede (SEO-critical intro; moved out of the hero — round 1) ---------- */
.lede-section{padding:72px 0 8px;}
.lede-section .wrap{max-width:860px; text-align:center;}
.lede-section p{font-size:19px; line-height:1.75; color:var(--ink-soft); margin:0;}

/* ---------- Manifesto lede (round 10, owner: "fallo più bello") ----------
   Restyles the intro paragraph into a centered statement block: a
   .divider--gradient sits above the copy, the copy is a single wide
   (~54ch) measure with 4 key-offering phrases picked out as gradient text.
   Markup keeps .lede-section AND adds .manifesto (modifier, not a
   replacement — same convention as .band + .band--dark elsewhere in this
   file); every property that needs a new value is overridden here via the
   .lede-section.manifesto compound selector (specificity 0,2,0), which
   beats the plain .lede-section rules above (0,1,0) regardless of source
   order. Copy text is byte-for-byte unchanged from round 1 — only markup
   (divider + strong wraps) and these rules are new. */
.lede-section.manifesto{padding:96px 0;}
.lede-section.manifesto .wrap{max-width:54ch; text-align:center;}
.lede-section.manifesto .divider{margin:0 auto 28px;}
.lede-section.manifesto p{
  font-size:clamp(1.25rem, 2.2vw, 1.65rem);
  line-height:1.55;
  color:var(--ink);
  margin:0;
}
/* "AND ITALIA" opening: bold ink, explicitly NOT gradient-filled — kept as
   its own class (rather than a bare <strong>, which would render the same
   via the UA bold default + inherited ink color) so the "no gradient"
   choice is self-documenting and doesn't silently depend on strong's
   default styling never changing. */
.manifesto-brand{font-weight:700; color:var(--ink);}
/* Gradient text fill for the 4 key-offering phrases ("assistenti vocali",
   "chatbot intelligenti", "avatar 3D", "centralini AI"). Base rule sets a
   solid violet color FIRST — the fallback for any browser without
   background-clip:text support. The @supports block only layers the
   gradient + transparent fill on top when the browser can actually render
   it, so an unsupported browser keeps readable solid-violet text instead
   of invisible transparent-on-transparent text.
   --magenta-deep (not --pink) is the gradient's second stop, deliberately
   — same reasoning as .team-band's background further down this file:
   --violet -> --pink against WHITE bottoms out around ~3.1-3.5:1 at the
   pink end (fails normal-text AA; only scrapes by on the large/bold-text
   3:1 floor). --violet -> --magenta-deep was checked at the violet stop,
   the midpoint, and the magenta-deep stop against this section's white
   background: 5.4:1 / 6.5:1 / 7.2:1 — every point on the gradient clears
   4.5:1 AA regardless of how a browser renders the blend across glyphs. */
.grad-text{color:var(--violet);}
@supports (background-clip:text) or (-webkit-background-clip:text){
  .grad-text{
    background:linear-gradient(135deg, var(--violet), var(--magenta-deep));
    -webkit-background-clip:text; background-clip:text;
    -webkit-text-fill-color:transparent; color:transparent;
  }
}

/* ---------- Phone showcase (home, directly below the lede — round 6) ----------
   Own centered section for the phone chatbot mock, reusing the C3 mockup's
   device-frame/phone-card/bubble/float-card components (previously wired
   only into the hero, removed in round 1's hero redesign). Markup:
   .phone-showcase > .wrap > .section-head + .phone-showcase-visual >
   .device-frame.reveal.phone-frame > .phone-card (.phone-head + .bubble×3 +
   .typing) with two .float-card siblings alongside the frame. */
.phone-showcase{padding-top:8px;}
.phone-showcase .section-head{margin-bottom:44px;}
/* Round 10: band changed from band--dark to the default light violet tint
   (device-frame is independently dark via its own gradient, so it still
   pops — see the round-10 report for the full band-sequence rationale).
   .section-eyebrow's base color (--pink, #ec4899) is tuned for DARK bands
   (band--dark reasserts the same --pink deliberately, since pink-on-navy
   has plenty of contrast); against this section's near-white tint
   background it measures ~3.1:1, which fails normal-text AA (this
   eyebrow is 13px/800 — well under the ~18.66px bold cutoff for the
   large-text 3:1 exception, so 4.5:1 is the real floor). Overridden here
   to --magenta-deep, ~7.2:1 against the tint — comfortably clears AA
   while staying in the same pink/magenta family. Scoped to
   .phone-showcase rather than changed in the base .section-eyebrow rule
   so bands with a different eyebrow-background pairing are untouched. */
.phone-showcase .section-eyebrow{color:var(--magenta-deep);}
.phone-showcase-visual{position:relative; max-width:380px; margin:0 auto;}
.device-frame{
  position:relative;
  max-width:352px;
  margin:0 auto;
  padding:16px;
  border-radius:42px;
  background:linear-gradient(165deg, #1d1130, #150c26);
  border:1px solid rgba(255,255,255,0.14);
  box-shadow:0 55px 110px -30px rgba(124,58,237,0.55), 0 20px 60px -18px rgba(236,72,153,0.35);
}
.device-frame::before{
  content:"";
  position:absolute; top:12px; left:50%; transform:translateX(-50%);
  width:58px; height:5px; border-radius:3px;
  background:rgba(255,255,255,0.22);
}
/* Spring entrance ON REVEAL (not on page load): piggybacks on the shared
   .reveal/.in toggle main.js flips via IntersectionObserver, just with a
   springier ease + scale than the plain .reveal fade-and-rise — the
   two-class selector out-specifies the generic .reveal/.reveal.in rules
   above without needing !important. */
.reveal.phone-frame{opacity:0; transform:translateY(34px) scale(0.92); transition:opacity 0.7s ease, transform 0.7s var(--spring);}
.reveal.phone-frame.in{opacity:1; transform:translateY(0) scale(1);}

.phone-card{
  position:relative;
  margin-top:22px;
  background:linear-gradient(160deg, rgba(255,255,255,0.10), rgba(255,255,255,0.04));
  border-radius:26px;
  border:1px solid rgba(255,255,255,0.16);
  padding:22px;
  backdrop-filter:blur(14px) saturate(140%);
  -webkit-backdrop-filter:blur(14px) saturate(140%);
}
.phone-head{
  display:flex; align-items:center; gap:10px;
  padding-bottom:14px; margin-bottom:14px;
  border-bottom:1px solid rgba(255,255,255,0.16);
}
.phone-avatar{
  width:36px;height:36px;border-radius:50%;
  background:linear-gradient(135deg, var(--violet), var(--pink));
  display:flex;align-items:center;justify-content:center;
  color:#fff; font-weight:800; font-size:14px; flex:none;
}
.phone-head strong{font-size:14px; color:#fff;}
.phone-head span{display:block; font-size:12px; color:rgba(255,255,255,0.65);}
.status-dot{width:8px;height:8px;border-radius:50%; background:#38d39f; display:inline-block; margin-right:5px;}

.bubble{max-width:82%; padding:12px 16px; border-radius:18px; font-size:14.5px; margin-bottom:10px;}
.bubble.bot{background:rgba(255,255,255,0.12); color:#fff; border-bottom-left-radius:6px;}
.bubble.user{background:linear-gradient(135deg, var(--violet), var(--pink)); color:#fff; margin-left:auto; border-bottom-right-radius:6px;}
/* Bubbles (and the typing indicator) appear sequentially once the frame is
   revealed: held paused on the pop keyframe's 0% frame until .phone-frame
   gets .in, then released with a per-bubble delay — so the sequence plays
   when the section scrolls into view, not silently during initial page
   load while the section is still off-screen/opacity:0. */
@media (prefers-reduced-motion: no-preference){
  .phone-frame .bubble, .phone-frame .typing{animation:pop 0.4s ease both; animation-play-state:paused;}
  .phone-frame.in .bubble, .phone-frame.in .typing{animation-play-state:running;}
  .phone-frame .bubble:nth-child(2){animation-delay:0.4s;}
  .phone-frame .bubble:nth-child(3){animation-delay:0.8s;}
  .phone-frame .typing{animation-delay:1.2s;}
}
@keyframes pop{from{opacity:0; transform:translateY(8px) scale(0.97);} to{opacity:1; transform:translateY(0) scale(1);}}

.float-card{
  position:absolute;
  background:rgba(20,14,38,0.72);
  color:#fff;
  border-radius:18px;
  border:1px solid rgba(255,255,255,0.18);
  box-shadow:0 18px 40px -18px rgba(0,0,0,0.5);
  padding:14px 16px;
  font-size:13px;
  font-weight:700;
  display:flex; align-items:center; gap:10px;
  z-index:2;
  backdrop-filter:blur(10px) saturate(140%);
  -webkit-backdrop-filter:blur(10px) saturate(140%);
}
@media (prefers-reduced-motion: no-preference){
  .float-card{animation:drift 7s ease-in-out infinite;}
}
@keyframes drift{0%,100%{transform:translateY(0);} 50%{transform:translateY(-10px);}}
.float-1{top:-6px; left:-14px;}
.float-2{bottom:22px; right:-18px; animation-delay:-3s;}
.float-icon{
  width:32px;height:32px;border-radius:10px;
  display:flex;align-items:center;justify-content:center;
  color:#fff; flex:none;
}
.float-1 .float-icon{background:linear-gradient(135deg,var(--amber),var(--pink));}
.float-2 .float-icon{background:linear-gradient(135deg,var(--violet),var(--violet-deep));}
@media (max-width:640px){
  .float-card{display:none;}
}

/* ---------- Compact inner-page header (page / article / listing) ---------- */
.page-header{
  position:relative;
  background:var(--dark-bg);
  color:var(--dark-text);
  overflow:hidden;
  padding:168px 0 64px;
}
.page-header::before{
  content:"";
  position:absolute; inset:0;
  background-image:url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='300'%20height='300'%3E%3Cfilter%20id='n3'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.85'%20numOctaves='3'%20stitchTiles='stitch'/%3E%3C/filter%3E%3Crect%20width='100%25'%20height='100%25'%20filter='url(%23n3)'/%3E%3C/svg%3E");
  opacity:0.05;
  mix-blend-mode:overlay;
  pointer-events:none;
}
.page-header-inner{position:relative; z-index:1; max-width:70ch;}
.page-eyebrow{
  display:inline-block;
  font-weight:800;
  font-size:13px;
  letter-spacing:0.08em;
  text-transform:uppercase;
  color:var(--pink);
  margin-bottom:14px;
}
.page-header h1{
  font-size:clamp(2.1rem, 4.2vw, 3.4rem);
  letter-spacing:-0.03em;
  color:#fff;
}
.page-header .page-sub{
  color:var(--dark-muted);
  font-size:17.5px;
  max-width:60ch;
  margin:14px 0 0;
}
.page-header .page-date{
  display:block;
  color:var(--dark-muted);
  font-size:14px;
  font-weight:600;
  margin-bottom:10px;
}

/* ---------- Photo page-header (round 11, capability 1) ----------
   Opt-in variant of .page-header: front-matter hero.bg (see tools/build.py
   render_header()) emits a real <img> — never a shared-CSS url(...) — so
   check.py only charges the ONE page that uses it, same reasoning as the
   home hero. Markup (build.py-generated, do not hand-author):
   <header class="page-header page-header--photo">
     <div class="page-header-bg" aria-hidden="true"><img src="..." alt=""></div>
     <div class="wrap page-header-inner">...</div>
   </header>
   Stacking: .page-header-bg (photo, no z-index) -> .page-header-bg::after
   (dark scrim, painted after the img since it's the bg wrapper's own
   ::after) -> .page-header-inner (z-index:1, always on top). The scrim is
   a left-weighted gradient (page-header text is left-aligned, unlike the
   centered home hero) checked for AA at every stop against white text:
   left edge ~0.92 opacity keeps the eyebrow/h1/sub readable even over a
   bright photo; the fade toward the right keeps the photo itself visible. */
.page-header--photo{background:var(--dark-bg);}
.page-header-bg{
  position:absolute; inset:0;
  overflow:hidden;
}
.page-header-bg img{
  width:100%; height:100%;
  object-fit:cover; object-position:center;
  display:block;
  transform:scale(1);
}
@media (prefers-reduced-motion: no-preference){
  .page-header-bg img{animation:pageHeaderDrift 18s ease-in-out infinite alternate;}
}
@keyframes pageHeaderDrift{from{transform:scale(1);} to{transform:scale(1.06);}}
.page-header-bg::after{
  content:"";
  position:absolute; inset:0;
  background:linear-gradient(100deg, rgba(21,12,38,.92) 0%, rgba(21,12,38,.82) 42%, rgba(21,12,38,.55) 72%, rgba(21,12,38,.32) 100%);
}
@media (max-width:640px){
  /* Narrow viewports stack the eyebrow/h1/sub at full .wrap width, so the
     scrim needs to darken the whole header evenly rather than fade toward
     an off-screen "right side" — matches the .hero mobile scrim override
     further down this file for the same reason. */
  .page-header-bg::after{background:linear-gradient(180deg, rgba(21,12,38,.90) 0%, rgba(21,12,38,.80) 45%, rgba(21,12,38,.68) 100%);}
}
@media (prefers-reduced-motion: reduce){
  .page-header-bg img{transform:none;}
}

/* ---------- Full-height photo header (av-oldstyle, old-site hero parity) ----------
   Opt-in modifier on .page-header--photo (front-matter hero.fullheight,
   see tools/build.py render_header()): the old theme's assistenti-virtuali
   hero was a ~100vh parallax photo band with the h1 + a numbered 01/02/03
   triad both living INSIDE it, not a compact banner followed by a separate
   section. .page-header-inner's default max-width:70ch (a reading measure,
   fine for a left-aligned h1+sub) is overridden here to the site's normal
   1180px content measure + centered text, wide enough for a 3-column
   .numbered-cols row to sit comfortably; hero.extra's markup (the triad)
   is rendered after the sub paragraph by render_header(), still inside
   this centered, scrim-topped column. */
.page-header--fullheight{
  min-height:92vh;
  display:flex;
  align-items:center;
}
.page-header--fullheight .page-header-inner{
  max-width:var(--maxw);
  width:100%;
  text-align:center;
}
.page-header--fullheight .page-sub{max-width:640px; margin:14px auto 0;}
@media (max-width:640px){
  .page-header--fullheight{min-height:auto; padding:140px 0 56px;}
}
/* micro-3 triad restyle: the fullheight hero's photo (near-black in the old
   site, mesh kept subtle) needs to read as a dark ambience behind the h1 —
   noticeably darker/flatter than the directional left-weighted scrim other
   .page-header--photo pages use for left-aligned text (that gradient is
   tuned to fade toward a visible photo on the right; this hero's content is
   centered, so a near-uniform near-black wash fits the reference better and
   keeps the triad's small pink label text comfortably AA). Verified against
   the actual background photo's pixels — see docs/.sdd/micro-3.md. */
.page-header--fullheight .page-header-bg::after{
  background:linear-gradient(180deg, rgba(6,10,20,.90) 0%, rgba(6,10,20,.85) 45%, rgba(6,10,20,.90) 100%);
}
/* Pink subtitle line (hero.extra content) between the h1 and the triad —
   same role/weight as the old theme's second stacked title line, recolored
   to the site's pink accent instead of a second same-size white h1. */
.hero-photo-sub{
  color:var(--pink);
  font-weight:700;
  font-size:clamp(1.15rem, 2.4vw, 1.55rem);
  letter-spacing:0.01em;
  margin:16px 0 0;
}
/* White-text variant of .numbered-cols (assets/css/style.css further below)
   for use INSIDE a dark photo hero: the default component assumes a light
   page background (gradient-clip numerals, --muted body copy), neither of
   which is legible over a photo — override both to solid white/near-white
   here. Text-align left per item (matches the default component and the
   old theme's triad) even though the surrounding hero text is centered.

   micro-3: old-site pixel reference has the label as ONE line — "01.
   ASSISTENTI VOCALI", number+dot+title together, small pink uppercase bold
   — not a giant white numeral stacked above the title. .numbered-col-num
   and h3 are adjacent inline-flow siblings in the markup (see hero.extra in
   content/assistenti-virtuali.html) with no whitespace between them in the
   source, so setting both to display:inline puts them on the same visual
   line with nothing between; ::after supplies the ". " separator and
   margin-right the gap before the title. */
.numbered-cols--photo{max-width:1040px; margin:36px auto 0; text-align:left;}
.numbered-cols--photo .numbered-col-num{
  display:inline;
  background:none; -webkit-background-clip:unset; background-clip:unset;
  -webkit-text-fill-color:unset; color:var(--magenta-bright);
  font-family:'Sora', sans-serif;
  font-size:15px;
  font-weight:800;
  letter-spacing:0.03em;
  text-transform:uppercase;
  margin:0 0.35em 0 0;
  line-height:1.3;
}
.numbered-cols--photo .numbered-col-num::after{content:".";}
.numbered-cols--photo h3{
  display:inline;
  color:var(--magenta-bright);
  font-size:15px;
  font-weight:800;
  letter-spacing:0.03em;
  text-transform:uppercase;
  margin:0;
  line-height:1.3;
}
.numbered-cols--photo p{
  color:rgba(255,255,255,.95);
  font-size:15px;
  line-height:1.8;
  margin-top:14px;
}
@media (min-width:641px){
  .numbered-cols--photo .numbered-col-num, .numbered-cols--photo h3{font-size:16px;}
  .numbered-cols--photo p{font-size:16px;}
}
/* ---------- Triad readability forensic fix (round: triad2-news) ----------
   The block above LOOKS like it already sets pink/white/15-16px on the
   triad's h3/p — and it did, right up until assets/css/style.css grew a
   generic ".numbered-col h3{font-size:18px; margin-bottom:8px;}" /
   ".numbered-col p{color:var(--muted); font-size:15px; line-height:1.6;
   margin:0;}" pair further down the file (round 11 restore-batch,
   "Numbered columns" section) for the PLAIN (non-photo) component variant.
   Both ".numbered-cols--photo h3"/"...p" above and ".numbered-col
   h3"/"...p" down there match the SAME elements here (the triad markup
   carries both "numbered-col" and the parent carries both "numbered-cols"
   + "numbered-cols--photo") at the SAME specificity — one class + one type,
   (0,1,1), each — and CSS breaks a specificity tie by source order, last
   rule wins. ".numbered-col h3"/"...p" sit later in the file, so on every
   real render the triad's paragraph silently fell back to var(--muted)
   (#5c5270, the muted grey-lavender the owner's screenshot compared showed)
   instead of white, and the label's font-size/margin got knocked to 18px/
   8px instead of the intended 15-16px. This is the cascade winner
   confirmed by direct read of the built assistenti-virtuali/index.html
   against this stylesheet — not a hypothetical.
   Fix: re-declare the same properties prefixed with the photo hero's own
   ancestor (.page-header--photo), which is never present on the plain
   .numbered-col/.numbered-cols usage elsewhere in content/ (grep-verified:
   only assistenti-virtuali.html's front-matter hero.extra uses
   numbered-cols--photo, and only inside a .page-header--photo). That
   raises specificity to (0,2,1) — strictly higher than the plain variant's
   (0,1,1) — so this wins regardless of source order or future edits
   anywhere else in the file, instead of relying on staying-later-in-file
   (the exact discipline that just failed once already). Body copy is pure
   #fff per the brief (not the .95-alpha near-white the entangled rule
   above still carries as its own unreachable value); label color moves
   from --pink to a brighter --magenta-bright (defined in :root above),
   restoring the old site's vivid #ff00ff-class pop within AA — see the
   contrast table in docs/.sdd/triad2-news.md. */
.page-header--photo .numbered-cols--photo h3{
  color:var(--magenta-bright);
  font-size:16px;
  font-weight:800;
  letter-spacing:0.04em;
  margin:0;
}
.page-header--photo .numbered-cols--photo p{
  color:#fff;
  font-size:16px;
  line-height:1.8;
  margin-top:14px;
}
@media (max-width:640px){
  .page-header--photo .numbered-cols--photo h3{font-size:15px;}
  .page-header--photo .numbered-cols--photo p{font-size:15px;}
}

/* ---------- Trust marquee ---------- */
.trust{padding:40px 0 44px;}
.trust-label{
  display:block;
  text-align:center;
  font-weight:700;
  color:var(--muted);
  font-size:14px;
  margin-bottom:18px;
}
/* .marquee (outer, unmasked) holds the masked/scrolling .marquee-viewport
   PLUS the pause button as siblings — the button must live OUTSIDE the
   viewport's edge-fade mask (below) or it would render partially
   transparent/clipped near the mask's fade zone. */
.marquee{position:relative; padding-right:56px;}
.marquee-viewport{
  overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
  mask-image:linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
}
.marquee-track{
  display:flex;
  align-items:center;
  width:max-content;
}
@media (prefers-reduced-motion: no-preference){
  /* Each half is 12 items (3 names x4 reps) instead of 3, i.e. ~4x the
     width of the original 3-item half. Duration is scaled by the same
     4x factor (24s -> 96s) to keep the scroll speed (px/s) constant
     rather than making the loop feel like it sped up. */
  .marquee-track{animation:marquee 96s linear infinite;}
  .marquee:hover .marquee-track,
  .marquee:focus-within .marquee-track,
  .marquee.is-paused .marquee-track{animation-play-state:paused;}
}
@keyframes marquee{from{transform:translateX(0);} to{transform:translateX(-50%);}}
/* a11y fix (task-7-a11y I-4, WCAG 2.2.2): the 96s infinite auto-scroll had
   no keyboard-reachable pause — every .marquee-item was a non-interactive
   <span>, so :focus-within (above) could never actually fire for a
   keyboard-only user. This button is the first genuinely focusable thing
   in .marquee, wired in assets/js/main.js to toggle the same
   animation-play-state via the .is-paused class (also still works via
   mouse hover/keyboard focus-within, both unchanged above). */
.marquee-pause{
  position:absolute;
  top:50%; right:0;
  transform:translateY(-50%);
  width:40px; height:40px;
  display:flex; align-items:center; justify-content:center;
  border-radius:50%;
  border:1px solid var(--border);
  background:var(--card);
  color:var(--ink-soft);
  cursor:pointer;
  transition:background 0.2s ease, color 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
}
.marquee-pause svg{width:16px; height:16px;}
.marquee-pause:hover{background:rgba(124,58,237,0.08); border-color:var(--violet-deep); color:var(--violet-deep);}
/* Owner directive: visually hide the pause button until it receives
   keyboard focus — the owner sees it sitting over the trust marquee AND
   the .marquee--logos partner strip on every page that embeds either and
   wants it gone from view, but the a11y fix above (a real, focusable
   control for keyboard users who can't rely on :hover/:focus-within) must
   keep working. Standard sr-only clip technique: collapsed to a 1x1px
   clipped box for everyone by default; :focus-visible (keyboard focus
   only, never a mouse click) restores the full circular button below.
   Mouse hover-to-pause (.marquee:hover above) is untouched — it pauses
   the track without ever needing this button to be visible. Applies to
   every .marquee instance (trust marquee + .marquee--logos) via this one
   shared rule, no per-page CSS. */
.marquee-pause:not(:focus-visible){
  position:absolute;
  width:1px; height:1px;
  padding:0;
  margin:-1px;
  overflow:hidden;
  clip:rect(0,0,0,0);
  white-space:nowrap;
  border-width:0;
}
@media (prefers-reduced-motion: reduce){
  /* Nothing to pause when the marquee is already static — see the
     reduced-motion block further down that wraps .marquee-track. */
  .marquee-pause{display:none;}
  .marquee{padding-right:0;}
}
/* Spacing lives on each item's margin-right, NOT on the track's flex `gap`.
   The track holds two identical item sequences back-to-back (real +
   aria-hidden .marquee-dup copy) so translateX(-50%) can loop seamlessly —
   but that only lands exactly on the second sequence's start if both
   halves are the same rendered width. A flex `gap` puts spacing *between*
   items only (5 gaps for 6 items: an odd count that can't split evenly
   into two equal halves), which undershoots -50% by half a gap and shows
   as a blank flash right before the loop. Each item owning its own
   trailing margin makes every one of the 6 items (including the last)
   contribute the same width+spacing, so the first 3 and last 3 are
   guaranteed equal-width halves and the loop is seamless. */
.marquee-item{
  font-family:'Sora', sans-serif;
  font-weight:700;
  font-size:16px;
  color:var(--ink-soft);
  opacity:0.72;
  white-space:nowrap;
  margin-right:64px;
}
@media (prefers-reduced-motion: reduce){
  .marquee-track{flex-wrap:wrap; justify-content:center; width:auto; row-gap:12px;}
  .marquee-dup{display:none;}
  /* The visible half now holds 12 items (3 names x4 reps) so the loop has
     enough width to scroll on wide screens. Static/reduced-motion mode
     doesn't scroll, so only the first 3 (the unique names) should show —
     without this, items 4-12 would wrap into repeated duplicate rows. */
  .marquee-item:nth-child(n+4){display:none;}
}

/* ---------- Section shells ---------- */
main{display:block;}
section{position:relative; padding:104px 0;}
.section-head{
  text-align:center;
  max-width:640px;
  margin:0 auto 56px;
}
.section-eyebrow{
  display:inline-block;
  font-weight:800;
  font-size:13px;
  letter-spacing:0.08em;
  text-transform:uppercase;
  color:var(--pink);
  margin-bottom:12px;
}
.section-head h2{font-size:clamp(1.9rem,3.4vw,2.6rem); letter-spacing:-0.02em;}
.section-head p{color:var(--muted); font-size:17px;}

/* ---------- "Display" section head (owner: restore the old theme's
   centered gradient-h2 signature — see docs/.sdd/sweep-caps.md) ----------
   Modifier, not a replacement — same convention as .lede-section.manifesto
   and .band/.band--dark elsewhere in this file. Markup:
     <div class="section-head section-head--display reveal">
       <span class="section-eyebrow">…</span>
       <h2>…</h2>
       <p>…</p>
     </div>
   The eyebrow needs no override here: .section-head is already
   text-align:center and .section-eyebrow is already uppercase/spaced/pink
   (small pink uppercase eyebrow, exactly the old-site look, out of the
   box). This modifier changes only two things:
   1. h2 gets the SAME gradient-text mechanics as .section-title-big below
      — solid --violet-deep fallback FIRST (forced-colors / no
      background-clip:text support), @supports-gated gradient using
      --amber-safe (not --amber — this sits on the light --bg, same
      reasoning as --amber-safe's own definition comment up top), reused
      gradFlow animation gated to prefers-reduced-motion:no-preference.
   2. The lead paragraph gets more room: .section-head's own 640px cap is
      widened to 70ch for this modifier only — plain .section-head usage
      elsewhere (e.g. the home page) keeps its narrower 640px measure
      untouched. */
.section-head--display{max-width:70ch;}
.section-head--display h2{
  font-weight:800;
  font-size:clamp(2.1rem, 4.4vw, 3.2rem);
  color:var(--violet-deep);
}
@supports (background-clip:text) or (-webkit-background-clip:text){
  .section-head--display h2{
    background:linear-gradient(120deg, var(--violet-deep), var(--pink) 45%, var(--amber-safe) 75%, var(--violet-deep));
    background-size:300% 100%;
    -webkit-background-clip:text; background-clip:text;
    -webkit-text-fill-color:transparent; color:transparent;
  }
}
@media (prefers-reduced-motion: no-preference){
  .section-head--display h2{animation:gradFlow 7s ease infinite;}
}
.section-head--display p{max-width:70ch; margin-left:auto; margin-right:auto; font-size:18px;}

/* ---------- Benefit head (owner: restore the old theme's small pink
   eyebrow + bold magenta title pairing on feature-grid/.card items,
   e.g. "CONTINUITÀ" / "Una certezza 24/7") ----------
   Drop-in replacement for a bare <h3> inside .card. Markup:
     <div class="card">
       <div class="benefit-head">
         <span class="benefit-eyebrow">CONTINUITÀ</span>
         <h3 class="benefit-title">Una certezza 24/7</h3>
       </div>
       <p>…body copy…</p>
     </div>
   AA note: raw --pink (#ec4899) is the codebase's DARK-band eyebrow
   color and measures only ~3.1:1 on white/--card (documented at
   .phone-showcase .section-eyebrow and .article-related
   .related-card-date above) — both of THOSE call sites swap to
   --magenta-deep for exactly this reason, and .benefit-eyebrow does the
   same rather than reintroducing the failing raw --pink on a light card.
   .benefit-title uses --violet-deep, already the established AA-safe
   text color for bold copy on white throughout this file (buttons, nav,
   case-list titles, etc. all use it unchallenged). No heading level is
   skipped: .benefit-title IS the h3, just styled — .benefit-eyebrow is a
   plain <span> sibling above it, not a heading. */
.benefit-head{margin-bottom:2px;}
.benefit-eyebrow{
  display:block;
  font-weight:800;
  font-size:12px;
  letter-spacing:0.09em;
  text-transform:uppercase;
  color:var(--magenta-deep);
  margin-bottom:6px;
}
.benefit-title{
  margin:0;
  font-size:20px;
  font-weight:800;
  letter-spacing:-0.01em;
  color:var(--violet-deep);
}

.reveal{
  opacity:0;
  transform:translateY(24px);
  transition:opacity 0.6s ease, transform 0.6s ease;
}
.reveal.in{opacity:1; transform:translateY(0);}

/* ---------- Bento grid (Soluzioni) ---------- */
.bento{
  display:grid;
  grid-template-columns:repeat(4, 1fr);
  grid-template-rows:repeat(3, minmax(180px, auto));
  grid-template-areas:
    "chat chat opera opera"
    "chat chat avatar page"
    "data data form form";
  gap:22px;
}
.bento-card{
  position:relative;
  background:var(--card);
  border-radius:var(--radius-xl);
  border:1px solid var(--border);
  padding:28px 26px;
  overflow:hidden;
  transition:transform 0.3s var(--spring), box-shadow 0.3s ease;
  display:flex;
  flex-direction:column;
}
.bento-card::before{
  content:"";
  position:absolute; inset:0;
  border-radius:inherit;
  padding:1.5px;
  background:linear-gradient(135deg, var(--violet), var(--pink), var(--amber));
  -webkit-mask:linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite:xor;
  mask-composite:exclude;
  opacity:0;
  transition:opacity 0.3s ease;
  pointer-events:none;
}
.bento-card::after{
  content:"";
  position:absolute; inset:0;
  background:radial-gradient(circle at 30% 15%, rgba(147,51,234,0.14), transparent 60%);
  opacity:0;
  transition:opacity 0.3s ease;
  pointer-events:none;
}
.bento-card:hover{transform:translateY(-6px); box-shadow:0 26px 55px -22px rgba(124,58,237,0.35);}
.bento-card:hover::before, .bento-card:hover::after{opacity:1;}
.bento-chat{grid-area:chat; background:linear-gradient(165deg,#faf5ff,#f3e6ff);}
.bento-opera{grid-area:opera; background:linear-gradient(165deg,#fff5fa,#fde9f2);}
.bento-avatar{grid-area:avatar;}
.bento-page{grid-area:page;}
.bento-data{grid-area:data;}
.bento-form{grid-area:form;}

.card-icon{
  width:52px;height:52px;
  border-radius:16px;
  display:flex;align-items:center;justify-content:center;
  margin-bottom:18px;
  color:#fff;
  flex:none;
}
.icon-1{background:linear-gradient(135deg, var(--violet), var(--violet-deep));}
.icon-2{background:linear-gradient(135deg, var(--pink), #f4638e);}
.icon-3{background:linear-gradient(135deg, var(--amber), #ff8a5c);}
.icon-4{background:linear-gradient(135deg, var(--violet-deep), var(--pink));}
.icon-5{background:linear-gradient(135deg, #38d39f, #2fb787);}
.icon-6{background:linear-gradient(135deg, var(--violet), var(--amber));}
.bento-card h3{font-size:19px; margin-bottom:8px; position:relative; z-index:1;}
.bento-card p{color:var(--muted); font-size:15px; margin-bottom:0; position:relative; z-index:1;}
.bento-chat h3, .bento-opera h3{font-size:20px;}

.mini-chat{position:relative; z-index:1; margin-top:auto; padding-top:18px; display:flex; flex-direction:column; gap:8px;}
.mini-bubble{max-width:80%; padding:9px 13px; border-radius:14px; font-size:13px; line-height:1.4;}
.mini-bubble.bot{background:#fff; color:var(--ink); border-bottom-left-radius:4px; box-shadow:0 4px 14px -8px rgba(21,12,38,0.25);}
.mini-bubble.user{background:linear-gradient(135deg, var(--violet), var(--pink)); color:#fff; margin-left:auto; border-bottom-right-radius:4px; font-weight:600;}
.mini-typing{display:inline-flex; gap:4px; padding:9px 13px; background:#fff; border-radius:14px; border-bottom-left-radius:4px; width:fit-content; box-shadow:0 4px 14px -8px rgba(21,12,38,0.25);}
.mini-typing span{width:5px;height:5px;border-radius:50%;background:var(--violet); opacity:0.6;}
@media (prefers-reduced-motion: no-preference){
  .mini-typing span{animation:bounce 1.2s infinite ease-in-out;}
  .mini-typing span:nth-child(2){animation-delay:0.15s;}
  .mini-typing span:nth-child(3){animation-delay:0.3s;}
  .typing span{animation:bounce 1.2s infinite ease-in-out;}
  .typing span:nth-child(2){animation-delay:0.15s;}
  .typing span:nth-child(3){animation-delay:0.3s;}
}
@keyframes bounce{0%,60%,100%{transform:translateY(0);} 30%{transform:translateY(-5px);}}
.typing{
  display:inline-flex; gap:4px; padding:12px 16px; background:rgba(255,255,255,0.12); border-radius:18px; border-bottom-left-radius:6px;
}
.typing span{width:6px;height:6px;border-radius:50%;background:#e9d5ff; opacity:0.8;}

.waveform{position:relative; z-index:1; display:flex; align-items:flex-end; gap:5px; height:36px; margin-top:16px;}
.waveform span{flex:1; max-width:8px; border-radius:3px; background:linear-gradient(180deg, var(--pink), var(--amber)); transform:scaleY(0.4); transform-origin:bottom;}
@media (prefers-reduced-motion: no-preference){
  .waveform span{animation:wave 1.1s ease-in-out infinite;}
}
.waveform span:nth-child(1){height:45%; animation-delay:-1.0s;}
.waveform span:nth-child(2){height:80%; animation-delay:-0.8s;}
.waveform span:nth-child(3){height:55%; animation-delay:-0.6s;}
.waveform span:nth-child(4){height:100%; animation-delay:-0.4s;}
.waveform span:nth-child(5){height:65%; animation-delay:-0.2s;}
.waveform span:nth-child(6){height:90%; animation-delay:0s;}
.waveform span:nth-child(7){height:50%; animation-delay:-0.5s;}
@keyframes wave{0%,100%{transform:scaleY(0.35);} 50%{transform:scaleY(1);}}

@media (max-width:900px){
  .bento{
    grid-template-columns:1fr 1fr;
    grid-template-rows:auto;
    grid-template-areas:
      "chat chat"
      "chat chat"
      "opera opera"
      "avatar page"
      "data data"
      "form form";
  }
}
@media (max-width:640px){
  .bento{
    grid-template-columns:1fr;
    grid-template-areas:
      "chat"
      "opera"
      "avatar"
      "page"
      "data"
      "form";
  }
  .bento-card{min-height:0;}
}

/* ---------- Case list (giant gradient numerals) ---------- */
.case-list{display:flex; flex-direction:column;}
.case-row{
  position:relative;
  display:flex;
  align-items:center;
  gap:32px;
  padding:38px 6px;
  border-top:1px solid var(--border);
  transition:transform 0.3s ease;
  text-decoration:none;
  color:inherit;
}
.case-list .case-row:last-child{border-bottom:1px solid var(--border);}
.case-row::after{
  content:"";
  position:absolute; left:0; right:0; bottom:-1px; height:2px;
  background:linear-gradient(90deg, var(--violet-deep), var(--pink), var(--amber));
  transform:scaleX(0);
  transform-origin:left;
  transition:transform 0.4s ease;
}
.case-row:hover, .case-row:focus-visible{transform:translateX(6px);}
.case-row:hover::after, .case-row:focus-visible::after{transform:scaleX(1);}
.case-num{
  flex:none;
  width:clamp(88px,10vw,140px);
  font-family:'Sora', sans-serif;
  font-weight:800;
  font-size:clamp(3rem,7vw,5.2rem);
  line-height:1;
  background:linear-gradient(135deg, var(--violet), var(--pink));
  -webkit-background-clip:text; background-clip:text; color:transparent;
  opacity:0.32;
  transition:opacity 0.3s ease;
}
.case-row:hover .case-num, .case-row:focus-visible .case-num{opacity:1;}
.case-badge{
  display:inline-block;
  background:rgba(147,51,234,0.08);
  border:1px solid rgba(147,51,234,0.2);
  color:var(--violet-deep);
  padding:5px 14px;
  border-radius:var(--radius-pill);
  font-size:12px;
  font-weight:700;
  margin-bottom:10px;
}
.case-content h3{font-size:22px; margin-bottom:6px;}
.case-content p{font-size:15px; color:var(--muted); margin-bottom:0;}
@media (max-width:640px){
  .case-row{flex-direction:column; align-items:flex-start; gap:8px; padding:28px 4px;}
  .case-num{font-size:2.6rem; width:auto;}
}

/* ---------- Stats ---------- */
.stats{
  display:grid;
  grid-template-columns:repeat(4, 1fr);
  gap:22px;
}
.stat-card{
  position:relative;
  background:linear-gradient(160deg, #ffffff, #faf5ff);
  border:1px solid var(--border);
  border-radius:var(--radius-lg);
  padding:32px 22px;
  text-align:center;
  overflow:hidden;
  transition:transform 0.3s var(--spring), box-shadow 0.3s ease;
}
.stat-card::before{
  content:"";
  position:absolute; inset:0;
  border-radius:inherit;
  padding:1.5px;
  background:linear-gradient(135deg, var(--violet), var(--pink));
  -webkit-mask:linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite:xor;
  mask-composite:exclude;
  opacity:0;
  transition:opacity 0.3s ease;
  pointer-events:none;
}
.stat-card:hover{transform:translateY(-6px); box-shadow:0 24px 50px -20px rgba(124,58,237,0.35);}
.stat-card:hover::before{opacity:1;}
.stat-num{
  font-family:'Sora', sans-serif;
  font-weight:800;
  font-size:clamp(2.6rem,5.5vw,3.6rem);
  line-height:1;
  background:linear-gradient(120deg, var(--violet-deep), var(--pink));
  -webkit-background-clip:text; background-clip:text; color:transparent;
  margin-bottom:10px;
}
.stat-card p{color:var(--ink-soft); font-weight:600; font-size:14.5px; margin-bottom:0;}

@media (max-width: 980px){
  .stats{grid-template-columns:repeat(2, 1fr);}
}
@media (max-width: 640px){
  .stats{grid-template-columns:1fr 1fr; gap:16px;}
}

/* ---------- Counters (old-site grve-counter parity) ----------
   Big gradient numbers that count up from 0 when scrolled into view
   (animated by main.js via requestAnimationFrame; instant final value
   under prefers-reduced-motion). No card chrome — plain centered numeral
   + label, unlike .stats which is boxed.
   Markup:
   <div class="counters">
     <div class="counter">
       <span class="counter-num" data-target="120" data-suffix="+">0</span>
       <p class="counter-label">Progetti realizzati</p>
     </div>
     ...
   </div>
   data-target: plain integer or decimal (e.g. "4.5") the count animates
   to — decimal precision in data-target is preserved in every animated
   frame. data-suffix (optional): text appended after the number once
   animation finishes each frame, e.g. "+", "%", " anni". */
.counters{
  display:grid;
  grid-template-columns:repeat(4, 1fr);
  gap:28px;
  padding:32px 0;
}
.counter{text-align:center;}
.counter-num{
  display:block;
  font-family:'Sora', sans-serif;
  font-weight:800;
  font-size:clamp(2.6rem,5.5vw,4rem);
  line-height:1;
  background:linear-gradient(120deg, var(--violet-deep), var(--pink) 45%, var(--amber) 75%, var(--violet-deep));
  background-size:300% 100%;
  -webkit-background-clip:text; background-clip:text; color:transparent;
  margin-bottom:10px;
}
@media (prefers-reduced-motion: no-preference){
  .counter-num{animation:gradFlow 7s ease infinite;}
}
.counter-label{color:var(--muted); font-weight:600; font-size:14.5px; margin:0;}
@media (max-width: 980px){
  .counters{grid-template-columns:repeat(2, 1fr);}
}
@media (max-width: 640px){
  .counters{grid-template-columns:1fr 1fr; gap:20px;}
}

/* ---------- Generic cards (listing template) ---------- */
.cards{display:grid; grid-template-columns:repeat(3, 1fr); gap:22px;}
.card{
  position:relative;
  background:var(--card);
  border:1px solid var(--border);
  border-radius:var(--radius-xl);
  padding:26px;
  text-decoration:none;
  color:inherit;
  display:flex;
  flex-direction:column;
  gap:10px;
  transition:transform 0.3s var(--spring), box-shadow 0.3s ease;
}
.card:hover, .card:focus-visible{transform:translateY(-6px); box-shadow:0 26px 55px -22px rgba(124,58,237,0.35);}
.card img{border-radius:14px; margin-bottom:6px;}
.card h3{font-size:19px; margin-bottom:0;}
.card p{color:var(--muted); font-size:15px; margin-bottom:0;}
.card .card-meta{color:var(--pink); font-weight:700; font-size:13px; text-transform:uppercase; letter-spacing:0.06em;}
@media (max-width:900px){.cards{grid-template-columns:1fr 1fr;}}
@media (max-width:640px){.cards{grid-template-columns:1fr;}}

/* ---------- Related-articles grid (round 11, capability 4) ----------
   Compact 3-5-card teaser grid for a product-page bottom ("Potrebbe anche
   interessarti" / related posts). Distinct from .cards (listing template):
   image is a fixed-ratio crop (not natural-size), body is tighter, and a
   date label replaces .card's uppercase category meta. auto-fit centers
   any count (3, 4 or 5) at any viewport, same technique as .feature-grid.
   Markup:
   <div class="related-grid">
     <a class="related-card" href="/blog/xxx/">
       <div class="related-card-media"><img src="..." alt="" loading="lazy" width="400" height="250"></div>
       <div class="related-card-body">
         <span class="related-card-date">12 marzo 2024</span>
         <h3>Titolo articolo</h3>
       </div>
     </a>
     ...
   </div>
   This plain <a>-wrapped form is still what content/home.html, the two
   assistenti-virtuali__smart-* pages, and tools/build.py's own
   _related_card_html() (article "Leggi anche" teasers) all use — none of
   those cards carry category data. content/blog.html and content/
   category__eventi.html's listing cards use a second, div-wrapped form
   instead (round: triad2-news) so each category can be its own real,
   independently-clickable link:
   <div class="related-card reveal">
     <div class="related-card-media">...</div>
     <div class="related-card-body">
       <span class="related-card-date">12 marzo 2024</span>
       <span class="related-card-cats">
         <a class="cat-pill" href="/category/eventi/">Eventi</a>
         <a class="cat-pill" href="/blog/">Chatbot</a>
       </span>
       <h3><a class="stretched-link" href="/blog/xxx/">Titolo articolo</a></h3>
       <p>Excerpt…</p>
     </div>
   </div>
   An <a> can't validly nest another <a> (HTML5: interactive content can't
   contain interactive content), so once each pill needs its own href the
   outer element can no longer be the link — the title's <a
   class="stretched-link"> carries the real link instead, and its ::after
   (below) is stretched via position:absolute;inset:0 over the ancestor
   .related-card (position:relative, set unconditionally so it's a no-op
   for the plain <a> form above) to keep the WHOLE card clickable exactly
   as before; the pills, as later DOM siblings with their own stacking
   context, paint above that pseudo-element and stay independently
   clickable. */
.related-grid{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap:24px;
  padding:32px 0;
}
.related-card{
  position:relative;
  display:flex;
  flex-direction:column;
  background:var(--card);
  border:1px solid var(--border);
  border-radius:var(--radius-lg);
  overflow:hidden;
  text-decoration:none;
  color:inherit;
  transition:transform 0.3s var(--spring), box-shadow 0.3s ease;
}
.related-card:hover, .related-card:focus-visible, .related-card:focus-within{transform:translateY(-6px); box-shadow:0 22px 46px -20px rgba(124,58,237,0.32);}
.related-card-media{aspect-ratio:16/10; overflow:hidden; background:var(--border-soft);}
.related-card-media img{width:100%; height:100%; object-fit:cover; display:block; margin:0; border-radius:0; transition:transform 0.4s var(--spring);}
.related-card:hover .related-card-media img{transform:scale(1.06);}
.related-card-body{padding:18px 20px 22px;}
.related-card-date{
  display:block;
  font-size:12.5px;
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:0.06em;
  color:var(--pink);
  margin-bottom:8px;
}
.related-card-body h3{font-size:16.5px; line-height:1.35; margin:0;}
/* Title hover/focus state (round: triad2-news) — old-site parity: the
   old theme's related-post titles shifted from plain ink to violet on
   hover; restore that here. The stretched-link form's <a> is the h3's
   only child so this recolors identically whether the surrounding
   .related-card is an <a> (plain teaser usage) or a <div> (blog/category
   listing usage, stretched-link) — both trigger off the shared
   .related-card:hover/:focus-within above. */
.related-card-body h3 a{color:inherit; text-decoration:none; transition:color 0.25s ease;}
.related-card:hover .related-card-body h3, .related-card:focus-within .related-card-body h3,
.related-card:hover .related-card-body h3 a, .related-card:focus-within .related-card-body h3 a{color:var(--violet-deep);}
/* .stretched-link — see the markup-contract comment above: expands its own
   <a>'s clickable area to the whole position:relative .related-card via an
   absolutely-positioned, otherwise-invisible ::after. z-index:1 so it
   paints above the card's own background/media but the pills (z-index:2,
   below) still paint above IT and stay clickable. */
.stretched-link::after{content:""; position:absolute; inset:0; z-index:1;}
/* Category pill badges (round: triad2-news) — one per category, small and
   rounded like the old theme's category tags; Eventi links to its own
   listing (/category/eventi/), every other category falls back to /blog/
   (no dedicated listing page exists for it — same fallback tools/build.py's
   render_article_chips() already uses for the per-article chip row, kept
   consistent sitewide). Sits above .stretched-link's ::after (z-index:2 on
   a position:relative row) so pills stay independently clickable instead
   of triggering the card's own title link. */
.related-card-cats{position:relative; z-index:2; display:flex; flex-wrap:wrap; gap:6px; margin:0 0 8px;}
.cat-pill{
  display:inline-flex;
  align-items:center;
  padding:3px 10px;
  border-radius:var(--radius-pill);
  background:rgba(124,58,237,0.08);
  border:1px solid var(--border);
  color:var(--violet-deep);
  font-weight:700;
  font-size:11px;
  text-transform:uppercase;
  letter-spacing:0.03em;
  text-decoration:none;
  transition:background 0.2s ease, color 0.2s ease;
}
.cat-pill:hover, .cat-pill:focus-visible{background:var(--violet-deep); color:#fff;}
/* Round 11 restore (batch 3): an optional excerpt paragraph, used when
   .related-grid stands in for a full listing (blog/category archive)
   rather than a tight 3-5 card teaser — not part of the original
   capability's markup contract, but the class already scopes it safely. */
.related-card-body p{color:var(--muted); font-size:14.5px; line-height:1.5; margin:8px 0 0;}

/* ---------- Avatar initials ---------- */
.avatar-initials{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:56px;height:56px;
  border-radius:50%;
  background:linear-gradient(135deg, var(--violet), var(--pink));
  color:#fff;
  font-family:'Sora', sans-serif;
  font-weight:800;
  font-size:18px;
  flex:none;
  line-height:1;
}
.avatar-initials.sm{width:36px;height:36px; font-size:13px;}
.avatar-initials.lg{width:88px;height:88px; font-size:28px;}

/* ---------- Article typography ---------- */
.article{
  max-width:70ch;
  margin:0 auto;
  font-size:17px;
}
.article h2{font-size:clamp(1.5rem,2.6vw,2rem); margin-top:1.6em; letter-spacing:-0.02em;}
.article h3{font-size:clamp(1.2rem,2vw,1.5rem); margin-top:1.4em;}
.article p{margin-bottom:1.2em; color:var(--ink-soft);}
.article a{color:var(--violet-deep); text-decoration:underline; text-underline-offset:2px;}
.article a:hover{color:var(--pink);}
.article ul, .article ol{margin-bottom:1.2em; color:var(--ink-soft);}
.article li{margin-bottom:0.4em;}
.article blockquote{
  margin:1.8em 0;
  padding:4px 0 4px 22px;
  border-left:3px solid var(--violet-deep);
  color:var(--ink-soft);
  font-style:italic;
  font-size:19px;
}
.article figure{margin:2em 0;}
/* Natural size, centered, never upscaled — real width/height attrs exist
   from round 4, so the browser reserves the correct box before the image
   loads; forcing width:100% here was stretching small source images past
   their native resolution and made them blurry (round 5 fix). */
.article figure img{
  width:auto;
  max-width:100%;
  height:auto;
  display:block;
  margin-inline:auto;
  border-radius:var(--radius-lg);
}
.article figcaption{margin-top:10px; font-size:14px; color:var(--muted); text-align:center;}
/* ---------- Floated side figure (residual-closure fix, old-site
   "alignright" 1/3-column image with text wrap) ----------
   Add alongside .article figure, e.g. class="figure figure--float-right",
   placed just before the paragraph(s) it should sit beside. Text in the
   following paragraph(s) wraps around it until a clearing element (the
   next <h2>, which already has margin-top, reads fine unclearled — add
   style="clear:both" on it only if the wrap looks tight). Stacks to full
   width, no float, below 640px — a floated 1/3 column would be too
   narrow to read on a phone-width column. */
.figure--float-right{float:right; width:38%; max-width:320px; margin:6px 0 20px 28px;}
@media (max-width:640px){
  .figure--float-right{float:none; width:auto; max-width:100%; margin:2em 0;}
}
.article pre{
  background:var(--dark-bg);
  color:var(--dark-text);
  padding:18px 20px;
  border-radius:14px;
  overflow-x:auto;
  font-size:14px;
  margin-bottom:1.4em;
}
.article code{
  background:rgba(124,58,237,0.08);
  padding:0.15em 0.4em;
  border-radius:6px;
  font-size:0.92em;
}
.article pre code{background:none; padding:0;}
.article hr{border:none; border-top:1px solid var(--border); margin:2.4em 0;}

/* ---------- Article-template trio (round 11 follow-up) ----------
   Three components tools/build.py bakes onto every template:"article"
   fragment (docs/.sdd/round-11-trio.md): a category/tag chip row right
   under the header, a "Potrebbe anche interessarti" related-posts block,
   and prev/next chronological post nav — both appended at the end of the
   same .article wrap div, so the whole page keeps one 70ch reading column
   top to bottom (matches .page-header-inner's own 70ch measure above it).
   All three are hand-marked reveal (not add_top_level_reveal(), which is
   .page-body-only) — .related-grid is already one of main.js's stagger
   groups, so the 3 related cards fade in with the same per-item delay as
   /blog/'s and /category/eventi/'s listing cards. */
.article-tags{
  display:flex;
  flex-wrap:wrap;
  gap:10px;
  margin:0 0 32px;
}
.chip{
  display:inline-flex;
  align-items:center;
  padding:7px 16px;
  border-radius:var(--radius-pill);
  background:rgba(124,58,237,0.08);
  border:1px solid var(--border);
  color:var(--violet-deep);
  font-weight:700;
  font-size:13px;
  letter-spacing:0.01em;
  text-decoration:none;
  transition:background 0.25s ease, color 0.25s ease, transform 0.25s var(--spring), box-shadow 0.25s ease;
}
.chip:hover, .chip:focus-visible{
  background:var(--violet-deep);
  color:#fff;
  transform:translateY(-2px);
  box-shadow:0 10px 22px -10px rgba(124,58,237,0.45);
}
/* ---------- Free-form keyword tags (residual-closure fix) ----------
   Old-site posts also carried a free-form keyword-tag list distinct from
   the category chips above (which link to a listing page) — these have no
   listing page to link to in the rebuilt site, so they render as plain,
   non-interactive chips (muted color, no hover lift) rather than dead or
   misleading links. Markup: reuse .article-tags as the row wrapper, with
   <span class="chip chip--tag">Keyword</span> children; drop in right
   before the article's own closing content, same wrap column. */
.chip--tag{
  background:transparent;
  border-color:var(--border);
  color:var(--muted);
  font-weight:600;
  cursor:default;
}
.chip--tag:hover, .chip--tag:focus-visible{
  background:transparent;
  color:var(--muted);
  transform:none;
  box-shadow:none;
}
/* No component-local reduced-motion override needed here: the sitewide
   `*{transition-duration:0.001ms !important}` catch-all (bottom of this
   file) already collapses this hover transition to instant, same as every
   other plain hover-transform component (.card, .related-card, ...). */

.article-related{margin:56px 0 8px;}
.article-related h2{font-size:clamp(1.5rem,2.6vw,2rem); letter-spacing:-0.02em; margin-bottom:4px;}
/* AA fix scoped to this new usage only: .related-card-date's sitewide
   --pink is ~3.45:1 on the card's white background (large-text-only safe,
   see docs/.sdd/task-7-a11y.md I-2/I-3) — fine at /blog/'s and /category/
   eventi/'s larger listing-card scale, but this trio's brief requires AA
   at this 12.5px size. --magenta-deep is the codebase's own established
   AA-safe swap-in for exactly this case (~7.2:1 on white/tint — see the
   .band--pink / .phone-showcase .section-eyebrow precedent above), so it
   overrides the shared class here without touching the two listing pages
   that already ship the --pink version. */
.article-related .related-card-date{color:var(--magenta-deep);}

.post-nav{
  display:flex;
  flex-wrap:wrap;
  gap:16px;
  margin:48px 0 8px;
  padding-top:32px;
  border-top:1px solid var(--border);
}
.post-nav-link{
  flex:1 1 240px;
  display:flex;
  flex-direction:column;
  gap:6px;
  padding:18px 22px;
  border:1px solid var(--border);
  border-radius:var(--radius-lg);
  background:var(--card);
  text-decoration:none;
  color:inherit;
  transition:transform 0.3s var(--spring), box-shadow 0.3s ease, border-color 0.3s ease;
}
.post-nav-link:hover, .post-nav-link:focus-visible{
  transform:translateY(-4px);
  box-shadow:0 18px 38px -20px rgba(124,58,237,0.32);
  border-color:var(--violet-deep);
}
.post-nav-next{margin-left:auto; align-items:flex-end; text-align:right;}
.post-nav-eyebrow{
  font-size:12px;
  font-weight:800;
  text-transform:uppercase;
  letter-spacing:0.07em;
  color:var(--violet-deep);
}
.post-nav-title{
  font-family:'Sora', sans-serif;
  font-weight:700;
  font-size:15.5px;
  line-height:1.35;
  color:var(--ink);
}

/* ---------- Tables (article body + page-body) ---------- */
.table-wrap{overflow-x:auto;}
.article table,
.page-body table{
  width:100%;
  min-width:480px;
  border-collapse:collapse;
  margin:0 0 1.4em;
  font-size:15px;
}
.article th, .article td,
.page-body th, .page-body td{
  padding:12px 16px;
  border:1px solid var(--border);
  text-align:left;
  vertical-align:top;
}
.article th,
.page-body th{
  background:rgba(147,51,234,0.08);
  color:var(--ink-soft);
  font-weight:700;
}

/* ---------- Standard page-body container (page template) ----------
   Wraps {{content}} for template:"page" in tools/build.py — the default
   horizontal rhythm/measure for inner pages, sibling to the narrower
   .article measure used by blog posts. */
.page-body{
  max-width:1140px;
  margin-inline:auto;
  padding-inline:24px;
}
/* Same no-upscale/centered rule as .article figure img above — covers a
   bare <img> dropped directly into page-body prose (outside .split/.card,
   which style their own images below). */
.page-body img{
  width:auto;
  max-width:100%;
  height:auto;
  display:block;
  margin-inline:auto;
}
/* Round 7 fix: a raw <video width height> (e.g. the Smart Page demo) has
   no intrinsic CSS at all, so at its native 1280x720 it overflowed its
   .page-body container edge-to-edge with no side margin instead of
   shrinking/centering like the framed <img> rules just above. Same
   contract, site-wide (not scoped to .page-body — a .split-media video
   gets it too). */
video{
  max-width:100%;
  height:auto;
  display:block;
  margin-inline:auto;
  border-radius:var(--radius-lg);
}
.page-body figure{margin:2em 0;}
.page-body figcaption{margin-top:10px; font-size:14px; color:var(--muted); text-align:center;}

/* ---------- Split (media + text, 2-col) ----------
   Markup: <div class="split"><div class="split-media">...</div>
   <div class="split-text">...</div></div> — media first (left) by default;
   add .reverse to put media on the right. Stacks to one column <900px. */
.split{
  display:grid;
  grid-template-columns:1fr 1fr;
  align-items:center;
  gap:48px;
  padding:32px 0;
}
.split-media{order:1; display:flex; justify-content:center; align-items:center;}
.split-text{order:2;}
.split.reverse .split-media{order:2;}
.split.reverse .split-text{order:1;}
/* Natural size, never stretched to fill the grid column — same fix as
   .article figure img / .page-body img above. */
.split img{
  width:auto;
  max-width:100%;
  height:auto;
  display:block;
  border-radius:var(--radius-lg);
  transition:box-shadow 0.25s ease, transform 0.25s var(--spring);
}
/* Hover micro-interaction (round 6, old-site interactivity parity): subtle
   lift + shadow deepen. Shadow (a depth/color cue, not motion) always
   applies; the actual translateY lift is gated so reduced-motion users
   still get the shadow feedback with zero transform. */
.split img:hover{box-shadow:0 26px 55px -22px rgba(124,58,237,0.35);}
@media (prefers-reduced-motion: no-preference){
  .split img:hover{transform:translateY(-6px);}
}
@media (max-width:900px){
  .split{grid-template-columns:1fr; gap:28px; padding:24px 0;}
  .split-media, .split-text,
  .split.reverse .split-media, .split.reverse .split-text{order:initial;}
}

/* ---------- Split, flat-graphic modifier (round 11 batch-2 addition) ----------
   Add alongside .split, e.g. class="split split--flat", when the image is a
   borderless illustration meant to float directly on the band's flat color
   (old-site role: a poster-like background-image:contain graphic, not a
   framed photo card) rather than the default rounded/drop-shadowed .split
   img treatment. Breathing room around the graphic stands in for the old
   background-image's 14% padding. */
.split--flat .split-media{padding:28px;}
.split--flat .split-media img{border-radius:0; box-shadow:none;}
.split--flat .split-media img:hover{box-shadow:none; transform:none;}

/* ---------- Half-bleed split (round 11, capability 3) ----------
   Distinct from .split above: the photo fills its entire half edge-to-edge
   (background-image on a full-height column, no frame/radius/shadow), the
   other half is a solid/gradient color panel carrying the text — vs.
   .split's small contained, rounded, drop-shadowed <img>. Full-bleed to
   the viewport edge on the photo side via the same 100vw/negative-margin
   trick as .band/.cta-band.
   Markup (photo left by default; add .reverse to put it right):
   <div class="split-bleed">
     <div class="split-bleed-media" style="background-image:url('/assets/media/xxx.jpg')"></div>
     <div class="split-bleed-panel split-bleed-panel--violet">
       <div class="split-bleed-panel-inner"><h2>...</h2><p>...</p></div>
     </div>
   </div>
   Panel color modifiers: --violet (default if omitted) / --coral / --dark.
   Stacks to one column (photo on top) below 900px. */
.split-bleed{
  position:relative;
  width:100vw;
  margin-left:calc(50% - 50vw);
  margin-right:calc(50% - 50vw);
  display:grid;
  grid-template-columns:1fr 1fr;
  min-height:420px;
}
.split-bleed-media{
  order:1;
  background-size:cover;
  background-position:center;
  background-color:var(--border-soft);
  min-height:320px;
}
.split-bleed-panel{
  order:2;
  position:relative;
  display:flex;
  align-items:center;
  padding:64px max(24px, calc(50vw - 570px)) 64px 56px;
  color:#fff;
}
.split-bleed.reverse .split-bleed-media{order:2;}
.split-bleed.reverse .split-bleed-panel{order:1; padding:64px 56px 64px max(24px, calc(50vw - 570px));}
.split-bleed-panel-inner{max-width:46ch;}
.split-bleed-panel h2, .split-bleed-panel h3{color:#fff;}
.split-bleed-panel p{color:rgba(255,255,255,.9);}
/* Two-CTA row (residual-closure fix, assistenti-virtuali closing band):
   a primary + secondary button side by side, wrapping to stacked full-
   width buttons on narrow panels instead of overflowing the 46ch measure. */
.split-bleed-cta-row{display:flex; gap:14px; flex-wrap:wrap; align-items:center;}
.split-bleed-panel--violet{background:linear-gradient(160deg, var(--violet-deep), var(--violet));}
.split-bleed-panel--coral{background:linear-gradient(160deg, var(--coral), var(--pink));}
.split-bleed-panel--dark{background:var(--dark-bg);}
@media (max-width:900px){
  .split-bleed{grid-template-columns:1fr; width:auto; margin-inline:0;}
  .split-bleed-media, .split-bleed.reverse .split-bleed-media{order:1; min-height:240px;}
  .split-bleed-panel, .split-bleed.reverse .split-bleed-panel{order:2; padding:48px 24px;}
}

/* ---------- Photo bleed strip (residual-closure fix, formazione Eventi) ----------
   A single full-bleed, UNCROPPED image strip — distinct from .split-bleed
   (which crops a photo to a background-image:cover half-column): this is
   for a source image whose full frame matters (e.g. an event poster with
   baked-in text), full-width via the same 100vw/negative-margin trick,
   scaled by width with intrinsic height (never upscaled beyond its own
   natural size, capped so a tall source can't dominate the viewport).
   Markup: <div class="photo-bleed"><img src="..." alt="..." width height loading="lazy"></div> */
.photo-bleed{
  width:100vw;
  margin-left:calc(50% - 50vw);
  margin-right:calc(50% - 50vw);
  overflow:hidden;
  line-height:0;
}
.photo-bleed img{width:100%; height:auto; max-height:640px; object-fit:cover; display:block; margin:0;}

/* ---------- Hero cards-floating-on-photo (round 11 batch-2 addition) ----------
   Fills _SUMMARY.md "Gaps requiring a NEW design capability" #2 — a
   drop-shadowed, teal-accented white card row pulled up onto a
   .page-header--photo's bottom edge, each linking onward (in-page anchor
   or off-page). Drop this as the FIRST element of .page-body right after
   a hero with front-matter hero.bg, so add_top_level_reveal()'s scroll
   reveal still applies (it's a normal top-level .page-body child; the
   negative margin only affects paint position, not DOM/stacking scope).
   Markup:
   <div class="hero-cards-photo">
     <a class="hero-cards-photo-card" href="#anchor-or-/url/">
       <h3>Title</h3>
       <p>Body copy.</p>
       <span class="hero-cards-photo-link">Vuoi saperne di più?</span>
     </a>
     ...
   </div>
   Card count is auto-fit like .feature-grid, so 2-5 cards all center
   correctly. Requires a .page-header--photo above it to look intentional
   (works, but reads as a plain floating-card row otherwise) — this rule
   deliberately does NOT depend on that selector so it stays reusable on
   any page shape.

   Header clearance (a11y-final-touchup fix): this row's -104px margin-top
   pulls it up 104px over whatever precedes it, so that header needs at
   least 104px of empty padding-bottom below its own text or the card row
   paints over the tail of the lede paragraph (observed: "...gli obiettivi
   del" clipped on /intelligenza-artificiale/). tools/build.py render_header()
   sniffs the fragment body for this class and, when found, adds
   page-header--has-cards to the header (see that rule right below) so the
   padding is reserved automatically — never depend on a page's own
   padding-bottom being "tall enough by accident". Below the 640px
   breakpoint the row switches from overlap to plain stacking (drops below
   the header with normal margin) instead of chasing the same "overlap +
   clearance" math against a header whose height swings much more once the
   h1/sub wrap across extra lines at narrow widths. */
.hero-cards-photo{
  position:relative;
  z-index:2;
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(min(260px, 100%), 320px));
  justify-content:center;
  gap:22px;
  max-width:1140px;
  margin:-104px auto 44px;
  padding:0 24px;
}
/* 104px card-row overlap + 32px breathing room (holds at 1280/1440/1920 —
   the overlap amount is a fixed px pull, not viewport-relative, so it
   doesn't shift with wider desktop widths). Reset to the base .page-header
   padding-bottom below 640px, where .hero-cards-photo stops overlapping. */
.page-header--has-cards{padding-bottom:136px;}
.hero-cards-photo-card{
  display:block;
  background:#fff;
  border-radius:var(--radius-lg);
  padding:34px 30px 28px;
  box-shadow:0 26px 54px -22px rgba(21,12,38,.4);
  border-bottom:4px solid #38d4db;
  text-decoration:none;
  color:inherit;
  transition:transform 0.3s var(--spring), box-shadow 0.3s ease;
}
.hero-cards-photo-card:hover{transform:translateY(-6px); box-shadow:0 32px 62px -20px rgba(21,12,38,.48);}
.hero-cards-photo-card h3{margin:0 0 10px; font-size:1.2rem; letter-spacing:-0.01em;}
.hero-cards-photo-card p{margin:0 0 16px; color:var(--ink-soft); font-size:15px; line-height:1.55;}
.hero-cards-photo-link{
  display:inline-block;
  font-weight:700;
  font-size:14px;
  color:var(--violet-deep);
  text-decoration:underline;
  text-underline-offset:3px;
}
.hero-cards-photo-card:hover .hero-cards-photo-link{color:var(--pink);}
.hero-cards-photo-card .card-meta{display:block; color:var(--pink); font-weight:700; font-size:13px; text-transform:uppercase; letter-spacing:0.06em; margin-bottom:8px;}
@media (max-width:640px){
  /* Drop below the header instead of overlapping it (a11y-final-touchup
     fix): a fixed negative pull here would need to out-guess a header
     height that swings a lot more at this width (h1/sub wrapping across
     extra lines), so stacking in normal flow with a plain gap is the
     robust choice rather than a second overlap-vs-clearance calculation.
     page-header--has-cards's extra padding-bottom (above) is only needed
     for the desktop overlap, so it resets to the component's own base
     64px here too. */
  .page-header--has-cards{padding-bottom:64px;}
  .hero-cards-photo{margin-top:32px;}
}

/* ---------- Numbered columns (round 11 restore-batch, old-site "01/02/03
   hero triad" parity) ----------
   Centered multi-column row of large gradient numerals introducing a
   short set of named items — the old theme's hero-row product triad
   (e.g. "01. ASSISTENTI VOCALI") and the smart-chatbot "01/02/03 icon
   columns" benefit list both used this pattern with an icon-font glyph
   swapped here for a plain numeral, matching the site's existing
   icon-font -> SVG/typographic modernization elsewhere. auto-fit centers
   any 2-4 item count at any viewport (same technique as .icon-features/
   .feature-grid/.related-grid). Works standalone (light sections) and
   nested inside .band--dark (that rule's h3/p color overrides apply to
   nested numbered-col headings/body automatically, no extra markup).
   Markup:
   <div class="numbered-cols">
     <div class="numbered-col">
       <span class="numbered-col-num" aria-hidden="true">01</span>
       <h3>Title</h3>
       <p>Body copy.</p>
     </div>
     ...
   </div> */
.numbered-cols{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(min(230px, 100%), 1fr));
  gap:36px 32px;
  padding:36px 0;
}
.numbered-col-num{
  display:block;
  font-family:'Sora', sans-serif;
  font-weight:800;
  font-size:2.6rem;
  line-height:1;
  margin-bottom:12px;
  background:linear-gradient(120deg, var(--violet-deep), var(--pink));
  -webkit-background-clip:text; background-clip:text; color:transparent;
}
.numbered-col h3{font-size:18px; margin-bottom:8px;}
.numbered-col p{color:var(--muted); font-size:15px; line-height:1.6; margin:0;}
@media (max-width:640px){
  .numbered-cols{grid-template-columns:1fr; max-width:340px; margin-inline:auto;}
}

/* ---------- Feature grid (page-body card grid, 2-3 cols) ----------
   Responsive card grid reusing .card from the listing template. Round 7
   fix: same repeat(3,1fr)-always-reserves-3-tracks bug as .icon-features
   above — vocabolario-artificiale.html alone nests dozens of these with
   anywhere from 1 to 9 cards, and every one under 3 sat left-aligned in
   its row instead of centered. auto-fit + justify-content:center centers
   any card count at any viewport; the old 900px/640px column-count
   breakpoints are gone since auto-fit already recomputes continuously.
   min(280px,100%) on the track floor stops a single card from forcing a
   fixed-280px track wider than an ultra-narrow (<328px) viewport. */
.feature-grid{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(min(280px, 100%), 360px));
  justify-content:center;
  gap:22px;
  padding:32px 0;
}

/* ---------- Section title / lead (old-site airy centered composition) ----------
   .section-title-big: huge centered gradient display heading (reuses the
   same gradFlow keyframes as .hero h1 .grad). .lead-italic: centered
   italic serif lead paragraph, measure-capped at 60ch, set in the
   self-hosted Playfair Display italic (round 5 — see assets/fonts/fonts.css),
   falling back to Georgia if the woff2 hasn't loaded yet. Markup:
   <h2 class="section-title-big">Servizi</h2>
   <p class="lead-italic">...</p> */
/* Solid fallback FIRST (readable violet), same pattern as .grad-text above —
   covers browsers/contexts without background-clip:text support (M-1) and
   is also what forced-colors/high-contrast modes fall back to. Gradient
   uses --amber-safe, not --amber: .section-title-big always sits on the
   light --bg page background, where plain --amber fails the 3:1 large-text
   floor (2.00:1) — see the --amber-safe definition above (I-2). */
.section-title-big{
  font-family:'Sora', 'Plus Jakarta Sans', sans-serif;
  font-weight:800;
  text-align:center;
  font-size:clamp(2.4rem, 6vw, 4.2rem);
  line-height:1.05;
  letter-spacing:-0.02em;
  margin:0 auto 0.35em;
  color:var(--violet-deep);
}
@supports (background-clip:text) or (-webkit-background-clip:text){
  .section-title-big{
    background:linear-gradient(120deg, var(--violet-deep), var(--pink) 45%, var(--amber-safe) 75%, var(--violet-deep));
    background-size:300% 100%;
    -webkit-background-clip:text; background-clip:text;
    -webkit-text-fill-color:transparent; color:transparent;
  }
}
@media (prefers-reduced-motion: no-preference){
  .section-title-big{animation:gradFlow 7s ease infinite;}
}
.lead-italic{
  max-width:60ch;
  margin:0 auto 1.6em;
  text-align:center;
  font-family:'Playfair Display', Georgia, serif;
  font-style:italic;
  font-weight:400;
  font-size:clamp(1.1rem, 2.2vw, 1.4rem);
  line-height:1.7;
  color:var(--ink-soft);
}
/* Light-on-dark variant — for a .lead-italic dropped inside a dark/gradient
   band (e.g. .team-band, .band--dark) where the default ink-soft color
   would be near-invisible. rgba, not solid #fff, so it reads as a softer
   secondary line under a solid-white heading; still >=6.3:1 against every
   stop of the team-band gradient (0.92-opacity white on --magenta-deep is
   the darkest/lowest-contrast stop combination). */
.lead-italic--on-dark{color:rgba(255,255,255,0.92);}

/* ---------- Icon features (old-site airy centered icon columns) ----------
   Centered multi-column grid, NO card boxes: inline-SVG icon on top,
   uppercase pink bold title, centered body text. 4-up desktop -> 2x2
   tablet -> 1 mobile. The :last-child:nth-child(odd) rule spans + centers
   a lone leftover item so an odd item count never leaves an orphan column
   dangling in the last row at any breakpoint.
   Markup:
   <div class="icon-features">
     <div class="icon-features-item">
       <span class="icon-features-icon"><svg>...</svg></span>
       <h3>Title</h3>
       <p>Body copy.</p>
     </div>
     ...
   </div> */
/* Round 7 fix: repeat(4,1fr) always reserved 4 equal-width tracks, so a
   3-item (or any <4-item) group filled tracks 1-3 left to right and sat
   visibly left-shifted with an empty 4th column instead of centered.
   auto-fit computes as many minmax(220,260) tracks as the row can hold,
   collapses any that end up with no item in them to 0 width, and
   justify-content:center then centers the resulting (now-narrower) track
   set as a block — so ANY item count (1 through however many fit) centers
   itself with no phantom trailing column, at every viewport (the old
   900px/640px breakpoints forcing an explicit column count, plus the
   :last-child(odd) span hack for a lone leftover, are gone: auto-fit
   already recomputes the column count continuously as the container
   narrows, and centers every row including a partial one). */
.icon-features{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(min(220px, 100%), 260px));
  justify-content:center;
  gap:40px 28px;
  padding:36px 0;
}
.icon-features-item{
  display:flex;
  flex-direction:column;
  align-items:center;
  text-align:center;
  gap:14px;
  transition:transform 0.2s ease;
}
.icon-features-icon{
  display:flex;
  align-items:center;
  justify-content:center;
  width:56px;
  height:56px;
  flex:none;
  transition:transform 0.2s ease;
}
.icon-features-icon svg{width:100%; height:100%; display:block; transition:stroke 0.2s ease;}
/* Hover micro-interaction (round 6, old-site interactivity parity): icon
   scale + violet->pink stroke shift + slight card lift. The color shift
   (an authored CSS declaration) overrides the SVGs' own stroke="#7c3aed"
   presentation attribute — presentation attributes sit below author rules
   in the cascade, no !important needed. Transforms are wrapped so
   reduced-motion users still get the color change with no scale/lift. */
.icon-features-item:hover .icon-features-icon svg{stroke:var(--pink);}
@media (prefers-reduced-motion: no-preference){
  .icon-features-item:hover{transform:translateY(-3px);}
  .icon-features-item:hover .icon-features-icon{transform:scale(1.12);}
}
.icon-features-item h3{
  font-size:14px;
  font-weight:800;
  text-transform:uppercase;
  letter-spacing:0.07em;
  color:var(--pink);
  margin:0;
}
.icon-features-item p{
  color:var(--muted);
  font-size:15px;
  line-height:1.6;
  margin:0;
  max-width:26ch;
}
/* Below one 220-260px track's-worth of room (plus its gap), auto-fit alone
   would still try to fit exactly one full-width-ish track — pin it to a
   single centered column explicitly so a narrow phone never gets a
   near-viewport-width item with no side breathing room. */
@media (max-width:640px){
  .icon-features{grid-template-columns:1fr; max-width:260px; margin-inline:auto;}
}

/* ---------- Colored band utility (old-site vc_row colored/parallax section parity) ----------
   Full-bleed section wrapper for use inside .page-body/.article flows
   (which are otherwise max-width constrained) — the plain-content
   counterpart to .cta-band below, for parity with the old theme's colored
   vc_row bands. Some fragments nest a .wrap/.split/.icon-features/
   .feature-grid/.counters directly for the readable-width column; others
   (e.g. a bare .split dropped straight inside .band, no .wrap) rely on
   .band's own inline padding for that constraint — round 7 fix: swapped
   the old width:100vw + margin-left/right:calc(50%-50vw) full-bleed trick
   (which left NO horizontal padding, so content with no nested .wrap sat
   edge-to-edge and got clipped at the viewport edge) for margin-inline on
   an auto-width box, with the horizontal padding itself doing double duty:
   max(24px, calc(50vw - 570px)) guarantees a 24px minimum gutter on narrow
   viewports while centering content at a 1140px measure (matches
   .page-body's own max-width) on wide ones.
   Markup:
   <div class="band"><div class="wrap">
     <h2>...</h2><p>...</p>
   </div></div>
   .band--dark: dark-purple variant (parity with old dark/parallax bands) —
   add alongside .band, e.g. class="band band--dark". .band--pink /
   .band--coral: light tint variants (like the default violet tint) for
   plain-content bands that alternate color without needing dark bg/white
   text — do NOT use on a band whose content assumes a dark bg (e.g. one
   nesting a .btn-white, which reads AA only against a dark background). */
.band{
  position:relative;
  margin-inline:calc(50% - 50vw);
  width:auto;
  background:linear-gradient(180deg, rgba(147,51,234,0.07), rgba(236,72,153,0.05));
  padding:72px max(24px, calc(50vw - 570px));
}
.band--dark{
  background:var(--dark-bg);
  color:var(--dark-text);
}
.band--dark h2, .band--dark h3, .band--dark h4{color:#fff;}
.band--dark p{color:var(--dark-muted);}
.band--dark .section-eyebrow, .band--dark .page-eyebrow{color:var(--pink);}
.band--pink{background:linear-gradient(180deg, rgba(236,72,153,0.09), rgba(245,165,36,0.05));}
.band--coral{background:linear-gradient(180deg, rgba(255,138,92,0.11), rgba(236,72,153,0.05));}

/* ---------- Band, photo variant (residual-closure fix) ----------
   Same mechanism as .cta-band--photo above: add alongside .band (and
   .band--dark for the white-text palette), set the photo via an INLINE
   style attribute on that same element so check.py's shared-stylesheet
   weight accounting isn't charged against every page loading style.css:
   <div class="band band--dark band--photo" style="background-image:url('/assets/media/xxx.jpg')">
     <div class="split">...</div>
   </div>
   A dark/violet gradient scrim (::before) keeps nested text AA-legible
   over any photo. Direct children get position:relative + z-index:1 to
   paint above the scrim — painting-order note: an absolutely-positioned
   ::before with z-index:auto still falls in the SAME stacking-context
   bucket as other z-index:auto positioned boxes (painted by tree order,
   not automatically behind static in-flow content), so without an
   explicit z-index the scrim can end up on top of the real content; see
   .cta-band-inner's own explicit z-index:1 for the same reason. True
   parallax (background-attachment:fixed) on wide viewports with support;
   narrower/no-support falls back to the same slow background-position
   drift used by .cta-band--photo (reduced-motion: static either way). */
.band--photo{background-size:cover; background-position:center;}
.band--photo::before{
  content:"";
  position:absolute; inset:0;
  /* Both stops kept high-opacity (unlike a directional vignette) on
     purpose: .band's .split/.split.reverse can put the text column on
     EITHER side, so the scrim can't safely go light on one end the way
     .page-header--photo's left-weighted scrim can (that one always has
     text on the left). Same ~.8+ floor as .cta-band--photo's own scrim.
     Colors are CSS custom properties (--band-scrim-1/-2, default =
     original violet-black pair) so a specific instance can re-tint the
     wash — e.g. violet->blue vs violet->pink for two adjacent product
     bands — via inline style="--band-scrim-2:...", without a new
     modifier class per color combo. */
  background:linear-gradient(120deg, var(--band-scrim-1, rgba(21,12,38,.88)), var(--band-scrim-2, rgba(76,30,120,.80)));
}
.band--photo > *{position:relative; z-index:1;}
/* .band--dark p normally uses --dark-muted (calibrated for AA against the
   flat --dark-bg solid color only) — over a photo+scrim the effective
   background can run lighter at the scrim's low-opacity end depending on
   the photo, so body copy needs a brighter, higher-contrast near-white
   here to stay AA at every point a text column can land (see the scrim
   comment above). */
.band--photo p{color:rgba(255,255,255,.92);}
/* Same reasoning as .band--photo p above: .band--dark's --pink eyebrow
   color (~2:1 against the scrim's lighter end) is calibrated for the flat
   --dark-bg solid, not a photo scrim — lighten it here so it clears AA
   even over a bright patch of photo. */
.band--photo .section-eyebrow, .band--photo .page-eyebrow{color:#ffd1e8;}
@media (min-width:900px){
  @supports (background-attachment:fixed){
    .band--photo{background-attachment:fixed;}
  }
}
@media (max-width:899px){
  @media (prefers-reduced-motion:no-preference){
    .band--photo{animation:bandPhotoDrift 26s ease-in-out infinite alternate;}
  }
}
@keyframes bandPhotoDrift{from{background-position:50% 30%;} to{background-position:50% 70%;}}

/* ---------- Team band (Chi Siamo pyramid, round 8, old-site parity) ----------
   Full-bleed gradient band (violet-deep -> magenta-deep, 135deg diagonal —
   the C3-palette equivalent of the old theme's rgba(55,1,191)/rgba(165,6,112)
   band) holding both the centered intro (h2 + divider + .lead-italic) and,
   nested below in the same band, the tiered team grid: a wide 2-up lead row
   (CEO + CTO) followed by two 3-up rows. --magenta-deep (not --pink) is used
   as the second stop deliberately: pure white text on --pink alone is only
   ~3.5:1 (fails AA body text); white on --magenta-deep is ~7.2:1. Markup:
   <div class="team-band">
     <div class="wrap team-band-intro"><h2>...</h2><div class="divider"></div>
       <p class="lead-italic lead-italic--on-dark">...</p></div>
     <div class="wrap team-tiers">
       <div class="team-tier team-tier--lead"><div class="team-member">
         <span class="avatar-initials lg avatar-initials--ring">GS</span>
         <h3>Name</h3><p class="team-role">Role</p></div>...</div>
       <div class="team-tier"><div class="team-member">...</div>...</div>
     </div>
   </div>
   2/3/3 columns down to 900px, 2-up down to 640px, single column below
   that ("mobile stacks"). */
.team-band{
  position:relative;
  width:100vw;
  margin-left:calc(50% - 50vw);
  margin-right:calc(50% - 50vw);
  background:linear-gradient(135deg, var(--violet-deep), var(--magenta-deep));
  color:#fff;
  padding:88px max(24px, calc(50vw - 570px));
}
.team-band h2, .team-band h3{color:#fff;}
.team-band-intro{max-width:640px; text-align:center;}
.team-tiers{margin-top:64px; display:flex; flex-direction:column; gap:36px;}
.team-tier{display:grid; grid-template-columns:repeat(3, 1fr); gap:32px 28px;}
.team-tier--lead{grid-template-columns:repeat(2, 1fr); max-width:640px; margin-inline:auto;}
.team-member{display:flex; flex-direction:column; align-items:center; text-align:center; gap:14px;}
.team-member h3{font-size:18px; margin:0;}
.team-role{
  color:rgba(255,255,255,0.85);
  font-weight:700;
  font-size:13px;
  text-transform:uppercase;
  letter-spacing:0.06em;
  margin:-8px 0 0;
}
/* .avatar-initials keeps its own violet->pink fill (already a distinct,
   lighter gradient than the band behind it); the ring is what separates it
   from a same-family background at a glance. */
.avatar-initials--ring{box-shadow:0 0 0 3px rgba(255,255,255,0.5), 0 14px 30px -16px rgba(8,4,16,0.55);}
@media (max-width:900px){
  .team-tier, .team-tier--lead{grid-template-columns:repeat(2, 1fr);}
}
@media (max-width:640px){
  .team-tier, .team-tier--lead{grid-template-columns:1fr; max-width:280px; margin-inline:auto;}
}
/* .team-tier--solo: a final centered single-member row (round: Roberto
   Capri addition, old-site 9-member pyramid parity). Reuses the same
   repeat(3, 1fr) grid + gap as a normal .team-tier (no width override
   needed — the lone .team-member is simply placed in the middle track,
   so it renders at the identical column width as any 3-up row's card).
   No empty spacer divs required in markup, unlike the old site's literal
   flanking-empty-columns approach. Below 900px .team-tier collapses to
   2 then 1 columns (existing rules above, which already match this
   element via its shared .team-tier class) — grid-column:2 would no
   longer center reliably once the track count changes, so those two
   breakpoints spread + center the member across every available track
   instead. */
.team-tier--solo .team-member{grid-column:2;}
@media (max-width:900px){
  .team-tier--solo .team-member{grid-column:1 / -1; justify-self:center;}
}

/* ---------- Colored divider (round 6-7, old-site accent-bar parity) ----------
   Formalizes the short colored accent bar a few fragments were already
   hand-rolling as one-off inline <hr style="width:...background:..."> tags
   under a heading — same 56x4px rounded-bar shape, now a reusable class
   with four color variants so consecutive section headings down a page can
   alternate instead of repeating the same violet bar throughout.
   Default (no modifier) = centered + violet, for use under a centered
   heading (e.g. .section-title-big, or an h2 in a text-align:center
   wrapper). Add .divider--left alongside a left-aligned heading (the
   common case for a plain <h2> inside .page-body/.split-text prose).
   Add ONE color modifier: .divider--pink / .divider--coral /
   .divider--gradient (violet is the unmodified default).
   Markup: <h2>...</h2><div class="divider divider--left divider--pink"></div>
   .divider-line: thin 1px full-measure rule for a subtler separation
   between stacked blocks that aren't under a heading (e.g. between two
   unrelated list groups) — <hr class="divider-line"> or a plain div. */
.divider{
  width:56px;
  height:4px;
  border-radius:999px;
  margin:18px auto 0;
  background:var(--violet);
}
.divider--left{margin-left:0; margin-right:auto;}
.divider--pink{background:var(--pink);}
.divider--coral{background:var(--coral);}
.divider--gradient{background:linear-gradient(90deg, var(--violet), var(--pink), var(--coral));}
.divider-line{
  display:block;
  width:100%;
  height:1px;
  border:none;
  background:var(--border);
  margin:0;
}

/* ---------- Asymmetric coral/white split CTA (round 11 restore, batch 3) ----------
   Old-site "formazione" family CTA: a wide coral/pink statement panel next
   to a narrower white action card — distinct from the plain centered
   .cta-band below. Full-bleed via the same 100vw/negative-margin trick.
   Markup:
   <div class="cta-split">
     <div class="cta-split-statement"><h2>Big statement</h2></div>
     <div class="cta-split-card">
       <h3>...</h3><p>...</p><a class="btn btn-primary" href="...">...</a>
     </div>
   </div>
   Stacks to one column (statement on top) below 900px. */
.cta-split{
  position:relative;
  width:100vw;
  margin-left:calc(50% - 50vw);
  margin-right:calc(50% - 50vw);
  display:grid;
  grid-template-columns:2fr 1fr;
  align-items:stretch;
}
.cta-split-statement{
  background:linear-gradient(135deg, var(--coral), var(--pink));
  display:flex;
  align-items:center;
  padding:56px max(24px, calc(50vw - 630px)) 56px 56px;
}
.cta-split-statement h2{
  color:#fff;
  font-size:clamp(1.7rem, 3.2vw, 2.5rem);
  line-height:1.18;
  margin:0;
  max-width:18ch;
}
.cta-split-card{
  background:#fff;
  display:flex;
  flex-direction:column;
  justify-content:center;
  padding:44px 40px;
  box-shadow:0 30px 60px -30px rgba(21,12,38,0.28);
}
.cta-split-card h3{margin-bottom:12px;}
.cta-split-card p{color:var(--muted); margin-bottom:22px;}
@media (max-width:900px){
  .cta-split{grid-template-columns:1fr; width:auto; margin-inline:0;}
  .cta-split-statement{padding:44px 24px;}
  .cta-split-statement h2{max-width:none;}
  .cta-split-card{padding:36px 24px;}
}

/* ---------- Accordion (round 11 restore, batch 3) ----------
   Styled <details>/<summary> pane, C3-skinned replacement for the old-site
   click-to-expand accordion (formazione__corso). Native <details> gives
   keyboard/AT support for free with zero JS. Each pane is independent
   (not exclusive-open), matching the old widget which also allowed
   multiple panes open at once.
   Markup:
   <div class="accordion">
     <details class="accordion-item">
       <summary>Question <span class="accordion-icon" aria-hidden="true"></span></summary>
       <div class="accordion-body"><p>...</p></div>
     </details>
     ...
   </div> */
.accordion{display:flex; flex-direction:column; gap:14px; padding:8px 0 32px;}
.accordion-item{
  background:var(--card);
  border:1px solid var(--border);
  border-radius:var(--radius-lg);
  overflow:hidden;
}
.accordion-item summary{
  list-style:none;
  cursor:pointer;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:16px;
  padding:22px 26px;
  font-family:'Sora', 'Plus Jakarta Sans', sans-serif;
  font-weight:800;
  font-size:17px;
  color:var(--ink);
}
.accordion-item summary::-webkit-details-marker{display:none;}
.accordion-item summary:focus-visible{outline:3px solid var(--violet-deep); outline-offset:-3px;}
.accordion-icon{
  flex:none;
  width:26px; height:26px;
  border-radius:50%;
  background:linear-gradient(135deg, var(--violet), var(--pink));
  color:#fff;
  display:flex; align-items:center; justify-content:center;
  font-size:16px;
  line-height:1;
  transition:transform 0.25s var(--spring);
}
.accordion-icon::before{content:"+";}
.accordion-item[open] .accordion-icon{transform:rotate(135deg);}
.accordion-body{padding:0 26px 28px; color:var(--ink-soft);}
.accordion-body > :first-child{margin-top:0;}
.accordion-body > :last-child{margin-bottom:0;}
@media (prefers-reduced-motion: reduce){
  .accordion-icon{transition:none;}
}

/* ---------- CTA band (full-width violet-gradient, centered) ----------
   Markup: <div class="cta-band"><div class="cta-band-inner">...</div></div>
   Full-bleed via the 100vw/negative-margin trick so it spans the viewport
   even inside a padded .page-body; body{overflow-x:hidden} guards against
   the 100vw-vs-scrollbar rounding overflow this trick can introduce. */
.cta-band{
  position:relative;
  width:100vw;
  margin-left:calc(50% - 50vw);
  margin-right:calc(50% - 50vw);
  background:linear-gradient(135deg, var(--violet), var(--pink));
  color:#fff;
  text-align:center;
  padding:64px 24px;
}
.cta-band-inner{max-width:640px; margin:0 auto; position:relative; z-index:1;}
.cta-band h2, .cta-band h3{color:#fff; margin-bottom:14px;}
.cta-band p{color:rgba(255,255,255,0.92); max-width:52ch; margin:0 auto 28px;}
.cta-band .btn-primary{
  background:#fff;
  color:var(--violet-deep);
  box-shadow:0 20px 45px -18px rgba(21,12,38,0.4);
}
.cta-band .btn-primary:hover{box-shadow:0 24px 50px -16px rgba(21,12,38,0.5);}

/* ---------- CTA band, photo variant (round 11, capability 2) ----------
   Add alongside .cta-band, e.g. class="cta-band cta-band--photo", and set
   the photo via an INLINE style attribute on that same element:
   <div class="cta-band cta-band--photo" style="background-image:url('/assets/media/contact-1920x1080.jpg')">
   <div class="cta-band-inner">...</div></div>
   Inline (not a shared style.css url(...)) so check.py's shared-stylesheet
   weight accounting isn't charged against every page that loads style.css —
   only the page that authors this instance references the photo at all
   (same reasoning as the home hero / photo page-header above). Pair the
   inline background-image with a <link rel="preload" as="image"> in that
   fragment's own <head> if the band is above the fold, so check.py's
   budget actually reflects it — see HERO_PRELOAD_HTML in tools/build.py
   for the pattern (a per-fragment "jsonld"-style head addition isn't wired
   for this yet; a plain <link> the fragment can't emit itself, so treat
   this band as below-the-fold/lazy in practice).
   Violet gradient overlay (::before) keeps CTA text AA-legible over any
   photo. True parallax (background-attachment:fixed) only on wide
   viewports with support; narrower/no-support falls back to a slow
   background-position drift for the same "photo isn't static" feel. */
.cta-band--photo{
  background-color:var(--violet-deep);
  background-size:cover;
  background-position:center;
}
.cta-band--photo::before{
  content:"";
  position:absolute; inset:0;
  background:linear-gradient(135deg, rgba(124,58,237,.87), rgba(236,72,153,.82));
}
.cta-band--photo .cta-band-inner{position:relative; z-index:1;}
@media (min-width:900px){
  @supports (background-attachment:fixed){
    .cta-band--photo{background-attachment:fixed;}
  }
}
@media (max-width:899px){
  @media (prefers-reduced-motion:no-preference){
    .cta-band--photo{animation:ctaPhotoDrift 24s ease-in-out infinite alternate;}
  }
}
@keyframes ctaPhotoDrift{from{background-position:50% 30%;} to{background-position:50% 70%;}}

/* ---------- CTA band, plain variant (residual-closure fix) ----------
   Old-site "simplest CTA on site": a minimal white/bordered band instead
   of the violet gradient — add alongside .cta-band, e.g.
   class="cta-band cta-band--plain". Declared AFTER .cta-band's own
   .btn-primary rule (same specificity, source order decides) so the
   button reverts to the sitewide gradient treatment instead of the
   gradient-band's white-pill button. */
.cta-band--plain{
  background:var(--card);
  box-shadow:inset 0 1px 0 var(--border), inset 0 -1px 0 var(--border);
}
.cta-band--plain h2, .cta-band--plain h3{color:var(--ink);}
.cta-band--plain p{color:var(--muted);}
.cta-band--plain .btn-primary{
  background:linear-gradient(135deg, var(--violet), var(--pink));
  color:#fff;
  box-shadow:var(--shadow-violet);
}

/* ---------- CTA-split micro-interactions (residual-closure fix, corso page) ----------
   Opt-in modifier for ONE .cta-split instance (content/formazione__corso-
   di-intelligenza-artificiale-per-le-imprese.html): add alongside
   .cta-split, e.g. class="cta-split cta-split--interactive". Two
   page-specific effects layered on the existing component:
   1) a clip-path wipe reveal (replaces the generic .reveal fade/rise for
      THIS instance only, via the 2-class selector's higher specificity)
   2) a mouse-parallax tilt on .cta-split-card, wired in assets/js/main.js
      ("CTA-split mouse-parallax tilt"), skipped entirely under
      prefers-reduced-motion (checked in JS before any listener attaches). */
.cta-split--interactive.reveal{
  opacity:0;
  transform:none;
  clip-path:inset(0 100% 0 0);
  transition:clip-path 0.9s var(--spring), opacity 0.3s ease;
}
.cta-split--interactive.reveal.in{
  opacity:1;
  clip-path:inset(0 0 0 0);
}
.cta-split--interactive .cta-split-card{
  transition:transform 0.25s ease-out, box-shadow 0.25s ease-out;
  transform-style:preserve-3d;
  will-change:transform;
}
.cta-split--interactive .cta-split-card:hover{box-shadow:0 40px 70px -30px rgba(21,12,38,0.35);}
@media (prefers-reduced-motion:reduce){
  .cta-split--interactive.reveal{clip-path:none; transition:none;}
  .cta-split--interactive .cta-split-card{transition:none;}
}

/* ---------- Marquee, logo variant (residual-closure fix, and-point partners) ----------
   Reuses .marquee/.marquee-viewport/.marquee-track/.marquee-item/
   .marquee-pause verbatim (see "Trust marquee" above) with real <img>
   logos instead of plain text names: add class="marquee--logos" on the
   outer .marquee and class="marquee-item--logo" on each .marquee-item,
   wrapping a single <img>. Desaturated-by-default, full color on
   hover/focus (same "quiet until you look" treatment as the old static
   grid, now animated). The reduced-motion fallback differs from the
   text marquee's own (which intentionally keeps only 3 of 12 repeated
   name-items): a logo carousel has NO repeats within a half, so ALL
   non-dup items must stay visible statically, not just the first 3. */
.marquee-item--logo{display:flex; align-items:center; margin-right:56px;}
.marquee-item--logo img{
  height:44px; width:auto; display:block;
  filter:grayscale(100%); opacity:0.7;
  transition:filter 0.25s ease, opacity 0.25s ease;
}
.marquee-item--logo:hover img, .marquee-item--logo:focus-within img{filter:grayscale(0%); opacity:1;}
@media (prefers-reduced-motion:reduce){
  .marquee--logos .marquee-item:not(.marquee-dup){display:flex !important;}
}

/* ---------- Triangle shape-divider (residual-closure fix, and-point hero) ----------
   Decorative single-triangle wedge at a .page-header--photo's bottom
   seam, C3 equivalent of the old-site "shape divider" widget. Drop as the
   FIRST element of .page-body (before any .hero-cards-photo row that
   follows it): height + equal negative margin-top net to zero extra
   page-flow space, so it paints over the header's bottom edge without
   shifting anything below it (a later .hero-cards-photo's own -104px
   margin still measures from the same pre-divider flow position). z-index
   2 matches .hero-cards-photo's, so later DOM content (the cards) paints
   on top where the two overlap, same "layered above the seam" look.
   Fill color is the page background, so the header reads as if it has an
   angled bottom edge instead of a straight one.
   Markup:
   <div class="shape-divider shape-divider--triangle" aria-hidden="true">
     <svg viewBox="0 0 1200 60" preserveAspectRatio="none"><polygon points="0,0 1200,0 600,60"/></svg>
   </div> */
.shape-divider--triangle{
  position:relative;
  z-index:2;
  width:100vw;
  margin-left:calc(50% - 50vw);
  margin-right:calc(50% - 50vw);
  height:46px;
  margin-top:-46px;
  margin-bottom:0;
  line-height:0;
  pointer-events:none;
}
.shape-divider--triangle svg{display:block; width:100%; height:100%;}
.shape-divider--triangle polygon{fill:var(--bg);}
@media (max-width:640px){
  .shape-divider--triangle{height:26px; margin-top:-26px;}
}

/* ---------- Flipbook: real page-turn brochure book (brochure page) ----------
   Click cover -> assets/js/main.js dynamic-import()s assets/js/flipbook.js,
   which dynamic-import()s the vendored assets/js/vendor/pdfjs/pdf.min.mjs
   to parse the actual PDF and render each page to a <canvas>; the book UI
   (spreads, page-turn, nav, counter, fullscreen) is plain DOM/CSS built by
   flipbook.js, not a PDF viewer widget — no PDF toolbar/chrome anywhere.
   None of that is a static <script>/<link>, so it never touches
   tools/check.py's page-weight budget or external-host scan.
   Markup:
   <div class="flipbook" id="flipbook">
     <button class="flipbook-cover" id="flipbookCover" aria-expanded="false"
             aria-controls="flipbookViewer">
       <span class="flipbook-cover-face">...</span>
       <span class="flipbook-cover-spine" aria-hidden="true"></span>
     </button>
     <div class="flipbook-viewer" id="flipbookViewer" hidden><!-- JS --></div>
     <div class="flipbook-fallback" id="flipbookFallback" hidden>
       <object ...>...</object>
     </div>
   </div>
   assets/js/main.js toggles class="is-open" on .flipbook (cover click) —
   CSS rotateY + backface-visibility:hidden does the cover-opening reveal;
   flipbook.js then builds the book inside #flipbookViewer, or main.js
   shows #flipbookFallback if that import/render chain rejects. <noscript>
   in the fragment neutralizes the cover and shows the fallback directly,
   since the cover button can't do anything without JS. */
.flipbook{position:relative; min-height:70vh; border-radius:var(--radius-lg); perspective:2400px;}
.flipbook-viewer{position:relative; width:100%; height:100%; min-height:70vh; border-radius:var(--radius-lg); overflow:hidden; border:1px solid var(--border);}
.flipbook-fallback{position:relative; width:100%; height:100%; min-height:70vh; border-radius:var(--radius-lg); overflow:hidden; border:1px solid var(--border);}
.flipbook-fallback object{width:100%; height:100%; min-height:70vh; display:block;}
.flipbook-cover{
  position:absolute; inset:0; z-index:2;
  width:100%; border:0; font:inherit; appearance:none;
  cursor:pointer;
  display:flex; align-items:center; justify-content:center; text-align:center;
  border-radius:var(--radius-lg);
  background:linear-gradient(135deg, var(--violet-deep), var(--pink) 65%, var(--coral));
  box-shadow:0 30px 60px -20px rgba(21,12,38,.4);
  transform-origin:left center;
  transform-style:preserve-3d;
  backface-visibility:hidden;
  transition:transform 0.9s cubic-bezier(.4,0,.2,1), box-shadow 0.9s ease;
}
.flipbook-cover-face{max-width:340px; padding:32px; backface-visibility:hidden; display:block;}
.flipbook-cover-face img{margin:0 auto 18px; display:block;}
.flipbook-cover-title{display:block; font-size:26px; font-weight:800; color:#fff; margin-bottom:10px;}
.flipbook-cover-desc{display:block; color:rgba(255,255,255,.88); margin-bottom:22px;}
.flipbook-cover-cta{
  display:inline-block; font-weight:700; font-size:14.5px;
  padding:12px 26px; border-radius:var(--radius-pill);
  background:#fff; color:var(--violet-deep);
}
.flipbook-cover-spine{position:absolute; inset:0 auto 0 0; width:14px; background:linear-gradient(90deg, rgba(0,0,0,.32), transparent); border-radius:var(--radius-lg) 0 0 var(--radius-lg);}
.flipbook-cover:focus-visible{outline:3px solid #fff; outline-offset:-6px;}
.flipbook.is-open .flipbook-cover{transform:rotateY(-100deg); box-shadow:none; pointer-events:none;}
@media (prefers-reduced-motion:reduce){
  .flipbook-cover{transition:none;}
}

/* ---- loading state (assets/js/main.js inserts this while flipbook.js +
   pdf.js load and the first pages render) ---- */
.flipbook-loading{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:14px; min-height:70vh; color:var(--dark-muted); font-weight:600; font-size:14.5px;
  background:var(--dark-bg);
}
.flipbook-spinner{
  width:38px; height:38px; border-radius:50%;
  border:4px solid rgba(255,255,255,.2); border-top-color:#fff;
  animation:flipbookSpin .8s linear infinite;
}
@keyframes flipbookSpin{to{transform:rotate(360deg);}}
@media (prefers-reduced-motion:reduce){
  .flipbook-spinner{animation-duration:1.6s;}
}

/* ---- the book itself (built by assets/js/flipbook.js) ---- */
.flipbook-book{
  width:100%; height:100%; min-height:70vh;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:16px; padding:28px 16px;
  background:var(--dark-bg); color:var(--dark-text);
  outline:none;
}
.flipbook-book:focus-visible{box-shadow:inset 0 0 0 3px var(--violet-deep);}
.flipbook-main{width:100%; max-width:760px; display:flex; align-items:center; gap:12px;}
.flipbook-nav{
  flex:none; width:42px; height:42px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:#fff; color:var(--violet-deep); border:0; cursor:pointer;
  box-shadow:0 10px 20px -8px rgba(0,0,0,.35);
}
.flipbook-nav:disabled{opacity:.3; cursor:default;}
.flipbook-nav:focus-visible{outline:3px solid #fff; outline-offset:2px;}
/* .flipbook-stage-wrap is the fixed-size, NEVER-transformed zoom viewport:
   it carries the book-frame chrome (rounded corners, shadow, background)
   AND the overflow:hidden clip that bounds the click-to-zoom feature's
   pan/scale (see assets/js/flipbook.js's "click-to-zoom by zone" block for
   the coordinate math). .flipbook-stage, inside it, is the layer that
   actually receives `transform:translate(...) scale(...)` — sized to fill
   the wrap at 100%/100%, so scaling/panning IT never disturbs the wrap's
   own box (which is what the reader perceives as "the frame"). */
.flipbook-stage-wrap{
  position:relative; flex:1 1 auto; min-width:0; perspective:2400px;
  border-radius:var(--radius-md); overflow:hidden;
  background:#fff;
  box-shadow:0 30px 70px -25px rgba(0,0,0,.55);
}
.flipbook-stage{
  position:relative; width:100%; height:100%; margin:0 auto;
  transform-origin:0 0;
  transition:transform .3s ease;
  cursor:zoom-in;
}
.flipbook-book.is-reduced .flipbook-stage{transition:none;}
/* Zoomed: dragging pans (grab/grabbing); a plain click zooms back out (see
   the JS's click-vs-drag threshold). Edge-click page-turning is disabled
   while zoomed (pointer-events:none lets the drag/click logic own the
   whole stage instead) — prev/next <button>s and ArrowLeft/ArrowRight
   stay live since neither lives inside .flipbook-hit or .flipbook-stage. */
.flipbook-book.is-zoom-active .flipbook-stage{cursor:grab;}
.flipbook-book.is-zoom-active.is-panning .flipbook-stage{cursor:grabbing;}
.flipbook-book.is-zoom-active .flipbook-hit{pointer-events:none;}
.flipbook-hit{position:absolute; top:0; bottom:0; width:12%; z-index:50; cursor:pointer; background:transparent; border:0; padding:0;}
.flipbook-hit--left{left:0;}
.flipbook-hit--right{right:0;}
.flipbook-leaf{
  position:absolute; top:0; left:50%; width:50%; height:100%;
  transform-style:preserve-3d;
  transform-origin:left center;
  transition:transform .9s cubic-bezier(.4,0,.2,1);
}
.flipbook-book.is-reduced .flipbook-leaf{transition:none;}
.flipbook-leaf-face{
  position:absolute; inset:0;
  backface-visibility:hidden; -webkit-backface-visibility:hidden;
  background:var(--card);
  cursor:zoom-in;
}
.flipbook-book.is-zoom-active .flipbook-leaf-face{cursor:grab;}
.flipbook-book.is-zoom-active.is-panning .flipbook-leaf-face{cursor:grabbing;}
.flipbook-leaf-face--back{transform:rotateY(180deg);}
.flipbook-leaf-face canvas{display:block; width:100%; height:100%; object-fit:contain;}
/* Hi-res overlay canvas (see makeFace()/ensureZoomSource() in flipbook.js):
   hidden until click-to-zoom paints and reveals it for THIS face, at which
   point it covers the normal (lower-res) canvas so scale(2.5) on the
   stage never magnifies an under-resolved raster. */
.flipbook-leaf-face-zoom{position:absolute; inset:0; display:none; width:100%; height:100%; object-fit:contain;}
.flipbook-leaf-face.is-zoom-source canvas:first-child{display:none;}
.flipbook-leaf-face.is-zoom-source .flipbook-leaf-face-zoom{display:block;}
.flipbook-toolbar{display:flex; align-items:center; justify-content:center; gap:16px;}
.flipbook-counter{font-weight:700; font-size:14px; color:var(--dark-muted); min-width:64px; text-align:center;}
.flipbook-fs{
  width:36px; height:36px; border-radius:50%; border:1px solid rgba(255,255,255,.3);
  background:transparent; color:var(--dark-text); cursor:pointer;
  display:flex; align-items:center; justify-content:center;
}
.flipbook-fs:focus-visible{outline:3px solid #fff; outline-offset:2px;}
.flipbook-book:fullscreen{background:#15122a; padding:40px 24px;}
@media (max-width:820px){
  .flipbook-leaf{width:100%; left:0;}
  .flipbook-main{gap:6px;}
  .flipbook-nav{width:36px; height:36px;}
}

/* ---------- Forms ---------- */
.field{margin-bottom:20px; text-align:left;}
.field label{
  display:block;
  font-weight:700;
  font-size:14.5px;
  color:var(--ink-soft);
  margin-bottom:7px;
}
.field .hint{display:block; font-size:13.5px; color:var(--muted); margin-top:6px;}
.field input,
.field textarea,
.field select{
  width:100%;
  font-family:inherit;
  font-size:16px;
  padding:12px 16px;
  border-radius:14px;
  border:1px solid var(--border);
  background:#fff;
  color:var(--ink);
}
.field textarea{min-height:140px; resize:vertical;}
.field input:focus-visible,
.field textarea:focus-visible,
.field select:focus-visible{outline:3px solid var(--violet-deep); outline-offset:2px;}
.field.has-error input,
.field.has-error textarea,
.field.has-error select{border-color:var(--error);}
.field-error{
  display:block;
  color:var(--error);
  font-size:14px;
  font-weight:600;
  margin-top:7px;
}
.checkbox-field{display:flex; align-items:flex-start; gap:10px;}
.checkbox-field input{width:auto; flex:none; margin-top:3px;}
.checkbox-field label{margin-bottom:0; font-weight:500;}

/* ---------- Lead-capture form card (round 11, capability 5) ----------
   Card-chrome wrapper for a short, embedded qualify-and-route lead form
   (company/name/email/phone) — distinct from the plain in-column
   /contatti/ form (content/contatti.html), which has no card chrome of
   its own because it already sits inside .split-text. Reuses .field /
   .checkbox-field / .btn as-is; posts to the SAME /contact.php as the
   main contact form. Fields azienda/telefono are OPTIONAL server-side
   (contact.php: see field() calls for 'azienda'/'telefono') so this
   component and the plain contatti.html form both work unmodified against
   one endpoint. A fixed "oggetto" hidden input tags the inbox subject with
   which page/CTA the lead came from — set its value per instance.
   Markup:
   <div class="lead-form">
     <div class="lead-form-head"><h3>Richiedi informazioni</h3>
       <p>Lascia i tuoi contatti, ti ricontattiamo entro pochi giorni lavorativi.</p></div>
     <div id="formNotice" class="field-error" role="alert" style="display:none;"></div>
     <form class="lead-form-fields" action="/contact.php" method="post" novalidate>
       <div class="field"><label for="lf-azienda">Azienda</label>
         <input type="text" id="lf-azienda" name="azienda" autocomplete="organization" required></div>
       <div class="field"><label for="lf-nome">Nome e cognome</label>
         <input type="text" id="lf-nome" name="nome" autocomplete="name" required></div>
       <div class="field"><label for="lf-email">Email</label>
         <input type="email" id="lf-email" name="email" autocomplete="email" required></div>
       <div class="field"><label for="lf-telefono">Telefono</label>
         <input type="tel" id="lf-telefono" name="telefono" autocomplete="tel"></div>
       <input type="hidden" name="oggetto" value="Richiesta AND Point">
       <input type="hidden" name="messaggio" value="Richiesta informazioni inviata dal modulo lead.">
       <!-- contact.php still requires nome/email/messaggio/consenso
            non-empty (unchanged validation — round 11 only ADDED optional
            azienda/telefono, see contact.php's doc comment): the hidden
            'messaggio' above satisfies that with zero server-side changes.
            honeypot 'sito' + hidden 't' timestamp + 'consenso' checkbox:
            copy verbatim from content/contatti.html's form, incl. its
            inline Date.now()/error-notice script -- contact.php rejects
            any submission missing them. -->
       <button type="submit" class="btn btn-primary">Invia richiesta</button>
     </form>
   </div> */
.lead-form{
  background:var(--card);
  border:1px solid var(--border);
  border-radius:var(--radius-xl);
  padding:36px 32px;
  box-shadow:0 30px 60px -30px rgba(124,58,237,0.28);
}
.lead-form-head{margin-bottom:22px;}
.lead-form-head h3{margin-bottom:8px;}
.lead-form-head p{color:var(--muted); font-size:15px; margin:0;}
.lead-form-fields .field{margin-bottom:16px;}
.lead-form-fields > .btn{width:100%; margin-top:4px;}
@media (min-width:640px){
  .lead-form-fields{display:grid; grid-template-columns:1fr 1fr; gap:0 16px;}
  .lead-form-fields > .btn{grid-column:1 / -1;}
}

/* ---------- Map facade card, click-to-load (round 11 cap. 6; upgraded to
   a real embed on user gesture) ----------
   Styled "locality card" (CSS-only faux-map visual: grid lines + radial
   glow + a bobbing pin) behind a "Carica la mappa" click-to-load button,
   so the Google Maps embed only ever entered the DOM on a user gesture.
   Owner override (map-always-on directive): the map must be visible
   immediately, no load button, no click required. The facade is gone —
   assets/js/main.js no longer has a click handler for it — and
   content/contatti.html now emits a real, static
   <iframe title="Mappa sede AND Italia" loading="lazy"
     src="https://www.google.com/maps?q=<address>&output=embed"
     allow="fullscreen" referrerpolicy="no-referrer-when-downgrade">
   directly. loading="lazy" keeps it from blocking the page's first paint
   even though it's static markup. tools/check.py's EXTERNAL_ALLOWLIST
   carries a matching "google.com" entry (owner-approved static embed) so
   this legitimately-external <iframe src> still passes. The "Apri in
   Google Maps" link-out stays as the secondary, no-JS-required option.
   Markup:
   <div class="map-card">
     <div class="map-card-visual">
       <iframe title="Mappa sede AND Italia" loading="lazy"
         src="https://www.google.com/maps?q=...&output=embed"
         allow="fullscreen" referrerpolicy="no-referrer-when-downgrade"></iframe>
     </div>
     <div class="map-card-body">
       <h3>Dove ci trovi</h3>
       <p>AND ITALIA SRL<br>Corso Massimo d'Azeglio 60 – Torino</p>
       <a class="btn btn-secondary" href="https://www.google.com/maps/search/?api=1&amp;query=45.0487464,7.6798599" target="_blank" rel="noopener">Apri in Google Maps</a>
     </div>
   </div> */
.map-card{
  position:relative;
  border-radius:var(--radius-lg);
  overflow:hidden;
  background:var(--dark-bg);
  color:var(--dark-text);
}
.map-card-visual{
  position:relative;
  height:380px;
}
.map-card-visual iframe{display:block; width:100%; height:100%; border:0;}
.map-card-body{padding:28px 26px 30px;}
.map-card-body h3{color:#fff; margin-bottom:10px;}
.map-card-body p{color:var(--dark-muted); margin-bottom:20px;}
.map-card-body .btn{width:100%;}

/* ---------- CTA finale (dark) ---------- */
.cta-section{
  position:relative;
  background:var(--dark-bg);
  color:var(--dark-text);
  overflow:hidden;
  padding:128px 0 108px;
  text-align:center;
}
.cta-section::before{
  content:"";
  position:absolute; inset:0;
  background-image:url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='300'%20height='300'%3E%3Cfilter%20id='n2'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.85'%20numOctaves='3'%20stitchTiles='stitch'/%3E%3C/filter%3E%3Crect%20width='100%25'%20height='100%25'%20filter='url(%23n2)'/%3E%3C/svg%3E");
  opacity:0.05;
  mix-blend-mode:overlay;
  pointer-events:none;
}
.cta-section .blob{opacity:0.5;}
.cta-section .cb1{width:380px;height:380px; top:-160px; right:-100px; background:radial-gradient(circle, var(--violet), transparent 70%);}
.cta-section .cb2{width:300px;height:300px; bottom:-140px; left:-80px; background:radial-gradient(circle, var(--pink), transparent 70%);}
.cta-inner{position:relative; z-index:1;}
.cta-section h2{
  font-size:clamp(2.2rem,5vw,3.6rem);
  letter-spacing:-0.03em;
  color:#fff;
  margin-bottom:20px;
}
.cta-section p{color:var(--dark-muted); max-width:52ch; margin:0 auto 34px; font-size:17.5px;}

/* ---------- Footer ---------- */
footer.site{
  position:relative;
  background:var(--dark-bg);
  color:var(--dark-text);
  padding:56px 0 32px;
  border-top:1px solid var(--dark-border);
}
.footer-top{
  display:flex;
  justify-content:space-between;
  gap:40px;
  flex-wrap:wrap;
  margin-bottom:40px;
}
.footer-brand .logo{margin-bottom:16px; flex-shrink:0;}
.footer-brand .logo-img{height:34px; width:auto; max-width:none;}
.footer-brand p{color:var(--dark-muted); font-size:14.5px; max-width:32ch;}
.footer-links{display:flex; gap:56px; flex-wrap:wrap;}
.footer-col h3{font-size:14px; text-transform:uppercase; letter-spacing:0.06em; color:var(--dark-muted); margin-bottom:14px;}
.footer-col a{
  display:block;
  text-decoration:none;
  color:var(--dark-text);
  font-weight:600;
  font-size:14.5px;
  margin-bottom:10px;
}
.footer-col a:hover{color:var(--pink);}
.footer-bottom{
  display:flex;
  justify-content:space-between;
  align-items:center;
  flex-wrap:wrap;
  gap:16px;
  padding-top:28px;
  border-top:1px solid var(--dark-border);
}
.footer-bottom p{margin:0; color:var(--dark-muted); font-size:13.5px;}
.socials{display:flex; gap:12px;}
.socials a{
  width:38px;height:38px;
  border-radius:50%;
  border:1px solid var(--dark-border);
  display:flex;align-items:center;justify-content:center;
  color:var(--dark-muted);
  transition:background 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
.socials a:hover{background:linear-gradient(135deg, var(--violet), var(--pink)); color:#fff; border-color:transparent; transform:translateY(-2px);}

/* ---------- Consent banner ---------- */
.consent-banner{
  position:fixed;
  left:16px; right:16px; bottom:16px;
  max-width:640px;
  margin:0 auto;
  z-index:400;
  background:var(--dark-bg);
  color:var(--dark-text);
  border-radius:var(--radius-lg);
  border:1px solid var(--dark-border);
  box-shadow:0 30px 60px -20px rgba(21,12,38,0.5);
  padding:22px 24px;
}
.consent-banner[hidden]{display:none;}
.consent-banner p{font-size:14.5px; color:var(--dark-muted); margin-bottom:16px;}
.consent-banner p a{color:var(--dark-text); text-decoration:underline;}
.consent-actions{display:flex; gap:12px; flex-wrap:wrap;}
.consent-actions .btn{padding:10px 22px; font-size:14.5px;}
/* a11y fix (task-7-a11y I-3): .btn-outline's default color:var(--violet-deep)
   on transparent computes only 3.45:1 against the banner's --dark-bg
   (#150c26) at this 14.5px normal-text size — fails the 4.5:1 AA floor
   (only "Rifiuta" uses .btn-outline here; "Accetta" is .btn-primary,
   unaffected). Reuse the .btn-secondary treatment already proven to pass
   on dark surfaces elsewhere on the site (#fff text, translucent
   background) instead of a one-off color. */
.consent-banner .btn-outline{
  background:rgba(255,255,255,0.08);
  color:#fff;
  border-color:rgba(255,255,255,0.55);
}
.consent-banner .btn-outline:hover{background:rgba(255,255,255,0.18); border-color:#fff; color:#fff;}

/* ---------- Responsive ---------- */
@media (max-width: 640px){
  section{padding:68px 0;}
  .hero{padding:120px 0 76px; background-position:32% 42%;}
  .hero::after{background:linear-gradient(180deg, rgba(5,10,30,.90) 0%, rgba(5,10,30,.80) 42%, rgba(5,10,30,.55) 72%, rgba(5,10,30,.32) 100%);}
  .cta-section{padding:80px 0 72px;}
  .footer-links{gap:32px;}
}

/* ---------- Reduced motion: disable all animation/transition ---------- */
@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto;}
  *,*::before,*::after{
    animation-duration:0.001ms !important;
    animation-iteration-count:1 !important;
    transition-duration:0.001ms !important;
    scroll-behavior:auto !important;
  }
  .reveal{opacity:1; transform:none;}
  /* .reveal.phone-frame's own opacity/transform (higher specificity than
     the plain .reveal rule above) must be neutralized explicitly here too,
     same instant-visible outcome as every other .reveal element. */
  .reveal.phone-frame{opacity:1; transform:none;}
}
