:root {
  /* --- rampa blu: campionata dal master --------------------------------
     I gradini escono uno a uno da gradient.-2.png, letti alla luminosità
     che ogni token portava già: cambia la tinta, non la chiarezza, quindi
     nessun contrasto si muove. Scegliere un gradino, non inventare una
     tinta.

     Il master non ha una tinta sola: ha un arco. 196-200° in HSL sulle luci,
     213-217° sulle ombre, e la rotazione va con la luminosità. Le due rampe
     di prima quell'arco l'avevano schiacciato su un angolo unico, ognuna
     prendendo un capo e stendendolo su tutto. La 196° teneva la tinta delle
     luci e mandava le ombre nel teal — è il bagliore che si vedeva. La 213°
     ha preso quella delle ombre e ha spostato l'errore in cima: da --blue-600
     in su ogni gradino stava 7-16° oltre il master, e --mist-200 era il caso
     peggiore. Qui ogni gradino porta la tinta che il master ha alla sua
     altezza, e nient'altro.

     Dal master viene anche il croma, che nella metà scura era del 15-25%
     sopra il vero: --blue-700 stava a 0.051 contro 0.043.

     Dieci gradini: --blue-850 e --blue-600 servono al gradiente, che con
     otto lasciava un vuoto di trenta L* fra --blue-800 e --blue-500 — era
     lì che si vedeva la banda. */
  --blue-950: #04070c;
  --blue-950-rgb: 4, 7, 12;
  --blue-900: #080e15;
  --blue-850: #0e1720;
  --blue-800: #172532;
  --blue-700: #283d50;
  /* Accento sul chiaro. Non è --blue-500: quel gradino cade a L* 63 e su
     bianco tiene 2.88:1, illeggibile. Questo fa 6.13:1 e chiude la falla del
     4.44:1 di due rampe fa, che il commento stesso dava per insufficiente
     sul testo corrente. */
  --blue-600: #49657a;
  --blue-500: #819cad;
  --blue-400: #abc2ce;
  --blue-300: #cbdce3;
  --blue-200: #dbe7ed;

  /* Foschia: i fondi chiari. I primi tre stanno ancora dentro il master e
     sono campionati come tutto il resto. --mist-100 no: a okL 0.966 sta sopra
     il punto più chiaro del master (0.933), perché non è una fermata del
     gradiente ma il fondo di pagina, e gli serve stare più in alto del
     chiaro da cui l'hero riparte. La sua tinta è l'arco del master
     prolungato di poco oltre il capo; a quel croma la rotazione non arriva a
     un livello su otto bit, e infatti il valore si muove di un digit. */
  --mist-400: #d4e1e9;
  --mist-300: #d5e3ea;
  --mist-200: #dfeaee;
  --mist-100: #eef5f9;

  /* --- turchese: adesso è la marca ------------------------------------
     Sta sui dettagli che segnalano uno stato — risposta scelta, tappa
     conclusa, esito positivo. Finché l'azione era blu, questo era l'unica
     tinta fuori dalla rampa e la regola era di non usarlo come accento
     generale: due sistemi accostati si sarebbero letti come un errore.

     Adesso l'azione è ciano e quella regola si rovescia. Il vecchio #39cdf6
     stava a 221° con croma 0.132; --brand-2 sta a 214° con 0.104: sette gradi
     e un filo di croma di distanza, cioè due ciani quasi uguali accanto. È
     esattamente il difetto che il commento di prima descriveva. Quindi il
     token resta — venti punti del sito lo chiamano — ma punta alla marca.

     Il contrasto non si muove: sui fondi scuri dove vive faceva 10.80:1 e
     8.04:1, adesso fa 10.93:1 e 8.13:1. */
  --teal-500: var(--brand-2);

  /* --- neutra e azione: le rampe del sito -----------------------------
     Dalla proposta ColorBox, adottate per tutto il sito il 4 settembre 2026:
     fondi, testi, fili, pulsanti, schede. La blu qui sopra non è sparita —
     resta la rampa dei gradienti, perché è campionata dal master e la
     neutra non scende fino al suo nero (L* 6 contro 2): l'hero, la FAQ e la
     chiusura, che continuano quel gradiente in tinta piatta, restano sulla
     blu. Gli indici sono quelli dello strumento, dal chiaro allo scuro, come
     per le rampe di stato: non dicono la chiarezza, la dice il gradino. */
  --steel-0: #f2f5f6;
  --steel-1: #e2e9ec;
  --steel-2: #cfdbdf;
  --steel-3: #baccd3;
  --steel-4: #a2bcc7;
  --steel-5: #89aab9;
  --steel-6: #7198a9;
  --steel-7: #598397;
  --steel-8: #426d83;
  --steel-9: #2e576d;
  --steel-10: #1e4053;
  --steel-11: #112938;
  --steel-12: #07141c;

  /* I nomi di prima restano come alias: --steel-* è il nome che la rampa
     porta nell'app, --neutral-* quello con cui il sito la chiama in 112
     punti. Un valore solo, due nomi, nessun componente da riscrivere. */
  --neutral-0: var(--steel-0);
  --neutral-1: var(--steel-1);
  --neutral-2: var(--steel-2);
  --neutral-3: var(--steel-3);
  --neutral-4: var(--steel-4);
  --neutral-5: var(--steel-5);
  --neutral-6: var(--steel-6);
  --neutral-7: var(--steel-7);
  --neutral-8: var(--steel-8);
  --neutral-9: var(--steel-9);
  --neutral-10: var(--steel-10);
  --neutral-11: var(--steel-11);
  --neutral-12: var(--steel-12);

  /* --- azione: il ciano ------------------------------------------------
     La rampa dell'azione non è più blu. Il ciano stacca dalla steel, che il
     blu di prima non faceva: erano due blu accanto, e l'accento non si
     leggeva come tale.

     Segna solo ciò che si può premere o che ha il fuoco. Il decorativo —
     icone, occhielli, marcatori, avatar — sta sulla steel.

     I gradini 3 e 4, il ciano vivo, tengono solo sul bianco: su --steel-1 o
     più scuro cadono sotto il 3:1. Su superficie tinta si passa da
     --accent. */
  --brand-0: #e3f7fb;
  --brand-1: #a8e2ef;
  --brand-2: #61cde5;
  --brand-3: #00b3d5;
  --brand-4: #0097b8;
  --brand-5: #007ea0;
  --brand-6: #006485;
  --brand-7: #004f6f;
  --brand-8: #003a58;
  --brand-9: #002943;

  /* --- inchiostro: sei gradini, solo per il testo ----------------------
     Rampa nuova. La steel fa le superfici, i bordi e i separatori; il testo
     esce di qui e da nessun'altra parte. Sei gradini dal velato al pieno:
     0-2 servono allo scuro, 3-5 al chiaro. */
  --ink-0: #9da3a9;
  --ink-1: #868c92;
  --ink-2: #6f747b;
  --ink-3: #565b62;
  --ink-4: #3f4349;
  --ink-5: #0d131b;

  --ink: var(--ink-5);
  --ink-deep: var(--ink-5);
  --paper: var(--neutral-0);

  /* Canali per le trasparenze. rgba() non legge un token esadecimale e
     color-mix() non vale il rischio (vedi .shimmer): i veli d'inchiostro, le
     luci sullo scuro e il vetro si scrivono rgba(var(--ink-rgb), α), così il
     colore viene dalla rampa e nel componente resta solo l'opacità. Sono i
     canali di neutral-12, neutral-11, neutral-6, neutral-0 e del turchese,
     scritti per esteso perché è l'unico modo di passarli a rgba(). */
  --ink-rgb: 7, 20, 28;
  --night-rgb: 17, 41, 56;
  --mid-rgb: 113, 152, 169;
  --paper-rgb: 242, 245, 246;
  --surface-rgb: 255, 255, 255;
  --teal-rgb: 97, 205, 229;

  /* --- stati: tre rampe dalla proposta ColorBox ------------------------
     Adottate il 4 settembre 2026, e solo loro: della stessa proposta non è
     entrata la portante — meno profonda della blu, si ferma a L* 6 dove
     la blu arriva a 2, e ai gradienti non serve — e nemmeno l'azione.
     Gli indici sono quelli dello strumento, 0-9 dal chiaro allo scuro, e non
     dicono la chiarezza: --warning-3 sta a L* 82 dove --danger-3 sta a 67.
     Per questo nei componenti si passa dai semantici (--ok, --warn, --error
     e le loro varianti), mai dal gradino. */
  --success-0: #d9fffc;
  --success-1: #76e9e1;
  --success-2: #24d5c9;
  --success-3: #0dbfb1;
  --success-4: #00a99b;
  --success-5: #009487;
  --success-6: #007e72;
  --success-7: #00675d;
  --success-8: #004f47;
  --success-9: #003630;

  --warning-0: #fff3d3;
  --warning-1: #ffe4a6;
  --warning-2: #ffd17b;
  --warning-3: #ffc056;
  --warning-4: #fea33a;
  --warning-5: #fd8824;
  --warning-6: #f96f14;
  --warning-7: #e65509;
  --warning-8: #c83e03;
  --warning-9: #b02f00;

  --danger-0: #fff1f2;
  --danger-1: #ffc8cc;
  --danger-2: #ff9ea6;
  --danger-3: #ff7785;
  --danger-4: #ff5969;
  --danger-5: #eb3856;
  --danger-6: #d32149;
  --danger-7: #b71140;
  --danger-8: #980837;
  --danger-9: #76022d;

  /* Corallo dei grafici: campiture larghe, mai testo. Non è il rosso di
     segnalazione — quello è --error, più sotto. */
  --bad: #ff5f56;

  /* --- gradienti di marca ---------------------------------------------
     La verticale è il fondo principale: scende dal nero al chiaro passando
     per tutta la rampa, e le sue fermate sono i token qui sopra, non tinte
     a parte. Le altre due sono la foschia chiara e l'alone sfocato.

     Dal master vengono i colori, non le fermate: quelle stanno dove le porta
     la luminosità. Il perché è scritto sulla rovesciata, qui sotto. */
  --grad-sky: linear-gradient(
    180deg,
    var(--blue-950) 0%,
    var(--blue-900) 2%,
    var(--blue-850) 4.5%,
    var(--blue-800) 9.6%,
    var(--blue-700) 17.3%,
    var(--blue-600) 29.5%,
    var(--blue-500) 45.6%,
    var(--blue-400) 55.8%,
    var(--blue-300) 62.8%,
    var(--blue-200) 66.1%,
    var(--mist-100) 70%
  );
  /* La verticale rovesciata: apre chiara e chiude sul nero. È il fondo
     dell'hero della home. La discesa è compressa in alto — sotto il 30% il
     fondo è già scuro, perché è lì che comincia il testo bianco.

     Le fermate non sono a intervalli pari: ognuna sta dove la porta la sua
     luminosità, così la discesa perde la stessa quantità di L* per pixel dal
     principio alla fine.

     Questa regola *non* è la curva del master, e la differenza è voluta. Il
     master lineare non è: brucia quasi metà della sua gamma in un quarto solo
     e poi tira una coda scura lunga. Ricalcarlo si è provato, e su una
     superficie di questa misura a otto bit per canale non regge: i tratti
     lenti della sua curva allargano le bande da 4 a 9 pixel e le bande
     visibili passano da 2 a 7. Il master quel problema non ce l'ha perché è
     un'immagine e si porta dietro la propria grana; un gradiente dichiarato
     in CSS ha solo i livelli che gli dà il canale. Quindi dal master vengono
     i colori — quelli sì, uno a uno — e la ripartizione resta quella che non
     fa scalini.

     Nemmeno ripartire per numero di livelli a 8 bit conviene, che pure
     porterebbe la banda peggiore a 3 pixel: sposta la discesa più in basso e
     il titolo, sulle finestre corte, scende a 2.64:1 — sotto il 3:1 dei testi
     grandi. La ripartizione per luminosità è il punto in cui le due cose
     stanno insieme: banda peggiore 4 pixel, titolo da 3.03:1 a 4.92:1.

     La discesa chiude al 70%, non al 50: compressa in cima soffocava il
     chiaro. Oltre non si può allungare — il titolo comincia verso il 36%
     dell'altezza e lì il bianco tiene 4.92:1; più giù scenderebbe sotto il
     3:1 dei testi grandi.

     Questa è la versione di base, in sRGB. Più sotto un `@supports` la
     ridichiara in oklab dove il browser lo consente. Non si può mettere
     `in oklab` direttamente qui: se il browser non lo capisce il token resta
     invalido *al momento dell'uso*, e l'hero si ritroverebbe senza fondo —
     non con un fondo peggiore. */
  --grad-sky-invert: linear-gradient(
    180deg,
    var(--mist-100) 0%,
    var(--blue-200) 3.9%,
    var(--blue-300) 7.2%,
    var(--blue-400) 14.2%,
    var(--blue-500) 24.4%,
    var(--blue-600) 40.5%,
    var(--blue-700) 52.7%,
    var(--blue-800) 60.4%,
    var(--blue-850) 65.5%,
    var(--blue-900) 68%,
    var(--blue-950) 70%,
    var(--blue-950) 100%
  );

  /* Grana. Sta in un token perché serve identica in due posti — sull'hero e
     sul blocco che lo segue — e due copie che divergono lascerebbero una
     cucitura visibile al confine. Serve anche a un secondo scopo: il rumore
     spezza le bande che un gradiente lungo produce a 8 bit per canale. */
  /* Il rumore non è decorazione: è il dither che spezza le bande di un
     gradiente a 8 bit. Troppo basso e smette di farlo. Sul chiaro non entra
     comunque — a tenerlo fuori è la maschera su .hero::after, non questo
     valore — quindi qui si può dargli corpo senza sporcare il bianco. */
  --grain-opacity: 0.3;
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
  /* Foco dal basso, alla maniera di Nothing. La differenza con --grad-sky
     non sono i colori — sono gli stessi — ma la curva: le fermate seguono la
     caduta della luce, che va con l'inverso del quadrato della distanza.
     Il primo quarto della scala consuma il 6% del raggio invece del 24%, così
     il nucleo resta acceso e la discesa si allunga verso il bordo. È quello a
     leggersi come luce invece che come sfumatura.

     Attenzione alla coda: da --blue-900 a --blue-950 restano trentasei
     scalini interi da spendere su quasi cinquecento pixel, cioè bande da
     tredici. In CSS quella coda si vede sempre; per questo Nothing la
     immagine se la cuoce invece di dichiararla. Va usata con `--grain-lift`
     sopra, che somma invece di moltiplicare e quindi sul quasi-nero esiste
     ancora. */
  --grad-glow: radial-gradient(
    125% 78% at 50% 104%,
    var(--mist-100) 0%,
    var(--blue-200) 1.6%,
    var(--blue-300) 2.6%,
    var(--blue-400) 4.4%,
    var(--blue-500) 8.1%,
    var(--blue-600) 16.4%,
    var(--blue-700) 24.6%,
    var(--blue-800) 32.4%,
    var(--blue-850) 39.2%,
    var(--blue-900) 44.6%,
    var(--blue-950) 100%
  );

  /* La discesa di /compliance-intelligence: dal nero al bianco pieno. È la
     terza faccia della stessa rampa — --grad-sky scende sulla foschia,
     --grad-sky-invert sale dal chiaro al nero, questa chiude su #ffffff e non
     su --mist-100. È la ragione per cui la pagina si legge più bianca delle
     altre: sotto la rampa non c'è velatura, c'è bianco.

     La discesa è lineare in L* fino a L* 86 — stessa uniformità di --grad-sky,
     stessa quantità di chiarezza persa a ogni pixel — e da lì entra nel bianco
     con una coda quadratica invece che di colpo.

     La coda serve perché la rampa finisce dentro una pagina bianca. Arrivando
     a velocità costante, l'ultimo pixel di sfumatura sta a 0.23 L* dal fondo e
     il successivo è fondo: non c'è gradino misurabile, ma la rampa si stacca
     lo stesso e si legge come una fascia appoggiata sopra invece che come una
     luce che si spegne. Con la coda l'ultimo tratto scende a 0.056 L* per
     pixel, un quarto del ritmo del corpo, e il punto in cui la rampa smette
     non si trova più.

     Le fermate non sono quindi proporzionali a L* fino in fondo, ma lo sono
     dove conta: nel corpo. Comprimere invece il corpo — provato, --blue-600
     spostato al 62%, all'80%, all'84% — si vede eccome: la discesa perde
     l'uniformità e il fondo passa quasi tutta la sua altezza nei gradini
     centrali, i meno carichi della scala, e diventa slavato.

     (Il grano non c'entra col colore: spegnendolo il croma si muove di due
     punti su cento e la luminosità di uno. Misurato.) */

  /* Rovesciata: il bianco sta in cima e la rampa scende fino al nero, che e'
     esattamente il fondo della prima scena del palco. Stesse fermate e stessi
     gradini del master — cambia il verso, non la tavola. */
  --grad-ci: linear-gradient(
    0deg,
    var(--blue-950) 0.0%,
    var(--blue-900) 1.66%,
    var(--blue-850) 4.57%,
    var(--blue-800) 10.14%,
    var(--blue-700) 19.2%,
    var(--blue-600) 32.92%,
    var(--blue-500) 50.8%,
    var(--blue-400) 62.6%,
    var(--blue-300) 70.76%,
    var(--blue-200) 75.81%,
    var(--mist-100) 84.17%,
    #ffffff 100%
  );

  --grad-mist: linear-gradient(135deg, var(--mist-100) 0%, var(--mist-200) 55%, var(--mist-300) 100%);
  --grad-haze:
    radial-gradient(80% 60% at 26% 0%, rgba(203, 220, 227, 0.85), transparent 64%),
    linear-gradient(170deg, var(--blue-700) 0%, var(--blue-800) 45%, var(--blue-900) 100%);

  /* --- token semantici: il tema chiaro è quello di base ---------------
     Fondo, testo e accento vengono dalla neutra e dall'azione. Il fondo di
     pagina è il primo gradino della neutra e non il bianco: il bianco è la
     superficie sollevata, ed è il salto fra i due a far leggere una scheda
     come scheda. Il testo secondario e il terziario sono gradini pieni, non
     più inchiostro velato: sul fondo tengono 10.10:1 e 5.17:1. */
  --bg: var(--steel-0);
  --bg-alt: var(--steel-0);
  --fg: var(--ink-5);                  /* 17.02:1 sul fondo, 18.65:1 sul bianco */
  --fg-muted: var(--ink-4);            /* 9.08:1 */
  --fg-faint: var(--ink-3);            /* 6.25:1 */
  --line: var(--steel-2);
  --surface: #ffffff;
  --surface-2: rgba(var(--ink-rgb), 0.045);
  --card: var(--steel-1);
  --section-grey: var(--steel-0);
  /* 6.21:1 sul fondo: regge anche sul testo corrente. Dove finisce
     dentro un paragrafo va comunque accompagnato dalla sottolineatura, così
     non è il colore da solo a portare il significato.

     Solo per ciò che si può premere o che ha il fuoco: link, tab attiva,
     bordi a fuoco, casella, segno della FAQ. L'azione porta quattro volte il
     croma della neutra, e messa su un'icona o un occhiello non segnala
     niente: fa solo un blu in più accanto agli altri. Il decorativo — icone,
     occhielli, marcatori, avatar — sta sulla neutra. */
  --accent: var(--brand-6);
  /* --- stati ------------------------------------------------------------
     Tre famiglie, tre ruoli ciascuna: il fondo tenue, il pieno per
     riempimenti e fili, il colore da testo. Il gradino non è lo stesso nelle
     tre. Warning è la rampa più chiara e il suo pieno, --warning-3, su
     bianco fa 1.6:1: è un riempimento su cui si scrive in inchiostro, non un
     inchiostro — il testo salta al gradino 8, che fa 5.1:1. Nelle altre due
     il testo sta un gradino sotto il pieno.

     I rossi stanno sui gradini chiari, non sui cupi: il pieno è il 5
     (3.66:1 sul fondo — basta a fili, bordi e icone, non a un testo) e il
     testo il 6, l'unico gradino chiaro che sul fondo passa il 4.5:1
     (4.69:1). --error non è --bad (#ff5f56): quel corallo serve ai
     grafici, in campiture larghe, e per un bordo non arriva al 3:1. A
     differenza del #c8402f di prima, dentro `.on-dark` --error viene
     ridichiarato: quello restava uguale e sullo scuro scendeva a 3.1:1. */
  --ok-sutil: var(--success-0);
  --ok: var(--success-6);
  --ok-texto: var(--success-7);
  --warn-sutil: var(--warning-0);
  --warn: var(--warning-3);
  --warn-texto: var(--warning-8);
  --error-sutil: var(--danger-0);
  --error: var(--danger-6);
  --error-texto: var(--danger-7);
  --shadow: 0 14px 34px rgba(var(--ink-rgb), 0.12);

  /* Primario in contrasto pieno col fondo, non in blu: chiaro sulle sezioni
     scure, inchiostro su quelle chiare. Sta nei token perché `.on-dark` li
     ribalta da solo, senza regole per componente. L'inchiostro del tasto è
     neutral-11 e non il 12 del testo: alla chiarezza del tasto di prima, che
     era scuro e non nero. */
  --btn-bg: var(--steel-11);
  --btn-bg-hover: var(--steel-12);
  --btn-fg: var(--steel-0);


  /* Plus Jakarta Sans per titoli e wordmark, Inter per il testo. Variable
     font dal link in head: tutti i pesi disponibili (200–800 / 100–900). */
  --font-display: "Plus Jakarta Sans", "Inter", system-ui, sans-serif;
  --font-text: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* --- scala tipografica ---------------------------------------------
     Un gradino è corpo + interlinea + spaziatura, e ogni testo del sito ne
     prende uno: mai un corpo proprio. Se un gradino va ritoccato si ritocca
     qui e cambia ovunque insieme. I corpi grandi sono fluidi fra un telefono
     da 390px e un desktop da 1440px, scritti rem + vw così seguono anche lo
     zoom del testo; fuori da quell'intervallo si fermano ai due capi.

       gradino   390 → 1440px   ruolo
       display   41.6 → 89.6    apertura delle pagine editoriali: home,
                                prodotto, blog, team, manifesto CI
       h1        33.6 → 54.4    titolo delle pagine di servizio e di lettura
                                (get-started, articolo, privacy, 404, design
                                system) e frasi-manifesto della CI
       h2        28 → 44        titolo di sezione, capitolo CI, feature
       h3        24 → 34.4      fascia CTA, titolo di colonna (split del
                                team), scheda alta del bento
       h4        21 → 26        scheda in evidenza, tab e pannello della
                                panoramica, titoletto dentro un articolo
       h5        18.4 → 20      scheda compatta, tappa della roadmap, domanda
                                dei problemi e delle FAQ, catena CI
       h6        17             titoletto di servizio (regole del DS)
       quote     20.8 → 30.4    citazioni e frasi editoriali
       xl        18 → 22        incipit di un articolo: l'unico testo sopra
                                il corpo di lettura che non è un titolo
       lg        17 → 19.2      lead sotto un titolo di pagina o di sezione
                                (anche la descrizione di ogni feature e la
                                nota dell'hero CI); corpo di lettura lunga
                                (articolo, privacy, prosa CI)
       md        16             corpo di interfaccia: è il body
       sm        15             testo secondario: schede, FAQ, nav, bottoni
       xs        13             meta: date, ruoli, etichette, note legali
       2xs       12             occhielli maiuscoli e micro-etichette

     A desktop i titoli salgono di 1.18-1.32 per gradino (display a parte,
     che stacca di 1.65 apposta); a telefono la scala si stringe a 1.08-1.24,
     perché sette gradini fra 16 e 42px più larghi non ci stanno. Sotto i
     380px il display segue la finestra: vedi il @media qui sotto. Il mock
     dell'interfaccia (.mock) e le micro-etichette delle campiture (.ds-cmp)
     restano fuori scala: sono miniature, non testo di pagina.

     Il display arriva al suo massimo a 1700px di finestra e non a 1440: sono
     gli stessi due estremi di prima — 41.6px sotto i 390, 89.6 in cima — ma
     la salita è più lunga, quindi fra i 390 e i 1700 il titolo è più piccolo
     dell'11%, e da 1700 in su non cambia di un pixel.

     La ragione è di composizione, non di gusto. Il claim della home è la riga
     più lunga del sito: 11.8 volte il corpo, cioè 1057px a 89.6. Alla vecchia
     curva quel massimo si raggiungeva già a 1440, dove lo shell misura 1368
     di luce: il titolo ne prendeva il 77% e quel che resta a destra non basta
     più a niente — il portatile dell'apertura si riduceva a 540px a 1440 e
     419 a 1280. Spostando il massimo a 1700 il claim scende a 984px a 1440 e
     915 a 1280, e il portatile risale a 480. Sopra i 1700, dove lo spazio non
     manca, il titolo resta quello di prima: a 2560 ne prende il 54%. */
  --text-display: clamp(2.6rem, 1.706rem + 3.664vw, 5.6rem);
  --text-h1: clamp(2.1rem, 1.617rem + 1.981vw, 3.4rem);
  --text-h2: clamp(1.75rem, 1.379rem + 1.524vw, 2.75rem);
  --text-h3: clamp(1.5rem, 1.259rem + 0.99vw, 2.15rem);
  --text-h4: clamp(1.3125rem, 1.196rem + 0.476vw, 1.625rem);
  --text-h5: clamp(1.15rem, 1.113rem + 0.152vw, 1.25rem);
  --text-h6: 1.0625rem;
  --text-quote: clamp(1.3rem, 1.077rem + 0.914vw, 1.9rem);
  --text-xl: clamp(1.125rem, 1.032rem + 0.381vw, 1.375rem);
  --text-lg: clamp(1.0625rem, 1.011rem + 0.21vw, 1.2rem);
  --text-md: 1rem;
  --text-sm: 0.9375rem;
  --text-xs: 0.8125rem;
  --text-2xs: 0.75rem;
  /* Ruoli con un nome proprio: alias di un gradino, non gradini in più. */
  --text-lead: var(--text-lg);
  --text-eyebrow: var(--text-2xs);

  /* Interlinea e spaziatura di ogni gradino. Più il corpo cresce, più
     l'interlinea si stringe e le lettere si avvicinano: a 90px l'aria
     naturale del font è troppa, a 13px è appena sufficiente. */
  --leading-display: 1.04;
  --leading-h1: 1.08;
  --leading-h2: 1.1;
  --leading-h3: 1.15;
  --leading-h4: 1.2;
  --leading-h5: 1.3;
  --leading-h6: 1.35;
  --leading-quote: 1.35;
  --leading-xl: 1.45;
  --leading-lg: 1.55;
  --leading-body: 1.65;
  --tracking-display: -0.035em;
  --tracking-h1: -0.03em;
  --tracking-h2: -0.03em;
  --tracking-h3: -0.02em;
  --tracking-h4: -0.015em;
  --tracking-h5: -0.012em;
  --tracking-h6: -0.01em;
  --tracking-quote: -0.02em;
  --tracking-xl: -0.015em;

  /* Contenitore largo, margini laterali stretti: il testo lungo resta
     comunque leggibile perché le colonne di prosa hanno un loro max-width. */
  --shell: 1440px;
  --shell-narrow: 780px;
  --radius: 14px;
  --radius-lg: 22px;

  --header-h: 72px;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Il minimo di 2.6rem del display viene da un telefono da 390px. A 320 la
   parola più lunga fra i titoli display — "Approfondimenti", in apertura del
   blog — misura da sola 322px contro i 280 della colonna: non potendo andare
   a capo sforava, e con lei scorreva di lato l'intera pagina. Sotto i 380px il
   display smette di avere un minimo fisso e segue la finestra. */
@media (max-width: 380px) {
  :root { --text-display: 10.4vw; }
}

/* In oklab l'interpolazione avviene in uno spazio percettivamente uniforme:
   i passaggi perdono la gobba scura che sRGB lascia a metà fra due tinte, ed
   è questo — più delle fermate fitte — a togliere lo scalino. */
@supports (background-image: linear-gradient(in oklab, red, blue)) {
  :root {
    --grad-sky-invert: linear-gradient(
      180deg in oklab,
      var(--mist-100) 0%,
      var(--blue-200) 3.9%,
      var(--blue-300) 7.2%,
      var(--blue-400) 14.2%,
      var(--blue-500) 24.4%,
      var(--blue-600) 40.5%,
      var(--blue-700) 52.7%,
      var(--blue-800) 60.4%,
      var(--blue-850) 65.5%,
      var(--blue-900) 68%,
      var(--blue-950) 70%,
      var(--blue-950) 100%
    );
    --grad-ci: linear-gradient(
      0deg in oklab,
      var(--blue-950) 0.0%,
      var(--blue-900) 1.66%,
      var(--blue-850) 4.57%,
      var(--blue-800) 10.14%,
      var(--blue-700) 19.2%,
      var(--blue-600) 32.92%,
      var(--blue-500) 50.8%,
      var(--blue-400) 62.6%,
      var(--blue-300) 70.76%,
      var(--blue-200) 75.81%,
      var(--mist-100) 84.17%,
      #ffffff 100%
    );
  }
}

/* Ribalta i token per un intero blocco. Tutto ciò che sta dentro si adatta
   da solo: non serve una regola per componente. */
.on-dark {
  --bg: var(--steel-11);
  --bg-alt: var(--steel-12);
  /* Sullo scuro il testo non esce da ink. La rampa dell'inchiostro è fatta
     per il fondo chiaro: si ferma a #9da3a9, e a quella chiarezza su un fondo
     scuro non si legge come testo pieno — si legge come testo spento. Qui il
     testo torna al capo chiaro della steel, dove il bianco è bianco.
     Rispetto alla rampa di prima (0/3/5) i tre gradini salgono a 0/2/4: più
     chiari tutti e tre, stessa gerarchia. */
  --fg: var(--steel-0);               /* 13.71:1 sul fondo del blocco */
  --fg-muted: var(--steel-2);         /* 10.62:1 */
  --fg-faint: var(--steel-4);         /* 7.55:1 */
  --line: var(--steel-8);
  /* Superficie e scheda sono gradini pieni, non più bianco velato: la scheda
     sta un gradino sopra la superficie, come sul chiaro sta un gradino
     sotto. Il velo resta solo per --surface-2, che è un passaggio del
     cursore e non una superficie. */
  --surface: var(--steel-10);
  --surface-2: rgba(var(--paper-rgb), 0.06);
  --card: var(--steel-9);
  --section-grey: var(--steel-11);
  /* Sullo scuro l'accento resta blu — il gradino chiaro dell'azione,
     7.67:1 sul fondo. Il turchese non entra qui: è dei dettagli di
     stato. */
  --accent: var(--brand-2);
  --shadow: 0 20px 46px rgba(var(--ink-rgb), 0.4);

  /* Stati sullo scuro. Il pieno sale di due gradini (4 al posto del 6;
     warning resta al 3, che sullo scuro fa 9.6:1) e il testo un gradino
     sopra il pieno: così tutti e nove passano il 4.5:1 sia sul fondo del
     blocco sia sul proprio fondo tenue. Il fondo tenue non può essere un gradino:
     nessuna delle tre rampe scende abbastanza — warning si ferma a L* 40 — e
     un gradino pieno sul fondo scuro sarebbe una toppa, non un velo. È il
     pieno al 14% di opacità, translucido come --surface e --card qui sopra e
     per la stessa ragione: sullo scuro il fondo sotto è spesso un gradiente.
     I canali sono quelli di --success-4, --warning-3 e --danger-4 scritti
     per esteso, perché rgba() non legge un token. */
  --ok-sutil: rgba(0, 169, 155, 0.14);
  --ok: var(--success-4);
  --ok-texto: var(--success-3);
  --warn-sutil: rgba(255, 192, 86, 0.14);
  --warn: var(--warning-3);
  --warn-texto: var(--warning-2);
  --error-sutil: rgba(255, 89, 105, 0.14);
  --error: var(--danger-4);
  --error-texto: var(--danger-3);

  --btn-bg: var(--steel-0);
  --btn-bg-hover: var(--steel-1);
  --btn-fg: var(--steel-12);


  background: var(--bg);
  color: var(--fg);
}


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

/* La barra è agganciata: senza riserva, ogni salto a un'ancora — il link
   «Vai al contenuto» per primo — fermerebbe la pagina con il bersaglio già
   coperto dai 72px di vetro. */
html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 1rem);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* Il footer si appoggia al fondo della viewport anche quando la pagina e'
   corta: senza, sotto la sua fascia restava una striscia di fondo alta quanto
   lo scarto, che si legge come un pezzo di pagina rotto. Il main si prende
   quel che avanza e il footer torna al bordo.
   Sicuro da rendere flex: apertura (position: absolute) e velatura di fondo
   (::before, position: fixed) sono fuori flusso e non diventano voci del
   flex; script e style in fondo al body sono display: none. */
body {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  margin: 0;
  font-family: var(--font-text);
  font-size: var(--text-md);
  line-height: var(--leading-body);
  color: var(--fg);
  background: var(--bg-alt);
  -webkit-font-smoothing: antialiased;
}

body > main { flex: 1 0 auto; }

img { max-width: 100%; display: block; }

a { color: inherit; text-decoration: none; }

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  margin: 0;
  font-weight: 600;
}

/* Il gradino di partenza segue il livello del tag. Una classe di componente
   può spostare un titolo su un altro gradino — l'h2 nella colonna stretta
   dello split prende h3, l'h3 di una scheda compatta prende h5 — ma allora
   porta con sé anche interlinea e spaziatura di quel gradino, mai il corpo
   da solo: sono le tre cose insieme a fare il gradino. */
h1 { font-size: var(--text-h1); line-height: var(--leading-h1); letter-spacing: var(--tracking-h1); }
h2 { font-size: var(--text-h2); line-height: var(--leading-h2); letter-spacing: var(--tracking-h2); }
h3 { font-size: var(--text-h3); line-height: var(--leading-h3); letter-spacing: var(--tracking-h3); }
h4 { font-size: var(--text-h4); line-height: var(--leading-h4); letter-spacing: var(--tracking-h4); }
h5 { font-size: var(--text-h5); line-height: var(--leading-h5); letter-spacing: var(--tracking-h5); }
h6 { font-size: var(--text-h6); line-height: var(--leading-h6); letter-spacing: var(--tracking-h6); }

p { margin: 0; }

/* --- niente parole orfane nel testo corrente -----------------------------
   `text-wrap: pretty` chiede al browser di rifare gli a capo delle ultime
   righe pur di non lasciarci una parola sola. Era già sparso su una parte dei
   testi (bento, citazioni, sottotitoli); qui vale per tutti quelli lunghi,
   perché la regola è la stessa ovunque e tenerla per componente significava
   dimenticarsene — è così che le schede dei problemi si sono ritrovate
   «unica.» e «bilancio.» da sole in fondo.
   I titoli non entrano in questo elenco: lì serve `text-wrap: balance`, che
   pareggia le righe invece di curare solo l'ultima. */
.section__lead,
.feature__desc,
.blog-banner__lead,
.roadmap__body,
.manifesto__item p,
.standards__card p,
.article__excerpt,
.article__body p,
.prose p,
.ds-grad__lead {
  text-wrap: pretty;
}

ul, ol, dl, dd { margin: 0; padding: 0; list-style: none; }

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

.skip-link {
  position: absolute;
  left: 1rem; top: -100px;
  z-index: 200;
  padding: 0.7rem 1.1rem;
  background: var(--btn-bg);
  color: var(--btn-fg);
  border-radius: 8px;
  font-weight: 600;
  transition: top 0.2s var(--ease);
}
.skip-link:focus { top: 1rem; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* --------------------------------------------------------------- layout -- */

.shell {
  width: 100%;
  max-width: var(--shell);
  margin: 0 auto;
  padding: 0 clamp(1.25rem, 3vw, 2.25rem);
}

.shell--narrow { max-width: var(--shell-narrow); }

.section {
  padding: clamp(4rem, 9vw, 7.5rem) 0;
  background: var(--bg);
}

/* Le sezioni dentro il blocco scuro non hanno fondo proprio. */
.hero-block .section { background: transparent; }

/* Prima zona scura: niente grigi. Il testo secondario resta bianco pieno e il
   terziario scende appena, quanto basta a tenere una gerarchia senza perdere
   leggibilità sul fondo. */
.hero-block {
  --fg-muted: var(--neutral-0);
  --fg-faint: rgba(var(--paper-rgb), 0.88);
}

/* Raccordo fra un blocco e il successivo: la sezione che segue sale di
   `--round` e arrotonda i suoi angoli alti, scoprendo il colore di quella
   sopra. È un raccordo di forma, non di colore — quindi non c'è nessuna rampa
   che possa lasciare bande, che è il difetto di qualunque fascia sfumata:
   un `colore -> transparent` è una rampa di *alfa*, e l'alfa a 8 bit su
   parecchi pixel produce sempre gradini.

   Chi sta sopra deve riservare lo spazio con `padding-bottom: var(--round)`,
   altrimenti il raccordo mangia l'ultima riga di contenuto. */
.hero-block + *,
.page-hero + *,
.roadmap + .blog-teaser {
  --round: clamp(1.75rem, 4vw, 3.25rem);

  position: relative;
  z-index: 1;
  margin-top: calc(var(--round) * -1);
  border-top-left-radius: var(--round);
  border-top-right-radius: var(--round);
}

.section--dark { background: var(--bg-alt); }

.section--light { background: var(--bg-alt); }

.section__title {
  font-size: var(--text-h2);
  max-width: 26ch;
}


.section__title--center {
  margin-inline: auto;
  text-align: center;
  max-width: 24ch;
}

.section__lead {
  margin-top: 1.1rem;
  max-width: 62ch;
  font-size: var(--text-lg);
  line-height: var(--leading-lg);
  color: var(--fg-muted);
}

.section__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
  margin-bottom: 2.75rem;
}

/* Intestazione impilata e allineata a sinistra: titolo e riga di
   accompagnamento uno sopra l'altro, al margine della colonna. È la forma
   dell'archivio di riferimento — il centraggio lì non c'è. */
.section__head--stack {
  display: block;
  margin-bottom: clamp(1.75rem, 3vw, 2.5rem);
}

.section__head--stack .section__title {
  max-width: 30ch;
}

.section__head--stack .section__lead {
  margin-top: 0.5rem;
}

/* Variante con un richiamo in fondo a destra, sulla stessa riga del titolo:
   i correlati in fondo a un articolo. */
.section__head--split {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
}

/* Intestazione centrata e impilata: il testo mantiene la sua misura di lettura
   ed è il margine automatico a centrarlo. */
.section__head--center {
  display: block;
  text-align: center;
  margin-bottom: clamp(2.5rem, 5vw, 3.5rem);
}

/* Il titolo si ferma ben prima del bordo del contenitore: a piena larghezza
   un titolo lungo faceva due righe sbilanciate, con una sola parola sotto.
   `balance` distribuisce le parole fra le righe invece di riempirle a turno. */
.section__head--center .section__title {
  max-width: 22ch;
  margin-inline: auto;
  text-wrap: balance;
}

.section__head--center .section__lead { margin-inline: auto; }

/* Titolo su una riga sola. Le misure qui sopra servono ai titoli lunghi, che
   altrimenti si stirerebbero per tutta la pagina; questo ne ha 34 e con 22ch
   andava a capo per un soffio. Deve stare *dopo* quelle regole: la specificità
   è la stessa e a parità vince l'ultima. Il limite si toglie, la riga non si
   forza: su viewport stretti deve poter andare a capo, o esce dallo schermo. */
.blog-teaser .section__head--center .section__title {
  max-width: none;
  text-wrap: nowrap;
}

@media (max-width: 720px) {
  .blog-teaser .section__head--center .section__title { text-wrap: balance; }
}

.eyebrow {
  font-size: var(--text-eyebrow);
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-bottom: 0.9rem;
}

/* ----------------------------------------------------------------- icone -- */

/* Set Lucide, inline via views/partials/icon.ejs. Senza argomenti l'icona
   prende la dimensione del testo accanto a cui sta: è quasi sempre la misura
   giusta, e resta giusta quando il testo cambia corpo.
   Lucide disegna il tratto per 2 su griglia 24; qui sta a 1.75, che regge sia
   sul chiaro sia dentro .on-dark senza assottigliarsi. È un token: si cambia
   qui per tutto il sito, o su un blocco solo ridefinendo --icon-stroke. */
.icon {
  inline-size: var(--icon-size, 1em);
  block-size: var(--icon-size, 1em);
  stroke-width: var(--icon-stroke, 1.75);
  flex: none;
  vertical-align: -0.145em;
}

/* ---------------------------------------------------------------- figure -- */

/* Figure isometriche generate (scripts/build-figures.js), inline via
   views/partials/figure.ejs. Ogni figura porta la propria viewBox: la
   larghezza la decide chi la usa, l'altezza segue.

   Il tratto NON scala con la figura. È la regola che tiene insieme la
   famiglia: la stessa figura a 90px e a 300px deve avere lo stesso filo,
   altrimenti da piccola sembra un disegno e da grande un contorno spesso.
   `non-scaling-stroke` lo misura in pixel di schermo invece che in unità di
   viewBox, ed è l'unico modo di ottenerlo senza generare due file. */
.figure {
  display: block;
  width: 100%;
  height: auto;
}

.figure path {
  vector-effect: non-scaling-stroke;
  stroke-width: var(--figure-stroke, 1.2);
}

/* Il riempimento è il colore del fondo su cui la figura sta, e serve a una
   cosa sola: tappare quello che le passa dietro. Senza, due volumi
   sovrapposti si mostrano le linee a vicenda e la figura smette di avere
   profondità — si legge come un groviglio piatto.
   Il valore lo mette chi usa la figura, perché solo lui sa su che fondo la
   mette; senza, il ripiego è trasparente e si torna al filo di ferro. */
.figure__fill {
  fill: var(--figure-fill, none);
  stroke: none;
}

/* I pieni portano la forma, le guide la spiegano. Il bianco sta a 0.85 di
   opacità e non a pieno: resta `currentColor`, così sul chiaro diventa
   inchiostro da solo, e la trasparenza fa il resto. Scriverlo come
   rgba(255,255,255,.85) darebbe lo stesso identico bianco qui e un fantasma
   invisibile su qualunque pagina chiara. */
/* Bianco pieno, non velato: l'opacità che stava qui le rendeva grigie e le
   figure sembravano spente accanto al testo, che è bianco al 100%. */
.figure__solid { opacity: 1; }

/* --- alone -----------------------------------------------------------------
   Due ombre concentriche per tratto: una stretta, che dà al filo un bordo
   acceso, e una larga e appena accennata, che è quella che si legge come
   luce. Una sola ombra larga fa nebbia, una sola stretta fa contorno: serve
   la coppia.

   L'alone sta sui TRATTI e non sul gruppo. Il gruppo contiene anche la sagoma
   piena che serve all'occlusione: un filtro sul gruppo la includerebbe, e
   invece di illuminare gli spigoli disegnerebbe un alone attorno all'intero
   blocco, sagoma compresa. È il motivo del `:not(.figure__fill)`.

   Il raggio è in unità di viewBox, non in pixel di schermo come lo spessore
   del tratto: cresce quindi con la figura, che è quel che serve — a 320px
   l'alone è di tre pixel scarsi, e se un giorno la figura raddoppia raddoppia
   con lei invece di restare un filo.

   Sull'accento l'alone è più marcato che sul bianco: il turchese ha meno
   luminanza del bianco e senza quel po' in più si spegnerebbe accanto. */
.figure__solid path:not(.figure__fill) {
  filter:
    drop-shadow(0 0 1px rgba(var(--paper-rgb), 0.4))
    drop-shadow(0 0 4px rgba(var(--paper-rgb), 0.14));
}

.figure__guide path {
  filter:
    drop-shadow(0 0 1.2px rgba(var(--teal-rgb), 0.6))
    drop-shadow(0 0 5.5px rgba(var(--teal-rgb), 0.3));
}

/* A contrasto forzato l'alone è rumore: il sistema ridipinge i tratti e la
   sfocatura resta come una macchia attorno. */
@media (forced-colors: active) {
  .figure__solid path:not(.figure__fill),
  .figure__guide path { filter: none; }
}

/* Non più in uso: i dischi attenuati sono spariti quando il diagramma è
   diventato una griglia di blocchi. Resta il gancio, se servirà. */
.figure__solid--faint { opacity: 0.5; }

/* PROVA — annotazioni in turchese invece che in bianco attenuato.
   L'idea: il tratteggio è la parte che porta il concetto, ed è la stessa cosa
   che fa la parola in evidenza nella domanda sotto. Dargli la stessa tinta
   lega le due, e l'annotazione smette di essere un dettaglio da cercare.
   Colore e opacità stanno in due token per poter tornare indietro cambiando
   una riga: senza valore, il tratteggio resta bianco tenue com'era. */
.figure__guide {
  stroke: var(--figure-guide, currentColor);
  opacity: var(--figure-guide-opacity, 0.2);
  stroke-dasharray: 6 5;
}

/* Stesso tratto del resto: a distinguere l'annotazione dall'oggetto bastano
   colore e tratteggio. Un peso diverso faceva sembrare le due cose disegnate
   da mani diverse. */

/* -------------------------------------------------------------- buttons -- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.7rem 1.3rem;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: transform 0.18s var(--ease), background-color 0.18s var(--ease),
              border-color 0.18s var(--ease);
}

.btn--lg { padding: 0.9rem 1.7rem; font-size: var(--text-md); }

/* L'icona nel tasto sta appena sopra il corpo dell'etichetta: a 1em, accanto a
   un testo in 600, si legge come un refuso. Il `gap` del tasto fa la spaziatura.

   L'icona nomina la cosa, non il gesto: accanto a «Scarica» ci va un documento,
   non una freccia che scende — il verbo è già scritto, ripeterlo in figura non
   aggiunge niente, mentre la figura dell'oggetto dice che cosa si riceve.
   Sta in testa quando nomina l'oggetto, in coda quando indica che si va
   altrove: quella è l'unica freccia ammessa, e solo dove porta fuori. */
.btn .icon { --icon-size: 1.15em; }

/* Schermi da 320px: "Scopri la Compliance Intelligence" in un tasto grande
   misura 312px contro i 280 della colonna, e siccome `nowrap` non lascia
   andare a capo l'eccedenza usciva dal viewport — l'intera pagina scorreva di
   lato, non solo il tasto. Qui il `nowrap` cade e i fianchi si stringono:
   l'etichetta va su due righe, che è l'unica alternativa a un tasto più
   largo dello schermo. */
@media (max-width: 380px) {
  .btn {
    max-width: 100%;
    white-space: normal;
    text-align: center;
  }

  .btn--lg { padding-inline: 1.15rem; }
}

/* Primario in contrasto pieno col fondo, non in blu: bianco sulle sezioni
   scure, inchiostro su quelle chiare. In entrambi i casi il testo sta sopra
   i 14:1, molto oltre il minimo di 4.5. */
.btn--accent {
  background: var(--btn-bg);
  color: var(--btn-fg);
}

.btn--accent:hover {
  background: var(--btn-bg-hover);
  transform: translateY(-1px);
}

/* Secondario: nessun riempimento, solo il tratto. Il colore non e' scritto
   qui ma preso dal testo — currentColor — e il testo segue --fg, che .on-dark
   ribalta: bianco sulle fasce scure, inchiostro su quelle chiare, senza una
   regola per contesto.

   Prima il fondo era --surface, cioe' bianco pieno sulle pagine chiare: un
   tasto pieno che si spacciava per secondario. E il tratto era --line, la
   stessa riga da 13% che divide i blocchi: troppo debole per dire dove
   finisce un pulsante. */
.btn--ghost {
  border-color: currentColor;
  border-width: 1.5px;
  color: var(--fg);
  background: transparent;
}

/* Il richiamo chiude la sezione invece di anticiparla: sta sotto il contenuto,
   centrato. Lo usano la galleria di prodotto e la fascia di articoli, quindi il
   nome non è di nessuna delle due. */
.section__foot {
  margin-top: clamp(2.5rem, 4vw, 3.5rem);
  text-align: center;
}


/* L'hover non riempie il tasto: lo vela appena. --surface-2 e' inchiostro al
   4.5% sul chiaro e bianco al 6% sullo scuro, quindi si adatta da solo — il
   bianco al 28% scritto a mano di prima, sulle pagine chiare, schiariva il
   bordo invece di segnarlo. */
.btn--ghost:hover {
  background: var(--surface-2);
  transform: translateY(-1px);
}

/* --------------------------------------------------------------- header -- */

/* Forma di base: trasparente, appoggiata in cima al documento. Da sola la
   barra scorrerebbe via con la pagina — a tenerla in vista è
   `site-header--sticky` qui sotto, che è la variante che l'header monta. */
.site-header {
  position: absolute;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: transparent;
}

/* --- variante agganciata, a tutta larghezza -------------------------------
   Attiva: è la classe che porta l'header in views/partials/header.ejs. La
   barra resta in vista scorrendo e oltre la soglia si accende di vetro a
   tutta larghezza. Il vetro è davvero trasparente: a renderla leggibile è la
   sfocatura, non una velatura coprente. Il colore del testo dipende da cosa
   le passa sotto — lo decide il JS con `.is-over-dark` — perché un solo
   colore fisso sarebbe illeggibile sopra metà pagina. */
.site-header--sticky {
  position: fixed;
}

.site-header--sticky.is-stuck {
  background: rgba(var(--surface-rgb), 0.82);
  backdrop-filter: saturate(180%) blur(16px);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  box-shadow: 0 1px 0 rgba(var(--ink-rgb), 0.08);
}

.site-header--sticky.is-stuck .site-header__inner {
  --fg: var(--ink);
  --fg-muted: var(--ink-4);
  --accent: var(--brand-6);
  color: var(--fg);
}

/* Sopra le zone scure la barra si inverte. */
.site-header--sticky.is-stuck.is-over-dark {
  background: rgba(var(--night-rgb), 0.72);
  box-shadow: 0 1px 0 rgba(var(--paper-rgb), 0.08);
}

.site-header--sticky.is-stuck.is-over-dark .site-header__inner {
  --fg: var(--steel-0);
  --fg-muted: var(--steel-2);
  --accent: var(--brand-2);
}

/* A riposo, sopra un'apertura scura, la barra prende il bianco: gli stessi
   valori che `.is-over-dark` dà alla barra agganciata. La regola serve perché
   l'header sta fuori da `.hero-block` e da solo erediterebbe l'inchiostro del
   chiaro.

   `.is-over-dark` in più: prima la regola valeva per ogni apertura, perché
   l'hero della home era scuro fin dal bordo alto. Col gradiente nuovo non lo
   è più — parte a rgb(238,245,249) e non arriva allo scuro prima di y=220 —
   e la barra si dipingeva bianca su quasi bianco: 1.40:1, illeggibile. Ora
   comanda il JS, che il fondo lo misura invece di dedurlo dalla classe.
   Quando sotto passa chiaro la regola non si applica e l'header torna a
   ereditare l'inchiostro, che è esattamente quel che serve. */
body:has(.hero) .site-header.is-over-dark:not(.is-stuck) .site-header__inner {
  --fg: var(--steel-0);
  --fg-muted: var(--steel-2);
  --accent: var(--brand-2);

  color: var(--fg);
}

/* Il server marca `on-dark` le aperture scure, e senza quel default
   lampeggerebbero d'inchiostro prima che il JS giri. La home non è più fra
   quelle: da quando il gradiente parte quasi bianco la sua rotta dichiara
   `lightHeader`, così la barra è giusta già al primo disegno: prima usciva
   bianca su quasi bianco, 1.02:1 sui link misurato a 1440x900.

   `is-over-light` resta la rete: la mette il JS dopo aver misurato il fondo
   davvero dipinto, e ribalta il default per le aperture che restano scure ma
   che sotto la barra, a quell'altezza, chiare lo sono. A barra agganciata non
   serve: comanda `.is-stuck`, che ha già le sue due varianti. */
.site-header.on-dark.is-over-light:not(.is-stuck) .site-header__inner {
  --fg: var(--ink);
  --fg-muted: var(--ink-4);
  --accent: var(--brand-6);

  color: var(--fg);
}

/* Il tasto pieno non segue `--fg`: ha un fondo suo, e a deciderlo sono
   `--btn-bg` e `--btn-fg`, che `.on-dark` ribalta per l'intero blocco. Finché
   quei due token restavano appesi alla classe del server, il tasto non
   guardava cosa gli passa sotto: sulla home usciva bianco sull'apertura quasi
   bianca — pillola 242,245,246 su fondo 213,227,233, cioè 1.20:1 — e sopra le
   fasce chiare agganciate faceva 1.52:1. Lo stesso difetto su /product da
   y=900 in giù.

   Qui seguono il fondo misurato, come già il testo: dove passa chiaro il tasto
   è inchiostro con etichetta bianca — gli stessi valori delle pagine che
   aprono chiare, 13.3:1 sul vetro — e dove passa scuro torna bianco con
   etichetta d'inchiostro. Vale agganciata e da ferma, perché il fondo cambia
   in tutti e due gli stati.

   Il menu del telefono non lo tocca: i suoi token li dichiara `.site-nav`, che
   sta più vicino al tasto e vince per eredità, non per specificità. */
.site-header.is-over-light .site-header__inner {
  --btn-bg: var(--steel-11);
  --btn-bg-hover: var(--steel-12);
  --btn-fg: var(--steel-0);
}

.site-header.is-over-dark .site-header__inner {
  --btn-bg: var(--steel-0);
  --btn-bg-hover: var(--steel-1);
  --btn-fg: var(--steel-12);
}

/* --- variante a pillola sospesa -------------------------------------------
   Attiva, insieme a `--sticky`: scorrendo la barra non si accende da bordo a
   bordo, si raccoglie in una pillola di vetro sospesa al centro. È la forma
   di dendoo.it.

   La fascia a tutta larghezza della variante agganciata va quindi spenta:
   accesa insieme alla pillola disegnerebbe due oggetti sovrapposti, la
   pillola dentro la sua stessa banda. Il vetro passa al contenitore
   interno — è lui la pillola — e qui resta solo il trasporto. */
.site-header--pill.site-header--sticky.is-stuck,
.site-header--pill.site-header--sticky.is-stuck.is-over-dark {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
}

.site-header--pill.site-header--sticky.is-stuck .site-header__inner {
  max-width: 66rem;
  padding: 0.5rem 0.5rem 0.5rem 1.5rem;
  backdrop-filter: saturate(170%) blur(16px);
  -webkit-backdrop-filter: saturate(170%) blur(16px);
}

/* Sopra le zone chiare: velo bianco appena accennato e testo scuro. */
.site-header--pill.site-header--sticky.is-stuck .site-header__inner {
  --fg: var(--ink);
  --fg-muted: var(--ink-4);
  --accent: var(--brand-6);

  color: var(--fg);
  background: rgba(var(--surface-rgb), 0.5);
  box-shadow:
    inset 0 0 0 1px rgba(var(--surface-rgb), 0.5),
    0 8px 30px rgba(var(--ink-rgb), 0.1);
}

/* Sopra le zone scure: il velo si inverte e il testo torna bianco. */
.site-header--pill.site-header--sticky.is-stuck.is-over-dark .site-header__inner {
  --fg: var(--steel-0);
  --fg-muted: var(--steel-2);
  --accent: var(--brand-2);

  background: rgba(var(--paper-rgb), 0.07);
  box-shadow:
    inset 0 0 0 1px rgba(var(--paper-rgb), 0.14),
    0 8px 30px rgba(var(--ink-rgb), 0.28);
}

.site-header--pill.site-header--sticky.is-stuck .site-nav { gap: 1.4rem; }

.site-header--pill.site-header--sticky.is-stuck .site-nav__cta { padding: 0.6rem 1.15rem; }

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  border-radius: 999px;
  transition:
    max-width 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    padding 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    background-color 0.35s var(--ease),
    box-shadow 0.35s var(--ease),
    color 0.35s var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  .site-header__inner { transition: none; }
}

.logo {
  display: inline-flex;
  align-items: center;
  line-height: 1;
}

/* Wordmark ufficiale (public/images/logo-dendoo.svg), inline così eredita
   il colore dal contesto: bianco su fondo scuro, ink su fondo chiaro. */
.logo__svg {
  display: block;
  height: 25px;
  width: auto;
  color: var(--fg);
}

.logo--sm .logo__svg { height: 21px; }

.site-nav {
  display: flex;
  align-items: center;
  gap: 1.75rem;
}

.site-nav__link {
  font-size: var(--text-sm);
  /* Colore pieno, non attenuato: le voci di menu sono navigazione primaria. */
  color: var(--fg);
  transition: opacity 0.18s var(--ease);
}

.site-nav__link:hover { opacity: 0.7; }

.site-nav__link.is-active { font-weight: 600; }

.nav-toggle {
  display: none;
  width: 42px; height: 42px;
  align-items: center;
  justify-content: center;
  background: var(--card);
  border: 0;
  border-radius: 10px;
  cursor: pointer;
  color: inherit;
}

.nav-toggle__bars { display: block; width: 18px; }

.nav-toggle__bars span {
  display: block;
  height: 1.5px;
  background: currentColor;
  border-radius: 2px;
  transition: transform 0.22s var(--ease), opacity 0.22s var(--ease);
}

.nav-toggle__bars span + span { margin-top: 5px; }

.nav-toggle[aria-expanded="true"] .nav-toggle__bars span:first-child {
  transform: translateY(3.5px) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] .nav-toggle__bars span:last-child {
  transform: translateY(-3px) rotate(-45deg);
}

/* ------------------------------------------------ riempimento allo scroll -- */

/* Il testo si tinge man mano che la sezione entra in pagina. Il gradiente è
   ritagliato sulla forma dei glifi e la sua soglia è `--fill`, che il JS
   aggiorna in base a quanto l'elemento ha attraversato la finestra.
   Verticale e non orizzontale: così si riempie riga per riga, nell'ordine in
   cui si legge, invece che in diagonale su tutte le righe insieme.
   Valore iniziale 100%: senza JS il testo resta pieno e leggibile. */
.reveal-fill {
  --fill: 100%;
  background-image: linear-gradient(
    180deg,
    var(--fg) calc(var(--fill) - 12%),
    var(--fg-faint) calc(var(--fill) + 4%)
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

@supports not ((background-clip: text) or (-webkit-background-clip: text)) {
  .reveal-fill { color: var(--fg); -webkit-text-fill-color: var(--fg); }
}

@media (forced-colors: active) {
  .reveal-fill {
    background-image: none;
    color: var(--fg);
    -webkit-text-fill-color: var(--fg);
  }
}

/* ----------------------------------------------------- micro-interazioni -- */

/* Le frecce nei link scorrono in avanti al passaggio: segnala la direzione
   dell'azione meglio di un cambio di colore. Il <span> che le contiene è già
   `aria-hidden`, quindi il movimento non tocca ciò che viene letto. */
.overview__link span,
.card__cta span {
  display: inline-block;
  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}

@media (hover: hover) {
  .overview__link:hover span,
  .card__cta:hover span,
  .card__link:hover .card__cta span { transform: translateX(4px); }
}

/* Risposta al tocco: senza, su mobile la pressione non dà alcun segnale
   finché la pagina non cambia. */
.btn:active { transform: translateY(1px) scale(0.985); }

.card:active { transform: translateY(-1px); }

.faq__q:active { opacity: 0.72; }

@media (prefers-reduced-motion: reduce) {
  .overview__link span,
  .bento__art { transition: none; }
}

/* ----------------------------------------------- entrate allo scorrimento -- */

/* Il contenuto entra quando attraversa il bordo della finestra. Lo stato di
   partenza è dietro `.js-motion`, messo dal JS sull'<html>: senza JS nulla è
   nascosto, quindi la pagina resta leggibile e indicizzabile. */
.js-motion [data-enter] {
  opacity: 0;
  transform: translateY(18px);
}

.js-motion [data-enter].is-in {
  opacity: 1;
  transform: none;
  /* Curva lunga in uscita: parte decisa e si posa. */
  transition:
    opacity 0.55s cubic-bezier(0.16, 1, 0.3, 1) var(--enter-delay, 0ms),
    transform 0.7s cubic-bezier(0.16, 1, 0.3, 1) var(--enter-delay, 0ms);
}

@media (prefers-reduced-motion: reduce) {
  .js-motion [data-enter] { opacity: 1; transform: none; }
}

/* -------------------------------------------- fondo che vira allo scroll -- */

/* Come su icreon: entrando nella zona di chiusura, il fondo scuro vira al blu
   di marca. Il colore sta su uno strato ::before la cui opacità è `--bg-reveal`
   (0..1), aggiornata dal JS leggendo lo scroll. Animare un'opacità è composita:
   niente ricalcoli di layout a ogni frame. Senza JS resta il fondo scuro. */
/* Un contenitore solo per banda e footer: il colore sta qui, quindi la zona
   è una superficie continua. Con due strati separati, ciascuno sopra una base
   diversa, restava un salto di tono nel punto di giunzione. */
.closing {
  position: relative;
  /* Stesso fondo della FAQ che la precede: erano due neri diversi e nel
     punto di giunzione si vedeva la riga. */
  background: var(--blue-950);
}

/* Il viraggio prende tutta la zona scura, non solo la fine: articoli, FAQ e
   chiusura sono tre blocchi neri di fila, e tingerne solo due lascia la
   cucitura un gradino più su invece di toglierla. Fondere i soli fondi di
   base non basta: quelli coincidono già: è arrivando in fondo che il cierre
   vira al blu e chi resta fuori resta nero, ed è lì che si vedono insieme.
   Il gradiente è quasi orizzontale, così blocchi impilati di altezza diversa
   lo disegnano identico e nei punti di giunzione non resta cucitura. */
.blog-teaser,
.faq { position: relative; }

.blog-teaser > *,
.faq > * { position: relative; z-index: 1; }

.blog-teaser::before,
.faq::before,
.closing::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* Orizzontale esatto, non «quasi». Tre gradi di inclinazione sembrano
     niente, ma la lunghezza dell'asse di un gradiente li fa pesare
     sull'altezza: su un blocco da 390x1216 il verso verticale vale il 14%
     della corsa, e ogni blocco la ricomincia da capo. Misurato al confine fra
     le domande e la chiusura, il salto era da rgb(20,57,73) a rgb(14,53,68) —
     una riga netta attraverso una zona che deve leggersi come una superficie
     sola. A 90 gradi l'altezza non entra più nel conto: tre blocchi impilati
     dipingono la stessa rampa e il confine sparisce. */
  background: linear-gradient(90deg, var(--blue-800), var(--blue-700) 52%, var(--blue-800));
  /* Il blog ha gli angoli alti arrotondati dal raccordo: senza ereditare il
     raggio il velo li ridisegna quadrati e lo spigolo ricompare. */
  border-radius: inherit;
  /* La transizione copre gli scatti fra un fotogramma di scroll e l'altro:
     senza, su rotelle a scatti il viraggio si vede procedere a gradini. */
  opacity: var(--bg-reveal, 0);
  transition: opacity 0.45s cubic-bezier(0.33, 1, 0.68, 1);
}

.closing > * { position: relative; z-index: 1; }

.cta-band .btn--accent {
  transition: background-color 0.35s var(--ease), color 0.35s var(--ease),
              transform 0.18s var(--ease);
}

.is-bg-revealed .btn--ghost { border-color: rgba(var(--paper-rgb), 0.55); }

.cta-band .btn--ghost,
.cta-band .eyebrow,
.site-footer,
.site-footer a {
  transition: color 0.4s var(--ease), border-color 0.4s var(--ease);
}

/* Sul blu i toni attenuati non reggono: si risalgono per l'intera zona. */
.is-bg-revealed {
  --fg-muted: var(--steel-2);
  --fg-faint: var(--steel-4);
}

/* --------------------------------------------------- testo con gradiente -- */

/* Il gradiente scorre dentro la sagoma delle lettere: `background-clip: text`
   ritaglia lo sfondo sulla forma del glifo e il colore trasparente lo lascia
   vedere. Si anima `background-position`, non il gradiente stesso: spostare
   una posizione è economico, ricalcolare le tinte a ogni frame no.

   Gli strati sono due. Sotto l'onda di fondo, che dà alle lettere il loro
   corpo in movimento; sopra un lampo stretto che le attraversa in senso
   contrario e con un'altra inclinazione — 78° contro 100°, quindi i due non
   sono paralleli e l'incrocio disegna una losanga di luce invece di una banda.

   L'onda è due picchi bianchi e due avvallamenti, ripetuti due volte. Gli
   avvallamenti sono il punto debole: è lì che la parola può sprofondare nel
   fondo. Sotto la prima riga del titolo la discesa dell'hero sta fra #7d98a9
   sugli schermi bassi e #587488 su un 1080, e sul caso peggiore --blue-400
   tiene 1.63:1 — sotto il 3:1 dei testi grandi. Il fondo dell'onda sale quindi
   a --blue-200 (2.40:1 sul peggiore, 3.90:1 sul 1080) e quello profondo passa
   a --teal-500.
   È la differenza di *croma*, non di luce, a staccare la parola dal campo: il
   fondo qui porta la tinta che il master ha a questa altezza, e le fermate di
   prima erano tutte quella stessa tinta schiarita — la parola era più chiara
   del cielo, non altro dal cielo. Il turchese è l'unica tinta della tavola fuori dal blu, e qui sta
   nella proporzione che gli spetta: due avvallamenti stretti e la frangia del
   lampo, non l'accento della parola, che resta blu.

   Le liste stanno in due token perché servono identiche due volte — qui e
   nell'`@supports` in oklab più sotto — e due copie che divergono si
   vedrebbero come uno scarto di tinta fra un browser e l'altro. */
.shimmer {
  --shimmer-stops:
    #ffffff 0%,
    var(--steel-1) 12%,
    var(--brand-0) 25%,
    var(--brand-2) 37%,
    #ffffff 50%,
    var(--steel-1) 62%,
    var(--brand-0) 75%,
    var(--brand-2) 87%,
    #ffffff 100%;

  /* Nucleo bianco con la frangia turchese in entrata e fredda in uscita: è
     l'aberrazione cromatica di un riflesso speculare. Il turchese resta un
     bordo sottile, non il colore del lampo.
     L'alfa serve: il lampo deve lasciar passare l'onda che gli sta sotto. È
     una rampa di alfa, ma corta e ritagliata dentro le aste delle lettere,
     non stesa su una superficie grande — è lì che l'alfa fa bande, non qui.
     I valori sono letterali e non token: dentro `rgba()` un token esadecimale
     non entra, e `color-mix()` non vale il rischio — se una sola tinta del
     gradiente non viene capita cade tutto `background-image`, e con il testo
     trasparente il titolo sparirebbe. Sono --brand-2 e --steel-1 scritti per
     esteso, come già altrove nel file. */
  --shimmer-glint:
    rgba(255, 255, 255, 0) 44%,
    rgba(97, 205, 229, 0.5) 47.5%,
    rgba(255, 255, 255, 0.95) 50%,
    rgba(226, 233, 236, 0.5) 52.5%,
    rgba(255, 255, 255, 0) 56%;

  background-image:
    linear-gradient(78deg, var(--shimmer-glint)),
    linear-gradient(100deg, var(--shimmer-stops));
  background-size: 300% 100%, 200% 100%;
  background-position: 0 0, 0 0;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  animation: shimmer-pan 6.8s linear infinite;
}

/* In sRGB il tratto dal turchese al bianco passa per grigi slavati e il lampo
   perde proprio il croma per cui è lì. In oklab la saturazione regge fino in
   fondo. Sta in un `@supports` perché `in oklab` dentro la regola di base
   renderebbe la dichiarazione invalida dove non è capita, e il titolo
   resterebbe senza gradiente: cioè trasparente, quindi invisibile. */
@supports (background-image: linear-gradient(in oklab, red, blue)) {
  .shimmer {
    background-image:
      linear-gradient(78deg in oklab, var(--shimmer-glint)),
      linear-gradient(100deg in oklab, var(--shimmer-stops));
  }
}

/* Un'animazione sola muove entrambi gli strati: `background-position` è una
   proprietà unica con una lista, e due animazioni separate si sovrascriverebbero
   invece di sommarsi.
   Le posizioni non sono scelte a occhio. Con `background-size: W` e
   `background-position: P`, lo scarto vale P/100 × (C − W) con C la larghezza
   dell'elemento: il fondo a 200% e 200% percorre 2C, cioè due periodi interi
   della sua trama; il lampo a 300% e −150% percorre 3C, cioè una trama intera.
   Numeri interi da entrambe le parti, quindi l'ultimo fotogramma coincide con
   il primo e il giro non salta.
   I due strati vanno a passi diversi apposta. Il fondo corre — una cresta ogni
   3.4s — e il lampo no: 0.44 C/s, un solo passaggio per giro, 0.82s per
   scavalcare un punto della parola. È la differenza fra i due a farsi vedere;
   se corrono insieme non si legge né l'uno né l'altro.
   Il lampo va a velocità costante. Una versione precedente gli dava fermate
   spaziate in modo diseguale, per dargli peso: a tre passaggi per giro quel
   peso si leggeva come scatto, non come inerzia. Con un passaggio solo e lento
   la rampa lineare basta, ed è l'unica che non ha nulla da nascondere nel
   punto in cui il giro si richiude. */
@keyframes shimmer-pan {
  from { background-position:    0 0,   0 0; }
  to   { background-position: -150% 0, 200% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .shimmer { animation: none; }
}

/* Se il testo trasparente non è supportato — o in modalità a contrasto
   forzato — il titolo tornerebbe invisibile: qui si ripristina il colore. */
@supports not ((background-clip: text) or (-webkit-background-clip: text)) {
  .shimmer {
    color: var(--fg);
    -webkit-text-fill-color: var(--fg);
  }
}

@media (forced-colors: active) {
  .shimmer {
    background-image: none;
    color: var(--fg);
    -webkit-text-fill-color: var(--fg);
  }
}

/* ----------------------------------------------------------------- hero -- */

/* Hero e striscia loghi condividono un unico fondo: il gradiente scende
   dietro i loghi e si chiude su --ink, senza stacchi. */
.hero-block {
  --round: clamp(1.75rem, 4vw, 3.25rem);

  position: relative;
  padding-bottom: var(--round);
  /* Niente `overflow: hidden` qui: renderebbe questo blocco il contenitore di
     scorrimento e `position: sticky` al suo interno smetterebbe di funzionare.
     Il ritaglio serve solo all'illustrazione, quindi sta su .hero. */
  /* Nessun `padding-top`: l'altezza della barra la assorbe .hero, così il suo
     gradiente comincia davvero a filo pagina, sotto l'header, invece che
     settantadue pixel più in basso. */
  /* Tinta piatta: il gradiente adesso è dell'hero soltanto, e l'hero chiude
     sul nero. Il blocco parte dallo stesso nero, così le tre sezioni che
     seguono ci si appoggiano senza scalino. */
  background-color: var(--blue-950);
}

.hero-block::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* `overlay` scurisce e schiarisce a seconda del pixel sotto, quindi la
     grana resta grana: con `screen` schiariva soltanto e il blu si slavava. */
  opacity: var(--grain-opacity);
  mix-blend-mode: overlay;
  background-image: var(--grain);
  background-repeat: repeat;
  background-size: 200px 200px;
}

/* Il contenuto sta sopra la grana. */
.hero-block > * { position: relative; z-index: 1; }

/* Il blocco di testo sta al centro dello schermo. `safe` disinnesca il
   centraggio quando il contenuto è più alto dello spazio: senza, su viewport
   bassi il blocco traboccherebbe da entrambi i lati e la prima riga finirebbe
   sotto la barra. Dichiarato due volte perché dove `safe` non è supportato la
   riga cade e resta quella sopra.
   Il padding è simmetrico — è il centraggio a collocare il blocco — e serve
   solo come margine di sicurezza quando il contenuto supera lo schermo.
   Non è posizionato: il video che contiene resta ancorato all'hero intero,
   cioè allo schermo. */
.hero__main {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  justify-content: safe center;
  padding: calc(var(--header-h) + 2rem) 0;
}

.hero {
  position: relative;
  overflow: hidden;
  /* Tiene da solo la prima schermata. L'intestazione è in `position: absolute`
     e ci sta sopra, quindi 100svh è esattamente lo schermo. */
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  /* Solo il lineare. Un alone radiale qui è una rampa di *alfa*, non di
     colore: con alfa 0.26 restano sessantasei scalini interi da distribuire
     su trecento pixel, cioè bande da quasi cinque — ben oltre i tre che
     l'occhio già distingue. Il lineare, per confronto, sta sotto i due. La
     luce la fa l'oggetto, e il riferimento è un lineare puro. */
  background-image: var(--grad-sky-invert);
  background-repeat: no-repeat;
  /* La rampa chiude prima della fine del blocco, e la sua altezza segue il
     testo invece dello schermo. Il testo sta al centro, quindi la sua distanza
     dal bordo alto vale (100svh - 475px) / 2, con 475px l'altezza del blocco
     su desktop: cala molto più in fretta dell'altezza del viewport. Con una
     percentuale fissa il testo su schermi bassi si ritrovava sul cielo chiaro,
     a 2.9:1. Dividendo, la prima riga cade sempre alla stessa frazione della
     discesa qualunque sia lo schermo — ed è il divisore, non le fermate, a
     decidere quale. Comprimere la rampa invece di spostare le fermate lascia
     intatte le sue proporzioni interne, che sono calibrate per non lasciare
     bande.

     Le due aperture del sito devono avere la stessa ampiezza verticale — sono
     la stessa discesa, una dal chiaro e l'altra dal nero — e per averla non
     basta scegliere un divisore: bisogna che sia la stessa *funzione*
     dell'altezza. Con un divisore fisso le due curve si incrociano in un punto
     solo e divergono ovunque: a 0.75 la home era il 33% più corta di /product
     su un viewport da 700 e il 20% più lunga su uno da 1200. Coincidevano a
     900px e basta, che è il monitor su cui si era guardata.

     Da qui i due termini del `min()`.

     Il primo insegue /product a ogni altezza: la sua discesa vale il 52% di
     (100svh + --round), e dividendo per il 70% a cui chiude questa rampa
     restano 0.743. Finché comanda questo, le due aperture sono identiche.

     Il secondo è il pavimento di contrasto, e serve perché il titolo non sta
     al centro della discesa ma a una distanza fissa dal bordo alto:
     (100svh - 475px) / 2, con 475px l'altezza del blocco di testo. Quella
     distanza cala molto più in fretta dell'altezza dello schermo, quindi su
     viewport bassi la prima riga risale verso il cielo chiaro — è così che
     finiva a 2.9:1 prima che ci fosse una normalizzazione. Moltiplicando per
     1.95 la rampa resta abbastanza corta da tenere il titolo sopra 3:1 fin
     dove il centraggio ha ancora senso.

     Il taglio fra i due cade intorno agli 850px: sopra vince l'inseguimento e
     la home è identica a /product, sotto vince il pavimento e la discesa si
     accorcia quel tanto che serve a non perdere la prima riga.

     Il contrasto del titolo va da 3.03:1 sugli schermi bassi a 4.92:1 su un
     1080. Il titolo è --text-display, 41.6px al minimo: la soglia è 3:1, non
     4.5 — il 5.33:1 di prima era margine, non requisito, ed è quel margine
     che si è speso per l'ampiezza. Il sommario non è in discussione: sta
     150px più in basso, dove il fondo è già a L* 27 e il contrasto sopra 10:1.

     Il minimo del clamp serve quando il contenuto supera lo schermo e il
     centraggio si disinnesca. */
  background-size: 100% clamp(
    240px,
    min(calc((100svh + 3.25rem) * 0.743), calc((100svh - 475px) * 1.95)),
    100%
  );
  /* Sotto la rampa il colore lo mette questa tinta piatta, la stessa su cui la
     discesa chiude: senza, l'hero resterebbe trasparente e la grana in
     `overlay` di ::after non avrebbe un fondo con cui fondersi — si vedrebbe
     come una fascia grigia. */
  background-color: var(--blue-950);
}

/* La grana dell'hero. Stessa sorgente e stessa scala del blocco, così al
   confine non c'è stacco; sopra il fondo ma sotto il contenuto, che sta a
   z-index 2. */
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: overlay;
  background-image: var(--grain);
  background-repeat: repeat;
  background-size: 200px 200px;
  /* Spenta del tutto sulla fascia chiara — lì il bianco deve restare pulito —
     e piena da metà in giù, dove combacia con quella del blocco. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, transparent 11.8%, #000 39.2%);
  mask-image: linear-gradient(180deg, transparent 0%, transparent 11.8%, #000 39.2%);
}

/* Sotto i 1200px il portatile si impila (la soglia è quella di .hero__mock,
   più sotto) e l'apertura diventa più alta dello schermo: il
   centraggio si disinnesca, il titolo torna in cima a cento pixel dal bordo e
   la rampa non può superare i quattrocento — al 25% della discesa il bianco
   del titolo tiene 3:1, che è la soglia dei testi grandi. Il `min()` comanda
   la larghezza sui telefoni stretti e l'altezza su quelli bassi e sui tablet:
   a 393x852 la rampa chiude a 324px, a 375x667 a 300, a 768x1024 a 389 e a
   1100x800 a 304. Misurato sulla prima riga: da 3.5:1 sul tablet verticale a
   5.7:1 su un 1024x768. */
@media (max-width: 1199px) {
  .hero { background-size: 100% clamp(300px, min(92vw, 38svh), 400px); }
}

.hero__inner { position: relative; z-index: 2; }

/* Trattamento "dichiarazione": corpo molto grande, interlinea stretta e
   crenatura negativa, così la frase pesa più di tutto il resto della pagina. */
.hero__headline {
  font-size: var(--text-display);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  font-weight: 600;
  max-width: 21ch;
}

/* Un paragrafo solo, che va a capo da sé: la misura lo tiene su due righe
   piene invece di una lunga e una corta. */
.hero__description {
  margin-top: clamp(1.5rem, 2.6vw, 2rem);
  max-width: 44rem;
  font-size: var(--text-lg);
  line-height: var(--leading-lg);
  color: var(--fg-muted);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.85rem;
  margin-top: clamp(2rem, 3.5vw, 2.75rem);
}

/* Il secondo tasto cade sulla traiettoria della base del portatile: fino a
   ventun pixel a 1440, dove i due vincoli della larghezza si pareggiano — il
   conto sta sotto .hero__mock, e sotto i 1280 lo scavalcamento si annulla da
   sé. La sovrapposizione è voluta: l'alternativa era stringere l'oggetto di
   cento pixel, cioè perdere l'allineamento fra la sua cima e la prima riga
   del claim.

   Quel che non andava è che il tasto fosse trasparente: la punta d'alluminio
   gli passava *dentro*, attraverso l'etichetta, e a distinguere una
   sovrapposizione da una collisione è solo il fatto che qualcosa copra
   qualcos'altro. Sopra ci passa già — la colonna di testo sta a z-index 2 e
   l'oggetto a -1 — e qui prende anche il riempimento.

   Il riempimento è la tinta su cui la rampa chiude, cioè il fondo stesso
   dell'apertura: i tasti cadono sempre oltre il 70% della discesa, dove
   --grad-sky-invert è già --blue-950 piatto, su ogni schermo e a ogni
   altezza. A vedersi il tasto resta il contorno di prima — nessun pieno che
   si accende — e diventa opaco solo per quel che nasconde. */
.hero__actions .btn--ghost { background-color: var(--blue-950); }

/* L'hover del secondario è un velo translucido, e scritto com'è — la
   scorciatoia `background` — rimetterebbe il portatile in trasparenza proprio
   mentre ci si punta sopra. Qui il velo sale a strato sopra la tinta, che
   resta. */
.hero__actions .btn--ghost:hover {
  background-color: var(--blue-950);
  background-image: linear-gradient(var(--surface-2), var(--surface-2));
}

/* Gli standard stanno fra titolo e sottotitolo, come occhiello: lo stesso
   maiuscoletto spaziato di .eyebrow e lo stesso accento, che dentro .on-dark
   è il gradino chiaro della rampa. Cade oltre metà della discesa, dove il
   fondo è già --blue-800 o più scuro: sopra i 7:1. */
.hero__standards {
  margin-top: clamp(1.1rem, 1.6vw, 1.5rem);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-faint);
}

/* Il portatile in prospettiva, a destra del testo: la base posata sulla linea
   dei tasti e la cima che sale sopra il claim, come su /product.

   Quel che lo colloca però non è una linea di pagina, è il testo. Ancorarlo al
   filo dello shell lo teneva a 618px — su un 2560 il 24% della finestra — e
   portarlo al margine di pagina lo faceva grande ma staccato, con una trincea
   di 350px in mezzo. Sta a destra del claim: comincia dove finisce «ISO» e si
   estende da lì, uscendo dallo shell quanto gli serve e fermandosi molto prima
   del bordo della finestra. È l'unico elemento del sito che esce dalla
   colonna, e la ragione è che è l'unico che non è testo.

   `--claim-w` è l'inchiostro della seconda riga del claim. Misurato: 1057px a
   89.6px, cioè 11.8 volte il corpo — la stessa stringa a corpi diversi scala
   con il corpo, quindi legarlo a `--text-display` lo fa seguire da sé fin giù
   al telefono, invece di valere solo dove il clamp è al massimo. Il
   coefficiente però è preso sul testo, non è una proporzione di comodo: se il
   copy del claim cambia va rimisurato, e con lui si sposta il portatile.

   Il conto di `left` parte dalla sagoma. Il pieno non comincia al bordo della
   scatola: c'è il 4.22% di fascia trasparente, e sopra ci sono i gradini della
   prospettiva, che nella fascia dove cade la seconda riga del claim cominciano
   al 48-50% del pieno. In tutto 0.4946 della larghezza — misurato per fasce
   sul PNG, non stimato. La scatola quindi *scavalca* il claim, e il pieno gli
   si posa accanto a 2.5rem: lo stacco misurato resta fra i 31 e i 57px da
   1401 a 2560.

   La larghezza non è una proporzione della finestra: è il massimo che entra,
   e il criterio è che la cima del portatile arrivi alla prima riga del claim.
   Il portatile e il blocco di testo occupano così la stessa fascia verticale
   — base sulla linea dei tasti, cima sulla riga del titolo — e si leggono
   come una cosa sola invece che come due oggetti accostati.

   Il conto: il pieno è alto 0.4914 volte la scatola, il blocco vale
   `237px + 2.08 * --text-display`, quindi per allineare le cime servono
   `482px + 4.23 * --text-display` — 861px a corpo pieno. Ma non è quel
   numero a comandare: comanda il margine di pagina, perché legato al claim
   il fianco dell'oggetto cade a `shell + gutter + claim + stacco + 0.4707 *
   larghezza` e oltre un certo punto sfonda il bordo della finestra.
   Rovesciato per la larghezza, con 36px di margine da tenere, è il secondo
   termine del `min`.

   Da lì il comportamento. Da 1200 a 1500 il fianco destro dell'oggetto cade
   esatto sul filo interno dello shell, la stessa verticale del tasto «Parla
   con noi»: 525px a 1200, 620 a 1280, 724 a 1366, 814 a 1440 — e proprio a
   1440 quei 814 coincidono con l'allineamento delle cime, che è dove il
   pareggio fra i due vincoli cade. Sopra, il margine si allarga e l'oggetto
   cresce fino al tetto: 837 a 1600, 882 a 1728, 960 dai 1900 in su, che è la
   misura su cui la composizione è stata approvata — e lì la cima passa una
   cinquantina di pixel sopra il titolo, come nella referenza.

   Un vincolo è stato lasciato cadere di proposito: la punta della base contro
   il secondo tasto. Tenerla fuori limitava la larghezza a 712px a 1440, cioè
   cento sotto l'allineamento, e la base è la parte più bassa e più sottile
   dell'oggetto — lo z-index negativo la fa passare *dietro* il tasto, e a
   schermo si legge come una sovrapposizione voluta, non come una collisione.
   Lo scavalcamento massimo è 21px a 1440 e si annulla sotto i 1280.

   Il terzo termine è l'altezza: ancorato alla base il portatile cresce verso
   l'alto, e su una finestra larga ma bassa gli farebbe passare la cima dietro
   la barra. La base sta a (100svh + 423) / 2 dal bordo alto, la cima deve
   restare 24px sotto la barra e il pieno è alto 0.4914 volte la larghezza: da
   qui `101.75svh + 235px`, che morde solo sotto i 760px di altezza.

   Il contenitore è `.hero`, larga come la finestra: da lì il primo termine di
   `left`, che ritrova il filo dello shell, e il conto di `bottom`. Il blocco
   di testo si centra nella sua colonna, che tiene tutta l'apertura, quindi il
   fondo dei tasti sta a (100% - blocco) / 2 dal bordo basso.

   Il blocco non è una costante, perché due delle sue righe sono il claim: vale
   237px — norme, sommario, tasti e i loro stacchi — più le due righe di
   titolo, cioè 2.08 volte il corpo. Misurato: 423px a 89.6, 402 a 80.1, 386 a
   74.2. Con la costante di prima (423, metà 13.22rem) la base finiva dai 3 ai
   23px sotto la linea dei tasti appena il titolo scendeva sotto il massimo. L'ultimo termine sconta la fascia sotto la base: 13.2% della
   larghezza, ombra che non arriva mai a 61 di alfa e a schermo non si vede ma
   occupa la scatola. Così la base cade esattamente sulla linea dei tasti.

   Lo z-index negativo lo tiene sotto il testo: la colonna di testo non
   dichiara nulla, ma è un elemento flex con `z-index: 2` e ci passa sopra. */
.hero__mock {
  --claim-w: calc(11.8 * var(--text-display));
  --mock-w: min(
    960px,
    calc(
      (100vw - 112px - max(0px, (100vw - var(--shell)) / 2) - var(--claim-w))
      * 2.1245
    ),
    calc(101.75svh + 235px)
  );
  position: absolute;
  z-index: -1;
  left: calc(
    max(0px, (100% - var(--shell)) / 2)
    + clamp(1.25rem, 3vw, 2.25rem)
    + var(--claim-w) + 2.5rem
    - var(--mock-w) * 0.4946
  );
  bottom: calc(
    50%
    - 118.5px - 1.04 * var(--text-display)
    - var(--mock-w) * 0.132
  );
  width: var(--mock-w);
  height: auto;
  pointer-events: none;
}

.hero__mock img {
  display: block;
  width: 100%;
  height: auto;
}

/* Il fianco a fianco vale finché il massimo che entra è un'apertura e non una
   miniatura: a 1200 sono 448px, a 1100 già solo 340. Da lì si impila, con la
   misura e il centraggio del portatile impilato di /product: 620px sul
   tablet, a tutta colonna meno i margini sul telefono. */
@media (max-width: 1199px) {
  /* Fra i 781 e i 1199px la colonna di testo tiene una schermata intera per
     sé, e il portatile comincia dove quella finisce — sotto i 780 passa in
     testa, vedi il blocco del telefono. Serve `min-height` e non basta il
     `flex: 1 0 auto` che ha di suo: quello la fa crescere solo se c'è spazio
     libero, e con il portatile in fila spazio libero non ce n'è più — la
     colonna tornava alta quanto il suo contenuto e il portatile si infilava
     nella prima schermata per farsi tagliare dal bordo. */
  .hero__main { min-height: 100svh; }

  .hero__mock {
    /* `relative` e non `static` perché serve lo z-index: sopra la grana, che è
       `.hero::after` a z-index 1, e fuori dallo shell il portatile non ne
       eredita più il valore. Ma `relative` legge ancora `left` e `bottom`
       della regola del desktop, e li applica come scarti visivi: senza
       azzerarli il portatile finiva ottocento pixel a destra, fuori campo. */
    position: relative;
    left: auto;
    bottom: auto;
    z-index: 2;
    /* I margini di pagina vanno rimessi a mano: fuori dallo shell, `100%` è
       la finestra intera e il portatile arrivava a quattordici pixel dal
       bordo. Il `clamp` è lo stesso dello shell, alla lettera. */
    width: min(calc(100% - 2 * clamp(1.25rem, 3vw, 2.25rem)), 620px);
    margin-inline: auto;
    margin-block: clamp(2rem, 6vw, 3.5rem) 0;
  }
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* ------------------------------------------------------------- partners -- */

/* Loghi e citazione sono un blocco solo: i clienti, e poi la voce di uno di
   loro. Perché si legga così, lo stacco sopra la striscia dev'essere più largo
   di quello che la separa dalla citazione, e le due misure non vengono da qui:
   sopra ci sono i 7.5rem di sezione della galleria, sotto i 7rem di padding
   che la citazione ha dentro di sé. Il padding qui aggiunge la differenza che
   manca sopra, e le righe dopo il blocco tolgono alla citazione lo stacco
   esterno che la allontanerebbe di nuovo.
   `overflow` serve solo a contenere il marquee. */
.partners {
  padding-top: clamp(1.5rem, 3vw, 2.5rem);
  overflow: hidden;
}

/* Sotto la citazione lo stacco si contava tre volte: i 7rem che l'isola ha
   dentro di sé, i 5rem della sezione e i 7.5rem che la roadmap apre sopra —
   312px, quasi il doppio dei 160 che separano la galleria dalla striscia dei
   loghi. Il blocco ora si chiude come si è aperto: l'aria è quella dell'isola,
   e sotto resta una fascia sola. Fissa e non in `vw` perché la parte che
   cresce con la finestra la mette già il padding dell'isola, che sotto i 780
   si dimezza: una fascia proporzionale si dimezzerebbe con lui e il titolo
   della roadmap finirebbe addosso alla citazione. */
.partners + .quote { padding-block: 0; }
.quote + .roadmap { padding-top: 3.5rem; }

.partners__headline {
  text-align: center;
  font-size: var(--text-sm);
  color: var(--fg-faint);
  margin-bottom: 1.5rem;
}

/* Le maschere laterali sfumano i loghi ai bordi invece di tagliarli netti. */
.partners__marquee {
  --marquee-duration: 46s;
  position: relative;
  mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
}

.partners__track {
  display: flex;
  width: max-content;
  animation: marquee var(--marquee-duration) linear infinite;
}

.partners__marquee:hover .partners__track { animation-play-state: paused; }

.partners__group {
  display: flex;
  align-items: center;
  flex: none;
  gap: clamp(2.5rem, 5vw, 4.5rem);
  padding-right: clamp(2.5rem, 5vw, 4.5rem);
}

.partners__item { flex: none; }

/* Sul fondo chiaro i loghi vanno in inchiostro, al 70% perché una fila di
   marchi in nero pieno peserebbe più della citazione che accompagna.
   `brightness(0)` moltiplica per zero e conserva l'alfa: porta al nero sia i
   file neri su trasparente sia quello bianco (Studio Vale, `white: true` in
   data/home.js), che quindi non ha più bisogno di un caso a parte. Il giorno
   che la striscia tornasse su fondo scuro, la coppia da rimettere è
   `brightness(0) invert(1)` qui e `filter: none` sulla variante bianca. */
.partners__logo {
  /* L'altezza arriva da data/home.js, per logo; la larghezza la ricava il
     browser dalla proporzione degli attributi width/height, quindi la casella
     è giusta già prima che il file sia scaricato. */
  width: auto;
  opacity: 0.7;
  filter: brightness(0);
}

.partners__placeholder {
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

/* Lo spostamento è la larghezza esatta di *un* gruppo, che il JS misura e
   scrive in `--marquee-shift` dopo aver clonato quanto serve. Il -50% di
   ripiego vale solo finché i gruppi sono due, cioè senza JS. */
@keyframes marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(var(--marquee-shift, -50%)); }
}

/* Senza animazione la striscia resta ferma, centrata e scorribile a mano. */
@media (prefers-reduced-motion: reduce) {
  .partners__track { animation: none; }
  .partners__marquee { overflow-x: auto; }
}

/* ---------------------------------------------------------- pain points -- */

/* Problema e soluzione in una sezione sola. In alto la domanda, sotto le tre
   situazioni in colonna con un'icona ciascuna, poi i vantaggi allineati a
   sinistra e la bento. Due fili tratteggiati verticali corrono dalle
   situazioni fin sotto i vantaggi: sono loro a dire che le due metà sono la
   stessa cosa — la domanda e la sua risposta. */

.pain__intro {
  text-align: center;
  margin-bottom: clamp(2.5rem, 4.5vw, 3.5rem);
}

.pain__title {
  margin-inline: auto;
  font-size: var(--text-h2);
  letter-spacing: var(--tracking-h2);
  line-height: var(--leading-h2);
  text-wrap: balance;
}

/* Il telaio porta i due fili. Stanno a un terzo e a due terzi, cioè sui
   confini delle tre colonne, e in basso passano dietro il blocco dei vantaggi
   (z-index 0 contro 1) sciogliendosi verso la bento: un filo che si ferma
   netto sopra un titolo si legge come un errore, uno che sfuma no.
   Il tratteggio è un gradiente ripetuto e non `border-style: dashed`: del
   bordo tratteggiato passo e proporzioni li decide il browser, e cambiano da
   uno all'altro. */
.pain__frame { position: relative; }

/* Filo continuo, non tratteggiato: il tratteggio nelle figure vuol dire
   «questo manca», e ripeterlo sui divisori dava alla stessa convenzione due
   significati diversi nella stessa sezione. Qui il filo separa e basta.

   Sfuma da nulla a pieno e torna a nulla: nasce e muore nel vuoto invece di
   tagliare netto contro il bordo della sezione. La rampa di alfa non fa bande
   perché la superficie è larga un pixel — è su campiture larghe che l'alfa a
   otto bit si vede a gradini, non su un filo.

   Il pieno arriva dove stanno le domande: è lì che il divisore serve davvero.

   I due non finiscono alla stessa quota, e non è una svista. Sotto le domande
   c'è il blocco dei vantaggi, allineato a sinistra: il titolo arriva a 797px
   e il sommario a 626. Il divisore di sinistra cade a 456 — dentro il testo —
   e deve spegnersi prima; quello di destra cade a 912, cioè oltre la fine di
   ogni riga, e non ha niente da incrociare. Può quindi scendere fino in
   fondo, e conviene: la sezione si chiude su una verticale sola invece che su
   due mozzate alla stessa altezza. */
.pain__frame::before,
.pain__frame::after {
  content: "";
  position: absolute;
  z-index: 0;
  top: 0;
  bottom: 0;
  width: 1px;
  pointer-events: none;
}

/* A sinistra: spento nel vuoto fra la griglia delle domande e il titolo dei
   vantaggi, che gli passerebbe attraverso. */
.pain__frame::before {
  left: calc(100% / 3);
  background-image: linear-gradient(
    180deg,
    transparent 0%,
    rgba(255, 255, 255, 0.08) 10%,
    rgba(255, 255, 255, 0.24) 26%,
    rgba(255, 255, 255, 0.3) 40%,
    rgba(255, 255, 255, 0.24) 54%,
    rgba(255, 255, 255, 0.08) 64%,
    transparent 70%
  );
}

/* A destra: corre per tutto il telaio. Le stesse quantità, distribuite sul
   doppio della corsa, quindi la salita è più lenta e il pieno più lungo. */
.pain__frame::after {
  left: calc(100% / 3 * 2);
  background-image: linear-gradient(
    180deg,
    transparent 0%,
    rgba(255, 255, 255, 0.08) 7%,
    rgba(255, 255, 255, 0.24) 20%,
    rgba(255, 255, 255, 0.3) 42%,
    rgba(255, 255, 255, 0.3) 62%,
    rgba(255, 255, 255, 0.18) 82%,
    rgba(255, 255, 255, 0.05) 94%,
    transparent 100%
  );
}

.pain__grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

.pain__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0.5rem clamp(1rem, 3vw, 2.5rem) clamp(2.5rem, 4vw, 3.5rem);
  text-align: center;
}

/* Icona grande ma leggera: corpo da titolo, tratto più sottile del set, così
   resta un segno e non un'illustrazione — è quel che la distingue dalle foto
   delle schede degli articoli. */
.pain__icon {
  --icon-size: clamp(2.6rem, 3.4vw, 3.4rem);
  --icon-stroke: 1.2;

  margin-bottom: 1.4rem;
  color: var(--fg);
  opacity: 0.92;
}

/* La figura tiene la colonna in un modo che un glifo non può: qui la misura
   è la sua, non quella del testo accanto. Il margine è lo stesso dell'icona,
   così una colonna con figura e una con glifo restano allineate sul testo. */
.pain__figure {
  /* Sono illustrazioni, non glifi. La colonna è larga 456px con 80 di respiro
     ai lati: qui la figura ne occupa i tre quarti buoni. */
  width: clamp(190px, 22vw, 320px);
  margin-bottom: 1.75rem;
  color: var(--fg);
  /* Lo stesso turchese della parola in evidenza nella domanda qui sotto, e
     non un blu a parte: nella colonna il tratteggio e la parola dicono la
     stessa cosa — quello che manca o che torna — quindi devono essere lo
     stesso segnale. Con due azzurri vicini ma diversi sembravano due sistemi
     accostati per caso. */
  --figure-guide: var(--teal-500);
  --figure-guide-opacity: 1;
  /* Il fondo della zona scura: è quello che i volumi usano per tapparsi a
     vicenda. La grana di .hero-block su questo quasi-nero non arriva a un
     livello, quindi la campitura piena non lascia un buco visibile. */
  --figure-fill: var(--blue-950);
}

/* La domanda è un titolo di colonna, non un paragrafo: famiglia e peso dei
   titoli, e `balance` perché le due righe pesino uguale. */
.pain__q {
  max-width: 22ch;
  font-family: var(--font-display);
  /* Lo stesso gradino dei titoli della bento, non uno sotto. Le due griglie
     stanno una sull'altra nella stessa pagina e dicono cose dello stesso
     rango: tre situazioni e sei capacità. Con venti pixel contro ventisei
     sembrava che le colonne contassero meno delle schede. Esce dal gruppo
     dell'h5 — le domande della FAQ, le tappe della roadmap — e va bene così:
     conta di più il confronto con la griglia che ha sotto. */
  font-size: var(--text-h4);
  font-weight: 600;
  line-height: var(--leading-h4);
  letter-spacing: var(--tracking-h4);
  text-wrap: balance;
}

/* La parola in evidenza non può essere `--accent`: su questa zona l'accento è
   --blue-400, che è più spento del bianco del testo intorno. Una parola più
   spenta delle altre non si legge come evidenziata, si legge come attenuata —
   l'opposto di quel che deve fare.
   Il turchese è l'unica tinta della tavola fuori dal blu e ha quattro volte il
   croma: stacca per colore invece che per luminosità, quindi si vede accanto
   al bianco senza doverlo superare. È lo stesso ruolo che ha altrove nel
   sito — il dettaglio che segnala uno stato — e qui lo stato è il problema. */
.pain__accent { color: var(--teal-500); }

/* Sotto gli 820px le tre colonne si incolonnano e i fili verticali non hanno
   più confini da segnare: fra una situazione e l'altra passa un filo
   orizzontale, dello stesso tratteggio. */
@media (max-width: 820px) {
  .pain__frame::before,
  .pain__frame::after { display: none; }


  .pain__grid { grid-template-columns: 1fr; }

  .pain__item { padding: 2rem 0; }

  .pain__item:first-child { padding-top: 0; }

  .pain__item + .pain__item {
    background-image: repeating-linear-gradient(90deg, var(--line) 0 6px, transparent 6px 13px);
    background-repeat: no-repeat;
    background-size: 100% 1px;
    background-position: top;
  }
}

/* ---------------------------------------------------- solution / bento -- */

/* I vantaggi sono allineati a sinistra, non centrati come la domanda sopra:
   è la risposta, e cambia registro. Stanno dentro il telaio, sopra i fili
   (z-index 1). Il titolo tiene una misura di lettura e va a capo dove deve:
   la riga sola forzata di prima non ha senso in un blocco a bandiera. */
.solution__intro {
  position: relative;
  z-index: 1;
  padding-top: clamp(1.5rem, 3vw, 2.5rem);
  text-align: left;
}

.solution__intro .section__title {
  max-width: 24ch;
  font-size: var(--text-h2);
  text-wrap: balance;
}

.solution__intro .section__lead { max-width: 58ch; }

/* Griglia bento: sei schede su un telaio asimmetrico.
     a  b  b  c
     a  d  e  f
   `a` è alta due righe, `b` larga due colonne: sono le due voci di punta
   (Evidenza continua e Formazione efficace). */
.bento {
  /* Le schede stanno nel mondo dei render: lo stesso navy del fondo delle
     immagini (#05161e), solo un passo più chiaro. Il velo bianco translucido
     di prima le faceva grigie, fuori famiglia.
     Resta fuori dalla rampa, ma di poco meno di prima: sta a 201°, e la
     rampa campionata dal master a questa luminosità sta a 208.7° — sette gradi,
     non nove. Non è una svista: è agganciato ai PNG, e i PNG non ruotano con
     la tavola. Spostarlo qui staccherebbe la scheda dall'immagine che
     contiene, che è un difetto più visibile dei sette gradi. Si chiude
     riesportando i render, non ritoccando questa riga. */
  --card: var(--neutral-11);

  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-areas:
    "a b b b"
    "a c d d"
    "e e f f";
  grid-auto-rows: minmax(13rem, auto);
  gap: 1.1rem;
  margin-top: clamp(3rem, 5vw, 4rem);
}

.bento__card:nth-child(1) { grid-area: a; }
.bento__card:nth-child(2) { grid-area: b; }
.bento__card:nth-child(3) { grid-area: c; }
.bento__card:nth-child(4) { grid-area: d; }
.bento__card:nth-child(5) { grid-area: e; }
.bento__card:nth-child(6) { grid-area: f; }

.bento__media {
  position: absolute;
  pointer-events: none;
  /* `screen`: il nero del render sparisce e resta solo il vetro. È questo a
     liberare l'arte dal suo quadrato — può stare a qualunque misura, uscire
     dagli angoli, appoggiarsi su schede di qualunque chiarezza. */
  mix-blend-mode: screen;
  /* I bordi del file si sciolgono comunque: se una scheda taglia l'arte, il
     taglio non mostra mai lo spigolo del quadrato. */
  -webkit-mask-image: radial-gradient(closest-side, #000 58%, transparent 92%);
  mask-image: radial-gradient(closest-side, #000 58%, transparent 92%);
}

/* Misure e posizioni per scheda, come le vecchie illustrazioni CSS: alcune
   grandi e debordanti, altre contenute — è lo sbilanciamento a dare ritmo. */
/* La torre non è più fotografica: la lente è vetro come le altre, quindi torna
   nel trattamento comune — `screen` e maschera radiale, senza il caso a parte
   che serviva alla foto della mano. Essendo alta due righe l'arte sta in cima
   e il testo le resta sotto, invece di dividersi la stessa fascia. */
/* Il riquadro non è l'oggetto: nei render il vetro occupa il 78% centrale, e
   la maschera ne scioglie il resto. Quindi per riempire una scheda serve un
   riquadro ~1,3 volte lo spazio che si vuole coprire, e conviene spingerlo
   verso il testo invece che schiacciarlo contro il bordo destro — è lì che si
   aprivano le bande morte. */
/* Il vetro non riempie il file: in ogni render occupa una porzione diversa
   (gli anelli sono larghi e bassi, la cartella quasi quadrata), e la maschera
   ne scioglie il contorno. Quindi le misure qui sotto non sono a occhio: il
   riquadro è calcolato perché l'oggetto — non la sua scatola — arrivi a filo
   del margine destro e stia dentro l'altezza della scheda. Così il taglio
   della scheda cade dove la maschera ha già sciolto il vetro e non si vede il
   filo netto, e fra testo e oggetto non restano bande morte. */
.bento__card:nth-child(1) .bento__media { width: 389px; right: -31px; top: -47px; transform: scaleX(-1); }
/* La scala è la sola che sborda di proposito: la scheda è larga tre colonne e
   alta una, e per coprirla deve uscire sopra e sotto. */
.bento__card:nth-child(2) .bento__media { width: 523px; right: -51px; top: -157px; }
.bento__card:nth-child(3) .bento__media { width: 273px; right: -66px; top: -31px; }
.bento__card:nth-child(4) .bento__media { width: 303px; right: -27px; top: -48px; }
.bento__card:nth-child(5) .bento__media { width: 316px; right: -28px; top: -54px; }
.bento__card:nth-child(6) .bento__media { width: 271px; right: -22px; top: -32px; }

/* Scheda scura con un alone appena percettibile in un angolo, come nei
   riferimenti. L'alone è neutro — bianco a bassa opacità, non una tinta — così
   resta luce e non colore, e si adatta da sé al fondo su cui sta. L'angolo
   alterna da una scheda all'altra. */
.bento__card {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 1.7rem;
  border-radius: var(--radius-lg);
  background:
    radial-gradient(70% 60% at 85% 90%, rgba(255, 255, 255, 0.07), transparent 70%),
    var(--card);
  overflow: hidden;
  transition: background-color 0.25s var(--ease), transform 0.25s var(--ease);
}

/* Alone che segue il puntatore: la posizione arriva da due custom property
   aggiornate al `pointermove`, così si muove senza toccare il layout. */
.bento__card::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s var(--ease);
  background: radial-gradient(
    18rem 18rem at var(--mx, 50%) var(--my, 50%),
    rgba(255, 255, 255, 0.10),
    transparent 70%
  );
}

@media (hover: hover) {
  .bento__card:hover::after { opacity: 1; }
}

/* L'illustrazione deriva appena: dà profondità senza spostare il testo. */
.bento__art {
  transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}

@media (hover: hover) {
  .bento__card:hover .bento__art { transform: scale(1.05) translateY(-4px); }
}

.bento__card:nth-child(even) {
  background:
    radial-gradient(70% 60% at 12% 8%, rgba(255, 255, 255, 0.075), transparent 70%),
    var(--card);
}

.bento__card:hover { transform: translateY(-3px); }

.bento__art {
  position: absolute;
  pointer-events: none;
  opacity: 0.9;
}

/* a — alta: pianeta grande in alto, tagliato a destra */
.bento__card:nth-child(1) .bento__art { width: 330px; top: -70px; right: -110px; }
/* b — larga: bersaglio a destra, tagliato in alto e a destra */
.bento__card:nth-child(2) .bento__art { width: 300px; top: -60px; right: -70px; }
/* c — checklist in basso a destra */
.bento__card:nth-child(3) .bento__art { width: 220px; bottom: -96px; right: -104px; }
/* d — alta a destra: pila di livelli in alto, tagliata a destra */
.bento__card:nth-child(4) .bento__art { width: 300px; top: -40px; right: -90px; }
/* e — larga: radar a destra */
.bento__card:nth-child(5) .bento__art { width: 265px; top: 50%; transform: translateY(-50%); right: -86px; }
/* f — sigillo in basso, tagliato sotto */
.bento__card:nth-child(6) .bento__art { width: 235px; bottom: -86px; right: -72px; }

.bento__text { position: relative; z-index: 1; }

.bento__title {
  font-size: var(--text-h4);
  line-height: var(--leading-h4);
  letter-spacing: var(--tracking-h4);
  margin-bottom: 0.5rem;
}

.bento__body {
  color: var(--fg-muted);
  font-size: var(--text-sm);
  max-width: 44ch;
  text-wrap: pretty;
}

.bento__title { text-wrap: balance; }

/* Nelle schede con oggetto a destra il testo tiene la metà sinistra. */
/* Larghezza del testo per scheda: si ferma prima dell'oggetto, che nelle
   schede larghe entra parecchio dentro. `text-wrap: pretty` evita la parola
   isolata sull'ultima riga. */
.bento__text { max-width: 100%; }

.bento__card:nth-child(1) .bento__text { max-width: 92%; }
.bento__card:nth-child(2) .bento__text { max-width: 62%; }
.bento__card:nth-child(3) .bento__text { max-width: 66%; }
.bento__card:nth-child(4) .bento__text { max-width: 60%; }
.bento__card:nth-child(5) .bento__text { max-width: 56%; }
.bento__card:nth-child(6) .bento__text { max-width: 62%; }

/* Nella scheda alta l'icona respira di più. Il titolo no: cresceva di un
   gradino e nella griglia si leggeva come una gerarchia che non c'è — le sei
   schede dicono sei cose dello stesso rango. Tutti a --text-h4, corpo a
   --text-sm, come li porta .bento__title. */

/* Sotto i 1000px cade il telaio asimmetrico e le schede scendono sotto i
   470px. A quella misura l'oggetto non sta più dietro il testo: è disegnato
   per una scheda larga, arriva a 300px e la copre quasi tutta, così titolo e
   corpo finivano sopra il vetro illeggibili. Da qui in giu' la scheda si
   impagina come quelle dei tre problemi — banda d'arte in cima, testo sotto
   sulla superficie pulita — e l'arte torna a vedersi intera invece che
   tagliata di sbieco dietro le parole.

   Le aree nominate spariscono: reggevano una scheda alta due righe, e con le
   schede a impaginazione libera l'altezza doppia si trascinerebbe dietro un
   vuoto. */
@media (max-width: 1000px) {
  .bento {
    grid-template-columns: repeat(2, 1fr);
    grid-template-areas: none;
    grid-auto-rows: auto;
  }

  .bento__card:nth-child(n) { grid-area: auto; }

  .bento__card {
    min-height: 0;
    justify-content: flex-start;
    padding: 0;
  }

  /* Stessa specificità delle regole per scheda qui sopra (0,3,0): stando
     dopo vincono senza bisogno di !important. */
  .bento__card:nth-child(n) .bento__media,
  .bento__card:nth-child(n) .bento__art {
    position: relative;
    inset: auto;
    flex: none;
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 10;
    object-fit: cover;
    transform: none;
    opacity: 1;
    /* Il taglio netto sopra il titolo si vedrebbe come un bordo: l'arte si
       scioglie verso il basso, come già fa ai lati sul desktop. */
    -webkit-mask-image: linear-gradient(180deg, #000 62%, transparent 99%);
    mask-image: linear-gradient(180deg, #000 62%, transparent 99%);
  }

  /* Lo `transform: none` qui sopra serve a togliere i centraggi: la lente però
     è specchiata di posa, non di posizione, e va rimessa. Stessa specificità,
     sta dopo, vince. */
  .bento__card:nth-child(1) .bento__media { transform: scaleX(-1); }

  .bento__card:nth-child(n) .bento__text {
    max-width: none;
    padding: 0 1.4rem 1.5rem;
  }
}

@media (max-width: 620px) {
  .bento {
    grid-template-columns: 1fr;
    gap: 1.5rem;
  }
}

/* ------------------------------------------------------- prod. overview -- */

/* Zona prodotto, quote e roadmap: un'unica tinta piatta di foschia, uguale
   per tutte le sezioni chiare di fila. Prima ognuna dipingeva il proprio
   `--grad-mist`: la diagonale ripartiva da capo a ogni confine e lasciava una
   riga netta fra un blocco e l'altro. `--section-grey` si ribalta da sé dentro
   `.on-dark`, quindi non serve una seconda regola per il tema scuro. */
.overview,
.showcase-section,
.partners,
.quote,
.roadmap {
  background: var(--section-grey);
}

/* Testata alla Linear: titolo e frase incolonnati a sinistra, e basta. Il
   richiamo stava qui in fondo a destra, appoggiato all'ultima riga della
   frase: ma il fondo di una riga di testo non è una linea che si vede, e con
   la frase a due righe il tasto finiva a mezz'aria in mezzo al bianco. È sceso
   sotto la schermata, in fondo alla colonna — vedi `.overview__cta`. Da un
   solo figlio il flex ereditato non serve più. */
.overview__head {
  display: block;
  /* In `vh` e non in `vw`. La schermata sotto è intera e più alta della
     finestra: quel che si decide qui non è quanto sia larga la pagina ma
     quanta cattura entra sopra la piega. Su una finestra bassa lo stacco si
     stringe da sé e la schermata sale. */
  margin-bottom: clamp(1.75rem, 4.5vh, 3rem);
}

.overview__head-text { min-width: 0; }

.overview__head .section__title { max-width: 24ch; }

.overview__head .section__lead { margin-top: 1rem; max-width: 54ch; }

/* Il richiamo chiude la colonna, sotto la schermata. Ha girato due volte:
   prima in fondo alla testata, appoggiato all'ultima riga della frase — ma il
   fondo di una riga di testo non è una linea che si vede, e con la frase a due
   righe il tasto finiva a mezz'aria; poi in fondo alla riga dei livelli, dove
   però era il quinto comando accanto a quattro voci, e l'unico a pastiglia:
   la riga smetteva di leggersi come un indice e cominciava a leggersi come una
   barra di pulsanti. Sotto la schermata non ha niente con cui competere,
   arriva dopo che si è visto di che si parla, e chiude la colonna sullo stesso
   asse su cui stanno menu e schermata.

   `justify-self` e non `margin-inline: auto`: in griglia basta quello a
   centrarlo lasciandogli la larghezza della sua etichetta. */
.overview__cta { justify-self: center; }

.overview__link {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--accent);
  white-space: nowrap;
}

.overview__link:hover { text-decoration: underline; }

/* Niente uscita dal shell sugli schermi larghi. La schermata l'aveva finché
   il menu le stava a fianco e le restavano 1030px: allargarla era l'unico
   modo di non lasciarla francobollo. Adesso il menu è sopra e la schermata
   prende già tutta la colonna, e i 176px per lato che guadagnava a 1920 li
   pagava in disallineamento — il bordo dell'immagine cadeva fuori dal titolo
   e fuori dal binario, che sono le due verticali su cui la fascia si regge.
   Il resto della pagina si ferma a 1440: qui si ferma con loro. */

/* Il menu non è più una colonna a fianco: è una riga sotto la testata. Con la
   colonna da 300px una cattura da 2160px viveva nei 1100 che restavano —
   ridotta a metà proprio dove c'è l'interfaccia da leggere. */
.overview__app {
  /* Quel che la schermata non può usare in altezza. Non è più un numero a
     occhio: è la somma di quel che le sta intorno dentro la finestra, misurata
     pezzo per pezzo. La testata della sezione non entra nel conto — quando si
     guarda la galleria il titolo è già passato sopra — ma tutto il resto sì,
     perché menu, schermata e richiamo si guardano insieme.

     barra fissa    var(--header-h)   72
     riga livelli   menu + frase      80
     due stacchi    gap dell'app    2x40
     richiamo       il tasto          49
     aria                             47   (sopra il menu e sotto il tasto)
                                     ---
                                     328 = --header-h + 16rem

     Misurato dopo: fra i 600 e i 1080 di finestra al blocco avanzano sempre
     fra i 47 e i 75 pixel, e la schermata cresce di 128 a ogni misura rispetto
     ai 25rem tondi di prima. */
  --overview-above: calc(var(--header-h) + 16rem);

  /* La colonna della fascia. La detta la schermata — è lei che deve entrare
     intera in quel che resta di finestra — e il menu ci si incolonna sopra.
     16/9 era il rapporto esatto delle catture, quando erano 2160x1215: la
     larghezza è l'altezza disponibile tradotta in larghezza. Le catture oggi
     sono 2160x1350, cioè 16/10, quindi la schermata esce un decimo più alta
     del budget — la fascia resta leggibile, ma non entra più esatta come la
     misura qui sotto promette. Il `max()` è il fondo: sotto
     quella misura la schermata smette di stringersi e sfora la finestra,
     perché un francobollo in mezzo al vuoto è un guasto, e uno sforo si
     scorre. */
  --overview-w: max(38rem, calc((100svh - var(--overview-above)) * 16 / 9));

  display: grid;
  /* Come lo stacco della testata: in `vh`, perché è altezza che la schermata
     paga alla piega. */
  gap: clamp(1.5rem, 4vh, 2.5rem);
}

/* Il menu sta nella colonna della schermata, non in quella del blocco, e si
   centra su di lei. Largo quanto tutto il blocco — con la schermata che ne
   occupava poco più della metà — la riga dei livelli cominciava trecento
   pixel prima del riquadro che governa: due misure diverse per una cosa sola,
   e il richiamo in fondo a destra allungava la riga oltre il bordo
   dell'immagine. Stessa larghezza e stesso asse: adesso si leggono insieme. */
.overview__menu {
  display: grid;
  justify-items: center;
  gap: 0.9rem;
  width: 100%;
  max-width: var(--overview-w);
  justify-self: center;
  text-align: center;
}

/* Nessun binario continuo sotto la riga. Ce n'era uno, largo quanto tutto il
   blocco, per dire a che punto si è nella sequenza: ma sotto la riga corre già
   il bordo superiore della schermata, e due orizzontali a poche decine di
   pixel l'una dall'altra tagliavano la sezione in fasce invece di legarla.
   A dire qual è il livello acceso basta il suo segmento. */
/* Parole nude, quindi lo stacco è quello di una riga di voci e non di
   quattro tasti: largo abbastanza da non leggerle come una frase, stretto
   abbastanza da tenerle un gruppo. Il margine verticale serve solo quando la
   riga va a capo su finestre strette. */
.overview__nav {
  display: flex;
  flex-wrap: wrap;
  /* Conta solo quando la riga va a capo: le voci spezzate restano sull'asse
     invece di accumularsi a sinistra. */
  justify-content: center;
  gap: 0.25rem clamp(1.25rem, 2.5vw, 2rem);
}

/* Le voci sono parole sottolineate, non pastiglie. Per un po' il livello
   acceso è stato una parola dentro una pastiglia grigia, la stessa forma del
   tasto in fondo alla riga: ma quattro tasti più un tasto si leggevano come
   cinque comandi dello stesso rango, e la riga smetteva di essere un indice.
   Il livello acceso lo dice la riga sotto la parola, che si stende da
   sinistra. Niente padding orizzontale: quel segno deve essere lungo quanto la
   parola, non quanto una scatola che la contiene, e senza padding le quattro
   voci si distanziano solo col `gap` della riga — una misura sola da
   guardare quando la riga si centra sulla schermata. */
.overview__tab {
  position: relative;
  padding: 0.3rem 0;
  border: 0;
  background: transparent;
  /* Le voci inattive erano troppo tenui per leggersi come menu. */
  color: var(--fg-faint);
  font: inherit;
  /* A sinistra: la voce è larga quanto il fantasma in grassetto (vedi
     .overview__tab-label::after), quindi da spenta la parola è più stretta
     della sua scatola. Senza sfondo l'eccedenza non si vede, e a sinistra la
     parola resta ferma sulla verticale del titolo a ogni peso. */
  text-align: left;
  cursor: pointer;
  transition: color 0.25s var(--ease);
}

.overview__tab:hover { color: var(--fg-muted); }

.overview__tab.is-active { color: var(--fg); }

/* La riga sotto la voce accesa. Sta su uno pseudo-elemento e non su
   `border-bottom` o `text-decoration`: comparendo non sposta niente, e copre
   la scatola — larga quanto la parola in grassetto, cioè quanto la parola da
   accesa. In `currentColor` e non nell'accento: è un segno di posizione, non
   un invito. */
.overview__tab::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1.5px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.32s cubic-bezier(0.16, 1, 0.3, 1);
}

.overview__tab.is-active::after { transform: scaleX(1); }

.overview__tab-label {
  font-weight: 500;
  /* Un gradino sopra le altre etichette: sono le quattro voci che reggono la
     galleria, non un titolo di scheda qualsiasi. */
  font-size: var(--text-h4);
  /* L'interlinea di corpo del sito (1.65) su questo corpo fa una riga da 38px
     per una lettera da 23: era quella, e non il padding, a gonfiare la
     voce. Una parola sola non ha interlinea da rispettare. */
  line-height: var(--leading-h4);
  letter-spacing: var(--tracking-h4);
  transition: font-weight 0.2s var(--ease);
}

.overview__tab.is-active .overview__tab-label { font-weight: 700; }

/* Da accesa la voce ingrassa. In colonna non spostava niente; in riga
   sposterebbe di lato le tre che seguono a ogni cambio. Il fantasma —
   la stessa parola in grassetto, alta zero e invisibile — tiene la voce
   già larga come sarà da accesa, e la riga non si muove più. */
.overview__tab-label::after {
  content: attr(data-label);
  display: block;
  height: 0;
  overflow: hidden;
  visibility: hidden;
  font-weight: 700;
}

/* La frase del livello acceso. Le quattro stanno nella stessa cella e si
   incrociano in dissolvenza come le schermate: la riga è alta quanto la più
   lunga, quindi al cambio di livello niente si sposta. */
.overview__note { display: grid; justify-items: center; }

.overview__note-line {
  grid-area: 1 / 1;
  max-width: 68ch;
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--fg-muted);
  text-wrap: pretty;
  opacity: 0;
  transition: opacity 0.3s var(--ease);
}

.overview__note-line.is-active { opacity: 1; }

/* La schermata si rimpicciolisce finché ci sta, intera. Sotto la testata e la
   riga dei livelli non resta abbastanza finestra per un 16:9 largo quanto il
   blocco: si è provato a tagliarlo in fondo, ed è peggio — di una cattura
   mozzata non si fida nessuno. Quindi resta intera e cede in scala.

   Il tetto si mette sulla LARGHEZZA e non sull'altezza: l'immagine sta a
   `width: 100%; height: auto`, quindi è la colonna a decidere l'altezza.
   Stringendo la colonna scendono insieme, e l'alone su ::before resta
   incollato al bordo dell'immagine invece di allargarsi oltre. La misura è
   `--overview-w`, dichiarata su `.overview__app` perché adesso non è solo
   sua: anche il menu ci si allinea.

   Centrata e non a filo di colonna: da stretta, appoggiata a sinistra,
   lascerebbe un vuoto grande quanto lei alla sua destra. */
.overview__screen {
  display: grid;
  min-width: 0;
  width: 100%;
  max-width: var(--overview-w);
  /* `justify-self` e non `margin-inline: auto`: con i margini automatici la
     cella smette di distendersi e si misura sul contenuto, che qui e'
     un'immagine a `width: 100%` — larghezza indefinita, quindi zero. */
  justify-self: center;
}

/* Alone dietro la schermata. Lo screenshot è quasi tutto bianco e la sezione è
   foschia chiara: senza, i due si confondono e il filo di contorno da solo non
   basta a staccarli. Sta su ::before invece che sull'immagine perché i pannelli
   si incrociano in dissolvenza — due ombre sovrapposte a metà transizione si
   sommano e si vedrebbero pulsare. Come primo elemento della cella viene
   dipinto sotto i pannelli senza bisogno di z-index, e senza margini si
   allinea esattamente alla schermata.

   Nessuno scostamento verticale: non è la caduta di un oggetto illuminato
   dall'alto ma un alone centrato sulla schermata, che si allarga uguale dai
   quattro lati. Tre raggi larghi e molto trasparenti invece di pochi e densi:
   la profondità la fa l'estensione della sfumatura, non la sua intensità. */
.overview__screen::before {
  content: "";
  grid-area: 1 / 1;
  border-radius: var(--radius-lg);
  box-shadow:
    0 0 70px rgba(var(--ink-rgb), 0.06),
    0 0 160px 10px rgba(var(--ink-rgb), 0.07),
    0 0 280px 30px rgba(var(--ink-rgb), 0.07);
}

/* I pannelli si sovrappongono nella stessa cella: quello uscente sfuma
   mentre l'entrante sale, invece di essere sostituito di colpo. */
.overview__panel {
  grid-area: 1 / 1;
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  transition: opacity 0.32s var(--ease), transform 0.42s cubic-bezier(0.16, 1, 0.3, 1);
}

.overview__panel[hidden] { display: block; }

.overview__panel.is-active {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

/* Didascalia del pannello: la porta il template dentro ogni schermata e la
   si vede solo dove il menu non c'è, cioè sul telefono. */
.overview__panel-label { display: none; }

.overview__panel-name {
  font-size: var(--text-h4);
  line-height: var(--leading-h4);
  font-weight: 700;
  letter-spacing: var(--tracking-h4);
  color: var(--fg);
}

.overview__panel-sub {
  font-size: var(--text-sm);
  color: var(--fg-muted);
}

/* La schermata è 16:9 e larga quanto il blocco: da sola è più alta di quanto
   resti di finestra sotto la testata, quindi la sezione in una schermata non
   ci sta. Si è provato a tagliarla in fondo per farcela stare — riquadro alto
   quanto avanzava e `object-fit: cover` — ed è la strada sbagliata: la cattura
   è la prova di quel che il prodotto fa, e mostrarne un terzo la indebolisce
   invece di invogliare. Nessun tetto e nessun ritaglio, quindi: la schermata
   si vede intera e quel che sfora si raggiunge scorrendo, come ogni altro
   blocco della pagina. A guadagnare terreno sopra ci pensano gli stacchi, che
   si misurano in altezza di finestra (vedi `.overview__head`). */
.overview__shot {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  /* Solo il filo di contorno, nessuna ombra. */
  box-shadow: inset 0 0 0 1px rgba(var(--ink-rgb), 0.1);
}

@media (prefers-reduced-motion: reduce) {
  .overview__panel,
  .overview__note-line,
  .overview__tab,
  .overview__tab::after { transition: none; }
}

/* Sul telefono la galleria smette di essere una galleria e diventa una
   colonna. Sul desktop la riga di nomi sceglie la schermata, che cambia nello
   stesso riquadro; sotto i 900px quella riga non ci sta, e un riquadro che
   cambia solo toccando una voce non lo dice a nessuno.

   Una striscia da scorrere di lato non risolveva: il gesto orizzontale dentro
   una pagina che scorre in verticale va scoperto, e comunque le schermate
   sono catture di un'interfaccia da 1440px — a 350 sono ridotte quattro
   volte, quindi qualunque cosa si faccia non si leggono. Tanto vale toglierle
   di mezzo dal gesto e metterle dove arrivano da sole: una sotto l'altra,
   ognuna col suo nome sopra, che compaiono scorrendo come tutto il resto
   della pagina. Nessun gesto nuovo da imparare, niente tagliato, e lo stesso
   ordine di lettura che ha /product. */
@media (max-width: 900px) {
  .overview__app { grid-template-columns: 1fr; }

  /* Il menu era la mappa di un riquadro che cambiava contenuto al suo posto.
     Incolonnate, le schermate si annunciano da sé — ognuna porta il proprio
     nome e la propria frase in `.overview__panel-label`. Via la riga di nomi
     e via la frase che la accompagnava: sarebbero due modi di dire la stessa
     cosa, e la frase per giunta ripetuta parola per parola. Il richiamo invece
     resta: se ne va il menu, non lui — e sta dove sta sul desktop, in fondo,
     dopo l'ultima schermata. */
  .overview__menu { display: none; }

  /* A filo di colonna e non centrato: qui la schermata prende tutta la
     larghezza del blocco, quindi il suo asse è il bordo sinistro, lo stesso
     del titolo e dei nomi sopra ogni cattura. */
  .overview__cta { justify-self: start; }

  .overview__screen {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(3.5rem, 11vw, 5rem);
    /* Nessun tetto: le quattro schermate sono incolonnate e si scorrono, non
       devono stare tutte insieme in una finestra. */
    max-width: none;
  }

  /* L'alone stava incollato all'unica schermata ferma al centro. Su quattro
     incolonnate sarebbe un quinto elemento in fila. */
  .overview__screen::before { display: none; }

  .overview__panel {
    grid-area: auto;
    /* Non si incrociano più in dissolvenza: ci sono tutte insieme. L'entrata
       resta, ma è quella di scorrimento che ha ogni altro blocco del sito —
       la aggiunge il JS marcando i pannelli con `data-enter`, solo qui. */
    opacity: 1;
    transform: none;
    pointer-events: auto;
    transition: none;
  }

  .overview__panel-label {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    margin-bottom: 1rem;
  }

  .overview__shot { width: 100%; }
}

/* --- vetrina prodotto -----------------------------------------------------
   Usata in /product. Ogni livello è una fascia a tre righe sovrapposte —
   testata, scena, piede — e non più una schermata affiancata a una colonna di
   testo che si alternava di lato.

   Il cambio non è di gusto. Con la schermata a fianco il testo stava in una
   colonna da 472px: quattro righe da quaranta battute per una frase che è il
   cuore del livello, mentre l'immagine ne prendeva il doppio. E l'alternanza
   destra/sinistra obbligava l'occhio a ricominciare da capo a ogni blocco.
   Impilando, la frase prende metà dello shell e misura cinquantatré battute
   per riga da 600px in su — la stessa misura a ogni larghezza, perché la
   colonna e il corpo crescono insieme. Il titolo trova il corpo di un titolo
   di sezione, e la schermata può andare al vivo invece di stare dentro una
   scheda larga due terzi.

   Le tre righe condividono la stessa griglia a metà: titolo, etichetta del
   piede e bordo della scena cadono sulla prima verticale, frase ed elenco
   sulla seconda. È quell'incolonnamento a fare la fascia; non serve un
   riquadro intorno, e infatti la scena non ce l'ha più. */
.showcase {
  display: grid;
  gap: clamp(4.5rem, 11.5vw, 10.5rem);
}


/* --- testata: titolo a sinistra, frase a destra --------------------------- */

.feature__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(1.75rem, 4vw, 4rem);
  /* In basso, non in alto. Le due colonne parlano a corpi lontanissimi — 48px
     di display a interlinea 1.02 contro 22px a 1.42 — e appoggiate in cima le
     prime righe non si incontrano da nessuna parte: il titolo parte più alto
     della frase di quanto le due maiuscole siano diverse. È il piede la riga
     che hanno in comune, ed è quella che si allinea. Dove la frase e il titolo
     sono alti uguale (cinque fasce su sei, 95px per parte) non cambia niente;
     dove la frase va a quattro righe — «Performance Training», 201 battute —
     è il titolo a scendere a incontrarla invece di restare sospeso in cima. */
  align-items: end;
  padding-bottom: clamp(2.5rem, 6.6vw, 6.5rem);
}

/* Interlinea 1.02 e non la 1.15 di una testata qualsiasi: il titolo è di due
   righe sempre — l'a capo lo porta il testo, con un <br> preso da
   `titleLines` in data/product.js — e a passo largo le due righe si leggevano
   come due titoli separati.
   Nessun `text-wrap: nowrap` a difendere quell'a capo: se un giorno una riga
   non entrasse nella colonna, meglio che vada a capo da sé — con `nowrap`
   sborderebbe nella colonna della frase e ci finirebbe sopra. */
.feature__title {
  font-size: var(--text-h2);
  line-height: var(--leading-h2);
  letter-spacing: var(--tracking-h2);
}

/* Circa 47 battute per riga, che è la misura della colonna di Linear. Misurato
   a 1440 sui sei passi di data/how-it-works.js: sotto le 160 battute la frase
   sta in tre righe piene, oltre passa a quattro (è il caso di «Performance
   Training», 201 battute). Il tetto vero non è il numero di righe ma la
   testata: la colonna destra può crescere quanto vuole finché resta più bassa
   dello spazio che il titolo di due righe si porta dietro — oltre, è la frase
   a dettare l'altezza della fascia e l'incolonnamento si perde. */
.feature__desc {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 400;
  line-height: var(--leading-lg);
  color: var(--fg-muted);
  max-width: 48ch;
  text-wrap: pretty;
}

/* --- scena: la schermata che sfuma in basso ------------------------------- */

/* La scena sta nello shell come tutto il resto, e non al vivo della finestra.
   Misurato su linear.app a 1440: l'illustrazione di tre sezioni su quattro sta
   esattamente nel contenitore del testo — da 48 a 1392, cioè le stesse due
   verticali del titolo e della frase — e le due che ne escono lo fanno da un
   lato solo, dove la scena continua fuori campo. Al vivo su entrambi i lati si
   perde proprio l'incolonnamento che regge la fascia: il bordo della schermata
   non cade più sotto niente.

   Una sola maschera, quindi, e su un asse solo: la caduta in basso. Sui fianchi
   non serve — lì la schermata ha un bordo vero, ed è giusto che si veda, perché
   è un oggetto intero dentro la colonna e non un frammento che esce dal quadro.

   Lo sfondo è il colore della tela delle schermate, campionato: #eef2fa. Non si
   vede quasi mai — l'immagine lo copre tutto — ma evita che, se un giorno
   arrivasse un'immagine di proporzioni diverse, si apra una fessura di fondo
   pagina in mezzo alla scena. */
.feature__stage {
  position: relative;
  height: clamp(16rem, 44vw, 41rem);
  background: var(--bg);
  /* Solo in cima: sotto non c'è angolo da smussare, la dissolvenza è già
     arrivata a zero molto prima del bordo. */
  border-radius: var(--radius) var(--radius) 0 0;
  /* Il raggio da solo ritaglia lo sfondo dell'elemento, non i figli: a tenere
     dentro gli angoli la schermata è questo. In Chrome la maschera la ritaglia
     già di suo, ma è un dettaglio di come Blink compone il livello — non una
     garanzia, e altrove l'angolo quadrato dell'immagine spunterebbe da quello
     tondo della scena. */
  overflow: hidden;
  /* Dove comincia la caduta, e con che curva.

     A 1440 la scena misura 1353x634 e la schermata, larga quanto lei, viene
     disegnata alta 846: `cover` si porta gia' via l'ultimo quarto dell'immagine
     prima ancora che la maschera cominci. Con la partenza al 62% restava piena
     solo la meta' superiore della schermata — misurato, 393px su 846 — e la
     dissolvenza si mangiava il titolo di sezione e la prima fila di schede,
     cioe' la parte che dice che cosa si sta guardando.

     La partenza va quindi al 76%: pieni 481px su 634, e la caduta occupa gli
     ultimi 152. Ma spostarla in basso e basta la renderebbe piu' ripida, e una
     rampa lineare corta si legge come una riga: l'occhio vede il punto esatto
     in cui l'alfa smette di essere 1. Percio' la rampa non e' lineare ma una
     smoothstep — 3t^2-2t^3 campionata in sette punti: parte piatta, quindi
     l'attacco non ha un bordo, e arriva a zero piatta sul fondo della scena,
     dove il taglio dev'essere gia' avvenuto. Il grosso della perdita si
     concentra a meta' rampa, lontano da tutt'e due gli estremi.

     La rampa e' corta e questo va a favore: a 152px un'alfa a otto bit scende
     di quasi due livelli per riga, molto sopra la soglia in cui si aprono le
     bande. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 76%,
      rgba(0, 0, 0, 0.94) 79.6%, rgba(0, 0, 0, 0.78) 83.2%,
      rgba(0, 0, 0, 0.58) 86.8%, rgba(0, 0, 0, 0.43) 89.2%,
      rgba(0, 0, 0, 0.22) 92.8%, rgba(0, 0, 0, 0.06) 96.4%,
      transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0, #000 76%,
      rgba(0, 0, 0, 0.94) 79.6%, rgba(0, 0, 0, 0.78) 83.2%,
      rgba(0, 0, 0, 0.58) 86.8%, rgba(0, 0, 0, 0.43) 89.2%,
      rgba(0, 0, 0, 0.22) 92.8%, rgba(0, 0, 0, 0.06) 96.4%,
      transparent 100%);
}

/* Il filo di contorno. Su Linear la scena non ce l'ha e non le serve: è UI
   accesa su fondo quasi nero, si stacca da sé. Qui la tela della schermata è
   (238,242,250) e la velatura della pagina le sta due o tre livelli distante —
   senza filo la scena non ha un bordo, ha una sfumatura di grigio.

   La maschera della scena vale anche qui, perché il filo le sta dentro, ma non
   basta. A parità di alfa una riga di un pixel resta visibile molto più a lungo
   di una schermata: l'occhio legge i bordi, e un contorno netto al 20% è ancora
   un contorno mentre il testo dietro è già sparito. Misurato, i due fili
   verticali arrivavano interi in fondo alla scena e finivano di netto — una
   cornice intorno a un contenuto che non c'era più.

   Perciò il filo porta una seconda maschera, più ripida di quella della scena:
   pieno per il primo quarto, dove stanno gli angoli tondi ed è lì che deve
   leggersi, e poi una rampa lunghissima fino al 94%. Le due maschere si
   moltiplicano perché stanno su elementi diversi, ed è quel prodotto a contare:
   al 76%, dove la scena è ancora piena, il filo è già sceso a un quarto e fa la
   cornice senza chiuderla; al 94% è a zero, quando alla schermata resta un
   sedicesimo scarso di alfa. Il filo muore così sempre un attimo prima del
   contenuto, che è l'ordine giusto — una cornice non deve sopravvivere a quello
   che incornicia. La rampa lunga fa sì che non finisca da nessuna parte in
   particolare: si spegne e basta.

   L'estremo era al 78% e con la scena piena fino al 76% sarebbe morto quasi di
   colpo, con la schermata ancora intera dentro: due fili verticali che si
   fermano a tre quarti di un contenuto che continua. */
.feature__stage::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  /* Della famiglia --line, come ogni altro filo del sito. */
  box-shadow: inset 0 0 0 1px rgba(var(--ink-rgb), 0.14);
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 25%, transparent 94%);
          mask-image: linear-gradient(to bottom, #000 0, #000 25%, transparent 94%);
}

/* --- scena vuota: il posto tenuto finché la schermata non arriva ---------- */

/* Una fascia senza immagine non salta la scena e non stampa un'immagine rotta:
   tiene il posto con le stesse misure, e dentro il nome di quello che ci andrà.
   Così la pagina si legge intera fin da subito e chi porta le schermate sa
   dove vanno — basta scrivere il nome del file in data/how-it-works.js.

   Niente maschera qui: la dissolvenza in basso serve a far credere che la
   schermata continui fuori campo, e senza schermata sfumerebbe soltanto la
   riga che dice che cosa manca. */
.feature__stage--empty {
  display: grid;
  place-items: center;
  background: var(--bg);
  border-radius: var(--radius);
  -webkit-mask-image: none;
          mask-image: none;
}

/* Tratteggiato, non il filo pieno delle scene piene: è la differenza fra un
   bordo e un posto tenuto, e si vede da lontano senza gridare. */
.feature__stage--empty::after {
  box-shadow: none;
  border: 1px dashed var(--line);
  -webkit-mask-image: none;
          mask-image: none;
}

.feature__stage-note {
  margin: 0;
  padding-inline: 1.5rem;
  font-size: var(--text-sm);
  letter-spacing: -0.01em;
  color: var(--fg-faint);
  text-align: center;
  text-wrap: balance;
}

/* `top` e non `center`: la schermata è alta più della scena, e a tagliarla in
   mezzo si perderebbe la barra degli strumenti — cioè la riga che dice che
   cosa si sta guardando. Tagliata in fondo, sotto la dissolvenza, si perde una
   fila di schede: esattamente quello che la dissolvenza vuole far credere,
   cioè che la pagina continui. */
.feature__shot {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

@media (max-width: 900px) {
  /* In colonna singola le due righe restano nell'ordine in cui si leggono:
     titolo, frase, schermata. La griglia a metà non ha più niente da
     incolonnare e si scioglie. */
  .feature__head {
    grid-template-columns: 1fr;
    gap: clamp(0.9rem, 3vw, 1.25rem);
  }

  /* Non `none`: a colonna unica la frase prende tutto lo shell, e a 900px
     sono 95 battute per riga — il doppio della misura leggibile. Il tetto in
     ch scala col corpo, quindi tiene sia sul tablet che sul telefono, dove
     e' la finestra a stringere prima di lui. */
  .feature__desc { max-width: 40ch; }

  /* Il titolo va a capo dove serve alla finestra, non dove lo vuole il copy:
     «e preparazione» su una riga da 320px non ci sta. */
  .feature__title { text-wrap: balance; }

  /* La scena è più corta che larga: sul telefono una schermata desktop tagliata
     a metà altezza non si legge comunque, e tenerla alta come sul desktop
     mangiava mezzo schermo per niente. */
  /* Il tetto a 18rem era tarato sul telefono e sul tablet tagliava troppo:
     a 900px la scena restava alta 288px su una schermata che ne misura 506,
     e con la dissolvenza sopra ne restava visibile appena un terzo. A 26rem
     la quota tagliata torna quella del desktop — un quinto scarso — a ogni
     larghezza sotto la soglia. */
  .feature__stage { height: clamp(11rem, 52vw, 26rem); }
}

@media (max-width: 560px) {
  /* Una colonna sola: «Monitoraggio performance» su metà di uno schermo da
     360px andava a capo, e un elenco in cui una voce su due è su due righe non
     si legge più come un elenco. */
}

/* --------------------------------------------------------------- mock UI -- */

.mock {
  border-radius: var(--radius);
  background: linear-gradient(180deg, #ffffff, var(--mist-100));
  overflow: hidden;
  font-size: 0.83rem;
  color: var(--fg);
}

.mock__chrome {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.7rem 0.9rem;
  border-bottom: 1px solid rgba(var(--ink-rgb), 0.07);
  background: rgba(var(--ink-rgb), 0.02);
}

.mock__dot {
  width: 9px; height: 9px;
  border-radius: 50%;
  background: rgba(var(--ink-rgb), 0.14);
}

.mock__crumb {
  margin-left: 0.6rem;
  font-size: 0.75rem;
  color: var(--fg-faint);
}

.mock__body {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 1px;
  background: rgba(var(--ink-rgb), 0.07);
  min-height: 290px;
}

.mock__col {
  padding: 1.1rem;
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  min-width: 0;
}

.mock__label {
  font-size: 0.7rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-bottom: 0.3rem;
}

.mock__row,
.mock__step,
.mock__metric,
.mock__answer {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.6rem 0.75rem;
  border-radius: 9px;
  background: rgba(var(--ink-rgb), 0.025);
  color: var(--fg-muted);
}

.mock__chip,
.mock__step-n {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 42px;
  height: 20px;
  padding: 0 0.4rem;
  border-radius: 5px;
  background: rgba(var(--mid-rgb), 0.12);
  color: var(--fg-faint);
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.06em;
}

.mock__step-n { min-width: 20px; border-radius: 50%; }

.mock__progress {
  margin-top: auto;
  height: 5px;
  border-radius: 3px;
  background: rgba(var(--ink-rgb), 0.08);
  overflow: hidden;
}

.mock__progress span {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--blue-500), var(--blue-400));
}

.mock__bubble {
  padding: 0.8rem 0.9rem;
  border-radius: 11px 11px 11px 3px;
  background: rgba(var(--mid-rgb), 0.07);
  color: var(--fg);
}

.mock__answer.is-selected {
  background: rgba(var(--teal-rgb), 0.18);
  color: var(--fg);
}

.mock__metric { flex-direction: column; align-items: flex-start; gap: 0.15rem; }

.mock__metric strong { color: var(--fg); font-size: 0.95rem; }

.mock__metric span { font-size: 0.78rem; }

.mock__metric--warn { background: var(--warn-sutil); }

.mock__metric--warn strong { color: var(--warn-texto); }

.mock__bar {
  display: grid;
  grid-template-columns: 92px 1fr 38px;
  align-items: center;
  gap: 0.6rem;
  color: var(--fg-muted);
}

.mock__bar i {
  display: block;
  height: 7px;
  border-radius: 4px;
  background: linear-gradient(90deg, var(--blue-500), var(--blue-400));
}

.mock__bar b { font-size: 0.78rem; color: var(--fg); text-align: right; }

.mock__clauses {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 6px;
}

.mock__clause {
  aspect-ratio: 1;
  border-radius: 4px;
  background: rgba(var(--teal-rgb), 0.55);
}

.mock__clause.is-partial { background: var(--warn); }
.mock__clause.is-warn { background: var(--error); }

.mock__legend {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: auto;
  font-size: 0.72rem;
  color: var(--fg-faint);
}

.mock__legend i {
  width: 9px; height: 9px;
  border-radius: 3px;
  display: inline-block;
}

.mock__legend i.is-ok { background: rgba(var(--teal-rgb), 0.55); }
.mock__legend i.is-partial { background: var(--warn); margin-left: 0.5rem; }
.mock__legend i.is-warn { background: var(--error); margin-left: 0.5rem; }

/* ---------------------------------------------------------------- quote -- */

.quote {
  /* Il fondo arriva dal gruppo unificato più in alto: qui solo la spaziatura.
     Ridichiararlo qui lo faceva vincere e la sezione restava bianca. */
  padding: clamp(2.5rem, 6vw, 5rem) 0;
  position: relative;
  overflow: hidden;
}

/* Texture a puntini dietro la citazione: dà carattere alla sezione senza
   aggiungere un'immagine. Sta sulla sezione e non sulla .shell perché quella
   è limitata a --shell e centrata: la trama si sarebbe fermata lì invece di
   arrivare ai bordi del viewport. */
.quote::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: radial-gradient(circle at center,
    rgba(23, 37, 50, 0.1) 1.1px, transparent 1.2px);
  background-size: 15px 15px;
  /* Spenti al centro, così il testo resta su fondo pulito e la trama si vede
     solo attorno. */
  -webkit-mask-image: radial-gradient(46% 64% at 50% 50%, transparent 26%, #000 86%);
  mask-image: radial-gradient(46% 64% at 50% 50%, transparent 26%, #000 86%);
}

.on-dark .quote::before {
  background-image: radial-gradient(circle at center,
    rgba(255, 255, 255, 0.14) 1.1px, transparent 1.2px);
}

.quote__inner {
  position: relative;
  text-align: center;
  padding: clamp(3.5rem, 8vw, 7rem) clamp(1.5rem, 6vw, 5rem);
  border-radius: var(--radius-lg);
}

.quote__text {
  position: relative;
  margin: 0;
}

/* Grazie e corpo grande: la citazione è l'unico punto in cui la pagina cambia
   voce, ed è ciò che la fa leggere come una testimonianza e non come un
   paragrafo qualsiasi. */
.quote__text p {
  /* La stessa famiglia dei titoli: la citazione non stacca per tipografia. */
  font-family: var(--font-display);
  font-size: var(--text-quote);
  font-weight: 400;
  line-height: var(--leading-quote);
  /* Crenatura negativa come sugli altri titoli: a questo corpo Plus Jakarta
     Sans senza serrare le lettere si allarga. */
  letter-spacing: var(--tracking-quote);
  /* Misura ampia: il blocco si sviluppa in larghezza, non a colonna. */
  max-width: 62ch;
  margin: 0 auto;
  text-wrap: pretty;
}

/* La porzione in evidenza. Solo il peso, non il colore: cambiare anche la
   tinta spezzerebbe la frase in due invece di darle un accento. */
.quote__strong { font-weight: 600; color: var(--fg); }

.quote__author {
  position: relative;
  margin-top: clamp(2rem, 3.5vw, 2.75rem);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.85rem;
}

.quote__avatar {
  flex: none;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  object-fit: cover;
  /* Filo interno: su un ritratto chiaro il cerchio si perderebbe sul fondo. */
  box-shadow: inset 0 0 0 1px rgba(var(--ink-rgb), 0.12);
}

/* Ripiego finché non c'è la foto: le iniziali nel cerchio. Il blocco resta
   completo, e il giorno che arriva l'immagine non cambia nient'altro. */
.quote__avatar--initials {
  display: grid;
  place-items: center;
  /* Neutro e non accento: è un ritratto mancante, non un comando. --paper e
     non --bg perché il cerchio è neutral-8 in tutti e due i temi, e il testo
     chiaro tiene 5.4:1 su entrambi. */
  background: var(--neutral-8);
  color: var(--paper);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.02em;
  box-shadow: none;
}

/* A sinistra e non centrato: accanto a un tondo, il testo centrato sembra
   fuori asse. */
.quote__who { display: block; text-align: left; }

.quote__name { display: block; font-weight: 600; }

.quote__name {
  font-weight: 600;
  font-size: var(--text-sm);
}

.quote__role {
  display: block;
  font-size: var(--text-xs);
  color: var(--fg-faint);
}

/* ---------------------------------------------------------------- cards -- */

/* Colonne dichiarate, non `auto-fit`: con `auto-fit` una lista di un solo
   articolo — e in archivio capita, quando gli altri sono in evidenza — si
   allargava a tutta la sezione, con la foto grande quanto lo schermo. Così
   invece resta larga un terzo e allineata a sinistra, come le altre. */
.cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.25rem;
}

@media (max-width: 900px) {
  .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 620px) {
  .cards { grid-template-columns: minmax(0, 1fr); }
}

/* Due per riga invece di tre, quindi foto più grandi: le schede in evidenza
   e i correlati in fondo a un articolo, che sono sempre due. */
.cards--featured {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.75rem;
}

@media (max-width: 780px) {
  .cards--featured { grid-template-columns: minmax(0, 1fr); }
}

.cards--featured .card__title { font-size: var(--text-h4); letter-spacing: var(--tracking-h4); line-height: var(--leading-h4); }

/* Niente fondo proprio: il blog è un'unica superficie bianca dall'apertura
   in fondo, e la foschia qui lasciava un salto di tono sulla giuntura con
   l'apertura. Il bianco lo mette il body di .page-blog. */
.archive { background: none; }

/* Il banner e l'elenco sono due sezioni ma una zona sola: sommando i loro
   padding, fra l'uno e l'altro sarebbe rimasto il doppio dello stacco. */
.archive--top { padding-top: calc(var(--header-h) + clamp(1.5rem, 3vw, 2.5rem)); }

.archive--top { padding-bottom: 0; }

.archive--list { padding-top: clamp(2.5rem, 5vw, 4rem); }

/* Banner rientrato: la forma la porta lui, non la sezione, così ai lati resta
   la foschia della pagina. */
.blog-banner {
  border-radius: var(--radius-lg);
  padding: clamp(3.5rem, 8vw, 6.5rem) clamp(1.5rem, 4vw, 3rem);
  text-align: center;
  background: var(--grad-haze);
  overflow: hidden;
}

.blog-banner__title {
  margin-top: 0.9rem;
  font-size: var(--text-h2);
  letter-spacing: var(--tracking-h2);
  line-height: var(--leading-h2);
  max-width: 20ch;
  margin-inline: auto;
  text-wrap: balance;
}

.blog-banner__lead {
  margin: 1.1rem auto 0;
  max-width: 56ch;
  color: var(--fg-muted);
}

/* I due blocchi stanno in una sezione sola. Divisi in due sezioni, i loro
   padding si sarebbero sommati e fra "In evidenza" e "Ultimi articoli"
   sarebbe rimasto il doppio dello stacco che c'è fra le sezioni di home. */
.archive__group + .archive__group {
  margin-top: clamp(3.5rem, 7vw, 6rem);
}

/* Nessuna superficie, nessun contorno, nessuna ombra: la scheda non è un
   contenitore ma una foto con due righe sotto. La forma la porta solo
   l'immagine, come le schermate della galleria di prodotto. */
.card {
  transition: transform 0.22s var(--ease);
}

.card:hover { transform: translateY(-3px); }

.card__link {
  display: flex;
  flex-direction: column;
  height: 100%;
}

/* Rapporto fisso, non l'altezza dell'immagine: le schede di una riga restano
   allineate anche con foto di proporzioni diverse. */
.card__media {
  margin: 0 0 1.15rem;
  aspect-ratio: 16 / 10;
  border-radius: var(--radius-lg);
  background: var(--surface-2);
  overflow: hidden;
}

.card__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Il raggio sta sulla cornice, che ritaglia col suo `overflow: hidden`, e
     all'immagine non serviva — finché non le si è dato un nome di transizione.
     Un elemento nominato, durante una transizione di pagina, viene sollevato
     fuori dalla pagina e disegnato per conto suo: il ritaglio di chi lo
     conteneva non lo segue. Misurato: 420 millisecondi di angoli vivi su ogni
     navigazione da e verso il blog, e la foto che tornava tonda di scatto a
     transizione finita. `inherit` prende il raggio della cornice qualunque
     sia. */
  border-radius: inherit;
}

/* Sul nero della fascia in home una foto scura resta senza contorno: le sue
   ombre e il fondo della sezione stanno alla stessa altezza — #04070c è luce 7
   su 255, e la saldatura del secondo articolo parte da 0 — così la scheda si
   dissolve nel blocco. Il velo alza il fondo scala invece di schiarire tutto:
   in `screen` i neri salgono al 9% e le alte luci restano dove sono, quindi la
   foto guadagna il bordo senza perdere il contrasto che ha. Vale solo sullo
   scuro: sul bianco di /blog e nella scheda dei correlati la stessa foto si
   stacca da sé. */
.on-dark .card__media {
  position: relative;
  /* Il velo deve fondersi con la foto sotto, non con il fondo della sezione. */
  isolation: isolate;
}

.on-dark .card__media::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: rgb(255 255 255 / 0.09);
  mix-blend-mode: screen;
}

@media (hover: hover) {
  .card__media img { transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1); }
  .card:hover .card__media img { transform: scale(1.04); }
}

.card__title {
  font-size: var(--text-h5);
  letter-spacing: var(--tracking-h5);
  line-height: var(--leading-h5);
  margin-bottom: 0.25rem;
  text-wrap: balance;
}

/* Sottotitolo della scheda: la parte dopo i due punti, in tono e corpo
   minori perché non competa con la prima riga. */
.card__subtitle {
  margin-top: 0;
  font-size: var(--text-sm);
  line-height: 1.4;
  color: var(--fg-muted);
  text-wrap: pretty;
}

/* La firma della scheda. Il peso e il tono sono suoi ovunque compaia: un grado
   più della riga che la ospita, perché un nome non è una frase e senza quel
   grado si leggerebbe come un sommario troncato. Il corpo invece no — quello
   glielo dà il posto in cui sta. */
.card__author {
  font-weight: 500;
  color: var(--fg-muted);
}

/* Sotto il titolo, in home, dove prende la riga del sottotitolo e quindi anche
   il suo corpo. In archivio sta invece dentro la riga della data e il corpo lo
   eredita da lei, senza bisogno di dichiararlo. */
.card__author--lead {
  margin-top: 0;
  font-size: var(--text-sm);
  line-height: 1.4;
}

.card__date {
  margin-top: 0.85rem;
  font-size: var(--text-xs);
  color: var(--fg-faint);
}

/* -------------------------------------------------------------- roadmap -- */

/* Una linea sola che attraversa tutte le tappe, non quattro barre affiancate.
   Il gradiente corre lungo il percorso: è la linea stessa a dire a che punto
   siamo, senza bisogno di leggenda. */
.roadmap__track {
  /* Il ritmo verticale è una catena sola, non tre numeri sciolti che vanno
     tenuti d'accordo a mano: la data occupa una riga di altezza fissa, la
     linea si prende la fascia che la separa dal titolo, e la sua posizione si
     ricava dalle due. Toccando `--rail-band` l'aria cresce sopra e sotto
     nella stessa misura, e la pallina resta centrata da sé.
     L'altezza della data è fissa apposta: se in una tappa sola andasse a capo,
     la linea si troverebbe più in basso lì che nelle altre. */
  --date-h: 1.3rem;
  --rail-band: 3.6rem;
  --rail-h: 3px;
  --dot-size: 13px;
  --rail-top: calc(var(--date-h) + (var(--rail-band) - var(--rail-h)) / 2);

  position: relative;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  column-gap: clamp(1.25rem, 3vw, 2.5rem);
  row-gap: 0;
  margin-top: clamp(3rem, 5vw, 4.5rem);
}

/* La linea sta sul contenitore e non sulle tappe: così il gradiente è uno
   solo e continuo. Diviso in quattro pezzi, ognuno ripartirebbe da capo e nei
   punti di giunzione comparirebbero quattro scalini.
   Le fermate cadono dove comincia ogni colonna, cioè sotto le palline: il
   colore della linea e quello del punto coincidono sempre.

   In coda non sfuma più. Prima scendeva a steel-2 e steel-1 — 1.29:1 e 1.12:1
   sul fondo della sezione, cioè niente — e finiva trasparente: l'ultimo
   tratto spariva, e con lui le due tappe che ci stavano sopra. Su una roadmap
   quella dissolvenza non si legge come «deve ancora succedere», si legge come
   il prodotto che si spegne. Adesso dal 50% in poi la linea tiene steel-6
   (2.83:1) fino in fondo: il futuro resta pianificato, non evanescente, e la
   progressione la portano il ciano in testa e il gradino scuro dell'oggi. */
.roadmap__track::before {
  content: "";
  position: absolute;
  top: var(--rail-top);
  left: 0;
  right: 0;
  height: var(--rail-h);
  border-radius: 2px;
  /* Si disegna da sinistra a destra quando la sezione entra: è il verso in
     cui si legge una roadmap, quindi il gesto racconta già qualcosa.
     `transform` e non `width`: la scala non tocca il layout, quindi
     l'animazione gira sul compositore senza ricalcoli. */
  background: linear-gradient(
    90deg,
    var(--teal-500) 0%,
    var(--steel-7) 25%,
    var(--steel-6) 50%,
    var(--steel-6) 100%
  );
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 1.15s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.js-motion .roadmap__track:has(.roadmap__step.is-in)::before { transform: scaleX(1); }

/* Senza JS il binario è già disegnato: `scaleX(0)` vale solo sotto
   `.js-motion`, che il JS aggiunge solo quando l'osservatore parte davvero. */
.roadmap__track::before { transform: scaleX(1); }

.js-motion .roadmap__track::before { transform: scaleX(0); }

.roadmap__step {
  position: relative;

  /* Il colore della pallina segue quello che la linea ha in quel punto. Le
     tappe future erano a neutral-2: 1.29:1 sul fondo, una pallina che non
     c'era. Adesso stanno sullo stesso steel-6 della coda della linea. */
  --dot: var(--steel-6);
  --ring: transparent;
}

.roadmap__step--done {
  /* Non il token di segnale condiviso: quello è --brand-2, tarato sui fondi
     scuri, e qui il fondo è chiaro — faceva 1.69:1, la pallina spariva.
     --brand-4 è il gradino più vivo che sul chiaro regge ancora il 3:1 di un
     elemento: 3.13:1 sul fondo, e la spunta scura dentro ne fa 5.43. Più su
     — il 3, che è il croma massimo della rampa — si scende a 2.28:1.
     I canali dell'alone sono quelli di --brand-4, perché rgba() non legge un
     token. */
  --dot: var(--brand-4);
  --ring: rgba(0, 151, 184, 0.2);
  /* Più grande delle altre: deve contenere la spunta e, sulla linea, è la
     tappa che si è guadagnata più peso. */
  --dot-size: 20px;
}

/* La spunta dice «fatto» a colpo d'occhio, senza dover leggere l'etichetta.
   Disegnata con due bordi di uno pseudo-elemento ruotato: nessun file, nessun
   SVG in linea.

   Bianca, non in inchiostro. Sulla carta l'inchiostro vinceva il confronto —
   5.43:1 contro 3.43 — ma è un segno di 5×9px a due pixel di tratto dentro un
   cerchio da 20: a quella misura lo scuro sul ciano non si legge come spunta,
   si legge come un buco nella pallina. Il bianco è il segno che si stacca
   davvero, e i 3.43:1 restano sopra il 3:1 che si chiede a un elemento
   grafico. Bianco pieno e non --steel-0, che a 3.13 ci passerebbe di misura
   e senza margine. */
.roadmap__step--done .roadmap__dot::after {
  content: "";
  position: absolute;
  top: 46%;
  left: 50%;
  width: 5px;
  height: 9px;
  border: solid #ffffff;
  border-width: 0 2px 2px 0;
  transform: translate(-50%, -50%) rotate(45deg);
}

.roadmap__step--progress {
  /* Un gradino sotto la fermata del 25%, come tutte le altre: la tappa viva è
     il punto più scuro del percorso, e da lì in poi la linea si assesta. */
  --dot: var(--steel-7);
  --ring: rgba(var(--mid-rgb), 0.18);
}

/* Pallina sulla linea: piena e con l'alone dove la tappa è viva, vuota dove
   deve ancora succedere. */
.roadmap__dot {
  position: absolute;
  /* Centrata sulla linea: si risale il padding del binario, si scende fino
     alla mezzeria della linea e si toglie mezza pallina. */
  top: calc(var(--rail-top) + var(--rail-h) / 2 - var(--dot-size) / 2);
  left: 0;
  width: var(--dot-size);
  height: var(--dot-size);
  border-radius: 50%;
  background: var(--dot);
  /* Il primo anello è del colore della sezione: stacca la pallina dalla linea
     che le passa sotto, senza dover interrompere il gradiente. Il secondo è
     l'alone, che solo le tappe vive accendono — le future lo lasciano
     trasparente e restano piatte. */
  box-shadow: 0 0 0 4px var(--section-grey), 0 0 0 9px var(--ring);
  transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) var(--enter-delay, 0ms);
}

/* Le palline spuntano una dopo l'altra, giusto dietro alla linea che le
   raggiunge. Il ritardo è lo stesso `--enter-delay` con cui il foglio scala
   le tappe: un solo valore governa testo e pallina, così non possono
   scollarsi. I passi sono larghi perché devono stare al passo della linea,
   non della griglia di schede da 70ms. */
.js-motion .roadmap__step { --enter-delay: 0ms; }
.js-motion .roadmap__step:nth-child(1) { --enter-delay: 140ms; }
.js-motion .roadmap__step:nth-child(2) { --enter-delay: 420ms; }
.js-motion .roadmap__step:nth-child(3) { --enter-delay: 700ms; }
.js-motion .roadmap__step:nth-child(4) { --enter-delay: 980ms; }

.js-motion .roadmap__step[data-enter] .roadmap__dot { transform: scale(0.3); }

.js-motion .roadmap__step[data-enter].is-in .roadmap__dot { transform: scale(1); }

@media (prefers-reduced-motion: reduce) {
  .js-motion .roadmap__track::before { transform: scaleX(1); }
  .js-motion .roadmap__step[data-enter] .roadmap__dot { transform: scale(1); }
  .roadmap__track::before,
  .roadmap__dot { transition: none; }
}

/* --- testo --------------------------------------------------------------- */

/* La data sta sopra la linea e resta minuta: è un riferimento, non un titolo.
   Niente maiuscolo né spaziatura allargata — la fila ne aveva già abbastanza,
   e a quel corpo il maiuscolo si legge peggio del minuscolo.
   `tabular-nums` tiene le cifre incolonnate fra una tappa e l'altra. */
.roadmap__date {
  margin: 0;
  /* Riga di altezza dichiarata, non ereditata: è su questa che si appoggia il
     calcolo di `--rail-top`. */
  height: var(--date-h);
  line-height: var(--date-h);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--fg-faint);
}

.roadmap__title {
  font-size: var(--text-h5);
  letter-spacing: var(--tracking-h5);
  line-height: var(--leading-h5);
  /* Lascia passare l'intera fascia della linea fra la data e sé: è la stessa
     misura che posiziona la linea, quindi l'aria sopra e sotto è per
     costruzione uguale. */
  margin: var(--rail-band) 0 0.65rem;
}

/* I titoli delle tappe future stavano in --fg-muted: erano previsioni, e si
   leggevano meno. Ma insieme alla coda della linea che sfumava facevano una
   cosa sola — la metà destra della roadmap sbiadiva, e quel che sbiadiva era
   il prodotto. Lo stato lo dicono la pallina, la linea e l'etichetta per gli
   assistivi; il titolo di una tappa pianificata sta in inchiostro pieno come
   le altre. */

.roadmap__body {
  font-size: var(--text-sm);
  color: var(--fg-muted);
  text-wrap: pretty;
}

/* Sul telefono la linea non sparisce: gira. Su due colonne restavano quattro
   palline sciolte e la roadmap smetteva di essere un percorso — quattro
   annunci separati, senza il verso che li lega. Le tappe si incolonnano e il
   binario diventa verticale, che è come si legge una sequenza su uno schermo
   alto: dall'alto in basso, con le palline infilate sopra.

   Una colonna sola già da 860px e non da 620: due colonne strette rompono
   comunque il filo, e un binario verticale per colonna sarebbero due
   percorsi al posto di uno. */
@media (max-width: 860px) {
  .roadmap__track {
    /* Mezzeria della colonna delle palline. Le palline non hanno tutte la
       stessa misura — quella fatta è più grande — quindi si centrano su
       questa ascissa invece di appoggiarsi al bordo, o starebbero su due
       verticali diverse. */
    --rail-x: 10px;

    grid-template-columns: 1fr;
    row-gap: 3rem;
    /* La fascia che sul desktop lascia passare la linea orizzontale qui si
       richiude: la data torna a fare da occhiello al titolo. */
    --rail-band: 0.5rem;
  }

  /* Stesso gradiente, ruotato di un quarto: le fermate cadono sulle quattro
     palline come cadevano sulle quattro colonne. Anche qui la coda non sfuma
     più — sul telefono sbiadiva perfino prima, perché la linea è lunga quanto
     la pagina e l'ultimo tratto restava sotto gli occhi più a lungo. */
  .roadmap__track::before {
    top: 1rem;
    right: auto;
    bottom: 0.75rem;
    left: calc(var(--rail-x) - var(--rail-h) / 2);
    width: var(--rail-h);
    height: auto;
    background: linear-gradient(
      180deg,
      var(--teal-500) 0%,
      var(--steel-7) 26%,
      var(--steel-6) 52%,
      var(--steel-6) 100%
    );
    transform-origin: top center;
    transform: scaleY(1);
  }

  /* Si disegna dall'alto in basso: `scaleX` qui schiaccerebbe la linea contro
     il suo asse invece di allungarla. */
  .js-motion .roadmap__track::before { transform: scaleY(0); }
  .js-motion .roadmap__track:has(.roadmap__step.is-in)::before { transform: scaleY(1); }

  /* Spazio per la pallina grande e per il suo anello. */
  .roadmap__step { padding-left: 2.4rem; }

  .roadmap__dot {
    top: 0.35rem;
    left: calc(var(--rail-x) - var(--dot-size) / 2);
  }
}

/* ------------------------------------------------------------------ FAQ -- */

/* Articoli e FAQ sono una zona sola: stesso nero, dichiarato una volta.
   Separarli con due valori diversi lasciava una cucitura visibile. */
.blog-teaser,
.faq {
  background: var(--blue-950);
}

/* La roadmap sta sopra il blog, che sale di `--round` e le arrotonda addosso
   i propri angoli: lo spazio va riservato qui, o il raccordo si mangia le
   date dell'ultima riga. */
.roadmap {
  padding-bottom: calc(clamp(4rem, 9vw, 7.5rem) + clamp(1.75rem, 4vw, 3.25rem));
}

.faq__list {
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  display: grid;
  gap: 0.75rem;
}

/* Schede bianche sul nero, come su dendoo.it: qui il contesto scuro non vale,
   i token vengono riportati a quelli chiari per il contenuto della scheda. */
.faq__item {
  /* Tutta la scheda è cliccabile, non solo la domanda: il cursore lo dice. */
  cursor: pointer;

  --fg: var(--ink);
  --fg-muted: var(--ink-4);
  --accent: var(--brand-6);

  border-radius: var(--radius);
  /* --paper e non --surface: la scheda è un'isola chiara dentro la zona
     scura, e --surface lì si ribalta — la scheda diventerebbe scura sotto
     il suo stesso inchiostro. --paper è il chiaro globale e non si ribalta. */
  background: var(--paper);
  color: var(--fg);
  transition: background-color 0.3s var(--ease);
}

.faq__item.is-open { background: var(--paper); }

.faq__h { margin: 0; font: inherit; }

.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  width: 100%;
  padding: 1.15rem 1.4rem;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  /* La domanda è il titolo della scheda: stesso gradino e stessa famiglia
     delle tappe della roadmap e delle schede compatte, così la home ha una
     misura sola per i titoli di elemento. */
  font-family: var(--font-display);
  font-size: var(--text-h5);
  line-height: var(--leading-h5);
  letter-spacing: var(--tracking-h5);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.faq__sign {
  flex: none;
  position: relative;
  width: 16px; height: 16px;
}

.faq__sign::before,
.faq__sign::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  background: var(--accent);
  border-radius: 2px;
  transition: transform 0.42s cubic-bezier(0.16, 1, 0.3, 1);
}

.faq__sign::before { width: 100%; height: 1.5px; }
.faq__sign::after  { width: 1.5px; height: 100%; }

.faq__item.is-open .faq__sign::after { transform: scaleY(0); }

.faq__a { color: var(--fg-muted); font-size: var(--text-sm); }

/* Il respiro sotto la risposta non è padding del contenitore: la riga di
   griglia a 0fr porta l'altezza a zero, ma il padding di un box non scende
   sotto sé stesso, e la scheda chiusa restava alta 22px più della domanda.
   Lo mette un blocco vuoto in coda, che con `overflow: hidden` sparisce
   insieme al testo. */
.faq__a-inner { padding: 0 1.4rem; }

.faq__a-inner::after {
  content: "";
  display: block;
  height: 1.35rem;
}

.faq__a-inner p + p { margin-top: 0.8rem; }

/* La chiusura è affidata a CSS: `grid-template-rows` da 0fr a 1fr anima
   l'altezza senza doverla misurare, quindi niente da sincronizzare a mano.
   Tutto è dietro `.is-ready`, che mette il JS: senza JS le risposte restano
   aperte e leggibili. */
.faq__list.is-ready .faq__a {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition:
    grid-template-rows 0.42s cubic-bezier(0.16, 1, 0.3, 1),
    opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}

/* `min-height: 0` è indispensabile: di default un elemento in griglia non
   scende sotto la propria altezza minima, quindi il padding interno resterebbe
   visibile anche a riga collassata. */
.faq__list.is-ready .faq__a > * {
  overflow: hidden;
  min-height: 0;
}

.faq__list.is-ready .faq__item.is-open .faq__a {
  grid-template-rows: 1fr;
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .faq__list.is-ready .faq__a { transition: none; }
}

/* ------------------------------------------------------------- CTA band --
   In uso solo su /product, dentro `.closing-light`: là i sei passi finiscono
   in un invito, ed è l'unico posto della pagina dove prenotare e scaricare il
   documento stanno affiancati. Altrove no — l'invito ad agire vive nella barra
   agganciata, visibile a ogni altezza di pagina, e ripeterlo appena sopra il
   piede sarebbe dirlo due volte. Per aggiungerla a una pagina basta includere
   views/partials/parla-con-noi.ejs dentro `.closing` / `.closing-light`. */

.cta-band {
  /* Misurato a 1440x900: fra l'ultima riga della pagina e il titolo della
     banda passavano 208px — i 120 di coda della sezione piu' gli 88 di
     apertura della banda — e fra i tasti e il piede altri 144. Due terzi di
     schermo di niente su una zona che chiede un'azione: chi arriva in fondo
     deve scorrere ancora per trovarla. La coda della sezione si accorcia
     altrove (vedi `main:has(+ .closing)`), l'apertura della banda scende a
     52 e la chiusura a 32: restano 124 sopra il titolo e 84 fino al piede,
     aria che si legge come stacco e non come pagina mancante. */
  padding: clamp(2.25rem, 4vw, 3.25rem) 0 clamp(1.5rem, 3vw, 2rem);
  background: transparent;
}

.cta-band__inner {
  /* Tutto su un fianco: titolo e tasti incolonnati a sinistra. Con lo
     space-between di prima stavano agli angoli opposti della banda. */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.75rem;
}

/* Il colore viene dai token: bianco dentro .on-dark, inchiostro sul chiaro. */
.cta-band__title { font-size: var(--text-h3); letter-spacing: var(--tracking-h3); line-height: var(--leading-h3); max-width: none; color: var(--fg); }

.cta-band__actions { display: flex; gap: 0.85rem; flex-wrap: wrap; }

/* ------------------------------------------------------------ page hero -- */

.page-hero {
  --round: clamp(1.75rem, 4vw, 3.25rem);

  position: relative;
  padding: var(--header-h) 0 var(--round);
  background: var(--grad-haze);
}

/* Il blocco di testo sta al centro verticale della fascia: l'altezza la dà il
   min-height, non la somma dei padding, e i padding restano solo a riservare
   la barra sopra e il raccordo sotto. Il --split del blog non c'entra: lì i
   due testi stanno agli angoli per scelta, non incolonnati al centro. */
.page-hero:not(.page-hero--split) {
  display: grid;
  align-content: center;
  min-height: clamp(22rem, 34vw, 30rem);
}

/* Centratura su .page-hero: vale per ogni pagina senza toccare i template,
   che hanno strutture interne diverse (alcuni con eyebrow, altri no). */
.page-hero { text-align: center; }

/* Il footer appoggiato al fondo lo dà già la regola sul body. A 404 e 500
   serve un passo in più: l'apertura deve prendersi tutto lo spazio che avanza
   e centrarci dentro il titolo, altrimenti resta della sua altezza naturale e
   il titolo si incolla sotto la barra invece di stare in mezzo a quel che ha. */
.page-error > main {
  display: flex;
  flex-direction: column;
}

.page-error > main > .page-hero { flex: 1 0 auto; }

/* Una superficie sola su tutte le pagine chiare — blog, articolo, prodotto,
   contatto, chi siamo e manifesto — ma non bianco pieno: una velatura mist
   dipinta con i toni della scala. Il livello sta fisso rispetto alla
   viewport, così anche le pagine lunghe mostrano sempre la sfumatura intera
   invece di diluirla sull'altezza del documento; la chiusura scura ci passa
   sopra col suo fondo opaco. */
.page-blog,
.page-product,
.page-start,
.page-team { background: var(--bg); }

.page-blog::before,
.page-product::before,
.page-start::before,
.page-team::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /* Adesso è rampa. Prima non lo era: i blu di allora portavano un velo
     grigio-verde che su una superficie così ampia si leggeva come sporco, e
     la velatura doveva uscire dalla tavola per trovare un celeste pulito.
     Quel velo era il croma di troppo dei 196°, e con la tavola fredda non
     c'è più — il celeste che si andava a cercare fuori è diventato il
     gradino chiaro della rampa. Due tinte estranee in meno, e la pagina
     smette di avere un fondo che non appartiene a nessuna scala. */
  background:
    radial-gradient(120% 95% at 0% 100%, rgba(203, 220, 227, 0.6) 0%, rgba(203, 220, 227, 0) 66%),
    radial-gradient(110% 85% at 100% 0%, rgba(219, 231, 237, 0.55) 0%, rgba(219, 231, 237, 0) 62%),
    linear-gradient(135deg, var(--mist-200) 0%, #ffffff 48%, var(--mist-300) 100%);
}

/* /compliance-intelligence non prende la velatura: apre sul nero e lava fino
   al bianco pieno, e sotto la rampa non c'è mist ma #ffffff. È l'unica pagina
   chiara del sito che non ha un fondo velato, ed è il motivo per cui si legge
   più bianca delle altre. */
.page-ci { background: var(--surface); }

/* Nessuna sezione dipinge una fascia propria: sopra la velatura una fascia si
   leggerebbe come un taglio. Il fondo lo mette solo il body. */
.page-start .section,
.page-ci .section { background: transparent; }

/* /team non e' piu' in questa lista, ne' in quella qui sotto: non ha piu' ne'
   sezioni ne' apertura `--split`. Adesso apre come /compliance-intelligence e
   il resto lo porta il palco — vedi `.team-hero` e `.team-palco`. */
.page-ci .page-hero--split { background: none; }

/* Tre sezioni di testo, non tre fasce di prodotto: i 120px per lato delle
   sezioni standard sommavano 240px di vuoto fra l'inciso di una e il titolo
   della successiva, e ogni blocco sembrava una pagina a sé. Qui 80 per lato. */
.page-ci .section { padding-block: clamp(3.5rem, 7vw, 5rem); }

/* L'apertura porta già il proprio stacco: la prima sezione non ne aggiunge
   un secondo (stessa eccezione di .page-hero--split + .section, che questa
   regola più in basso avrebbe scavalcato). */
.page-ci .page-hero--split + .section { padding-top: 0; }

/* La nota dell'apertura sta sotto un titolo da due righe: agli 88ch di
   default correva per 950px e andava a capo dove capitava. */
.page-ci .page-hero__note { max-width: 62ch; }

/* Il testo di lettura della pagina — sommari e prosa — un gradino sopra il
   corpo: è un manifesto, si legge dall'inizio alla fine, e a 17px atteunuato
   sembrava una didascalia. Il colore resta quello di lettura del sito. */


/* --------------------------------------------- aperture di pagina -- */
/* Ripristinato dal commit 51c7874. Questo blocco era stato cancellato per
   sbaglio: una sostituzione del blocco Compliance Intelligence ha preso un
   confine troppo alto e si è portata via anche le aperture di /product,
   /blog, /team e /get-started, che restavano senza misure — titolo al corpo
   di base e freccia dell'apertura larga quanto la pagina. */

.page-start .page-hero__lead { max-width: none; }

.page-blog .sheet {
  background: transparent;
  box-shadow: none;
}

.page-blog .sheet-wrap { background: transparent; }

.page-hero--light { background: none; }

.page-hero--light + .section { padding-top: 0; }

.page-hero--light:not(.page-hero--split) {
  min-height: 0;
  padding-top: calc(var(--header-h) + clamp(0.75rem, 5svh, 3.5rem));
  /* Il raccordo della sezione risale di --round: sotto quella soglia si
     mangerebbe il sottotitolo, come avverte la regola del raccordo. */
  padding-bottom: calc(var(--round) + clamp(0.25rem, 2svh, 1.25rem));
}

/* Il passo uno non ha nulla sotto di sé che risalga: dopo il modulo viene
   `.closing-light`, trasparente, che non si arrotonda addosso a niente — il
   `var(--round)` riservato qui sopra è spazio per un raccordo che non arriva
   mai. E l'aria sopra fa il paio con quella sotto: la riserva della barra resta
   nel conto, il respiro in più è lo stesso dei due lati. Non è pignoleria — è
   la condizione perché la centratura qui sotto cada davvero a metà fra la
   barra e il piede invece che trentadue pixel più in basso. */
.page-start .page-hero--light.page-hero--start {
  padding-top: calc(var(--header-h) + clamp(0.5rem, 2svh, 1.5rem));
  padding-bottom: clamp(0.5rem, 2svh, 1.5rem);
}

/* E il funnel sta in mezzo alla finestra, non appeso sotto la barra. Il piede
   appoggiato al fondo lo dà già la regola sul body; qui serve lo stesso passo
   in più di 404 e 500 — la fascia si prende l'altezza che avanza e ci centra
   dentro le due colonne. Misurata a 1512x1250 la pagina teneva 56px d'aria
   sopra il titolo e 287 fra il modulo e il piede: il blocco si leggeva
   scivolato in alto perché la fascia era alta quanto il suo contenuto e tutto
   lo spazio che restava cadeva dopo.

   A centrare sono i margini automatici, non `align-content: center`: quando il
   contenuto è più alto dello spazio — finestre basse, dove il modulo non ci
   sta comunque — i margini `auto` valgono zero e il blocco torna appoggiato in
   cima, sotto la barra. La centratura del box alignment invece sfonderebbe da
   tutte e due le parti, e la metà di sopra finirebbe sotto l'header senza modo
   di scorrere fin lì. Per lo stesso motivo la riga della griglia torna a
   `stretch`: deve riempire la fascia, altrimenti i margini non hanno spazio da
   distribuire. Il passo due non entra nel giro (`:has`): là sotto c'è il
   calendario, che l'altezza se la prende da sé. */
.page-start > main:has(.page-hero--start) {
  display: flex;
  flex-direction: column;
}

.page-start .page-hero--start {
  flex: 1 0 auto;
  align-content: stretch;
}

.page-start .start-split { margin-block: auto; }

.page-hero--light .page-hero__title { max-width: none; }

.page-hero--light .eyebrow { color: var(--fg-faint); }

.page-hero__title {
  max-width: none;
  margin-inline: auto;
}

.page-hero:not(.page-hero--split) .page-hero__title { text-wrap: balance; }

.page-hero__lead {
  margin-inline: auto;
  /* Niente parole orfane nemmeno nel sottotitolo. */
  text-wrap: pretty;
}

.page-hero__actions { justify-content: center; }

.page-hero__title { font-size: var(--text-h1); max-width: 24ch; }

.page-hero__lead {
  margin-top: 0.65rem;
  max-width: 60ch;
  font-size: var(--text-lead);
  line-height: var(--leading-lg);
  color: var(--fg-muted);
}

.page-hero__lead a { color: var(--accent); text-decoration: underline; }

.page-hero__actions { display: flex; gap: 0.85rem; flex-wrap: wrap; margin-top: 2rem; }

.page-blog .page-hero--split { background: none; }

.page-hero--dark {
  position: relative;
  overflow: hidden;
  /* Rovesciata rispetto alla home: scura in cima e bianca in fondo, così il
     blocco si salda alla sezione che segue senza nessuno stacco — è la stessa
     tinta, non due bianchi diversi accostati.

     La discesa occupa 52 punti percentuali, dal 40 al 92. Ne occupava 44 e
     prima ancora 24, e una rampa così corta non si legge come un cielo: si
     legge come una fascia chiara incollata sotto un fondo nero.

     Perché proprio il 40%: sotto di lì il sommario perde leggibilità. Il testo
     scende fino al 65.3% dell'altezza sulle finestre più basse, e lì il bianco
     attenuato tiene 3.66:1 — che è già stretto per un sommario, e partire più
     in alto lo affonderebbe. Il limite stava al 48 finché la rampa era quella
     calda; le tinte fredde sono più scure a parità di posizione e hanno
     comprato otto punti di respiro. Sono gli otto punti che separano una
     discesa dal parere una fascia.

     Le fermate sono ripartite per luminosità, come sulla verticale e per lo
     stesso motivo: è l'unica ripartizione che non lascia bande su una
     superficie di questa misura. La curva del master qui non entra — entrano
     i suoi colori.

     Ci sono tutti e nove i gradini. --blue-700 è rientrato: era escluso perché
     fuori tinta, 209° contro i 196° di una rampa che aveva schiacciato l'arco
     del master su un angolo unico. Campionato dal master porta la tinta della
     sua altezza e non fa più macchia; con lui --blue-600 e --blue-850 chiudono
     i due vuoti che restavano.

     L'ultimo tratto si apre invece di chiudere su un colore pieno: sotto c'è
     la velatura del body, che è fissa alla viewport e quindi non ha un colore
     prevedibile sotto il fondo del blocco. E si apre da --blue-300, non da un
     chiaro: qualunque fermata più chiara della velatura disegna una banda
     luminosa — la velatura arriva a #dbe7ed, e --blue-300 (#cbdce3) le resta
     sotto ovunque, così la salita resta monotona fino a dentro il fondo
     pagina. */
  background-image: linear-gradient(
    180deg,
    var(--blue-950) 40.0%,
    var(--blue-900) 41.7%,
    var(--blue-850) 43.7%,
    var(--blue-800) 47.9%,
    var(--blue-700) 54.3%,
    var(--blue-600) 64.4%,
    var(--blue-500) 77.7%,
    var(--blue-400) 86.2%,
    var(--blue-300) 92.0%,
    rgba(203, 220, 227, 0) 100%
  );
  background-repeat: no-repeat;
  background-size: 100% 100%;
}

.page-hero--dark .page-hero__grid { position: relative; z-index: 1; }

/* Il portatile di fronte — dendoo-dashboard-qualita-macbook.webp, la stessa Dashboard qualità
   del portatile in prospettiva della home, vista dritta. Il file è ritagliato
   ai confini dell'alfa, 1919x1086: a sinistra e a destra del pieno restano il
   5.73% e il 4.69% della larghezza di ombra morbida, sopra niente (0.18%),
   sotto il 3.41% dell'altezza. Lo schermo è un rettangolo e la sagoma riempie
   la scatola: qui non c'è un quarto vuoto in cui far passare il testo, quindi
   la scatola deve stare tutta a destra della colonna.

   La colonna arriva a 661px dal filo interno dello shell — il titolo a 643,
   la frase a 661, misurati — e fra lei e l'oggetto restano 40px. Sullo shell
   pieno (1440, dai 1510 in su) fanno 703px di oggetto, cioè 785 di scatola
   con le due fasce: è il tetto. Sotto, lo shell è la finestra e il conto lo
   fanno i 52vw: a 1401 danno 728 e la distanza dalla frase è 44px.

   Centrato in verticale sul blocco di testo, non sulla scatola del blocco:
   il testo si centra nella sua scatola di contenuto, che in cima ha in più
   l'altezza della barra, quindi il suo centro sta a 50% + header-h/2. La
   scatola del portatile è alta 0.566 volte la larghezza (1086/1919): metà,
   0.283, sopra il centro. A 1920 il portatile è alto 444px contro i 384 del
   testo, e sporge di trenta sopra e sotto.

   Il filo destro dell'oggetto cade sul filo interno dello shell, come il
   tasto «Parla con noi» in alto: la fascia a destra del PNG va scontata. Il
   `max()` serve fra 1401 e 1440, dove la finestra è più stretta dello shell
   e la differenza sarebbe negativa. */
.page-hero__mock {
  --mock-w: min(52vw, 785px);
  position: absolute;
  right: calc(
    max(0px, (100% - var(--shell)) / 2)
    + clamp(1.25rem, 3vw, 2.25rem)
    - var(--mock-w) * 0.0469
  );
  top: calc(50% + var(--header-h) / 2 - var(--mock-w) * 0.283);
  width: var(--mock-w);
  height: auto;
  pointer-events: none;
}

/* Il fianco a fianco vale solo dove il titolo ha davvero la sua metà: a 1400
   il titolo occupa quasi tutta la riga e il portatile gli finiva sopra le
   parole. Sotto questa misura si impilano — testo sopra, portatile sotto — e
   il blocco smette di essere alto una viewport per seguire il contenuto. */
@media (max-width: 1400px) {
  .page-hero__mock {
    position: static;
    /* Più piccolo che nel fianco a fianco, perché impilato scende verso la
       parte chiara del degradado. La base sta già fra l'81% e l'87%
       dell'altezza del blocco, dove il fondo ha la stessa luminanza
       dell'argento: campionato a 1400, 1024, 768 e 393 il contrasto fra base
       e fondo è 1.03-1.23, e il contorno lo tengono lo schermo e l'ombra.
       Più grande, finirebbe ancora più giù. */
    width: min(100%, 620px);
    margin: clamp(1.5rem, 4vw, 3rem) auto 0;
    transform: none;
  }
}

.page-product .page-hero--split {
  /* Tutto sopra la piega: barra, testo e portatile dentro una viewport, e
     niente della vetrina. Il `+ var(--round)` non e' aria in piu': e' lo
     stesso che la sezione seguente si riprende salendo a coprire il raccordo,
     quindi il suo bordo arrotondato si posa esattamente sulla piega invece di
     entrare in campo. A 92svh il titolo del primo livello e la sua frase
     restavano dentro la finestra di una trentina di pixel. */
  min-height: calc(100vh + var(--round, 3.25rem));
  min-height: calc(100svh + var(--round, 3.25rem));
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-top: calc(var(--header-h) + clamp(2rem, 4vw, 3.5rem));
  padding-bottom: clamp(2rem, 4vw, 3.5rem);
}

.page-product .page-hero__grid { gap: clamp(1.5rem, 2.4vw, 2.25rem); }

.page-product .page-hero__grid {
  min-height: 0;
  align-content: start;
  flex: none;
}

/* Dopo la regola qui sopra e non insieme all'altro blocco a 1400: le due
   regole hanno la stessa specificità, e quella del media query sta prima nel
   foglio, quindi perdeva sempre. Impilato il portatile chiude l'apertura
   invece di galleggiare a lato, e sotto di lui serviva più aria: erano 94px
   fino alla prima schermata contro i 115 che separano due blocchi. */
@media (max-width: 1400px) {
  /* Anche l'altezza sta qui e non nel blocco a 1400 di sopra, per la stessa
     ragione del padding: `min-height: 0` lassù perdeva contro il 100svh della
     regola base, che viene dopo nel foglio a parità di specificità. Impilato
     il portatile non entra comunque in una viewport, quindi il 100svh non
     teneva niente sopra la piega: apriva solo un vuoto sotto il portatile,
     alto quanto la differenza fra il contenuto e la viewport. */
  .page-product .page-hero--split {
    min-height: 0;
    padding-bottom: clamp(4.5rem, 9vw, 8rem);
  }
}

.page-product .showcase-section { background: transparent; }

.page-product .section { padding: clamp(3rem, 6vw, 5.5rem) 0 clamp(4rem, 7vw, 6.5rem); }

.page-product .showcase { margin-top: clamp(2.5rem, 5vw, 4.5rem); }

.page-product .page-hero__title--display { max-width: 14ch; }

/* Nel titolo «Come funziona dendōo» l'ultima parola è il marchio, non testo.
   Le due misure sono misurate, non stimate.

   L'altezza: la 'd' del wordmark deve essere alta quanto una 'd' composta.
   Nel Plus Jakarta Sans 600 del display l'ascendente misura 0.757em; dentro
   il viewBox 108x25 l'inchiostro della 'd' occupa il 89.7% dell'altezza.
   0.757 / 0.897 = 0.844em. Non si allinea l'altezza-x perché il marchio ce
   l'ha più generosa del carattere (69.7% contro 73% dell'ascendente): a
   pareggiare quella, le 'd' sarebbero risultate corte.

   Lo scarto: un inline-block appoggia il bordo inferiore sulla linea di base,
   ma sotto la base del marchio restano 5.32% di altezza vuota nel viewBox —
   il logo galleggerebbe di 0.0532 x 0.844 = 0.045em. Il vertical-align lo
   ridiscende esattamente di quella misura.

   `color: inherit` e non il --fg di .logo__svg: qui il fondo è scuro e il
   titolo è bianco, e il marchio deve seguirlo. */
.page-hero__brand {
  display: inline-block;
  vertical-align: -0.045em;
}

.page-hero__brand .logo__svg {
  height: 0.844em;
  width: auto;
  color: inherit;
}

/* Il sommario tiene la metà sinistra come il titolo. A 88ch stava su una
   riga sola di 962px e da 1600 in su passava sotto la base del portatile:
   misurato, 38px di sovrapposizione a 1600x900, 133 a 1920x1080, 136 a
   2560x1440 — «in un unico sistema» finiva scritto sull'argento. Da 1600 in su
   fra l'inizio del testo e la base restano 826-924px, quindi 52ch (650px) ci
   sta con margine a ogni misura. `balance` spartisce le due righe invece di
   lasciare la seconda corta: senza, la seconda riga restava di 371px sotto
   una prima che riempiva i 650. */
.page-product .page-hero__note {
  font-size: var(--text-lg);
  max-width: 52ch;
  text-wrap: balance;
}

.page-hero--split .page-hero__actions { justify-content: flex-start; margin-top: 0.5rem; }

.page-hero--split {
  position: relative;
  text-align: left;
  padding-top: calc(var(--header-h) + clamp(3rem, 6vw, 5rem));
  padding-bottom: calc(clamp(3rem, 6vw, 4.5rem) + var(--round));
}

.page-hero--split + .section { padding-top: 0; }

.page-hero__grid {
  display: grid;
  /* Il min-height dà corpo alla fascia; titolo e sommario stanno incollati e
     la coppia si mette al centro verticale dello spazio. Tarato sul contenuto
     reale: a 27rem restavano 194px d'aria da spartire fra sopra e sotto, e
     l'apertura sembrava mezza vuota. */
  min-height: clamp(15rem, 30vw, 21rem);
  align-content: center;
  gap: 1rem;
}

.page-hero__note {
  max-width: 88ch;
  margin: 0;
  font-size: var(--text-lead);
  line-height: var(--leading-lg);
  color: var(--fg-muted);
}

.page-hero__title--display {
  max-width: 15ch;
  margin: 0;
  font-size: var(--text-display);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  text-wrap: balance;
}

/* Qui stava un'eccezione a 20ch per /team, tarata sul titolo di 71 battute
   che quell'apertura aveva. Adesso il titolo è «Il nostro team»: 484px di
   testo dentro un limite di 860px, una riga sola, l'eccezione non toccava
   più niente. I 15ch di default bastano per tutti i display del sito. */

/* --- la colonna ------------------------------------------------------------
   Tipografia editoriale: una colonna di lettura stretta e un corpo grande,
   come le pagine di solo testo che reggono (Linear Method, Raycast «Why»,
   Getting Real). Le figure escono su una misura più larga; una sola fascia
   scura a tutta larghezza. */
.page-ci {
  /* 52rem a 21px sono ~70 caratteri per riga: la misura di lettura lunga,
     non quella stretta da 42rem che su uno schermo largo si leggeva come una
     colonna da telefono lasciata in mezzo alla pagina. */
  --ci-col: 52rem;
  --ci-gap: clamp(1.25rem, 2.8vw, 2.5rem);
  --ci-body: var(--text-lg);
  /* Le superfici della pagina non sono più bianche con l'ombra: su una pagina
     bianca piena una scheda bianca esiste solo per la sua ombra. Prendono la
     foschia di --card, che si stacca da sé (1.24:1), e l'ombra sparisce.
     Resta scuro un solo oggetto, il ponte della catena. */
}

/* Un bordo sinistro solo, quello dello shell: il testo si ferma alla sua
   misura di lettura, le figure arrivano fino in fondo. Due blocchi centrati
   di larghezza diversa davano due verticali e la pagina si leggeva sfalsata;
   allineati a sinistra, la larghezza dello schermo la usano le figure. */
.ci-col { max-width: var(--ci-col); }

.ci-col--wide { max-width: none; }

/* Il corpo di lettura: grande, ben interlineato, sulla misura della colonna.
   Il colore resta quello di lettura del sito. */
.ci-p {
  font-size: var(--ci-body);
  line-height: 1.6;
  color: var(--fg-muted);
  text-wrap: pretty;
}

.ci-p + .ci-p { margin-top: 1.7em; }

.ci-p strong { color: var(--fg); font-weight: 600; }

/* --- l'apertura -----------------------------------------------------------
   Apre sul nero e lava fino al bianco, il rovescio dell'hero della home. La
   rampa non si ferma col blocco: è alta il doppio dell'apertura, così il
   titolo e la nota stanno tutti nella metà scura e il bianco arriva sotto,
   dove comincia il primo capitolo. Fermarla al bordo del blocco lasciava la
   nota — che finisce all'87% dell'apertura — sopra --blue-400, a 1.85:1. */
/* L'apertura resta ferma e il resto della pagina le sale sopra: e' il fondo
   che sembra fermo e il contenuto che scorre. `sticky` e non `fixed` —
   `fixed` la toglierebbe dal flusso e il documento perderebbe la sua altezza,
   che e' il difetto di chi questo effetto lo fa con una libreria.
   Il raccordo che il sito usa gia' fra un blocco e il successivo e' proprio il
   gesto che serve: il capitolo sale con gli angoli alti arrotondati, e
   scoprendo la rampa sotto si vede che lo sta coprendo.
   Ferma anche nel flusso, cioe' sul telefono, e non solo per il gesto: la
   barra campiona il fondo sotto di se', e con l'apertura che scorreva via le
   passava sotto la coda chiara della rampa — misurato a 390x844, fra 550 e
   740px di scorrimento la pillola virava a inchiostro e senza velo, per poi
   tornare bianca sul pannello: un lampo. Agganciata, sotto la barra c'e'
   sempre la cima scura dell'apertura finche' non arriva il pannello, che e'
   scuro anche lui. */
.ci-hero,
.team-hero {
  position: sticky;
  top: 0;
  z-index: 0;
  min-height: 100svh;
  display: grid;
  align-content: center;
  padding: 0 0 clamp(3.5rem, 8vw, 6rem);
}

/* Lo stacco dall'alto lo prende lo shell, non la sezione, e non è una
   preferenza: `.on-dark` sta sullo shell, ed è lo shell che il JS della barra
   incontra sotto la pillola. Tenendo il padding sulla sezione, lo shell
   cominciava dopo l'intestazione — sopra restava un nastro di sezione nuda,
   scurissimo ma non marcato come scuro, e lì il menu usciva in inchiostro su
   --blue-950. Facendolo partire a filo pagina, la zona scura dichiarata
   coincide con quella dipinta. */
/* Lo stacco sopra il titolo non è più solo respiro: è la misura che porta il
   testo fuori dalla rampa. In inchiostro il titolo ha bisogno di L* 57 sotto
   di sé, che nella rampa sta al 72.4%; con la rampa a 440px fa 319px, e questo
   padding ne dà 328. */
/* Il contenuto sta in mezzo allo schermo, e sulla foschia scura si legge in
   bianco: e' il trattamento dell'apertura di /product. */
.ci-hero .shell,
.team-hero .shell { padding-top: var(--header-h); }

/* Invisibile: nessun fondo, nessun contenuto. Esiste solo perché
   `elementFromPoint` gli trovi sopra un antenato `.on-dark` e la barra resti
   bianca finché ci passa sopra. Finisce dove finisce il padding dello shell,
   cioè dove il fondo diventa abbastanza chiaro da reggere l'inchiostro. */
.ci-hero__top,
.team-hero__top {
  position: absolute;
  inset: 0 0 auto 0;
  height: calc(var(--header-h) + clamp(12rem, 18vw, 16rem));
  background: none;
}

/* Adesso la rampa è una fascia in cima e non segue più il blocco: il testo
   dell'apertura è in inchiostro e sta sul bianco, quindi alla rampa resta un
   vincolo solo, il menu. A riposo la pillola non ha velo — appoggia sul fondo
   nudo — e la barra è bianca finché il punto che il JS campiona sta dentro
   `.ci-hero__top`. Quel punto arriva al massimo a 174px (12px sotto la barra,
   più i 90 della soglia di aggancio), e lì il fondo deve ancora dare 4.5:1 sul
   bianco. Il punto è salito con la coda: comprimendo il corpo per far posto
   alla discesa morbida, --blue-600 è passato dal 40.32% al 32.92%, quindi a
   parità di altezza il fondo sotto la barra è più chiaro. 480px rimettono il
   margine, e restano 480 anche sul telefono, dove la barra è più alta e la
   soglia di aggancio cade più in basso nella rampa.

   Il bianco pieno arriva a 440px. Le versioni legate all'altezza del blocco lo
   portavano a 590, 887, 1100 e 1590. */
/* La rampa di /product, la stessa di .page-hero--dark: --blue-950 pieno per
   il primo 40% e poi la discesa al chiaro. E' quella che fa funzionare il
   bianco — barra e titolo stanno tutti dentro il tratto pieno — ed e' gia'
   l'apertura di un'altra pagina del sito, quindi non e' un trattamento nuovo.
   Le fermate sono ripartite per luminosita', come la', e per lo stesso
   motivo: e' l'unica ripartizione che non lascia bande su una superficie di
   questa misura.
   La coda chiara non fa scalino col palco: il pannello della prima scena le
   sale sopra e la copre mentre si scorre. */
.ci-hero::before,
.team-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: linear-gradient(
    180deg,
    var(--blue-950) 40%,
    var(--blue-900) 41.7%,
    var(--blue-850) 43.7%,
    var(--blue-800) 47.9%,
    var(--blue-700) 54.3%,
    var(--blue-600) 64.4%,
    var(--blue-500) 77.7%,
    var(--blue-400) 86.2%,
    var(--blue-300) 92%,
    rgba(203, 220, 227, 0) 100%
  );
  background-repeat: no-repeat;
  background-size: 100% 100%;
}



/* La grana, come sull'hero della home: non è decorazione, è il dither che
   rompe le bande a otto bit per canale. Spenta sulla coda chiara — lì il
   bianco deve restare pulito e non c'è più banda da rompere. */
/* La grana dell'apertura sta sulla rampa, non sul territorio: e' la rampa ad
   avere gradini da rompere, e sfuma con lei. */
.ci-hero::after,
.team-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: overlay;
  background-image: var(--grain);
  background-repeat: repeat;
  background-size: 200px 200px;
  /* La grana serve dove la rampa ha gradini da rompere, cioe' in cima dove
     e' chiara: in fondo il nero non ne ha. */
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 72%, transparent 96%);
  mask-image: linear-gradient(180deg, #000 0%, #000 72%, transparent 96%);
}

/* `.on-dark` porta con sé un fondo pieno, e qui il fondo è la rampa. */
.ci-hero .on-dark,
.team-hero .on-dark { background: none; }

/* Sulle altre pagine la pillola sopra lo scuro si appoggia al fondo e le basta
   un velo bianco al 7%: sotto ci passa --blue-800 o giù di lì, tinta piatta.
   Qui sotto ci passa una rampa, e nel suo ultimo tratto scuro il fondo è già
   un mezzotono: il bianco del menu ci scendeva a 4.2:1, testo piccolo. Il velo
   allora se lo porta lei — è l'unico punto del sito dove la pillola ha un
   fondo proprio invece di prenderlo in prestito. */
.page-ci .site-header--pill.site-header--sticky.is-stuck.is-over-dark .site-header__inner,
.page-team .site-header--pill.site-header--sticky.is-stuck.is-over-dark .site-header__inner {
  background: rgba(var(--night-rgb), 0.5);
}

/* Stessa misura dell'apertura di /team, /blog e /product: 120px facevano di
   questa pagina un'eccezione dentro il sito. Il token è quello, così restano
   allineate anche se cambia. */
/* 60rem è la misura a cui si fermano /team e /blog: il `ch` qui calcolava
   1539px e il titolo restava su una riga sola larga quanto lo shell. */
.ci-hero__title,
.team-hero__title {
  max-width: 60rem;
  font-size: var(--text-display);
  font-weight: 600;
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  text-wrap: balance;
}

.ci-hero__note,
.team-hero__note {
  max-width: 52ch;
  margin-top: clamp(2rem, 4vw, 3rem);
  font-size: var(--text-lg);
  line-height: var(--leading-lg);
  color: var(--fg-muted);
  text-wrap: pretty;
}

/* --- i capitoli ------------------------------------------------------------ */
/* Lo stacco fra un capitolo e il successivo è la somma dei due padding, e a 160
   per lato faceva 320px: esattamente il difetto che la regola di
   `.page-ci .section` qui sopra aveva già corretto — «ogni blocco sembrava una
   pagina a sé» — ma raddoppiato. Si vedeva soprattutto in fondo ai primi due
   capitoli, dove l'inciso li chiude: 72px sopra la nota e 320 sotto, e la nota
   restava sospesa nel vuoto. A 96 per lato lo stacco fra capitoli fa 192, il
   doppio esatto dei 96 che separano i blocchi *dentro* il capitolo: la
   gerarchia si sente ancora, ma come un a capo, non come una pagina nuova.
   Minimo e pendenza sono quelli di `.page-ci .section`; solo il tetto è più
   alto, perché questi sono i tre movimenti della pagina e non sottosezioni. */
/* Un solo passo verticale per tutta la pagina, e nessuna somma. Prima il
   capitolo portava la propria cassa e la fascia il proprio margine: sopra una
   fascia si sommavano (96 + 88 = 184), sotto no (88), e la stessa figura
   aveva due arie diverse ai due capi. Adesso la cassa del capitolo è zero e
   il passo lo dichiarano i blocchi uno per uno: i margini di fratelli
   adiacenti si fondono in uno, e siccome i capitoli non hanno più cassa la
   fusione attraversa anche il confine fra un capitolo e il seguente. Fra due
   blocchi qualsiasi resta --ci-passo, mai il doppio. */
.page-ci {
  --ci-passo: clamp(3.5rem, 7vw, 6rem);
  --round: clamp(1.75rem, 4vw, 3.25rem);
}

.ci-chapter { padding-block: 0; }

.ci-chapter > .shell { margin-block: var(--ci-passo); }

/* Il titolo non sta nella colonna del testo: alla misura di lettura andava a
   capo con mezzo schermo libero a destra. Qui ha una misura sua, più larga. */
/* Dentro una scena il titolo non ha una colonna di lettura da rispettare: e'
   solo, e su due righe spezzate perde forza. Misurato, il piu' lungo chiede
   886px su 1425 disponibili — ma quei 1425 ci sono solo sul desktop, quindi
   la riga unica vale li' e non piu' in basso. */
@media (min-width: 860px) {
  .ci-scena .ci-chapter__title { max-width: none; text-wrap: nowrap; }
}

.ci-chapter__title {
  max-width: 26ch;
  /* Il titolo e il paragrafo che apre il capitolo sono la stessa cosa detta in
     due tempi, e vanno letti insieme. A 64px l'aria sotto il titolo era più
     larga di quella fra un paragrafo e l'altro (1.7em, 35.7px): il titolo si
     staccava e galleggiava. Sotto quella misura torna attaccato al suo testo,
     e lo stacco fra blocchi resta quello di .ci-col + .ci-col. */
  margin-bottom: clamp(1.4rem, 2.6vw, 2.15rem);
  /* Il token dei titoli di sezione, lo stesso di /team e /product: erano a
     52px contro i 46 di tutto il resto del sito. */
  font-size: var(--text-h2);
  font-weight: 600;
  line-height: var(--leading-h2);
  letter-spacing: var(--tracking-h2);
  text-wrap: balance;
}

/* Stacco fra il testo e la figura che segue, e fra la figura e il testo. */
.ci-col + .ci-col { margin-top: clamp(3.5rem, 7.5vw, 6rem); }

/* --- l'equazione che non torna --------------------------------------------
   Le tre voci che il testo elenca — «documenti aggiornati, formazione erogata
   e audit superati» — passano tutte e tre, e la spunta lo dice. Il punto del
   capitolo è che la loro somma non fa la quarta casella: da sole, tre spunte
   in fila si leggevano come «va tutto bene», cioè il contrario del paragrafo
   che le introduce. Il segno prima dell'esito è un ≠, e l'esito che si dà per
   scontato porta la croce.

   Superfici in foschia e non bianche: la pagina è bianca piena, e una scheda
   bianca lì sopra esiste solo per la sua ombra. Con --card, che è il token
   delle schede del sito, la superficie si stacca da sé (1.24:1) e l'ombra non
   serve più. */
.ci-rows {
  display: grid;
  /* Sette colonne: tre addendi, i due `+`, il `≠` e l'esito. Gli operatori
     stanno nella griglia e non fra le schede con un margine, così le tre
     colonne restano uguali e il segno si centra da sé sul vuoto. */
  grid-template-columns:
    minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 1.28fr);
  align-items: stretch;
  gap: clamp(0.55rem, 1.3vw, 1.15rem);
  margin-top: clamp(3.5rem, 7.5vw, 6rem);
}

.ci-row {
  /* Dichiarati come variabili perché la casella dell'esito deve scalarli del
     proprio bordo: senza, i suoi 2px mangiano la cassa interna e il testo si
     posa due pixel più in alto degli altri tre. */
  --pad-y: clamp(1.75rem, 3vw, 2.25rem);
  --pad-x: clamp(1.5rem, 2.6vw, 2rem);

  display: grid;
  align-content: space-between;
  gap: clamp(1.5rem, 3vw, 2.25rem);
  padding: var(--pad-y) var(--pad-x);
  border-radius: var(--radius);
  background: var(--card);
}

/* L'esito non è una quarta attività: non ha superficie, ha un contorno
   tratteggiato, che è il segno di ciò che si dà per scontato senza
   verificarlo. */
/* Niente bordo: la scheda e' come le altre tre, e a distinguerla bastano la
   croce rossa davanti alla parola e il NON che apre la frase. Il tratteggio,
   accanto a tre schede piene, faceva leggere la quarta come un segnaposto. */

/* Icona a sinistra e segno a destra sulla stessa riga: da lontano si legge
   la fila dei segni, che è il punto del capitolo. */
.ci-row__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* --blue-500 su questa foschia fa 2.33:1, sotto la soglia di un elemento
   grafico. --blue-600 ne fa 4.96 e l'icona smette di essere un fantasma. */
.ci-row__icon {
  flex: none;
  --icon-size: 2rem;
  --icon-stroke: 1.4;
  color: var(--fg-faint);
}

/* Una riga sola. `balance` le spezzava in due per pareggiarle, ma qui non
   sono un titolo da comporre: sono le voci di una somma, e su due righe la
   somma si legge come quattro paragrafi.
   Il corpo scende sotto quello dei titoli: misurato, alla scala h5 la voce
   piu' lunga — «NON conforme nell'insieme» — chiede 290px su 240 di cassa, e
   nessuna larghezza di colonna la salva senza strozzare le altre tre. Sono
   etichette di uno schema, non titoli. */
.ci-row__text {
  font-family: var(--font-display);
  font-size: clamp(1rem, 1.27vw, 1.18rem);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.015em;
}

/* Una riga sola dove c'e' larghezza per una riga sola: sotto gli 860 le
   schede si incolonnano e sono strette, e `nowrap` le faceva sfondare la
   pagina — misurato, 570px di contenuto su 390 di finestra. */
@media (min-width: 860px) {
  .ci-row__text { white-space: nowrap; }
}

/* --- i segni ---------------------------------------------------------------
   Verde e rosso non stanno nella tavola: il turchese è il colore del segnale
   vivo su tutto il sito — le tappe accese, la lettura che risolve — e --error
   è il rosso già calibrato per essere leggibile. Il glifo prende su ciascuno
   il contrasto massimo: inchiostro sul turchese (8.2:1), bianco sul rosso
   (5.0:1). Il colore non porta il significato da solo: sotto c'è la forma,
   spunta o croce. */
.ci-mark {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
}

.ci-mark .icon {
  --icon-size: 0.95rem;
  --icon-stroke: 2.75;
}

/* Spunta chiara su ciano pieno, come la croce chiara sul rosso: sul celeste
   la spunta a inchiostro si leggeva come un segno diverso. --brand-4 e non
   --accent: il token si rovescia in .on-dark e la spunta resterebbe chiara su
   celeste. Il 4 è il gradino più vivo che tiene la spunta bianca sopra il
   3:1 — 3.13:1; dal 3 in su la spunta si perde. */
.ci-mark--ok  { background: var(--brand-4); color: var(--paper); }
.ci-mark--bad { background: var(--error);    color: var(--bg); }

/* --- gli operatori --------------------------------------------------------- */
/* Il piu' e il non-uguale sono due operatori della stessa formula: stessa
   misura e stesso colore. Il rosso stava sul secondo, e lo faceva leggere
   come un avviso invece che come un segno di calcolo — l'avviso lo porta gia'
   la croce dentro la scheda. */
.ci-op {
  display: grid;
  place-items: center;
  color: var(--fg);
}

.ci-op svg {
  display: block;
  width: clamp(1.5rem, 2.4vw, 2.1rem);
  height: auto;
}

/* Il ≠ è l'unico segno che cambia il senso della riga, e si vede: più grande
   degli addendi e nel rosso di segnalazione. */
/* Il ≠ non è un addendo: sta più largo degli altri due segni e ha aria
   propria ai lati, così la riga si legge come «questi tre» da una parte e
   «quello» dall'altra, non come quattro caselle in fila. */
.ci-op--ne { margin-inline: clamp(0.4rem, 1.2vw, 1.1rem); }



@media (max-width: 900px) {
  .ci-rows { grid-template-columns: 1fr; }
  .ci-op svg, .ci-op--ne svg { width: 1.4rem; }
}

/* --- la freccia dello scorrimento ------------------------------------------
   Semitrasparente e in movimento: dice «si scorre» senza pesare come un
   elemento dell'apertura. Scende e risale di pochi pixel, e l'opacita' la
   accompagna — un movimento solo, ripetuto piano, che si nota con la coda
   dell'occhio e non chiede attenzione.

   Il respiro sta sul disegno, non sul contenitore. Sul contenitore se lo
   mangiavano le dissolvenze di scorrimento, che riempiono tutta la timeline e
   hanno l'ultima parola sull'opacita': misurata, la freccia stava ferma a 1 a
   ogni altezza di pagina. Diviso in due — il battito sull'svg, le dissolvenze
   sullo span — i due moti si moltiplicano invece di escludersi.

   Fuori dal palco la freccia sta dov'e' sempre stata, in fondo alla prima
   schermata, e si spegne appena il pannello le sale addosso — vedi il blocco
   @supports qui sotto. Sul palco resta accesa per tutta la corsa. */
.ci-scorri,
.team-scorri {
  /* Misura e stacco dal fondo stanno in due variabili perche' non servono
     solo a disegnarla: da loro si calcolano l'aggancio alla prima schermata e
     il punto in cui la freccia cambia colore. */
  --scorri-misura: clamp(2rem, 4vh, 3rem);
  --scorri-stacco: clamp(1.5rem, 4vh, 2.75rem);

  /* Fissa sulla finestra in tutti e due i modi, e non nel documento:
     l'apertura e' agganciata, e una freccia assoluta nel documento le
     sarebbe salita sopra insieme al pannello invece di restare in fondo alla
     prima schermata. `bottom` sulla finestra vale anche sul telefono, dove
     la barra del browser cambia l'altezza: la freccia segue il bordo basso
     che si vede. */
  position: fixed;
  left: 50%;
  bottom: var(--scorri-stacco);
  z-index: 3;
  pointer-events: none;

  /* Inchiostro, non bianco: a riposo la freccia sta in fondo all'apertura,
     dove la rampa e' gia' arrivata al chiaro, e il bianco ci spariva dentro. */
  color: var(--ink);
  transform: translateX(-50%);
}

.ci-scorri svg,
.team-scorri svg {
  display: block;
  width: var(--scorri-misura);
  height: auto;
  animation: ci-scorri 2.4s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}

/* Il moto segue la freccia: e' diagonale, quindi scivola in diagonale. In
   verticale sembrava che tremasse invece che indicare. */
@keyframes ci-scorri {
  0%, 100% { opacity: 0.22; translate: -5px -5px; }
  50% { opacity: 0.6; translate: 5px 5px; }
}

/* Senza palco — il telefono — la freccia ha finito il suo lavoro appena si
   scorre: il pannello sale dal bordo basso e le passa addosso fra `stacco` e
   un quadrato piu' in giu' di scorrimento, ed e' li' che si spegne. La
   timeline e' lo scorrimento della pagina, quindi niente listener. Chi non
   ha le timeline non la vede: fissa e senza spegnimento resterebbe appesa
   per tutta la pagina, che e' il difetto da cui si parte. */
@supports (animation-timeline: scroll()) {
  .ci-scorri,
  .team-scorri {
    animation: ci-scorri-via linear both;
    animation-timeline: scroll(root);
    animation-range: 0 calc(var(--scorri-stacco) + var(--scorri-misura));
  }
}

@supports not (animation-timeline: scroll()) {
  .ci-scorri,
  .team-scorri { display: none; }
}

@keyframes ci-scorri-via {
  from { opacity: 1; }
  to { opacity: 0; }
}

/* Col palco la freccia resta accesa per tutta la corsa, appesa alla finestra e
   sopra le scene. Non e' un di piu': la vista sta agganciata per ottomila
   pixel, la pagina sotto non si muove e nessun bordo spunta dal basso, quindi
   il solo segno che c'e' ancora da leggere e' la freccia. Sparendo al primo
   scorrimento — che e' quel che faceva — lasciava lo schermo fermo e muto.

   Le due fermate sono misurate, non a occhio:

   - il colore. Alla partenza la freccia sta sulla coda chiara dell'apertura e
     in bianco ci sparirebbe; un attimo dopo le sale dietro il pannello scuro
     della prima scena e a sparire e' l'inchiostro — misurato, 1.08:1 su
     #04070d. Il bordo alto del pannello le passa addosso fra `stacco` e un
     quadrato piu' in giu' — il pannello e' scuro fin dal suo bordo, perche'
     gli strati di fondo coprono anche la cassa della barra (vedi .ci-fondo);
     prima cominciavano settantadue pixel piu' in basso e la fermata stava a
     `--header-h + stacco` — ed e' esattamente li' che il colore va da
     inchiostro a carta. Le lunghezze contano dall'inizio della timeline,
     cioe' da quando il palco affaccia dal bordo basso: qui cade a
     scorrimento zero, quindi sono pixel di pagina.
   - l'uscita. In fondo alla corsa il palco si stacca e sotto arriva l'invito:
     li' la freccia non direbbe piu' niente di vero e resterebbe a
     galleggiare sopra i tasti. */
/* Le due soglie sono quelle dei rispettivi palchi, e devono esserlo: il nome
   `--palco` nasce dentro il `@media` che accende il palco, e fuori di li' non
   esiste. Un'animazione che punta a una timeline assente non parte — non torna
   al comportamento di riserva, resta al suo stile di base — e la freccia si
   sarebbe piantata accesa e in inchiostro per tutta la pagina. Misurato sulla
   soglia di prima, 860: fra gli 860 e i 900 su /compliance-intelligence
   accadeva esattamente questo. Allineate, sotto la soglia comanda il blocco
   `scroll(root)` qui sopra e la freccia si spegne dopo la prima schermata. */
@supports (animation-timeline: view()) and (animation-range: contain 0%) {
  @media (min-width: 901px) and (min-height: 640px) and (prefers-reduced-motion: no-preference) {
    /* Il nome del palco nasce dentro il palco; portandone lo scopo sul main lo
       vede anche la freccia, che del palco e' sorella e non figlia. */
    .page-ci #main { timeline-scope: --palco; }

    .ci-scorri {
      animation: ci-scorri-carta linear both, ci-scorri-via linear both;
      animation-timeline: --palco, --palco;
      animation-range:
        var(--scorri-stacco)
        calc(var(--scorri-stacco) + var(--scorri-misura)),
        contain 90% contain 100%;
    }
  }

  /* /team accende il palco a 1025 e non a 901 — glielo impone la griglia delle
     persone, non il gusto: vedi `.team-palco`. La freccia segue quella soglia
     e non un'altra. */
  @media (min-width: 1025px) and (min-height: 640px) and (prefers-reduced-motion: no-preference) {
    .page-team #main { timeline-scope: --palco; }

    .team-scorri {
      animation: ci-scorri-carta linear both, ci-scorri-via linear both;
      animation-timeline: --palco, --palco;
      animation-range:
        var(--scorri-stacco)
        calc(var(--scorri-stacco) + var(--scorri-misura)),
        contain 90% contain 100%;
    }
  }
}

@keyframes ci-scorri-carta {
  from { color: var(--ink); }
  to { color: var(--paper); }
}

@media (prefers-reduced-motion: reduce) {
  /* Senza animazioni non si spegne, e fissa resterebbe appesa per tutta la
     pagina; e senza palco non c'e' una vista ferma da cui invitare a
     scorrere. */
  .ci-scorri,
  .team-scorri { display: none; }
}

/* --- il palco --------------------------------------------------------------
   Un palco solo per tutta la pagina. Dopo l'apertura non si scorre piu': la
   vista resta agganciata in cima e sono le nove scene a darsi il cambio dentro
   lo stesso riquadro. A scorrere e' il contenuto, non la pagina.

   La corsa e' alta e non porta niente: e' scorrimento di scorta, ed e' lei che
   la timeline misura. Mille schermate-viewport di altezza: una per la vista
   agganciata e una per ogni scena, cioe' novecento pixel di scorrimento a
   scena su mille di finestra.

   Il fondo se lo porta ogni scena, a tutta finestra. I due territori restano —
   buio sul divario, carta sul come e sul cosa, buio sulla tesi — ma non hanno
   piu' un bordo da nascondere: fra una scena e l'altra c'e' una dissolvenza,
   e una dissolvenza non lascia cuciture. E' la ragione per cui il palco
   sostituisce i territori invece di convivere con loro.

   Tutto sta dentro @supports e sopra gli 860px: senza timeline CSS la corsa
   torna alta quanto il contenuto, la vista non si aggancia, le scene perdono
   la sovrapposizione e si impilano nel flusso. Resta la pagina di sempre,
   senza l'effetto. Non esiste una versione rotta.

   Nel flusso la vista e' il pannello: un blocco scuro che sale sopra
   l'apertura agganciata — lo stesso gesto del palco, solo che poi si scorre.
   Il colore lo portano gli strati di fondo (qui sotto), e sopra il nero
   passano le scene una dopo l'altra. Il passo fra una scena e l'altra e' il
   passo della pagina, e lo mette la griglia: i margini delle scene, in una
   griglia, non si fondono — misurati a 390px, fra due scene restavano
   centododici pixel di margine piu' le casse. Marcata `.on-dark` anche lei:
   fra una scena e l'altra la sonda della barra cade sul pannello nudo, e
   senza il marchio il menu tornava inchiostro sul nero. */
.ci-palco__vista {
  position: relative;
  display: grid;
  row-gap: var(--ci-passo);
  padding-block: var(--ci-passo);
  /* `.on-dark` dipinge --bg; qui il fondo e' degli strati. */
  background: none;
}

/* La cassa si misura sull'altezza della finestra e non sulla larghezza: in un
   palco il vincolo e' verticale. Misurata, la scena piu' alta stava a 897px su
   900 con la cassa vecchia — su uno schermo basso traboccava. */
.ci-scena,
.team-scena { padding-block: clamp(1.5rem, 5vh, 4rem); }

/* Il fondo non sta sulla scena: sta sotto, su tre strati suoi. Sulla scena
   sfumerebbe insieme al contenuto, e a metà dissolvenza si vedrebbe quel che
   c'è dietro il palco — l'apertura agganciata, col suo titolo sopra la figura.
   È il difetto che si vedeva: «…telligence?» stampato sopra la timeline.

   Il primo strato è sempre opaco; gli altri due entrano al loro momento e
   restano. Così il colore cambia in dissolvenza fra due pieni, e dietro non
   si vede mai niente.

   Le classi --buio e --carta restano sulle scene, ma adesso portano soltanto
   i token del testo, non un colore. */
/* Trasparenti: il colore lo portano gli strati di fondo. Senza questo,
   .on-dark dipinge il suo --bg — --steel-11 — e le scene scure coprivano il
   --blue-950 degli strati: misurato, l'apertura chiudeva su #04070c e la
   prima scena ripartiva da #112938, cioe' proprio lo scalino che il
   rovesciamento della rampa doveva togliere. */
.ci-scena,
.team-scena {
  position: relative;
  background: none;
}

/* Coprono tutta la vista, cassa compresa, in tutti e due i modi. Nel palco
   la vista riserva i settantadue pixel della barra con un padding, e uno
   strato che stia nella cella della griglia comincia sotto quel padding:
   misurato a 1440x900, mentre il pannello saliva sopra l'apertura restava
   una fascia di settantadue pixel in cui si vedeva la rampa (rgb 90,119,140
   sopra il nero). Assoluti sul riquadro della vista, non c'e' fascia. Nel
   flusso sono loro il fondo del pannello: senza, le scene — trasparenti —
   lasciavano vedere l'apertura agganciata, titolo compreso. */
.ci-fondo,
.team-fondo {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.ci-fondo--base,
.ci-fondo--coda,
.team-fondo--base,
.team-fondo--coda { background-color: var(--blue-950); }

/* Lo strato di coda vira col resto della zona di chiusura: --bg-reveal lo
   aggiorna il JS leggendo lo scorrimento ed e' su :root. Senza, la tesi
   restava nera mentre l'invito subito sotto virava al blu, e nel mezzo si
   vedeva la cucitura. Sta su ::before perche' ::after qui porta la grana. */
.ci-fondo--coda::before,
.team-fondo--coda::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(90deg, var(--blue-800), var(--blue-700) 52%, var(--blue-800));
  opacity: var(--bg-reveal, 0);
  transition: opacity 0.45s cubic-bezier(0.33, 1, 0.68, 1);
}

/* La grana sugli strati scuri, come sul blocco scuro della home: non è
   decorazione, è il dither che rompe le bande a otto bit per canale. */
/* Anche il cierre porta la grana, e solo su questa pagina: sotto la vista del
   palco comincia .closing, che sul resto del sito e' liscio. Qui la vista
   arriva col grano e il cierre senza, e il confine fra i due si vede come una
   riga sottile a tutta larghezza — non e' un colore, e' la texture che si
   interrompe. Misurato: il salto di colore li' vale due punti, cioe' niente.
   Sta su ::after perche' ::before e' il velo che vira con lo scorrimento. */
.page-ci .closing,
.page-team .closing { position: relative; }

.page-ci .closing::after,
.page-team .closing::after,
.ci-fondo--base::after,
.ci-fondo--coda::after,
.team-fondo--base::after,
.team-fondo--coda::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: overlay;
  background-image: var(--grain);
  background-repeat: repeat;
  background-size: 200px 200px;
}


/* Sullo scuro il testo spento della tesi deve restare spento davvero: il
   token di serie qui vale bianco all'88% e il riempimento non si vedrebbe. */
.ci-scena--buio .reveal-fill,
.team-scena .reveal-fill { --fg-faint: rgba(var(--paper-rgb), 0.26); }

@supports (animation-timeline: view()) and (animation-range: contain 0%) {
  /* Sul telefono no: una vista alta quanto la finestra lascia le scene in
     mezzo al nulla, e novecento pixel di scorrimento a scena si sentono
     tutti.

     901 e non 860, e non e' un arrotondamento: a 900 gli schemi si
     incolonnano — l'equazione, la catena, la convergenza, la fascia — e una
     colonna di quattro schede non sta in una finestra alta quanto lo schermo.
     Fra gli 860 e i 900 il palco si accendeva su un contenuto impilato e lo
     tagliava: misurato a 860x620, la scena dell'equazione ne chiedeva
     centotrenta in piu' di quanti ce n'erano, la convergenza trecento. La
     soglia del palco e quella dell'impilamento devono essere la stessa, o
     resta una fascia di quaranta pixel in cui la pagina si vede a meta'.

     E un palco vuole anche un minimo di altezza. Sotto i 640 la scena della
     convergenza — la piu' piena — non ci sta comunque: misurata a 1280x600 ne
     chiedeva venti in piu', a 901x620 trentasei, e il palco li tagliava senza
     dire niente. Li' sotto la pagina torna quella del flusso: si scorre, e si
     legge tutto. Non e' una versione rotta, e' l'altra. */
  @media (min-width: 901px) and (min-height: 640px) and (prefers-reduced-motion: no-preference) {
    .ci-palco {
      height: 800vh;
      view-timeline-name: --palco;

      /* La chiusura risale sopra la coda vuota che il centraggio lascia sotto
         l'ultima scena. Quanto valga lo misura il JS (vedi `--palco-coda` in
         main.js): dipende dal testo della scena e dall'altezza della
         finestra, e il CSS non puo' saperlo. Senza JS vale zero e il palco
         resta com'era. Il riquadro del palco non cambia, quindi ne' la
         tenuta della vista ne' la corsa della timeline si spostano: si
         muove solo quel che viene dopo. */
      margin-bottom: calc(-1 * var(--palco-coda, 0px));
    }

    .ci-palco__vista {
      position: sticky;
      top: 0;
      height: 100vh;
      overflow: hidden;
      /* La barra e' fissa e copre i primi settantadue pixel: senza riservarli,
         la scena si centra su tutta la finestra e la sua cima finisce sotto il
         menu — da fuori sembra che il contenuto sia tutto spinto in basso.
         Sotto niente: la cassa e il passo del flusso qui non servono, le
         scene stanno tutte nella stessa cella. */
      padding: var(--header-h) 0 0;
      row-gap: 0;
      /* La riga e' una sola e la dimensiona la scena piu' alta: senza questo
         vincolo valeva settecentodue pixel su una finestra da seicentoventi,
         il `height: 100%` delle scene la seguiva e tutte e sette scendevano
         di trecentoquarantasette pixel. `minmax(0, 1fr)` la tiene dentro la
         vista, e il centraggio torna a misurarsi sullo schermo. */
      grid-template-rows: minmax(0, 1fr);
    }

    /* Entrano e restano: `palco-entra` non ha fermata d'uscita. A coprirli e' lo
       strato seguente, non la loro sparizione. */
    .ci-fondo--coda {
      animation: palco-entra linear both;
      animation-timeline: --palco;
    }

    /* Il colore cambia sul confine, dove non c'e' niente in scena: prima di
       li' la scena che esce ha ancora testo sopra, e vedere il fondo
       schiarirsi sotto un testo chiaro sarebbe un buco di contrasto. */
    .ci-palco__vista .ci-fondo--coda { animation-range: contain 84.8% contain 86.7%; }

    /* Il selettore e' piu' lungo del necessario di proposito: le regole delle
       scene stanno piu' in basso nel foglio e, a parita' di peso,
       vincerebbero. */
    .ci-palco__vista .ci-scena {
      grid-area: 1 / 1;
      width: 100%;
      height: 100%;
      margin: 0;
      display: grid;
      align-content: center;
      animation: palco-scena linear both;
      animation-timeline: --palco;
    }

    /* `contain` e' il tratto in cui la corsa copre la finestra: comincia
       quando il suo fondo tocca il bordo basso e finisce quando la sua cima lo
       raggiunge. E' li' che la vista sta ferma, quindi e' li' che le scene
       devono darsi il cambio. Nove fette da undici punti, con quattro di
       sovrapposizione: una scena e' gia' in uscita mentre la seguente entra, e
       in mezzo non resta mai il vuoto. */
    /* Tratti adiacenti, senza un punto di sovrapposizione: la scena che esce
       tocca lo zero esattamente dove la seguente parte da zero, quindi non c'e'
       nessun istante con due scene accese. Sovrapponendoli si vedeva il testo
       dell'una sopra quello dell'altra. Un settimo di corsa a testa, e il
       cambio avviene in un attimo in cui lo schermo porta solo il fondo. */
    .ci-palco__vista .ci-scena--1 { animation-name: palco-scena-prima; animation-range: contain 0% contain 14.286%; }
    .ci-palco__vista .ci-scena--2 { animation-range: contain 14.286% contain 28.571%; }
    .ci-palco__vista .ci-scena--3 { animation-range: contain 28.571% contain 42.857%; }
    .ci-palco__vista .ci-scena--4 { animation-range: contain 42.857% contain 57.143%; }
    .ci-palco__vista .ci-scena--5 { animation-range: contain 57.143% contain 71.429%; }
    .ci-palco__vista .ci-scena--6 { animation-range: contain 71.429% contain 85.714%; }
    .ci-palco__vista .ci-scena--7 { animation-name: palco-scena-ultima; animation-range: contain 85.714% contain 100%; }
  }
}

/* `visibility` accanto all'opacita', e non e' un di piu': a opacita' zero un
   elemento resta cliccabile e continua a rispondere a elementFromPoint. Le
   nove scene stanno nella stessa cella, quindi il punto sotto la barra
   trovava sempre l'ultima del DOM — la nona, che e' scura — e la barra
   restava in modo scuro anche sopra le scene chiare, col menu bianco su
   carta. Nascoste per davvero, il campione trova la scena che si vede.
   Vale anche per chi naviga da tastiera: dentro una scena spenta non si
   deve poter entrare col tab. */
@keyframes palco-entra {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Ogni scena ha una zona morta ai due capi: si spegne prima di arrivare al
   confine e si riaccende dopo averlo passato. In mezzo resta una finestra in
   cui lo schermo porta solo il fondo, ed e' li' dentro che il colore cambia.
   Senza, il fondo virava mentre una scena era ancora a meta' dissolvenza: si
   vedeva il testo schiarire insieme alla carta, e il passaggio da un
   territorio all'altro si leggeva come un errore invece che come un capitolo
   nuovo. Prima il fondo, poi il contenuto. */
@keyframes palco-scena {
  0%, 10% { opacity: 0; visibility: hidden; transform: translateY(26px); }
  24%, 76% { opacity: 1; visibility: visible; transform: none; }
  90%, 100% { opacity: 0; visibility: hidden; transform: translateY(-26px); }
}

/* La prima nasce gia' in scena: il palco si apre su di lei. */
@keyframes palco-scena-prima {
  0%, 76% { opacity: 1; visibility: visible; transform: none; }
  90%, 100% { opacity: 0; visibility: hidden; transform: translateY(-26px); }
}

/* L'ultima resta: quando la corsa finisce e' lei che il lettore ha davanti. */
@keyframes palco-scena-ultima {
  0%, 10% { opacity: 0; visibility: hidden; transform: translateY(26px); }
  24%, 100% { opacity: 1; visibility: visible; transform: none; }
}

/* --- la lastra -------------------------------------------------------------
   I quattro schemi del capitolo non stanno più sulla carta: stanno su una
   lastra scura. Non è una scelta di gusto, è dei render. Il vetro è
   fotografato in studio su fondo nero — misurati, gli angoli dei file stanno
   fra #000000 e #0b1419 — e su carta chiara ogni tessera si porterebbe
   dietro il proprio rettangolo di studio. Su --steel-12 quel rettangolo e la
   lastra sono parenti, e di visibile resta il vetro. È la stessa ragione per
   cui la scheda della bento è agganciata al fondo dei suoi PNG.

   Va scura la figura, non il capitolo: il testo di lettura resta sulla carta.
   Capitoli interi a tema alterno erano già stati provati e tolti perché il
   ritmo si appesantiva invece di scandirsi (vedi .ci-band); qui cambia
   superficie lo schema, non il discorso, e quella misura regge.

   Si ridefinisce --bg e non solo lo sfondo: dentro il blocco scuro l'anello
   delle palline della timeline si dipinge con --bg, e se la lastra fosse un
   altro gradino ogni pallina porterebbe un alone del colore sbagliato. */
/* Lo stacco che gli schemi portavano dalla colonna di testo adesso lo fa la
   cassa della lastra: sommati, erano due volte lo stesso vuoto. */
.ci-scena .ci-rows,
.ci-scena .ci-timeline { margin-top: 0; margin-bottom: 0; }

/* Lo stacco fra la testata del capitolo e il suo schema, dentro la fascia. */
/* Lo stacco fra i blocchi di una scena, in tutte e due le direzioni: il
   testo che introduce uno schema e il testo che lo commenta vogliono la
   stessa aria. Senza la seconda riga, la prosa che segue la catena e la tesi
   che chiude il capitolo restavano incollate a quel che avevano sopra —
   misurato, zero pixel, subito dopo un vuoto di novantasei. */
.ci-scena .ci-col + .ci-rows,
.ci-scena .ci-col + .ci-chain__flow,
.ci-scena .ci-col + .ci-timeline,
.ci-scena .ci-rows + .ci-col,
.ci-scena .ci-chain__flow + .ci-col,
.ci-scena .ci-timeline + .ci-col,
.ci-scena .ci-col + .ci-final { margin-top: clamp(2.5rem, 5vw, 4rem); }

/* L'invito dentro l'ultima scena. La banda nasce per stare fra due sezioni —
   porta la propria cassa verticale e il proprio `shell` — ma qui e' l'ultimo
   blocco di una scena che la cassa ce l'ha gia', e il suo shell starebbe
   dentro quello della scena: due volte lo stesso margine laterale, e i tasti
   rientrati rispetto al testo sopra. Qui la banda torna quel che deve essere
   in questo posto: un blocco di contenuto come gli altri, allineato al testo
   che lo precede.

   Lo stacco dalla tesi e' il piu' largo della scena, e deve esserlo: sopra si
   legge, qui si fa qualcosa, ed e' l'unico punto della pagina in cui il
   registro cambia. Misurati i primi 48px su una finestra da 1728x972: erano
   meno dei 64 che la tesi ha dai paragrafi qui sopra, cioe' il cambio di
   registro aveva meno aria di uno stacco fra due frasi, e i tasti sembravano
   appesi all'ultima riga. Adesso sono 80, fra i 64 della tesi e i 96 che
   separano due paragrafi. */
.ci-scena .cta-band {
  padding: 0;
  margin-top: clamp(3rem, 5.5vw, 5rem);
}

.ci-scena .cta-band__inner {
  max-width: none;
  padding-inline: 0;
  gap: clamp(1rem, 2.5vw, 1.5rem);
}

/* Le schede sono quelle della bento della home, e non una famiglia a parte:
   stesso navy pieno (--steel-11, un passo sopra la lastra), stessa luce
   d'angolo, stesso raggio, stesso faretto al passaggio del puntatore. Il velo
   col filo di prima era una terza superficie sul sito, e due famiglie di
   schede a tre sezioni di distanza si leggono come una svista.
   Anche la composizione è quella: il vetro grande in alto a destra, che
   sborda e viene tagliato dalla scheda, il testo in basso a sinistra.
   L'esito negato resta senza superficie — il tratteggio è il segno di ciò
   che si dà per scontato — ma prende raggio e cassa delle altre, o il testo
   non torna sulla stessa riga. */
.ci-scena,
.team-scena {
  --scheda:
    radial-gradient(70% 60% at 85% 90%, rgba(255, 255, 255, 0.07), transparent 70%),
    var(--steel-11);
}

.ci-scena .ci-row,
.ci-scena .ci-chain__node {
  --pad-y: 1.7rem;
  --pad-x: 1.2rem;

  position: relative;
  min-height: clamp(13rem, 20vw, 16rem);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: transform 0.25s var(--ease);
}

.ci-scena .ci-row,
.ci-scena .ci-chain__node { background: var(--scheda); }

.ci-scena .ci-chain__node {
  padding: var(--pad-y) var(--pad-x);
  justify-items: start;
  align-content: end;
  text-align: left;
}

/* Il vetro esce dal flusso e va nell'angolo, e la scheda lo taglia sul bordo
   come nella bento. La misura è sull'altezza della scheda e non sulla
   larghezza: sul telefono la scheda è larga e bassa, e a due terzi della
   larghezza il vetro arrivava a 231px su una scheda da 208 e si sedeva sul
   testo. Al 70% dell'altezza la riga del testo resta libera per costruzione,
   a qualunque larghezza. Il testo sta comunque sopra nell'ordine di pila. */
/* Grande, centrato e intero, con aria sopra. Ogni tessera porta l'oggetto al
   90% del quadrato: spingendola fuori fino al bordo di quel margine l'oggetto
   arrivava a filo della scheda, e a filo si legge come tagliato — la scala e
   il foglio sembravano decapitati anche senza perdere un pixel. Un oggetto
   intero con l'aria intorno e' un oggetto; a filo e' un ritaglio. */
.ci-scena .ci-row .ci-glass,
.ci-scena .ci-chain__node .ci-glass {
  position: absolute;
  top: 6%;
  left: 50%;
  width: auto;
  height: 76%;
  transform: translateX(-50%);
}

/* Le schede della catena sono piu' basse di quelle dell'equazione, e il 6% di
   una scheda bassa sono sei pixel: a filo. Qui l'aria e' in rem, cosi' non
   dipende dall'altezza della scheda. */
.ci-scena .ci-chain__node:not(.ci-chain__node--testo) .ci-glass {
  top: 1.1rem;
  height: 70%;
}

/* La scala è l'unico vetro del gruppo che ha un verso: sale. Intera e con
   l'aria sopra — la regola qui sopra — il suo scalino più alto resta una
   faccia piatta e orizzontale, e a questa misura si legge lo stesso come un
   taglio: è il «decapitato» che quella regola voleva evitare, e che con la
   scala si ripresenta comunque, senza che manchi un pixel.

   Se il taglio si legge per forza, tanto vale farlo apposta. La scala esce
   dal bordo alto e la scheda la taglia lì, netta: tagliata in cima dice che
   continua a salire, che è quel che la scheda racconta. Il verso conta —
   tagliata in basso, o a filo, sembrerebbe soltanto rotta.

   Cresce di quanto esce, così la base resta dove stava e il testo sotto non
   perde spazio: 88% di altezza contro 76%, spinta su di 12 punti. Vale solo
   per la scala; gli altri vetri sono oggetti chiusi e interi stanno bene. */
.ci-scena .ci-row .ci-glass--scala {
  /* Il 5% di cassa trasparente del render va scontato: il taglio si misura
     sull'oggetto, non sul riquadro. Cresce fino al 102% perché la base resti
     dove stava — con la sola salita la scala si accorciava e il testo sotto
     guadagnava un vuoto che nessuno aveva chiesto. */
  top: -19%;
  height: 102%;
}



.ci-scena .ci-row__text,
.ci-scena .ci-chain__name { position: relative; z-index: 1; }

/* Il faretto della bento, pari pari: due custom property aggiornate dal JS
   al pointermove (main.js), niente letture di layout. */
.ci-scena .ci-row::after,
.ci-scena .ci-chain__node::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s var(--ease);
  background: radial-gradient(18rem 18rem at var(--mx, 50%) var(--my, 50%), rgba(255, 255, 255, 0.10), transparent 70%);
}

@media (hover: hover) {
  .ci-scena .ci-row:hover,
  .ci-scena .ci-chain__node:hover { transform: translateY(-3px); }
  .ci-scena .ci-row:hover::after,
  .ci-scena .ci-chain__node:hover::after { opacity: 1; }
}

/* Il ponte era la sola superficie scura di un capitolo chiaro: sulla lastra
   quel rapporto si rovescia, e a --steel-11 sparirebbe dentro il fondo. Sale
   di un gradino e prende il filo turchese, così resta il punto in cui la
   pagina dice «questo lo facciamo noi». */
.ci-scena .ci-chain__bridge {
  padding: 1.25rem 1.8rem 1.35rem;
  border-radius: var(--radius-lg);
  background: var(--scheda);
  box-shadow: none;
}

/* --- il vetro --------------------------------------------------------------
   Il render arriva col fondo dello studio dipinto, e su questo il calibro
   della fascia qui sotto lascia gia' scritta la strada: non `screen`, che
   non e' trasparenza ma somma, e non una maschera radiale, che sulla
   cartella e sui gradini — l'oggetto arriva al 105-108% del raggio inscritto —
   taglierebbe gli angoli. La strada e' ritagliargli l'alfa, ed e' quel che
   questi cinque file portano.

   L'alfa e' estratta dalla luminanza, che per un vetro fotografato su nero
   e' gia' la copertura: il render e' luce additiva, quindi quanto un pixel
   e' chiaro e' quanto copre. Smontata la premoltiplicazione, l'oggetto si
   posa su qualunque fondo senza portarsi dietro il proprio rettangolo.

   Sono anche normalizzati: il lato lungo dell'oggetto vale il 90% del
   quadrato in tutti e cinque. Alla misura che gli ha dato il render si
   leggevano di dimensioni diverse messi in fila, e in fila ci stanno. */
.ci-glass {
  flex: none;
  display: block;
  width: var(--glass, clamp(5.5rem, 10vw, 9rem));
  aspect-ratio: 1;
}

.ci-glass img { display: block; width: 100%; height: 100%; }

/* Il vetro deriva di qualche pixel rispetto alla scheda mentre la scheda
   attraversa la finestra: profondita', non effetto. Legato allo scroll con
   una timeline CSS — niente JS, niente listener, e dove non c'e' supporto
   semplicemente non deriva. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .ci-glass img {
      animation: ci-deriva linear both;
      animation-timeline: view();
      animation-range: cover 12% cover 88%;
    }
  }
}

@keyframes ci-deriva {
  from { transform: translateY(11px); }
  to { transform: translateY(-11px); }
}

/* --- il segno davanti alla parola -----------------------------------------
   Non piu' una pastiglia piena nell'angolo della scheda: un glifo davanti
   all'etichetta, come si spunta una voce di elenco. La pastiglia competeva
   col vetro per l'angolo alto e faceva due centri di attenzione per scheda.
   Verde e non ciano: il ciano e' il colore dell'azione e del segnale vivo su
   tutto il sito, e qui non si sta segnalando niente — si sta dicendo che la
   voce e' fatta. --success-4 su questo fondo fa 5.6:1. */
.ci-segno {
  --icon-size: 1.15em;
  --icon-stroke: 2.6;

  flex: none;
  margin-right: 0.45em;
  vertical-align: -0.14em;
}

.ci-segno--ok { color: var(--success-2); }

.ci-segno--no { color: var(--error); }

/* Il segno di stato si appoggia alla prima riga del vetro, non al suo mezzo:
   a centoquaranta pixel di vetro, centrato, galleggiava. E la cassa la tengono
   tutte e quattro: l'esito negato non ha un vetro — non e' una quarta
   attivita', e' quel che si da' per scontato — ma senza riserva la sua scheda
   restava vuota in alto accanto a tre oggetti grandi, e la fila si sbilanciava.
   Il suo segno di linea cresce quanto basta a reggere il confronto, non tanto
   da diventare un oggetto. */
.ci-scena .ci-row__top { align-items: flex-start; }
/* Il segno passa a sinistra: l'angolo destro è del vetro, che nelle altre tre
   schede lo occupa sbordando. Nell'esito negato al posto del vetro sta il
   segno di linea, nello stesso angolo e con una misura che regga il
   confronto senza diventare un oggetto. */



/* --- la fascia scura -----------------------------------------------------
   L'unico fondo scuro della pagina, da bordo a bordo: il paragrafo si ferma
   sui due punti e la domanda che lo chiude viene detta a voce alta. Capitoli
   interi a tema alterno sono stati provati e tolti: con tre movimenti scuri
   il ritmo si appesantiva invece di scandirsi. */
.ci-band {
  /* Il bianco intorno alla fascia deve restare sotto lo stacco fra capitoli,
     altrimenti un episodio interno al capitolo separa quanto un capitolo: a
     152 contro i 192 di `.ci-chapter` erano ormai la stessa cosa. A 120 la
     scala torna leggibile — 96 fra i blocchi, 120 intorno alla fascia, 192 fra
     i capitoli. Il padding interno non si tocca: è spazio scuro, non vuoto. */
  margin-block: var(--ci-passo);
  padding-block: clamp(5.5rem, 13vw, 10rem);
}

/* Testo a sinistra, l'evidenza a destra: la lente rivela uno strato che
   intorno non si vede, che è quel che la domanda chiede di guardare. */
.ci-band__grid {
  display: grid;
  grid-template-columns: minmax(0, var(--ci-col)) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}

/* Sul fondo scuro il --fg-muted della pagina (68% di bianco) leggeva grigio e
   spento contro la domanda accanto: qui il paragrafo è la premessa che porta
   alla domanda, non una nota a margine, e sta un gradino più su. */
.ci-band__setup {
  font-size: var(--ci-body);
  line-height: 1.6;
  color: var(--fg-muted);
  text-wrap: pretty;
}

.ci-band__q {
  max-width: 26ch;
  margin-top: clamp(2.5rem, 5vw, 4rem);
  font-family: var(--font-display);
  /* Un gradino sopra i titoli di capitolo e sotto l'apertura: la gerarchia
     sta tutta nella scala del sito, 90 → 54 → 46. */
  font-size: var(--text-h1);
  font-weight: 600;
  line-height: var(--leading-h1);
  letter-spacing: var(--tracking-h1);
  text-wrap: balance;
}

/* L'immagine porta l'alfa vera, quindi qui non c'è niente da nascondere: il
   ritaglio è già il solo calibro. */
.ci-band__art {
  margin: 0;
  justify-self: end;
  width: min(100%, 30rem);
}

.ci-band__art img {
  display: block;
  width: 100%;
  height: auto;
  /* Né `mix-blend-mode: screen` né maschera, e la ragione sta nel file.
     Servivano tutt'e due finché il render arrivava col fondo dello studio
     dipinto: `screen` toglieva il nero, e siccome quel fondo nero pieno non
     era, una radiale `closest-side` scioglieva il quadrato che restava.
     Il calibro invece ha il canale alfa, quindi il vetro sta sul fondo per
     conto suo. È meglio così anche a prescindere: `screen` non è trasparenza,
     è somma — schiarisce quel che ha sotto, e su questa fascia il vetro
     scuro spariva invece di posarsi. E la radiale, tarata su un oggetto
     compatto come la lente, su un calibro largo quanto il riquadro
     cancellerebbe le ganasce e l'astina.
     Se un domani qui torna un render col fondo dipinto, la strada non è
     rimettere `screen`: è ritagliargli l'alfa. */
}

@media (max-width: 900px) {
  .ci-band__grid { grid-template-columns: 1fr; }
  .ci-band__art { justify-self: start; width: min(100%, 22rem); }
}

/* --- la catena -------------------------------------------------------------
   Due stati e il passaggio che trasforma il primo nel secondo. Il passaggio è
   il nostro, e a dirlo è il marchio: la pastiglia in mezzo è l'unica
   superficie scura del capitolo e porta il wordmark sopra il nome del
   passaggio. I due tratti sono pieni — il tratteggio, qui, vorrebbe dire
   «anello che manca», ed è esattamente il contrario. */
/* Le due caselle hanno una misura loro e non si prendono tutto il residuo:
   con `1fr auto 1fr` arrivavano a 500px per una riga di testo e leggevano
   vuote, mentre il ponte in mezzo restava corto. Adesso è la campata a
   prendersi lo spazio che avanza, che è quel che deve raccontare. */
.ci-chain__flow {
  display: grid;
  grid-template-columns:
    clamp(16rem, 24vw, 22rem) minmax(0, 1fr) clamp(16rem, 24vw, 22rem);
  align-items: center;
}

.ci-chain__node {
  display: grid;
  justify-items: center;
  gap: 1.1rem;
  padding: clamp(1.75rem, 3vw, 2.25rem) 1.5rem;
  border-radius: var(--radius);
  background: var(--card);
  text-align: center;
}

.ci-chain__name {
  font-family: var(--font-display);
  font-size: var(--text-h5);
  font-weight: 600;
  line-height: var(--leading-h5);
  letter-spacing: var(--tracking-h5);
  text-wrap: balance;
}

.ci-chain__link {
  position: relative;
  display: flex;
  align-items: center;
  --campata-pad: clamp(1.1rem, 2.5vw, 2.25rem);

  gap: 0.9rem;
  min-width: 0;
  padding: 0 var(--campata-pad);
}

/* --blue-400 su bianco fa 1.86:1: su una campata di trecento pixel il tratto
   spariva. --blue-500 ne fa 2.88 e la catena si legge da lontano senza
   diventare pesante come farebbe --blue-600. */
.ci-chain__link::before,
.ci-chain__link::after {
  content: "";
  flex: 1;
  height: 2px;
  background: var(--neutral-6);
}

/* Il ponte. Scuro perché è il solo punto del capitolo in cui la pagina dice
   «questo lo facciamo noi», e il wordmark lo dice senza aggiungere una parola
   al testo: il marchio non è copy. */
.ci-chain__bridge {
  flex: none;
  display: grid;
  justify-items: center;
  gap: 0.6rem;
  padding: 1rem 1.6rem 1.15rem;
  border-radius: var(--radius);
  background: var(--neutral-11);
  box-shadow: 0 12px 28px -14px rgba(var(--ink-rgb), 0.6);
}

.ci-chain__brand {
  display: block;
  width: 100%;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid rgba(var(--paper-rgb), 0.18);
}

.ci-chain__brand .logo__svg {
  height: 16px;
  margin-inline: auto;
  color: var(--neutral-0);
}

.ci-chain__label {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--neutral-0);
  white-space: nowrap;
}

/* Turchese sull'inchiostro: 8.2:1, ed è il colore che sul sito segna il
   segnale vivo. */
.ci-chain__label .icon {
  --icon-size: 1.1rem;
  --icon-stroke: 1.6;
  color: var(--teal-500);
}

.ci-chain__tip {
  position: absolute;
  /* Appoggiata alla fine del tratto, non al bordo della campata: con lo
     stacco laterale la punta restava a venti pixel dalla riga, sospesa. */
  right: calc(var(--campata-pad) - 4px);
  top: 50%;
  width: 9px;
  height: 9px;
  border: solid var(--neutral-6);
  border-width: 2.5px 2.5px 0 0;
  transform: translateY(-50%) rotate(45deg);
}

@media (max-width: 900px) {
  .ci-chain__flow { grid-template-columns: 1fr; }
  .ci-chain__link { flex-direction: column; gap: 0.9rem; min-width: 0; padding: 1.1rem 0 1.8rem; }
  .ci-chain__link::before,
  .ci-chain__link::after { flex: none; width: 2px; height: 1.2rem; }
  .ci-chain__tip { right: auto; top: auto; bottom: 0.5rem; left: 50%; transform: translateX(-50%) rotate(135deg); }
}

/* --- gli incisi -----------------------------------------------------------
   Filo a sinistra, niente scatola: frasi da tenere, non avvisi. */
.ci-aside {
  --aside-rule: var(--neutral-6);

  max-width: calc(var(--ci-col) * 0.88);
  margin-top: clamp(3rem, 6vw, 4.5rem);
  padding-left: 1.4rem;
  border-left: 2px solid var(--aside-rule);
  font-size: var(--text-md);
  line-height: 1.6;
  color: var(--fg-muted);
  text-wrap: pretty;
}

/* Il filo di questo inciso era ambra. Sul nero l'ambra e' il colore piu'
   acceso della pagina, e qui non sta segnalando niente: la frase e' una
   constatazione — il divario e' fra i rilievi piu' ricorrenti — non un
   avviso. E l'ambra, su questa pagina, ha gia' un solo significato: la tappa
   dello scostamento intercettato nella timeline. Usarla due volte per due
   cose diverse la annacqua. Filo neutro, e a fare il peso resta la frase. */
.ci-aside--warn { --aside-rule: var(--steel-7); }

/* --- l'incontro ------------------------------------------------------------
   Stessa grammatica della catena, verso rovesciato: le due schede portano
   ciascuna un segnale e le punte entrano nella pastiglia, da cui esce la
   lettura. Le schede sono più larghe di quelle della catena perché portano
   un paragrafo; la campata prende una misura fissa in mezzo. */
.ci-chain__flow--incontro {
  /* La calle in mezzo deve tenere la pastiglia piu' due tratti che si vedano:
     a 18rem la pastiglia da 170px lasciava otto pixel per parte, e la punta
     destra finiva sotto la scheda accanto, che le dipinge sopra. */
  grid-template-columns: minmax(0, 1fr) clamp(19rem, 27vw, 25rem) minmax(0, 1fr);
  /* Le due schede alla stessa altezza, come in una griglia bento; la
     pastiglia si centra da se' nella campata stirata. */
  align-items: stretch;
}

.ci-chain__link--incontro { --campata-pad: 0.75rem; }

/* Le due schede restano scure anche sul territorio chiaro: sono le stesse
   della bento della home, che pure sta sulla carta. Ma i token no — senza
   ribaltarli il testo resta inchiostro e sparisce dentro la scheda. Si mette
   .on-dark sul flusso e le si toglie il fondo pieno che porta con se': a
   dipingere sono le schede, non il flusso. */
.ci-chain__flow--incontro.on-dark { background: none; }

/* Le frecce che entrano nella pastiglia stanno sulla carta, non sulle schede:
   sul chiaro --neutral-6 e' quasi fondo. */
.ci-chapter .ci-chain__link--incontro::before,
.ci-chapter .ci-chain__link--incontro::after { background: var(--steel-5); }

.ci-chapter .ci-chain__link--incontro .ci-chain__tip { border-color: var(--steel-5); }

/* Scheda con testo: il vetro sta nel flusso, sopra il titolo, e non
   nell'angolo — con un paragrafo sotto, un oggetto assoluto al 68%
   dell'altezza si siederebbe sulle prime righe. Titolo e corpo sono quelli
   della scheda bento. */
.ci-scena .ci-chain__node--testo {
  align-content: start;
  gap: 0.6rem;
  min-height: 0;
}

/* Le schede della convergenza portano un paragrafo, non due parole: la cassa
   e' piu' larga di quella delle voci dell'equazione. Il fondo e' quello comune
   — la scena e' tornata sul buio perche' il vetro, ritagliato da render
   fotografati su nero, sulla carta si slavava. */
.ci-chain__node--testo {
  --pad-y: 2.5rem;
  --pad-x: 2.5rem;
}

/* --- la pastiglia ---
   Senza scatola e senza icone: resta il marchio, grande, e una riga che dice
   che cosa succede li' in mezzo. La scatola competeva con le due schede ai
   lati e faceva tre superfici in fila; il marchio da solo dice la stessa cosa
   e la dice piu' forte. */
.ci-scena .ci-chain__bridge {
  padding: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  gap: 0.75rem;
}

.ci-scena .ci-chain__brand {
  padding-bottom: 0;
  border-bottom: 0;
}

.ci-scena .ci-chain__brand .logo__svg { height: clamp(1.9rem, 3.2vw, 2.9rem); }

.ci-scena .ci-chain__label {
  gap: 0;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--fg-muted);
}

/* Nella convergenza il vetro sta nel flusso — sotto c'e' un paragrafo — ma
   centrato e sbordante in alto come nelle altre schede: e' la scheda a
   tagliarlo. */
/* Qui il margine negativo era in percentuale, e le percentuali dei margini si
   misurano sulla larghezza: -14% di una scheda da 482 faceva sessantasette
   pixel su un vetro di centosessanta, cioe' quasi mezzo oggetto via. In rem
   il taglio e' quello che si vede. */
.ci-scena .ci-chain__node--testo .ci-glass {
  position: static;
  /* La misura si lega all'altezza e non alla larghezza: nella scena piu'
     piena e' il verticale a essere stretto, e un vetro tarato sui pixel
     orizzontali la faceva traboccare appena la finestra si allargava.
     In variabile e non diretta: il taglio della scala si misura su questa
     stessa quota, e due numeri scollegati si sarebbero separati al primo
     ritocco. */
  --vetro: clamp(7rem, 20vh, 12rem);
  width: var(--vetro);
  height: auto;
  margin: 0.25rem auto 0.9rem;
  transform: none;
}

/* Qui il vetro e' statico, quindi il taglio non si fa con `top` ma con un
   margine negativo: prima annulla la cassa della scheda (--pad-y, che il
   vetro eredita), poi sale del 18,5% del vetro. Di quel 18,5 il 5 e' cassa
   trasparente del render, quindi dell'oggetto se ne va il 15 — la stessa
   quota della scheda dell'equazione, che ci arriva per un'altra strada.
   In calc sulla quota del vetro e non in rem fisso: il vetro si misura in vh
   e cambia con la finestra, e un taglio in rem sarebbe stato mezzo oggetto
   su una finestra bassa e un'unghia su una alta. */
.ci-scena .ci-chain__node--testo .ci-glass--scala {
  margin-top: calc((var(--pad-y) + var(--vetro) * 0.185) * -1);
}

.ci-chain__node--testo .ci-chain__name {
  margin: 0;
  font-size: var(--text-h4);
  line-height: var(--leading-h4);
  letter-spacing: var(--tracking-h4);
}

.ci-chain__body {
  position: relative;
  z-index: 1;
  margin: 0;
  max-width: 44ch;
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--fg-muted);
  text-wrap: pretty;
}

/* Le due punte stanno sulla pastiglia, una per lato, e guardano dentro:
   a destra quella di sinistra, a sinistra quella di destra. Sulla pastiglia
   e non sulla campata perché la posizione non dipende dalla sua larghezza. */
.ci-chain__link--incontro .ci-chain__bridge { position: relative; }

.ci-chain__bridge .ci-chain__tip {
  top: 50%;
  right: auto;
  bottom: auto;
}

.ci-chain__bridge .ci-chain__tip--a { left: calc(-0.9rem - 3px); transform: translateY(-50%) rotate(45deg); }
.ci-chain__bridge .ci-chain__tip--b { right: calc(-0.9rem - 3px); left: auto; transform: translateY(-50%) rotate(225deg); }

@media (max-width: 900px) {
  /* Anche questa variante si incolonna: la regola a tre colonne qui sopra
     batte quella generale del telefono per specificita', e a 390px le due
     schede restavano affiancate con il titolo tagliato a meta'. */
  .ci-chain__flow--incontro { grid-template-columns: 1fr; }

  /* In colonna le punte passano sopra e sotto la pastiglia, e guardano
     sempre dentro: in giù quella che viene da sopra, in su l'altra. */
  .ci-chain__bridge .ci-chain__tip--a { left: 50%; top: calc(-0.9rem - 3px); transform: translateX(-50%) rotate(135deg); }
  .ci-chain__bridge .ci-chain__tip--b { left: 50%; right: auto; top: auto; bottom: calc(-0.9rem - 3px); transform: translateX(-50%) rotate(-45deg); }
}

/* --- le tre letture -------------------------------------------------------
   Il segno sta davanti alla frase e non è più un filo a lato: due letture che
   non bastano e una che risolve, e il colore lo dice prima delle parole. */
.ci-reads__head {
  font-family: var(--font-display);
  font-size: var(--text-quote);
  line-height: var(--leading-quote);
  font-weight: 600;
  letter-spacing: var(--tracking-quote);
}

/* «Entrambi i segnali sono necessari.» e le tre letture che lo spiegano sono
   un blocco solo, non due. */
.ci-reads {
  margin-top: 1.9rem;
  display: grid;
  gap: 1.9rem;
}

.ci-read {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 1rem;
  font-size: var(--ci-body);
  line-height: 1.55;
  color: var(--fg-muted);
  text-wrap: pretty;
}

/* Il segno si allinea alla prima riga di testo, non al blocco: `start` da
   solo lo appoggia al bordo superiore della cassa. */
.ci-read .ci-mark { margin-top: 0.22em; }

.ci-read--ok { color: var(--fg); }

/* --- il confronto dei ritmi ------------------------------------------------
   Stesso linguaggio della roadmap della home — corsia, pallina, alone, spunta —
   perché è la stessa figura: un percorso nel tempo con delle tappe verificate.
   Cambia quel che racconta. Là è una riga sola che avanza; qui sono due ritmi
   accostati, e il confronto sta in quante tappe portano una spunta: una su
   dodici sopra, undici su dodici sotto.

   I due ritmi si distinguono per colore, non per densità di puntini. Sopra è
   tutto neutro — corsia spenta, binario tratteggiato, l'audit in bianco:
   l'audit non è nostro, è la verifica annuale, e in ciano si sarebbe letto
   come una tappa del prodotto. Sotto è tutto di marca. Provate con lo stesso
   ciano su tutt'e due, le righe si leggevano uguali e il confronto spariva.

   La corsia non è una scheda: è il canale in cui il ritmo scorre. Senza, le
   due righe erano due file di puntini sospese nel nero, e il confronto non
   aveva corpo.

   Le misure stanno tutte in cima e tutto il resto le legge: --stop è l'asse
   su cui si centrano corsia, binario e tappe; --size è la misura della
   singola pallina, che le varianti cambiano senza spostare l'asse. */
/* Le tre righe sono uno schema solo, e devono leggersi cosi'. A tenerle
   insieme sono le guide dei mesi: dodici fili verticali che salgono
   dall'asse fino in cima, attraversando tutt'e due le corsie. Sono loro a
   dire che l'audit di novembre e la tappa di novembre stanno sulla stessa
   colonna — prima lo si doveva dedurre. Il vuoto fra le righe scende di
   conseguenza: con le guide non serve piu' aria per separare, serve solo
   quella per respirare. */
.ci-timeline {
  --stop: 20px;
  --tacca: 8px;
  --rail-h: 3px;
  --anello: 4px;
  --corsia-h: calc(var(--stop) + 2rem);
  --asse: calc(var(--corsia-h) / 2);
  /* Le tappe agli estremi stanno a cavallo dello 0% e del 100%: senza rientro
     mezza pallina e tutto il suo anello cadrebbero fuori dal margine di
     pagina. La corsia se lo riprende, perché il canale deve arrivare dove
     arriva il testo. */
  --rientro: calc(var(--stop) / 2 + var(--anello) + 5px + var(--stop) * 0.4);
  /* Colore pieno e non velo: l'anello che stacca la pallina dal binario deve
     essere dello stesso colore della corsia, e un velo non ha un colore da
     dare. Con --bg al posto suo ogni tappa bucava la corsia con un cerchio
     scuro. */
  /* La timeline sta sul territorio chiaro, e i suoi colori sono quelli della
     roadmap della home: corsia appena piu' scura della carta, pallina di
     marca, spunta bianca dentro. */
  --corsia: color-mix(in oklab, var(--paper) 5%, var(--blue-950));

  display: grid;
  gap: clamp(1.25rem, 2.6vw, 2rem);
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

.ci-timeline__row {
  display: grid;
  grid-template-columns: minmax(9rem, 15rem) minmax(0, 1fr);
  gap: clamp(1rem, 3vw, 2.5rem);
  align-items: center;
}

/* Titolo e corpo della scheda bento, pari pari: sono le stesse due righe che
   nella home stanno sotto il vetro, e qui stanno accanto alla corsia. */
.ci-timeline__label {
  font-family: var(--font-display);
  font-size: var(--text-h4);
  font-weight: 600;
  line-height: var(--leading-h4);
  letter-spacing: var(--tracking-h4);
  text-wrap: balance;
}

.ci-timeline__sub { margin-top: 0.55rem; font-size: var(--text-sm); color: var(--fg-muted); text-wrap: pretty; }

/* Alta quanto la corsia più la riga d'etichetta che le cade sotto. */
.ci-timeline__rail {
  position: relative;
  height: calc(var(--corsia-h) + 1.4rem);
  margin-inline: var(--rientro);
}

/* La corsia sta sotto tutto il resto: z-index 0 contro l'1 del binario e
   delle tappe. Niente valori negativi, che uscirebbero dal riquadro. */
.ci-timeline__rail::after {
  content: "";
  position: absolute;
  z-index: 0;
  top: 0;
  inset-inline: calc(-1 * var(--rientro));
  height: var(--corsia-h);
  border-radius: 999px;
  background: var(--corsia);
}

/* Il binario di «oggi» è tratteggiato, e non è un vezzo: fra un audit e
   l'altro la continuità non c'è, e una linea piena direbbe il contrario. */
.ci-timeline__rail::before {
  content: "";
  position: absolute;
  z-index: 1;
  top: var(--asse);
  left: 0;
  right: 0;
  /* Piu' sottile e piu' chiaro del binario vivo: e' una riga da leggere, non
     da seguire. A 3px e steel-8 spariva nella corsia. */
  height: 2px;
  margin-top: -1px;
  border-radius: 2px;
  background: repeating-linear-gradient(90deg,
    var(--steel-5) 0 7px,
    transparent 7px 15px);
  opacity: 0.9;
}

/* La riga viva: corsia accesa, filo turchese intorno, binario pieno. In coda
   non sfuma — una dissolvenza, su un binario che deve dire «sempre
   disponibile», direbbe esattamente il contrario. */
.ci-timeline__rail--live { --corsia: color-mix(in oklab, var(--brand-4) 18%, var(--blue-950)); }

.ci-timeline__rail--live::before {
  height: var(--rail-h);
  margin-top: calc(var(--rail-h) / -2);
  background: linear-gradient(90deg,
    var(--brand-2) 0%,
    var(--brand-3) 48%,
    var(--brand-4) 100%);
}

/* Le tappe spuntano una dopo l'altra dietro al binario che le raggiunge, come
   sulla roadmap della home. Il ritardo arriva inline dal template insieme
   all'ascissa: sono la stessa cosa — la posizione nel tempo e nello spazio. */
.ci-stop {
  --size: var(--stop);

  transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) var(--tappa, 0ms);

  position: absolute;
  z-index: 1;
  top: var(--asse);
  left: var(--x, 50%);
  width: var(--size);
  height: var(--size);
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--dot, none);
  /* Il primo anello è del colore della fascia: stacca la pallina dal binario
     che le passa sotto senza dover interrompere la linea. Il secondo è
     l'alone, e lo accendono solo le tappe che dicono qualcosa. */
  box-shadow:
    0 0 0 var(--anello) var(--corsia),
    0 0 0 calc(var(--anello) + 5px) var(--ring, transparent);
}

/* L'etichetta cade sotto la corsia, non di fianco: di fianco, a un passo di
   novantasette pixel, finirebbe sopra la pallina vicina. */
.ci-stop i {
  position: absolute;
  top: calc(var(--corsia-h) / 2 + 0.45rem);
  left: 50%;
  transform: translateX(-50%);
  font-style: normal;
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--fg-muted);
  white-space: nowrap;
}

/* I mesi senza lettura: pallini piccoli, pieni e chiari — si devono vedere
   da lontano, ma non sono bianchi, che e' il colore dell'audit. A dire «qui
   non si guarda» bastano la misura e l'assenza di spunta. */
.ci-stop--muto {
  --size: var(--tacca);
  --dot: var(--steel-4);
  --ring: transparent;

  box-shadow: 0 0 0 var(--anello) var(--corsia);
}

/* Verificata. La spunta dentro sta in inchiostro e non in bianco: su questi
   due pieni — il bianco dell'audit e il ciano vivo della riga sotto — è lo
   scuro a staccare. Il bianco su ciano faceva 1.9:1 e la spunta si leggeva
   come una macchia. */
.ci-stop--fatto::after {
  content: "";
  position: absolute;
  top: 46%;
  left: 50%;
  width: calc(var(--size) * 0.2);
  height: calc(var(--size) * 0.42);
  border: solid var(--spunta, var(--steel-12));
  border-width: 0 calc(var(--size) * 0.1) calc(var(--size) * 0.1) 0;
  transform: translate(-50%, -50%) rotate(45deg);
}

/* L'audit: bianco, il più grande della figura, e l'unico segno acceso di
   tutta la riga di sopra. */
/* L'audit: il piu' grande della figura, e sulla carta e' inchiostro — era
   bianco quando la riga stava sul buio, e sul chiaro sarebbe sparito. La
   spunta dentro si ribalta con lui. */
.ci-timeline__rail:not(.ci-timeline__rail--live) .ci-stop--fatto {
  --size: 26px;
  --dot: var(--paper);
  --ring: rgba(var(--paper-rgb), 0.13);
}

/* Le letture della Compliance Intelligence: il ciano vivo della tavola, che
   sul fondo scuro è il gradino che si legge da lontano. */
/* Le letture della Compliance Intelligence: --brand-4 con la spunta bianca,
   la pallina conclusa della roadmap. Il gradino piu' vivo che sul chiaro
   regge ancora il 3:1 di un elemento grafico. */
.ci-timeline__rail--live .ci-stop--fatto {
  --dot: var(--brand-2);
  --ring: rgba(var(--teal-rgb), 0.2);
}

/* Uno scostamento intercettato. Non è un guasto della figura: è il punto del
   capitolo — leggere di continuo serve a vederlo mentre succede, non al
   prossimo audit. Dentro non ha spunta, perché non c'è niente da spuntare. */
.ci-stop--allerta {
  --size: 24px;
  --dot: var(--warn);
  --ring: rgba(255, 192, 86, 0.22);
}

/* --- l'asse dei mesi ---
   Una riga sola per tutt'e due i ritmi: è il confronto a dover essere
   leggibile, e due assi identici sarebbero due volte lo stesso dato. */
/* Nella colonna delle corsie, senza occhiello a sinistra: le due righe sopra
   dicono gia' che cosa si sta guardando. */
.ci-timeline__row--asse { margin-top: 0; }

.ci-timeline__mesi {
  grid-column: 2;
  position: relative;
  height: 1.1rem;
  margin-inline: var(--rientro);
}

.js-motion .ci-timeline__row[data-enter] .ci-stop { transform: translate(-50%, -50%) scale(0.2); }

.js-motion .ci-timeline__row[data-enter].is-in .ci-stop { transform: translate(-50%, -50%) scale(1); }

/* Il binario vivo si disegna da sinistra a destra, che e' il verso in cui si
   legge la riga. `transform` e non `width`: la scala non tocca il layout,
   quindi l'animazione gira sul compositore. */
.ci-timeline__rail--live::before { transition: transform 1.1s cubic-bezier(0.22, 0.61, 0.36, 1); }

.js-motion .ci-timeline__row[data-enter] .ci-timeline__rail--live::before {
  transform: scaleX(0);
  transform-origin: left center;
}

.js-motion .ci-timeline__row[data-enter].is-in .ci-timeline__rail--live::before { transform: scaleX(1); }

@media (prefers-reduced-motion: reduce) {
  .js-motion .ci-timeline__row[data-enter] .ci-stop { transform: translate(-50%, -50%); }
  .js-motion .ci-timeline__row[data-enter] .ci-timeline__rail--live::before { transform: none; }
}

/* La guida: un filo sottile che parte dalla sigla e sale per tutto lo schema.
   Sta a z-index -1 dentro il contesto isolato della timeline, quindi passa
   sotto le corsie e sotto le palline. Piu' alto di quanto serva: la timeline
   lo ritaglia in cima. */
.ci-timeline__mesi span::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: calc(100% + 0.5rem);
  z-index: -1;
  width: 1px;
  height: 100vh;
  background: rgba(var(--paper-rgb), 0.07);
}

.ci-timeline__mesi span {
  position: absolute;
  left: var(--x);
  transform: translateX(-50%);
  font-size: var(--text-2xs);
  font-variant-numeric: tabular-nums;
  color: var(--fg-faint);
}

@media (max-width: 720px) {
  /* Dodici tappe su trecentocinquanta pixel: trentadue di passo. La pallina e
     il suo anello scendono, o si toccano. Corsia e asse seguono da sé. */
  /* Le schede si incolonnano e si allargano: più alte, o il vetro — che si
     misura sull'altezza — rimpicciolisce proprio dove ha più spazio. */
  .ci-scena .ci-row,
  .ci-scena .ci-chain__node { min-height: 17rem; }

  /* Misurato: a 13px di pallina e 3 di anello l'ingombro era 29px su un passo
     di 28. */
  .ci-timeline { --stop: 12px; --tacca: 6px; --anello: 2px; --corsia-h: calc(var(--stop) + 1.4rem); gap: 1.25rem; }
  .ci-timeline__row { grid-template-columns: 1fr; gap: 0.55rem; }
  .ci-timeline__rail:not(.ci-timeline__rail--live) .ci-stop--fatto { --size: 18px; }
  .ci-stop--allerta { --size: 16px; }
  /* Un mese su tre: dodici sigle a quel passo si accavallano. E la riga torna
     nella colonna unica: il 2 del desktop, qui, apre una colonna implicita
     larga zero in cui le sigle si impilano una sull'altra. */
  .ci-timeline__mesi { grid-column: 1; }
  .ci-timeline__mesi span:not(:nth-child(3n + 1)) { display: none; }


  .page-hero__grid { min-height: 0; }
}

/* --- finestre basse ---------------------------------------------------------
   Un palco vive di altezza, e la maggior parte degli schermi ne ha molta meno
   di quanta se ne misura a 900px: con la barra del browser, un portatile da
   tredici pollici lascia seicento pixel scarsi. Misurate a 620, cinque scene
   su sette traboccavano.
   Qui non si toglie niente: si stringe tutto insieme — casse, vuoti, corpo
   del titolo, misura del vetro — cosi' che la scena resti una scena e non un
   contenuto tagliato a meta'. Le misure legate a vh si adattano da sole; le
   altre le dichiara questo blocco.
   Solo dove il palco c'e', cioe' dai 901px e dai 640 di altezza — le stesse
   soglie del palco, non delle vicine: nel flusso l'altezza della finestra non
   vincola niente, e sul telefono — 844 di finestra — questo blocco portava il
   corpo delle schede della convergenza a 13px. */
@media (min-width: 901px) and (min-height: 640px) and (max-height: 960px) {
  .ci-scena .ci-col + .ci-rows,
  .ci-scena .ci-col + .ci-chain__flow,
  .ci-scena .ci-col + .ci-timeline,
  .ci-scena .ci-rows + .ci-col,
  .ci-scena .ci-chain__flow + .ci-col,
  .ci-scena .ci-timeline + .ci-col,
  .ci-scena .ci-col + .ci-final { margin-top: clamp(1.25rem, 3.5vh, 2.5rem); }

  .ci-scena .ci-col + .ci-col { margin-top: clamp(1.25rem, 3.5vh, 2.5rem); }

  /* Su finestra bassa lo stacco dell'invito si misura in altezza come tutti
     gli altri della scena, e resta il piu' largo: a 900 vale 49 contro i 31
     della tesi. Il fondo sta sotto quel che valeva prima — a 901x640 la
     scena ha dodici pixel d'avanzo e non uno di piu'. */
  .ci-scena .cta-band { margin-top: clamp(1.75rem, 5.5vh, 5rem); }

  .ci-scena .ci-p + .ci-p { margin-top: 1em; }

  .ci-scena .ci-chapter__title { font-size: var(--text-h3); }

  .ci-scena .ci-glass { width: clamp(3.25rem, 9vh, 6.5rem); }

  /* Piu' specifica della regola qui sopra, che da sola non la raggiungeva.
     A 8vh il vetro della convergenza scendeva a cinquantaquattro pixel su una
     finestra da 680, contro i centonovantadue di una finestra alta: non era
     piu' un oggetto, era una miniatura.

     La convergenza e' la piu' piena delle sette scene ed e' lei a fissare il
     tetto: misurata a 1280x600 — la finestra piu' bassa in cui la scena
     ancora ci sta — a 16vh traboccava di sedici pixel, a 15vh ne avanzavano
     tre, a 14vh nove. Quei nove sono il margine che serve perche' una riga in
     piu' del paragrafo non tagli la scena, e a finestra normale il vetro
     resta comunque quasi il doppio di prima: centoventisei pixel a 900
     contro settantadue.

     Insieme al vetro si stringono i vuoti che gli stanno intorno — la sua
     aria sotto e il passo fra vetro, titolo e corpo. Su una finestra alta
     valgono quel che valevano; su una bassa restituiscono alla scena i pixel
     che il vetro si prende. */
  .ci-scena .ci-chain__node--testo .ci-glass {
    --vetro: clamp(4.5rem, 14vh, 12rem);

    margin-bottom: clamp(0.45rem, 1.5vh, 0.9rem);
  }

  .ci-chain__node--testo .ci-chain__name { font-size: var(--text-h5); line-height: var(--leading-h5); }

  .ci-chain__body { font-size: var(--text-xs); line-height: 1.45; }

  /* La cassa della scena scende con la finestra: a 620px erano trentuno pixel
     per lato e sono i primi che si possono togliere. */
  .ci-scena { padding-block: clamp(0.75rem, 3vh, 4rem); }

  /* La cassa scende, la statura no. `min-height: 0` toglieva alla scheda la
     sua unica altezza: il vetro e' assoluto, quindi `.ci-row__top` non pesa
     niente, e senza min-height la scheda si riduceva a cassa piu' una riga di
     testo — misurate a 1440x900, centosei pixel contro i duecentocinquantasei
     di una finestra alta. Il vetro si misura in percentuale di quell'altezza,
     quindi si rimpiccioliva con lei e la sua fascia finiva addosso al testo:
     ventisette pixel di sovrapposizione nell'equazione, quarantasette nella
     catena. E' la composizione stessa della scheda — oggetto grande sopra,
     etichetta sotto — che si perdeva, non solo qualche pixel.

     L'altezza torna, ma legata alla finestra invece che alla larghezza: in un
     palco il vincolo e' verticale, e 28vh tiene la proporzione fra vetro e
     testo che la scheda ha su una finestra alta. I 16rem del tetto sono gli
     stessi della regola di base, quindi sul confine dei 960 non c'e' scalino.
     Misurato, avanza sempre: a 1366x680 — il caso peggiore — la scena
     dell'equazione passa da 352 a 448 pixel su 608 disponibili. */
  .ci-scena .ci-row,
  .ci-scena .ci-chain__node {
    --pad-y: clamp(1rem, 2.6vh, 1.7rem);
    --pad-x: 1.6rem;

    min-height: clamp(11.5rem, 28vh, 16rem);
  }

  /* La scheda con testo no: la sua altezza la fa il paragrafo, e una statura
     minima le aggiungerebbe vuoto in fondo proprio nella scena piu' piena.
     Il passo fra vetro, titolo e corpo e' l'altra meta' dei vuoti che si
     stringono per far posto al vetro piu' grande. */
  .ci-scena .ci-chain__node--testo {
    gap: clamp(0.35rem, 1vh, 0.6rem);
    min-height: 0;
  }

  .ci-chain__node--testo {
    --pad-y: clamp(1.1rem, 3vh, 2.5rem);
    --pad-x: clamp(1.4rem, 3vh, 2.5rem);
  }

  .ci-scena .ci-row__top { min-height: 0; }

  .ci-timeline { gap: clamp(1rem, 3vh, 2.5rem); }

  .ci-band__q { margin-top: clamp(1.25rem, 3.5vh, 2.5rem); }

  .ci-reads { gap: clamp(0.85rem, 2.4vh, 1.9rem); margin-top: 1.25rem; }

  .ci-aside { margin-top: clamp(1.25rem, 3.5vh, 2.5rem); }
}

/* --- finestre basse e strette insieme ---------------------------------------
   Il vetro della convergenza si misura sull'altezza, ma quel che lo mette in
   crisi e' la larghezza: sotto i 1150px le due schede si stringono, il loro
   paragrafo passa da tre righe a quattro o cinque, e la scena chiede
   all'improvviso trenta o quaranta pixel in piu' senza che la finestra sia
   diventata piu' bassa. Misurato: a 901x640 traboccava di ventitre, a 960x640
   di sette, mentre a 1152x640 — appena piu' larga — ne avanzavano venticinque.
   L'unico pezzo elastico della scena e' il vetro, e qui cede: 9vh invece di
   14. Solo dove le due strettezze si sommano — oltre i 680 di altezza lo
   spazio c'e' anche a 1024, e rimpicciolirlo sarebbe una perdita gratuita. */
@media (min-width: 901px) and (max-width: 1150px) and (min-height: 640px) and (max-height: 680px) {
  .ci-scena .ci-chain__node--testo .ci-glass { --vetro: clamp(3.5rem, 9vh, 12rem); }
}

/* --- la chiusura ------------------------------------------------------------ */
/* È l'ultima cosa che si legge e chiude l'argomento, e sta sulla misura larga:
   dentro la colonna di lettura cadeva su cinque righe corte. Niente `ch`: a 54px
   di display 46ch calcolavano 1792px e il vincolo non mordeva mai — è lo stesso
   inciampo del titolo dell'apertura. 69rem sono le tre righe piene, e restano
   tre fino a 1360px. */
/* Sulla fascia i margini li fa la cassa della fascia: quelli di prima — nove
   rem sopra e quattro sotto — servivano a staccarla dalla prosa quando stava
   sul bianco, e qui si sommerebbero. */
.ci-final {
  max-width: 69rem;
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-h1);
  font-weight: 600;
  line-height: var(--leading-h1);
  letter-spacing: var(--tracking-h1);
  text-wrap: balance;
}

/* ----------------------------------------------------------------- team -- */

/* Le persone sono dodici, e dodici sta in quattro colonne esatte: tre file
   piene, quattro schede per fila, nessuno che avanza in fondo. Prima erano
   cinque colonne — il conteggio serviva quando le persone erano tredici, un
   numero primo che nessuna colonna divide, e cinque era l'unico che non
   lasciava una scheda sola a chiudere (13 = 5 + 5 + 3). Con dodici quel
   compromesso non serve più: 12 = 4 + 4 + 4 non ha resto.

   Il numero di file non cambia — tre prima, tre adesso — ed è il vincolo che
   conta, perché dentro il palco le tre file devono stare in una schermata
   sola sotto il titolo. Quel che cambia è la larghezza della scheda, e nella
   fascia stretta si vede: misurate a 1025x640 — il caso peggiore in cui il
   palco è acceso — cinque colonne davano 180px, e a quella misura
   «Valentina Ventrelli» e «Alessandro Cassina» andavano a capo, il che
   portava il blocco a 445px; quattro danno 229px, nessun nome si spezza e il
   blocco scende a 351px. A 1440x900 la scheda passa da 258px a 327px.

   Sotto i 1024px si torna a tre colonne — quattro file piene, sempre senza
   resto — e poi a due. La soglia non la decide più il nome che si spezza:
   quattro colonne reggono fino a 1000px circa (a 1024 la scheda è 220px e
   nessun nome va a capo, il primo a cedere è «Alessandro Cassina» intorno ai
   980). La decide il palco, che si accende a 1025: le due soglie devono stare
   insieme, o resta una fascia in cui il palco mostra una griglia che non è
   quella su cui è composto. Vedi il commento del @media qui sotto.

   Flex invece di grid perché `justify-content: center` centra la riga
   incompleta senza toccare quelle piene — una riga piena occupa il 100%
   esatto, quindi il centraggio non ha spazio da distribuire. Oggi riga
   incompleta non ce n'è, ma basta una persona in più o in meno perché torni.

   Colonne e gutter stanno in due custom property perché la misura della
   scheda si calcola da entrambe: cambiarne una sola nei @media sfalserebbe il
   conto e le colonne smetterebbero di essere quelle dichiarate. */
.team__grid {
  --team-cols: 4;
  --team-gap: 1.75rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--team-gap);
  margin-top: 2.5rem;
}

/* Le schede sono tutte della stessa misura. Prima quelle dell'ultima riga
   crescevano fino a chiuderla — 437px contro 251px a 1440 — per non lasciare
   un buco a destra: il blocco tornava un rettangolo pieno, ma le ultime
   persone avevano la tessera larga il doppio, e la griglia si leggeva come
   una gerarchia che non esiste. Meglio la riga corta e centrata, che è quel
   che `justify-content: center` fa da sé. */

@media (max-width: 1024px) {
  .team__grid { --team-cols: 3; }
}

@media (max-width: 780px) {
  .team__grid { --team-cols: 2; }
}

/* Una colonna sola sul telefono. A due, la scheda restava larga centonovanta
   pixel e il nome ci andava a capo quasi sempre: una tessera su due si
   leggeva su tre righe invece che su due, e la fila si sfalsava. A colonna
   piena il nome e il ruolo stanno ciascuno sulla propria riga.

   Con l'altezza minima, però, una scheda larga trecentonovanta e alta
   centododici sarebbe per metà vuota: quel minimo serviva a pareggiare le
   schede di una stessa fila, e di file, qui, non ce ne sono più. Quindi cade,
   e la scheda torna alta quanto il suo contenuto. */
@media (max-width: 620px) {
  .team__grid { --team-cols: 1; }
}

/* Finché non ci sono le foto, il contenitore è la scheda. È l'eccezione
   dichiarata alla regola di `.card`, che di superficie non ne ha apposta:
   là la forma la porta l'immagine, e qui l'immagine non c'è. Senza un
   riquadro, tredici nomi in griglia sarebbero un elenco sparso su un fondo
   vuoto.

   Il fondo e' --card, non --surface. Sul chiaro la scheda sta un gradino
   *sotto* la superficie, non sopra: e' scritto nel commento della palette, e
   qui e' anche l'unica strada possibile. La sezione e' gia' a L* 96, quindi
   verso l'alto restano quattro L* fino al bianco — misurati: la scheda bianca
   staccava di 3.85 e non si vedeva. --card scende a L* 92 e porta con se' un
   salto di croma, che a queste distanze si legge piu' della luminanza. Il
   gradino basta da solo: nessun filo intorno, nessun segno dentro. */
.team__card {
  /* La scheda si dichiara la propria larghezza: la griglia non è più grid e
     le colonne vivono qui, nel conto fra i due custom property del gruppo. */
  flex: 0 0 calc((100% - (var(--team-cols) - 1) * var(--team-gap)) / var(--team-cols));
  min-width: 0;
  display: flex;
  flex-direction: column;
  /* La misura non viene dal contenuto — un ruolo su due righe darebbe riquadri
     più alti degli altri e la fila si sfalserebbe. Fissata qui, la griglia
     tiene il suo ritmo e le schede restano pari. */
  min-height: clamp(7rem, 11vw, 9rem);
  padding: clamp(1.1rem, 1.8vw, 1.4rem);
  border-radius: var(--radius-lg);
  background: var(--card);
  transition: transform 0.22s var(--ease), box-shadow 0.22s var(--ease);
}

/* A colonna sola l'altezza minima non ha più niente da pareggiare: serviva a
   tenere pari le schede di una stessa fila, e di file non ce ne sono più. Una
   tessera larga quattrocento e alta centododici con dentro un nome e un ruolo
   è per metà vuota. Sta qui e non nel @media delle colonne perché quello viene
   prima di questa dichiarazione: a parità di specificità vince l'ultima. */
@media (max-width: 620px) {
  .team__card { min-height: 0; }
}

@media (hover: hover) {
  .team__card:hover {
    transform: translateY(-3px);
    box-shadow: var(--shadow);
  }
}

/* `auto` si prende l'aria che avanza: il nome cade sul fondo della scheda
   qualunque sia l'altezza, e sopra resta il vuoto — che è la forma della
   scheda, non un posto in attesa di qualcosa. */
.team__name {
  margin-top: auto;
  font-size: var(--text-h5);
  line-height: var(--leading-h5);
  letter-spacing: var(--tracking-h5);
  font-weight: 600;
  text-wrap: balance;
}

/* Oggi il ruolo ce l'hanno tutti e questa regola non scatta, ma il dato lo
   lascia facoltativo e la scheda senza ruolo tornerebbe a essere possibile:
   senza la riga sotto, il nome scenderebbe fin dove nelle altre sta il ruolo,
   e nella stessa fila si leggerebbe come la qualifica di qualcun altro.
   `:last-child` riconosce proprio quelle schede — il nome è l'ultima cosa che
   contengono — e riserva sotto lo spazio della riga che non c'è. La misura è
   quella vera della riga di ruolo, non un numero a occhio: corpo per
   interlinea, più lo stacco che la separerebbe dal nome. */
.team__name:last-child {
  margin-bottom: calc(var(--text-xs) * 1.4 + 0.3rem);
}

/* Sotto il nome, in grigio e senza peso: dice il ruolo e basta. Con il
   maiuscoletto in accento che aveva prima era la cosa più forte di una
   scheda che intorno non ha più né filo né segno. */
.team__role {
  margin-top: 0.3rem;
  font-size: var(--text-xs);
  font-weight: 400;
  line-height: 1.4;
  color: var(--fg-muted);
}

/* L'origine, sotto l'apertura. Prima erano due paragrafi dentro `.split`, con
   l'etichetta a sinistra a fare da bordo, poi un filo sopra il testo: ma un
   filo separa due blocchi chiari senza dare peso a nessuno dei due, e questo
   è il paragrafo che dice da dove viene tutto. Il peso glielo dà la fascia
   scura qui sopra.

   Un gradino sopra il corpo e sotto la nota dell'apertura: la nota è lead in
   grigio, questa è la voce della pagina e sullo scuro sta in bianco pieno.
   Alla stessa misura sarebbero due sommari uno sopra l'altro, e nessuno dei
   due prevarrebbe. La colonna è di lettura — 62ch — perché sono righe di
   prosa, non un titolo da distribuire. */
/* Il marchio al posto della parola, in linea nella frase. L'altezza è in `em`
   perché deve seguire il corpo del paragrafo a ogni breakpoint, e il valore
   viene da una misura: «dendōo» composto nello stile del paragrafo occupa
   75px con l'asta alta 16px, e a 0.78em il wordmark ne misura 74.1 con l'asta
   a 15.1. Prende cioè lo stesso posto della parola che sostituisce — un filo
   più basso, perché il marchio è un carattere da display più pieno e alla
   stessa altezza d'asta peserebbe più del testo intorno.

   `vertical-align` non è a occhio. Nel viewBox alto 25 la linea di base delle
   aste cade a 23.2748: sotto restano 1.73 unità, il 6.9% del riquadro. Un
   inline-block allineato alla base appoggia il proprio bordo inferiore sulla
   riga del testo, quindi il marchio salirebbe di quel 6.9% — che a 0.78em fa
   0.054em, ed è esattamente quanto lo si riporta giù. Verificato: la base
   delle aste del marchio cade sulla stessa riga di quelle del paragrafo. */
.team-origin__brand {
  display: inline-block;
  vertical-align: -0.054em;
}

.team-origin__brand .logo__svg {
  display: block;
  height: 0.78em;
  width: auto;
  color: inherit;
}

/* L'origine non e' piu' un paragrafo dentro una fascia: e' la prima scena del
   palco, e da sola deve reggere una schermata intera. Il corpo sale al gradino
   h3 e la misura si stringe: a 46ch la riga arrivava a 970px e a sessantasei
   battute, che a trentaquattro pixel di corpo e' oltre il limite di lettura.
   A 38ch la riga sta a 800 e le battute scendono a cinquantacinque, dentro il
   margine. Resta prosa, non tesi: il carattere e' quello del testo e il peso
   quello di sempre. */
.team-origin {
  max-width: 38ch;
  margin: 0;
  font-size: var(--text-h3);
  line-height: 1.4;
  letter-spacing: -0.015em;
  text-wrap: pretty;
}

/* La chiusura: constatazione e spinta, due righe che vanno lette come una.
   Sta al gradino h2 e non al display dell'apertura — quello è il titolo della
   pagina e resta l'unico — ma sopra qualunque testo corrente, perché è
   l'ultima cosa che si legge prima dell'invito. `balance` distribuisce le
   parole fra le righe: sono frasi corte, e a piena misura la seconda cadeva
   con una parola sola sotto. */
.team-close__line {
  max-width: 26ch;
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-h2);
  font-weight: 600;
  line-height: var(--leading-h2);
  letter-spacing: var(--tracking-h2);
  text-wrap: balance;
}

/* La seconda riga è la spinta e sta attaccata alla prima: un'interlinea
   piena le separerebbe in due affermazioni indipendenti.

   Ed è l'unico ciano di tutta la pagina. Era grigia — a pari peso le due frasi
   si contendevano l'accento e la constatazione, che è la premessa, perdeva —
   ma sul buio il grigio non serve più a farla stare indietro: serve a farla
   sparire. Il ciano la porta avanti invece che indietro, ed è la cosa giusta,
   perché questa riga è la domanda che il tasto qui sotto raccoglie. Lo stesso
   --brand-2 che .on-dark dà a --accent, cioè al tasto: la riga e il bottone
   che le risponde sono dello stesso colore, e si leggono come un gesto solo.
   Misurato: 10.9:1 sul --blue-950 del palco, 6.1:1 sul --blue-700 in cui la
   chiusura vira arrivandole sotto. */
.team-close__line--punch {
  margin-top: 0.35em;
  color: var(--brand-2);
}

/* --- il palco di /team -----------------------------------------------------
   Lo stesso di /compliance-intelligence, con tre scene invece di sette: da
   dove veniamo, chi siamo, perché. Dopo l'apertura non si scorre più — la
   vista resta agganciata in cima e sono le scene a darsi il cambio dentro lo
   stesso riquadro. La corsa alta sopra non porta niente: è scorrimento di
   scorta, ed è lei che la timeline misura.

   Quel che le due pagine hanno in comune sta scritto una volta sola, nelle
   regole di .ci-hero, .ci-fondo, .ci-scena e .ci-scorri, a cui questi nomi
   sono stati aggiunti con una virgola: l'apertura agganciata con la rampa, gli
   strati di fondo, la grana, le fermate dell'animazione. Qui resta quel che è
   di questa pagina — quante scene sono, dove si danno il cambio, e la griglia
   delle persone, che è il pezzo che decide tutto il resto.

   Nel flusso la vista è il pannello: un blocco scuro che sale sopra l'apertura
   agganciata, e sopra il suo nero passano le tre scene una sotto l'altra. Il
   passo lo mette la griglia — i margini, in una griglia, non si fondono. */
.team-palco__vista {
  position: relative;
  display: grid;
  row-gap: clamp(3.5rem, 7vw, 6rem);
  padding-block: clamp(3.5rem, 7vw, 6rem);
  /* `.on-dark` dipinge --bg; qui il fondo e' degli strati. */
  background: none;
}

@supports (animation-timeline: view()) and (animation-range: contain 0%) {
  /* 1025 e non 901 come su /compliance-intelligence, e la soglia è quella
     della griglia delle persone: a 1024 scende da quattro colonne a tre, e le
     dodici schede passano da tre file a quattro. Misurato a 1025x640 — il caso
     peggiore che qui accende il palco — il titolo e le tre file fanno 351px
     dove la schermata utile ne ha 504; a quattro file ne farebbero 452, che ci
     starebbero ancora ma con cinquantadue pixel d'aria in tutto.

     Non è quindi il taglio a decidere, è la composizione: sopra i 1024 la
     griglia è quella per cui la pagina è disegnata — tre file piene, nessuno
     che avanza in fondo — e il palco mostra quella. Sotto, lo schermo è già
     abbastanza stretto perché la lettura nel flusso sia la migliore.

     E un palco vuole anche un minimo di altezza: novecento pixel di
     scorrimento a scena, su una finestra bassa, si sentono tutti. Il
     pavimento è quello di /compliance-intelligence, 640. Lì sotto la pagina
     torna quella del flusso: si scorre, e si legge tutto. Non è una versione
     rotta, è l'altra. */
  @media (min-width: 1025px) and (min-height: 640px) and (prefers-reduced-motion: no-preference) {
    /* Quattro schermate-viewport: una per la vista agganciata e una per ogni
       scena, cioè novecento pixel di scorrimento a scena su mille di
       finestra. */
    .team-palco {
      height: 400vh;
      view-timeline-name: --palco;

      /* La chiusura risale sopra la coda vuota che il centraggio lascia sotto
         l'ultima scena. Quanto valga lo misura il JS (vedi `--palco-coda` in
         main.js): dipende dal testo della scena e dall'altezza della finestra,
         e il CSS non può saperlo. Senza JS vale zero e il palco resta com'è. */
      margin-bottom: calc(-1 * var(--palco-coda, 0px));
    }

    .team-palco__vista {
      position: sticky;
      top: 0;
      height: 100vh;
      overflow: hidden;
      /* La barra e' fissa e copre i primi settantadue pixel: senza riservarli,
         la scena si centra su tutta la finestra e la sua cima finisce sotto il
         menu. */
      padding: var(--header-h) 0 0;
      row-gap: 0;
      grid-template-rows: minmax(0, 1fr);
    }

    /* Lo strato di coda entra e resta: a coprirlo non c'e' la sua sparizione
       ma lo strato seguente. Qui serve solo a portare il viraggio della zona
       di chiusura fin dentro l'ultima scena — vedi .ci-fondo--coda::before —
       e si accende sul confine fra la seconda e la terza, dove in scena non
       c'e' niente: dentro una scena si vedrebbe il fondo cambiare sotto un
       testo gia' letto. */
    .team-fondo--coda {
      animation: palco-entra linear both;
      animation-timeline: --palco;
    }

    .team-palco__vista .team-fondo--coda { animation-range: contain 64.5% contain 66.5%; }

    /* Il selettore e' piu' lungo del necessario di proposito: le regole delle
       scene stanno piu' in basso nel foglio e, a parita' di peso,
       vincerebbero. */
    .team-palco__vista .team-scena {
      grid-area: 1 / 1;
      width: 100%;
      height: 100%;
      margin: 0;
      display: grid;
      align-content: center;
      animation: palco-scena linear both;
      animation-timeline: --palco;
    }

    /* Tratti adiacenti, senza sovrapposizione: la scena che esce tocca lo zero
       esattamente dove la seguente parte da zero, e il cambio avviene in un
       attimo in cui lo schermo porta solo il fondo. Un terzo di corsa a
       testa. */
    .team-palco__vista .team-scena--1 { animation-name: palco-scena-prima; animation-range: contain 0% contain 33.333%; }
    .team-palco__vista .team-scena--2 { animation-range: contain 33.333% contain 66.667%; }
    .team-palco__vista .team-scena--3 { animation-name: palco-scena-ultima; animation-range: contain 66.667% contain 100%; }

    /* --- la griglia, dentro una schermata ---
       Fuori dal palco le schede si misurano sulla larghezza: è la griglia a
       comandare, e in verticale c'è tutta la pagina. Dentro, il vincolo si
       rovescia — tre file devono stare in quel che avanza sotto il titolo — e
       le misure vengono dall'altezza della finestra. Misurate: a 1025x640 il
       titolo e le tre file fanno 351px su 504 di schermata utile, a 1440x900
       fanno 443 su 738, a 1920x1080 fanno 522 su 900. Il minimo di 5rem è
       l'altezza che la scheda ha comunque per conto suo, nome e ruolo
       compresi: sotto quella soglia il valore in vh non servirebbe a
       niente. */
    .team-scena--2 .section__head { margin-bottom: clamp(1.25rem, 3.5vh, 2.5rem); }

    .team-scena--2 .team__grid {
      --team-gap: clamp(1rem, 2.2vh, 1.75rem);

      margin-top: clamp(1.25rem, 3.5vh, 2.5rem);
    }

    .team-scena--2 .team__card { min-height: clamp(5rem, 12vh, 9rem); }
  }
}

/* Le schede delle persone sono quelle delle scene di
   /compliance-intelligence, che a loro volta sono quelle della bento della
   home: stesso navy pieno un gradino sopra il fondo, stessa luce d'angolo.
   Il --card di .on-dark le porterebbe su --steel-9, che è due gradini più su
   e su questo fondo si legge come un'altra famiglia di schede. */
.team-scena .team__card { background: var(--scheda); }

/* L'ultima scena è una citazione, non un paragrafo: sale al gradino h1, come
   la tesi che chiude /compliance-intelligence. La misura resta 26ch — la
   coppia di frasi è corta e a piena larghezza si leggerebbe come due righe
   sciolte invece che come un blocco. Misurate a 1025x640: 366px su 504. */
.team-scena--3 .team-close__line {
  font-size: var(--text-h1);
  line-height: var(--leading-h1);
  letter-spacing: var(--tracking-h1);
}

/* --------------------------------------------------------------- prose -- */

.prose h2 {
  font-size: var(--text-h4);
  letter-spacing: var(--tracking-h4);
  line-height: var(--leading-h4);
  margin-top: 2.5rem;
  margin-bottom: 0.8rem;
}

.prose h2:first-child { margin-top: 0; }

.prose p { color: var(--fg-muted); }

.prose p + p { margin-top: 1rem; }

/* ---------------------------------------------------------------- split -- */

/* Titolo a sinistra e testo a destra, su una riga di base comune. Incolonnati
   al centro i due blocchi non avevano un bordo cui agganciarsi e sembravano
   posati a caso: la colonna del titolo è quel bordo. */
.split {
  display: grid;
  /* 18rem: «Da dove veniamo» a h3 misura 274px, e la colonna deve tenerlo
     su una riga perché resti un'etichetta e non un titolo che va a capo. */
  grid-template-columns: minmax(0, 18rem) minmax(0, 1fr);
  gap: clamp(1.5rem, 5vw, 4rem);
  padding-top: clamp(2rem, 4vw, 3rem);
  border-top: 1px solid var(--line);
}

.split + .split { margin-top: clamp(2.5rem, 5vw, 4rem); }

.split__title {
  font-size: var(--text-h3);
  letter-spacing: var(--tracking-h3);
  line-height: var(--leading-h3);
  margin: 0;
  /* Resta in cima anche quando il testo accanto è lungo: è un'etichetta di
     sezione, non un titolo centrato sul paragrafo. */
  align-self: start;
}

.split__body {
  max-width: 68ch;
  color: var(--fg-muted);
  text-wrap: pretty;
}

@media (max-width: 780px) {
  /* In colonna il titolo torna sopra al suo testo. */
  .split { grid-template-columns: 1fr; gap: 0.9rem; }
}

/* -------------------------------------------------------------- article -- */

/* Zona a foschia su cui galleggiano i fogli. */
.sheet-wrap {
  background: var(--section-grey);
  padding-top: calc(var(--header-h) + clamp(1.5rem, 3vw, 2.5rem));
}

/* Sezioni contigue della stessa zona: senza, i due padding si sommerebbero e
   fra il foglio dell'articolo e quello dei correlati resterebbe il doppio
   dello stacco. */
.sheet-wrap + .sheet-wrap { padding-top: 0; }

/* I correlati stanno attaccati al foglio dell'articolo: sono la sua coda, non
   una sezione a parte. Lo stacco resta solo in fondo, verso la chiusura. */
.sheet-wrap--tight {
  padding-top: 0.75rem;
  padding-bottom: clamp(4rem, 9vw, 7.5rem);
}

/* Il foglio. Il testo dentro sta nella sua colonna di lettura, non a tutta
   la larghezza del foglio: è il padding orizzontale a stringerlo. */
.sheet {
  border-radius: clamp(1.25rem, 2.5vw, 2rem);
  background: var(--surface);
  padding: clamp(2.5rem, 6vw, 5rem) clamp(1.25rem, 4vw, 3rem);
}

.sheet--pad { padding: clamp(2rem, 4vw, 3rem); }

.article__head {
  max-width: 46rem;
  margin: 0 auto;
  text-align: center;
}

/* La data sta sopra il titolo, come nel riferimento. */
/* Sottotitolo: la parte dopo i due punti, in corpo e tono minori perché
   accompagni il titolo invece di raddoppiarlo. */
.article__subtitle {
  max-width: 44ch;
  margin: 0.9rem auto 0;
  font-size: var(--text-lg);
  line-height: var(--leading-lg);
  color: var(--fg-muted);
  text-wrap: pretty;
}

.article__meta {
  margin-bottom: 0.75rem;
  font-size: var(--text-xs);
  color: var(--fg-faint);
}

/* Nella riga sopra il titolo la firma sta un gradino avanti alla data: il nome
   è di chi scrive, la data è solo quando. Il punto mediano che li separa resta
   invece al tono della riga. */
.article__author {
  font-weight: 500;
  color: var(--fg-muted);
}

/* L'interruttore di lingua, in cima al foglio dell'articolo.

   Due scomparti dentro una pista sola, quello acceso sulla lingua che si sta
   leggendo: è la forma a dire che esiste un'altra versione, prima ancora del
   testo. Prima era un link in coda alla riga della firma, al corpo e al tono
   di quella riga, e non si vedeva — un comando ha bisogno di una sagoma.

   Sta a destra e fuori dalla testata centrata: è un comando, non una riga
   dell'articolo, e chi legge e basta deve poterlo scavalcare. */
.article__langs {
  display: flex;
  gap: 2px;
  width: fit-content;
  /* `auto` a sinistra lo manda a destra senza togliere il foglio dal flusso. */
  margin: 0 0 clamp(1.75rem, 3vw, 2.75rem) auto;
  padding: 3px;
  border-radius: 999px;
  background: var(--surface-2);
}

.article__lang {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  padding: 0.44rem 0.95rem;
  border-radius: 999px;
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1;
  color: var(--fg-muted);
  transition: background-color 0.2s ease, color 0.2s ease;
}

/* Lo scomparto acceso si stacca dalla pista: superficie piena e ombra corta,
   come una pastiglia appoggiata sopra invece che incassata. */
.article__lang.is-current {
  background: var(--surface);
  color: var(--fg);
  font-weight: 600;
  box-shadow: 0 1px 2px rgba(var(--ink-rgb), 0.16);
}

@media (hover: hover) {
  a.article__lang:hover {
    background: rgba(var(--ink-rgb), 0.05);
    color: var(--fg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .article__lang { transition: none; }
}

/* La bandiera è un disegno, non testo: le si toglie l'interlinea e le si dà
   un filo di corpo in più, che gli emoji disegnano più piccoli della riga. */
.article__lang-flag {
  font-size: 1.1em;
  line-height: 1;
}

/* Sullo stretto la pista si centra: a destra, con la testata centrata sotto,
   sembrerebbe caduta lì per sbaglio. */
@media (max-width: 560px) {
  .article__langs { margin-inline: auto; }
}

.article__title {
  font-size: var(--text-h1);
  line-height: var(--leading-h1);
  letter-spacing: var(--tracking-h1);
  /* 22ch spezzava questi titoli in quattro righe. La misura stretta serve al
     corpo del testo, non a un titolo: qui conta che si legga d'un fiato. */
  max-width: 26ch;
  margin-inline: auto;
  text-wrap: balance;
}

/* Copertina più stretta del foglio: è un'illustrazione dentro la pagina, non
   una fascia a tutta larghezza. */
.article__cover {
  max-width: 46rem;
  margin: clamp(2rem, 4vw, 3rem) auto 0;
}

/* Rapporto fisso e ritaglio: le foto arrivano con proporzioni qualsiasi e una
   verticale, lasciata alla sua altezza naturale, occupava tutto il primo
   schermo prima ancora di far leggere una riga. */
.article__cover img {
  display: block;
  width: 100%;
  /* `height: auto` è indispensabile: l'attributo height="563" sul tag conta
     come altezza definita e, con entrambe le dimensioni fissate,
     `aspect-ratio` viene ignorato. */
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--radius);
}

.article__body {
  max-width: 42rem;
  margin: clamp(2.25rem, 4vw, 3.25rem) auto 0;
  /* Corpo di lettura lunga: lo stesso gradino della prosa CI. */
  font-size: var(--text-lg);
}

.article__body p { color: var(--fg-muted); }

.article__body p + p { margin-top: 1.1rem; }

/* Titoletto di sezione: un gradino sopra il corpo di lettura (h4 su lg),
   con il peso di tutti gli altri titoli — non un titolo da display. Il margine sopra è molto più largo
   di quello sotto: lo lega al testo che introduce, non a quello che chiude la
   sezione precedente. */
.article__h {
  margin: clamp(2rem, 4vw, 2.75rem) 0 0.75rem;
  font-size: var(--text-h4);
  line-height: var(--leading-h4);
  font-weight: 600;
  letter-spacing: var(--tracking-h4);
}

.article__list {
  margin-top: 1.1rem;
  display: grid;
  gap: 0.7rem;
  padding-left: 1.3rem;
  list-style: disc;
  color: var(--fg-muted);
}

.article__list li { padding-left: 0.25rem; }

.article__list ::marker { color: var(--fg-faint); }

/* Il testo che riprende dopo un elenco: `p + p` non lo raggiunge, perche' il
   fratello precedente e' la lista, e `p` ha margine zero dal reset. Senza
   questa riga il paragrafo si incolla all'ultimo pallino. Stesso passo della
   prosa, cosi' il ritmo della colonna resta uno solo. */
.article__body .article__list + p { margin-top: 1.1rem; }

.article__excerpt {
  font-size: var(--text-xl);
  line-height: var(--leading-xl);
  color: var(--fg) !important;
  padding-left: 1.1rem;
  border-left: 2px solid var(--neutral-6);
}

/* --- la pagina di un articolo: colonna di lettura, com'è su Medium -------

   Vale solo per `.page-article`: l'informativa privacy riusa le stesse classi
   e resta com'era.

   Due sole misure su tutta la pagina, e sono centrate:

     --read       la colonna del testo. 720px con un corpo da 22 fanno 67
                  caratteri per riga. Medium ne tiene 680 con un corpo da 21:
                  gli stessi 66-68. È quello il numero che conta, non i pixel.
     --read-wide  la copertina e le schede dei correlati, che escono dalla
                  colonna senza arrivare al bordo della barra — le immagini
                  «outset» di Medium, 1024 contro 680.

   Il primo tentativo allineava tutto al filo della barra, a tutta la sua
   larghezza: misurato a 1440, il testo partiva da 36 e finiva a 805, e alla
   sua destra restavano seicento pixel di niente. Una colonna a bandiera vuole
   un margine per parte, non un vuoto da un lato solo: la barra è la cornice
   della pagina, non il righello del testo.

   Il testo resta a sinistra dentro la colonna — a bandiera, mai giustificato:
   quello non cambia. */

/* Fondo bianco. Il blog porta una velatura dipinta, giusta per una vetrina di
   schede: sotto quattromila pixel di testo è un velo che non serve a leggere.
   La velatura resta sull'archivio, che è `.page-blog` senza `.page-article`. */
.page-article { background: var(--surface); }

.page-article::before { content: none; }

.page-article {
  --read: 45rem;
  --read-wide: 64rem;
}

/* Il foglio non ha più un rientro suo: a stringere il testo è la colonna, e
   sullo stretto — dove la colonna non arriva a valere — il margine è già
   quello della barra. Il respiro sopra e sotto resta. */
.page-article .sheet { padding-inline: 0; }

.page-article .article__head {
  max-width: var(--read);
  margin-inline: auto;
  text-align: left;
}

/* Titolo e sottotitolo riempiono la colonna: a stringerli è lei. */
.page-article .article__title,
.page-article .article__subtitle {
  max-width: none;
  margin-inline: 0;
}

.page-article .article__cover {
  max-width: var(--read-wide);
  margin-inline: auto;
}

/* Lo stesso raggio delle schede da cui la foto arriva: erano 14 contro 22, e
   nel passaggio l'angolo cambiava misura a metà volo. */
.page-article .article__cover img { border-radius: var(--radius-lg); }

.page-article .article__body {
  max-width: var(--read);
  margin-inline: auto;
}

/* Il passo fra due paragrafi: da 17 pixel a 26, poco più di tre quarti di
   riga. Su una colonna lunga il passo stretto impastava i paragrafi in un
   blocco unico — è l'altra metà di quel che rende un testo lungo leggibile,
   insieme al fondo bianco. */
.page-article .article__body p + p,
.page-article .article__body .article__list + p { margin-top: 1.6rem; }

/* L'interruttore di lingua si appoggia al bordo destro della colonna, non a
   quello del foglio: è dell'articolo, e deve stare nel suo rettangolo. Il
   `max(0px, ...)` è lo stesso di ogni altro rientro calcolato del sito —
   quando la finestra è più stretta della colonna il margine si spegne invece
   di diventare negativo. */
.page-article .article__langs {
  margin-left: auto;
  margin-right: max(0px, (100% - var(--read)) / 2);
}

/* Sullo stretto la pista si centrava perché sotto c'era una testata centrata.
   Adesso la testata è a bandiera: la pista torna sul suo filo. */
@media (max-width: 560px) {
  .page-article .article__langs { margin-inline: 0 auto; }
}

/* La coda della sezione: sotto l'articolo adesso c'è l'invito, e i 120px di
   stacco fra due sezioni si sommavano agli 80 di respiro del foglio e ai 52
   di apertura della banda. Restano quei due, 132 in tutto — la stessa misura
   che la banda ha sulle altre pagine. */
.page-article .sheet-wrap { padding-bottom: 0; }

/* L'invito chiude l'articolo, quindi sta nella sua colonna: è l'ultimo blocco
   del testo, non una fascia della pagina. La misura comprende il margine
   della `.shell`, che resta a fare da riparo sullo stretto. */
.page-article .cta-band__inner {
  max-width: calc(var(--read) + 2 * clamp(1.25rem, 3vw, 2.25rem));
}

/* I correlati non sono più la coda dell'articolo — fra i due c'è l'invito — e
   chiudono la pagina. Stanno sulla misura larga, quella della copertina: sono
   dell'articolo anche loro, e a tutta larghezza avrebbero aperto una terza
   misura in fondo a una pagina che ne ha due.
   Lo stacco sotto lo decide la regola della zona di chiusura
   (`main:has(+ .closing-light)`), che li trova come ultima sezione. */
.page-article .article-related {
  background: transparent;
  padding-top: clamp(3rem, 6vw, 4.5rem);
}

.page-article .article-related .shell {
  max-width: calc(var(--read-wide) + 2 * clamp(1.25rem, 3vw, 2.25rem));
}

/* L'avviso di bozza sta su `--surface`, che qui è il fondo della pagina: sul
   bianco sarebbe bianco su bianco. Sale di un gradino. */
.page-article .notice { background: var(--surface-2); }

/* ----------------------------------------------------- form e notifiche -- */

/* Il funnel sta su una colonna sola (shell--narrow): form e avvisi non si
   allargano oltre la misura comoda di lettura, il calendario sì.
   Vale per il passo due: il calendario. Il modulo del passo uno ha la sua
   larghezza dalla colonna di .start-split, qui sotto. */
/* Il passo due (il calendario) resta su una colonna sola e centrata.
   Il passo uno non usa questa misura: sta nella propria colonna di
   .start-split, e la riempie. */
.start-flow { --start-col: 620px; }

.start-flow .form,
.start-flow .notice,
.start-flow .start__contact {
  max-width: var(--start-col);
  margin-left: auto;
  margin-right: auto;
}

/* --- passo uno: pagina a due colonne ---------------------------------- */

/* A sinistra che cosa stai prenotando, a destra il modulo. L'apertura
   centrata sopra ai campi spingeva il pulsante sotto la piega e lasciava
   mezza pagina vuota ai lati; qui le due metà lavorano insieme e il funnel
   entra tutto in una schermata. */
.page-hero--start { text-align: left; }

.start-split {
  display: grid;
  /* Stretta e vicina: la shell è da 1440 e le due metà, lasciate libere,
     finivano ai bordi opposti dello schermo con un corridoio vuoto in
     mezzo. A 900px con un varco di 3rem si leggono come una coppia. */
  gap: clamp(1.5rem, 2.5vw, 3rem);
  align-items: center;
  max-width: 900px;
  margin-inline: auto;
}

/* Sotto i 900px le due colonne diventerebbero due strisce: il modulo passa
   sotto al testo, che è l'ordine in cui si legge comunque. */
@media (min-width: 900px) {
  .start-split {
    /* La colonna del testo prende la misura del testo, non tutto lo spazio
       che avanza: a `1fr` restava larga il doppio del suo contenuto e il
       varco fra le due metà era il triplo di quello dichiarato. */
    grid-template-columns: minmax(0, max-content) minmax(0, 23rem);
    justify-content: center;
  }
}

/* Il titolo torna a sinistra: `margin-inline: auto` è quel che lo teneva al
   centro quando l'apertura era una fascia sopra al modulo. */
.start-split__intro .page-hero__title { margin-inline: 0; }

/* Stesso gradino dell'h1 delle altre pagine di servizio: la coerenza fra
   pagine viene prima. Il timore che a corpo pieno il titolo dettasse la
   larghezza della colonna non regge più: misurata a 1440 la colonna fa
   424px sia con l'h1 sia con un gradino sotto, e il titolo sta su una riga.
   La misura in ch è quel che lo tiene lì. */
.start-split__intro .page-hero__title {
  max-width: 14ch;
}

/* Il modulo riempie la sua metà: la centratura la fa già la griglia. */
/* Il sommario torna a sinistra come il titolo, e con la misura che detta la
   colonna: `.page-start .page-hero__lead` toglie ogni max-width perche' al
   passo due il sommario sta su una colonna sola e centrata. Qui invece la
   colonna e' dimensionata a max-content, e senza un tetto questa riga la
   tirerebbe larga quanto tutta la frase, schiacciando il form accanto. 38ch
   e' anche la misura sotto cui devono stare le tre righe del programma. */
.start-split__intro .page-hero__lead {
  margin-inline: 0;
  max-width: 38ch;
}

.start-split__panel { --start-col: 100%; }

/* L'avviso che prende il posto del modulo a invio riuscito (/documento-tecnico)
   sta da solo nella colonna: il margine sotto lo alzava di 12px sul centro. */
.start-split__panel .notice { margin-bottom: 0; }

/* --- il programma della call ------------------------------------------- */

/* Tre righe sotto al sommario: che cosa succede nella mezz'ora. Stanno nella
   colonna di sinistra, che senza di loro era mezza vuota — il form a destra è
   alto il doppio del testo che gli sta accanto, e quel vuoto lo riempiono
   senza costare un pixel di altezza alla pagina.

   Una riga per voce, e il vincolo è nel testo prima che nel CSS: le frasi
   stanno sotto la misura del sommario (38ch), che è ciò che detta la
   larghezza della colonna. Nessun `nowrap` a difenderle — a colonna stretta
   uscirebbe dallo schermo invece di andare a capo, che è il male peggiore. */
.start-check {
  margin: 2.4rem 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  /* Righe staccate, non un blocco di testo: sono tre cose distinte che
     succedono nella call, e a interlinea stretta si leggevano come un
     paragrafo andato a capo tre volte. */
  gap: 1.05rem;
}

.start-check li {
  display: grid;
  /* La spunta in colonna propria: con un `inline` l'eventuale seconda riga
     rientrerebbe sotto l'icona invece di allinearsi al testo. */
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: 0.6rem;
  font-size: var(--text-sm);
  color: var(--fg-muted);
}

/* La riga di ripiego chiude la colonna. Stacco pieno dall'elenco: non e' una
   quarta voce del programma della call — e' l'alternativa a tutta la pagina,
   e a interlinea da lista si leggeva come tale. */
.start-split__intro .start__contact { margin-top: 2.1rem; }

/* L'icona accompagna, non annuncia: tinta d'accento ma corpo sotto al testo,
   e alzata di un pelo perché l'occhio la vede bassa quando è centrata sulla
   riga intera. Ognuna dice la sua riga — tre spunte uguali non dicevano
   niente che il testo non dicesse già. */
.start-check .icon {
  --icon-size: 1.15rem;
  /* Il tratto da 2.25 serviva alla spunta nuda: questi disegni hanno
     dettaglio dentro e a quel peso si impastano. */
  --icon-stroke: 1.9;
  margin-top: 0.15rem;
  color: var(--fg);
}

/* Didascalia del pulsante che chiude il form, non una riga a se': e' quel
   pulsante che porta al calendario, e questa riga e' cio' che rende il clic
   una richiesta informata. Corpo e tinta sotto a tutto il resto del form —
   non deve competere con le etichette dei campi — ma il margine negativo la
   stringe al pulsante, perche' il grid del form separa di 1.1rem tutto quanto
   e cosi' la riga galleggiava fra captcha e bottone senza appartenere a
   nessuno dei due. */
/* Niente max-width né margini automatici: la riga è una cella del form a
   due colonne, e in griglia un margine auto sull'asse in linea spegne lo
   stretch e centra la cella a fit-content. Qui deve restare allineata a
   sinistra come i campi che spiega. */
.start__note {
  /* Didascalia del pulsante: vicina, ma non attaccata. Il -0.55rem di prima
     annullava esattamente il gap del form e le due righe si toccavano. */
  margin: 0.35rem 0 0;
  font-size: var(--text-xs);
  color: var(--fg-faint);
}

/* Un gradino sopra la riga, non fino all'inchiostro pieno: deve farsi
   riconoscere come il nome del servizio, non gridare. */
.start__note strong { color: var(--fg-muted); font-weight: 600; }

/* Solo per chi ha JavaScript spento e quindi un pulsante che non fa nulla. */
.start__alt { margin-top: 1.1rem; font-size: var(--text-xs); color: var(--fg-faint); }

.start__alt a { color: var(--accent); }

/* Il widget Calendly disegna già la sua card con bordo dentro l'iframe:
   una cornice nostra attorno crea l'effetto frame-dentro-frame. Il
   contenitore resta nudo; i margini negativi assorbono il padding interno
   del widget così la card si allinea alla colonna. */
.start__calendly { margin: 0; }

/* Taglia la colonna dove l'iframe disegna la sua barra di scroll. */
.start__calendly { overflow: hidden; }

/* Alto quanto il passo più alto del flusso Calendly (la conferma con i
   dettagli, ~950px): così l'iframe non deve mai scorrere. In più è largo
   18px oltre il bordo destro del contenitore: se il browser una barra la
   mostra comunque (zoom, "mostra sempre le barre" di macOS), cade nella
   striscia tagliata da overflow: hidden e non si vede. */
.start__calendly .calendly-inline-widget {
  height: 1000px;
  width: calc(100% + 18px);
}

@media (max-width: 759px) {
  /* Sul layout mobile la card è più compatta: meno vuoto sotto. */
  .start__calendly .calendly-inline-widget { height: 760px; }
}

/* Solo a colonna larga Calendly disegna la card con il suo padding attorno
   (54px sopra, 14px ai lati, misurati sul widget reale): i margini negativi
   la riallineano alla colonna. Sotto questa soglia il widget passa al layout
   mobile senza card e gli stessi margini coprirebbero quel che sta sopra. */
@media (min-width: 760px) {
  .start__calendly { margin: -54px -14px 0; }
}

.start__contact { margin-top: 1.5rem; font-size: var(--text-sm); color: var(--fg-muted); }

.start__contact a { color: var(--accent); font-weight: 600; }

/* Il form ha tre tempi, non uno: i dati, il consenso, la chiusura (captcha e
   invio). Con un gap unico da 1.1rem la spunta si incollava alla select e il
   captcha si leggeva come un quinto campo. Questo è il respiro che marca gli
   stacchi: un token solo, così le schermate basse lo stringono da un punto
   senza rincorrere le singole regole. */
.form {
  display: grid;
  /* Era 1.1rem quando fra un campo e l'altro non c'era nient'altro. Adesso
     ogni campo si porta sotto la riga del messaggio: sommate, le due misure
     allontanavano i campi il doppio del necessario. */
  gap: 0.55rem;
  --form-break: 1rem;
  /* Pillole come i bottoni: il form parlava una lingua diversa dal resto del
     sito, con i suoi angoli da 10px in mezzo a controlli tutti tondi. */
  --field-radius: 999px;
  /* I campi erano alti 52px per ospitare una riga di testo: l'interlinea da
     lettura (1.65) dentro un input diventa aria sprecata. A 1.35 la riga sta
     comoda lo stesso e restano 44px, il minimo toccabile. */
  --field-pad-y: 0.65rem;
  --field-pad-x: 1.15rem;
}

/* `hidden` da solo non basta: il `display: grid` qui sopra lo scavalca. Serve
   al modulo di /documento-tecnico, che a invio riuscito lascia il posto
   all'avviso. */
.form[hidden] { display: none; }

/* L'analitica di Zoho appende in coda allo stesso modulo un contenitore coi
   suoi campi nascosti e tre pixel: vuoto, ma nella griglia valeva una riga e
   il suo gap, 9px sotto al tasto. Spento non perde niente: i campi partono
   comunque col modulo e i pixel si caricano lo stesso. */
.form > [id^="wf_tr_div_"] { display: none; }

.form__field { display: block; }

/* L'etichetta non deve competere col campo: a 600 pesava quanto un
   titolino e la coppia etichetta-campo si leggeva a scatti. */
.form__label {
  display: block;
  margin-bottom: 0.45rem;
  font-size: var(--text-xs);
  font-weight: 500;
}

.form__opt { font-weight: 400; color: var(--fg-faint); }

/* L'asterisco è tutto l'apparato di segnalazione che questo form ha: marca
   i campi obbligatori sempre, in tinta discreta, e diventa rosso pieno solo
   sul campo che il server ha respinto. Niente riquadri d'avviso, niente
   frasi per ogni campo: chi guarda vede subito dove tornare. */
.form__req {
  color: var(--fg-faint);
  font-weight: 600;
}

.is-invalid .form__req,
.form__field.is-invalid .form__label .form__req { color: var(--error-texto); }

/* Il campo respinto si tinge di rosso al bordo — sul gruppo, quando il
   campo vive dentro un gruppo come il telefono. Al primo tocco l'utente
   sta già correggendo: da lì in poi torna la pelle normale (main.js toglie
   .is-invalid appena il campo cambia). */
.form__field.is-invalid input,
.form__field.is-invalid select,
.form__field.is-invalid .form__phone {
  border-color: var(--error);
}

.form__check.is-invalid input { outline: 1px solid var(--error); outline-offset: 2px; }

/* Lo spazio del messaggio è riservato sempre, anche quando è vuoto: se
   comparisse dal nulla spingerebbe giù tutto il resto del modulo, e chi sta
   scrivendo si vedrebbe scappare il campo da sotto il cursore. Riga sola,
   sotto al campo che riguarda — mai un riquadro in cima al form. */
.form__error {
  display: block;
  min-height: 1.05rem;
  margin-top: 0.3rem;
  /* Rientrato come il testo dentro la pillola: il messaggio parla di quel
     che si e' scritto, e al bordo del campo restava disallineato da tutto. */
  padding-left: var(--field-pad-x);
  font-size: var(--text-xs);
  line-height: 1.35;
  color: var(--error-texto);
}

/* La spunta non ha un contenitore proprio: il suo messaggio è una cella del
   form e va rientrato fin sotto al testo, non sotto la casella. */
.form__error--check {
  margin-top: -0.75rem;
  padding-left: 1.65rem;
}

/* Non un campo sbagliato ma il sistema che non è riuscito a procedere.
   Una riga sotto al pulsante, in rosso, senza riquadro: fuori dal
   linguaggio degli asterischi perché non è l'utente a doverla correggere. */
.form__failure {
  margin-top: -0.35rem;
  font-size: var(--text-sm);
  color: var(--error-texto);
}

.form input,
.form textarea {
  width: 100%;
  padding: var(--field-pad-y) var(--field-pad-x);
  font: inherit;
  line-height: 1.35;
  color: inherit;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--field-radius);
  transition: border-color 0.18s var(--ease), background-color 0.18s var(--ease);
}

/* Il campo resta bianco anche sotto al cursore: la velatura azzurra che
   aveva prima lo avvicinava troppo al fondo della pagina, che azzurro lo è
   già, e da fermo sembrava disattivato. Il focus lo dice il bordo. */
.form input:focus,
.form textarea:focus {
  outline: none;
  border-color: var(--accent);
}

/* La pillola vale per una riga sola: un testo lungo vuole angoli normali. */
.form textarea { resize: vertical; border-radius: var(--radius); }

.form button { justify-self: start; margin-top: var(--form-break); }

/* Telefono: prefisso +39 fisso fuori dal campo. Il bordo sta sul gruppo,
   l'input dentro lo perde — così il focus illumina l'insieme. */
.form__phone {
  display: flex;
  align-items: stretch;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--field-radius);
  transition: border-color 0.18s var(--ease), background-color 0.18s var(--ease);
}

.form__phone:focus-within {
  border-color: var(--accent);
}

/* Il prefisso è un select senza pelle propria: il bordo lo dà il gruppo. */
.form select.form__prefix {
  width: auto;
  border: 0;
  border-right: 1px solid var(--line);
  border-radius: var(--field-radius) 0 0 var(--field-radius);
  background-color: transparent;
  color: var(--fg-muted);
  padding: 0 1.7rem 0 var(--field-pad-x);
  background-position: right 0.55rem center;
  white-space: nowrap;
}

.form select.form__prefix:focus {
  border-color: var(--line);
}

.form .form__phone input,
.form .form__phone input:focus {
  border: 0;
  background: transparent;
  border-radius: 0 var(--field-radius) var(--field-radius) 0;
}

/* Select con la stessa pelle degli input; freccia disegnata perché
   `appearance: none` toglie anche quella nativa. */
.form select {
  width: 100%;
  padding: var(--field-pad-y) 2.6rem var(--field-pad-y) var(--field-pad-x);
  font: inherit;
  line-height: 1.35;
  color: inherit;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--field-radius);
  transition: border-color 0.18s var(--ease), background-color 0.18s var(--ease);
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23192436' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1.1rem center;
}

.form select:focus {
  outline: none;
  border-color: var(--accent);
}

/* Consenso privacy: la casella non eredita il layout degli input di testo. */
/* Nessuno stacco di gruppo qui: il campo sopra si porta dietro la riga del
   messaggio, vuota o piena, e sommata allo stacco il salto era di 16px piu'
   largo di ogni altro passo del modulo. */
.form__check {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  font-size: var(--text-sm);
  color: var(--fg-muted);
  cursor: pointer;
}

.form .form__check input {
  width: 1.05rem;
  height: 1.05rem;
  margin-top: 0.12rem;
  padding: 0;
  flex: none;
  accent-color: var(--accent);
}

.form__check a { color: var(--accent); }

/* Il widget non è un campo: sta con il consenso e il bottone, non con la
   select che lo precede. */
.form .cf-turnstile { margin-top: var(--form-break); }

/* REGOLA: la chiusura prende sempre lo sfondo della pagina, mai uno suo.
   Scura resta solo dove la pagina finisce già scura (la home); ovunque
   altro si usa .closing-light, che è trasparente e lascia passare il fondo
   reale — bianco, velatura mist del blog, o quel che c'è. Classe diversa
   da .closing apposta: non prende né lo sfondo scuro né il viraggio JS. */
.closing-light {
  background: transparent;
}


/* La pagina privacy riusa per intero il linguaggio degli articoli
   (page-blog + .sheet + .article__*): nessuno stile suo. */

.notice {
  padding: 0.9rem 1.1rem;
  border-radius: 10px;
  background: var(--surface);
  font-size: var(--text-sm);
  color: var(--fg-muted);
  margin-bottom: 1.5rem;
}

/* Sui token di stato, non più sul turchese scritto in rgba: così dentro
   `.on-dark` il fondo tenue e il filo si ribaltano da soli. `.notice` non
   dichiara un `border`, quindi il colore del filo resta inerte finché non lo
   fa — com'era anche prima: qui non si aggiunge un contorno che non c'era. */
.notice--ok {
  border-color: var(--ok);
  background: var(--ok-sutil);
  color: var(--fg);
}

.notice--error {
  border-color: var(--error);
  background: var(--error-sutil);
  color: var(--fg);
}

.notice--error ul { list-style: disc; padding-left: 1.2rem; margin-top: 0.4rem; }

/* --------------------------------------------------------------- footer -- */

/* La coda dell'ultima sezione, quella che tocca la zona di chiusura. Le
   sezioni respirano 120px per lato su desktop e 72 sul telefono, ed e' la
   misura giusta fra due sezioni: da una parte e dall'altra c'e' del testo.
   Contro la chiusura no — sotto c'e' solo l'invito e il piede — e quei 120
   si sommavano all'apertura della banda o del piede. Qui la coda vale due
   terzi: 72 su desktop, 44 sul telefono.

   `:has(+ .closing)` non e' un vezzo: la regola deve valere per l'ultima
   sezione di una pagina che chiude cosi', non per l'ultima sezione di un
   contenitore qualsiasi. Ogni vista del sito chiude con `</main>` seguito da
   `.closing` (scuro, la home) o `.closing-light` (trasparente, il resto).

   `.section:last-of-type` e non `:last-child`: su /compliance-intelligence
   l'ultimo figlio del main e' la freccia dello scorrimento, che sta fuori dal
   flusso. Restano fuori anche /get-started e la 404, che chiudono su una
   `.page-hero` e non su una `.section`: la' la coda e' gia' stretta (18px) e
   accorciarla ancora incollerebbe il modulo al piede. */
main:has(+ .closing, + .closing-light) > .section:last-of-type {
  padding-bottom: clamp(2.75rem, 5vw, 4.5rem);
}

/* Nessun bordo e nessuno stacco di fondo rispetto alla sezione sopra: la
   chiusura della pagina è una zona sola, non due riquadri accostati.

   Lo stacco in alto è lo stesso che apriva la banda di chiusura, ora che il
   piede la segue direttamente: l'aria fra l'ultima sezione e il piede resta
   quella di prima, senza la banda in mezzo. */
.site-footer {
  /* Stessa apertura della banda di chiusura: dove la banda non c'e' — home,
     blog, privacy, get-started — il piede segue direttamente l'ultima
     sezione, e i 88px di prima si sommavano ai 120 di coda della sezione.
     Adesso sono 52 su 72: lo stacco resta il piu' largo della pagina, ma
     smette di essere una schermata da scorrere a vuoto. */
  padding: clamp(2.25rem, 4vw, 3.25rem) 0 2.25rem;
  background: transparent;
}

.site-footer__inner {
  display: flex;
  /* Chiuse in basso, non appese in alto: il blocco legale a destra è più
     alto, e con flex-start il marchio restava sospeso a mezz'aria. */
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
}

.site-footer__brand { display: grid; gap: 0.75rem; }

.site-footer__privacy { font-size: var(--text-xs); color: var(--fg-muted); }
.site-footer__privacy:hover { color: var(--fg); }

.site-footer__legal {
  text-align: right;
  font-size: var(--text-xs);
  line-height: 1.7;
  color: var(--fg-faint);
}

.site-footer__company { color: var(--fg-muted); font-weight: 600; }

.site-footer__legal a { color: inherit; }
.site-footer__legal a:hover { color: var(--fg); }

/* --------------------------------------------- ritmo verticale sul mobile --

   I fondi dei `clamp` che governano il respiro sono numeri da desktop: fra due
   sezioni sono 64px sopra e 64 sotto, e su una finestra da 900px sono un
   settimo di schermo — giusto. Su un telefono alto 800 sono un sesto, e non
   arrivano da soli: al padding della sezione si somma il margine
   dell'intestazione, e a quello lo stacco del blocco che segue. Misurati sulla
   home a 390px erano 178px fra il tasto della galleria e la citazione, 160 fra
   la citazione e la roadmap, 152 fra la bento e il titolo del prodotto — un
   quinto di schermata di niente, che non si legge come aria ma come un pezzo
   di pagina che manca.

   Il primo tentativo aveva abbassato i fondi e alzato la parte in `vw`,
   contando su quella per recuperare: ma è il fondo a comandare per tutta la
   fascia dei telefoni — a 440px 11vw fa 48px e il `clamp` prende il minimo —
   e il risultato era meno aria di prima, non più. A 440 fra due sezioni
   restavano 112px, sotto i 128 del desktop.

   Quindi la pendenza torna quella di sempre, 9vw, e a cambiare è solo il
   fondo: da 4 a 4.5rem. A 440px fanno 72px per lato — 144 fra due sezioni,
   un settimo abbondante di schermata — e sono 72 su tutta la fascia dei
   telefoni, perché 9vw non arriva a superarli prima degli 800px. Alla soglia
   dei 780 la regola vale 72 e quella generale 70.2: lo scalino è di due
   pixel, cioè non c'è. Le proporzioni fra i respiri restano quelle di prima —
   quel che separa due sezioni resta il doppio di quel che separa due blocchi
   dentro la stessa. */
@media (max-width: 780px) {
  .section,
  .page-product .section { padding-block: clamp(4.5rem, 9vw, 7.5rem); }

  /* Il foglio di articoli e legali è una `.section` che porta anche la
     riserva per la barra: la regola qui sopra, avendo la stessa specificità e
     stando dopo, gliela cancellava e il titolo finiva a tredici pixel dalla
     pillola. Va ridichiarata, non esclusa: lo stacco sotto resta quello
     stretto di tutte le altre sezioni. */
  .sheet-wrap {
    padding-top: calc(var(--header-h) + clamp(1.75rem, 5vw, 3rem));
  }

  .sheet-wrap + .sheet-wrap { padding-top: 0; }

  .section__head { margin-bottom: clamp(3rem, 8vw, 4rem); }

  .section__foot { margin-top: clamp(3rem, 7.5vw, 3.75rem); }

  /* home: fra il cappello di sezione e quel che introduce */
  .pain__intro { margin-bottom: clamp(3rem, 8vw, 4rem); }
  .bento { margin-top: clamp(3rem, 8vw, 4rem); }
  .roadmap__track { margin-top: clamp(3rem, 8vw, 4rem); }
  .overview__head { margin-bottom: clamp(3rem, 8vw, 4rem); }

  /* home: fra l'ultima situazione e la risposta. Le due metà della sezione
     sono due titoli di pari grado uno sotto l'altro, e fra loro passavano i
     32px di padding dell'ultima voce più i 24 di questo blocco: cinquantasei
     in tutto, meno di quel che separa un titolo dal proprio sommario altrove
     nella pagina. Le due domande si leggevano di seguito, come se la seconda
     continuasse la prima. */
  .solution__intro { padding-top: clamp(2.5rem, 7vw, 3.25rem); }

  /* home: la citazione. Il riquadro ha un padding suo che si sommava a
     quello della sezione, e i due insieme facevano lo stacco più largo di
     tutta la pagina. */
  .quote { padding-block: clamp(1.75rem, 5vw, 2.5rem); }
  .quote__inner { padding: clamp(2.25rem, 7.5vw, 3.5rem) clamp(1.25rem, 5vw, 2rem); }
  .quote__author { margin-top: clamp(1.5rem, 4.5vw, 2.25rem); }

  /* home: la roadmap chiude la zona chiara e sotto sale il blocco scuro, che
     si arrotonda addosso di `--round`: quel raccordo è già stacco. */
  .roadmap { padding-bottom: calc(clamp(4.5rem, 9vw, 7.5rem) + clamp(1.25rem, 4vw, 2.5rem)); }

  .faq__list { margin-top: clamp(2.5rem, 7vw, 3.25rem); gap: 1rem; }
  /* La coda della FAQ la decide adesso la regola della zona di chiusura
     (`main:has(+ .closing)`): la FAQ e' l'ultima sezione della home e sotto
     non c'e' un'altra sezione da cui staccarsi, solo il piede. Il supplemento
     che stava qui — 92px invece di 72 — serviva alla roadmap, che ha un
     blocco scuro che le si arrotonda addosso; la FAQ l'aveva per copia. */

  /* La zona di chiusura sul telefono. I fondi generali valgono gia' 36 e 24
     su una finestra da 390; qui scendono a 32 e 20 perche' sotto la banda non
     c'e' una sezione ma il piede, e i due stacchi si sommano lo stesso.
     Misurato a 390x844: fra i tasti e il piede si passa da 92px a 52. */
  .cta-band { padding-block: clamp(2rem, 5vw, 2.5rem) clamp(1.25rem, 3.5vw, 1.75rem); }

  /* /product: quattro schermate incolonnate. Su desktop lo stacco è largo
     perché ognuna occupa mezzo schermo; sul telefono le schermate sono alte
     un quarto di quello e lo stacco restava più alto di loro. */
  .showcase { gap: clamp(4rem, 12vw, 5.5rem); }
  .page-product .showcase { margin-top: clamp(1.5rem, 5vw, 2.5rem); }

  /* /team e /compliance-intelligence */
  .split { padding-top: clamp(2.25rem, 6vw, 2.75rem); }
  .split + .split { margin-top: clamp(2.5rem, 7.5vw, 3.5rem); }
  .manifesto__list { gap: 1.25rem; }
  .manifesto__cta { margin-top: 2.5rem; }

  /* --- /product: prima il portatile ---------------------------------------
     Impilato, il portatile chiudeva l'apertura: si arrivava al titolo, alla
     frase e al tasto, e solo dopo — sotto la piega, o quasi — a quel che la
     pagina vende. Ma è lo schermo la ragione per cui il mockup sta lì, e la
     pagina si chiama «Come funziona»: mostrarlo per primo è la risposta alla
     domanda che il titolo pone.

     `.page-product .page-hero--split` è già una colonna flessibile e il
     mockup è un suo figlio diretto, fratello della `.shell` che porta il
     testo: basta l'ordine. Il margine si ribalta con lui — stava sopra per
     staccarsi dal tasto, adesso sta sotto per staccarsi dal titolo. */
  .page-hero--dark .page-hero__mock {
    order: -1;
    margin-top: 0;
    margin-bottom: clamp(1.5rem, 4vw, 2.5rem);
  }

  /* E sotto il testo si apre il cielo. Serve, e non è aria decorativa.

     Il degradato di questo blocco è rovesciato rispetto alla home: scuro in
     cima, chiaro in fondo, così si salda senza stacco alla sezione che segue.
     Le sue fermate sono frazioni dell'altezza del blocco — la discesa comincia
     al 40% e chiude al 92% — quindi con il portatile in testa il testo
     scivolava nella metà chiara. Misurato a 440x956: il sommario passava da
     2.51:1 in alto a 1.52:1 sull'ultima riga. Illeggibile.

     La stanza che manca è quella *sotto* l'ultima riga: perché il sommario
     resti sopra il --blue-600, fra la sua base e il fondo del blocco devono
     starci almeno 0.356 volte l'altezza del blocco. È un vincolo in pixel, non
     in percentuale di finestra — l'altezza del contenuto quasi non cambia da un
     telefono all'altro, mentre la finestra cambia del trenta per cento — e
     legarlo alla finestra è esattamente l'errore che si è provato prima:
     rimettere il `min-height: 100svh` dava 4.18:1 su un 440x956 ma 1.52:1 su un
     375x667, dove il contenuto riempie la finestra da solo e non avanza niente.

     Da qui una misura larga e quasi fissa. A 440 sono 352 pixel: il sommario
     chiude intorno al 60% dell'altezza, dove il bianco attenuato tiene sopra
     6:1, e i trecentocinquanta pixel che restano sono la discesa vera e propria
     — è lì dentro che il cielo arriva al chiaro, prima del raccordo. Il tasto
     resta sopra la piega su ogni telefono misurato.

     Il `vw` e non `svh`: la stessa ragione di sopra. Serve una misura che non
     collassi sugli schermi corti, ed è la larghezza a non collassare. */
  .page-product .page-hero--split { padding-bottom: clamp(18rem, 80vw, 24rem); }

  /* /compliance-intelligence */
  .standards__grid { margin-top: clamp(2.5rem, 7.5vw, 3.5rem); gap: 1.25rem; }
  .ci-jobs { gap: 1.25rem; }

  .ci-col + .ci-col { margin-top: clamp(1.75rem, 5.5vw, 2.5rem); }

  .ci-timeline { margin-bottom: clamp(1.75rem, 5.5vw, 3rem); }

  /* --- /get-started: il modulo prima di tutto ----------------------------
     Su due colonne il programma della call riempie il vuoto lasciato a
     sinistra dal form, che è alto il doppio del testo che gli sta accanto: non
     costa un pixel di altezza alla pagina. Incolonnato, quel calcolo si
     rovescia — le tre righe diventano centodieci pixel di altezza vera, messi
     esattamente fra chi ha appena letto di che si tratta e il campo che deve
     compilare. Il primo campo finiva sotto la piega.

     Fuori dal telefono resta dov'è: lì lo spazio glielo regala la griglia.
     `display: none` e non `visibility`: non è contenuto che serve al lettore
     di schermo più di quanto serva all'occhio — è la stessa promessa che il
     titolo e il sommario fanno già, in forma di elenco. */
  .start-check { display: none; }

  /* E il resto sale. L'apertura si teneva mezza schermata di respiro sopra il
     titolo (5svh su un telefono alto 956 fanno quarantotto pixel, più gli
     ottantaquattro della riserva): con il modulo subito sotto, quel respiro è
     spazio tolto ai campi. Resta la riserva della barra e poco più. */
  .page-start .page-hero--light.page-hero--start {
    padding-top: calc(var(--header-h) + 0.5rem);
  }

  /* La via di fuga chiude la colonna del testo e sta sopra il modulo: lo
     stacco pieno serviva a staccarla dall'elenco, che qui non c'è più. */
  .start-split__intro .start__contact { margin-top: 1.4rem; }

  /* /blog */
  .archive__group + .archive__group { margin-top: clamp(3rem, 9vw, 4.5rem); }

  /* articolo */
  .article__cover { margin-top: clamp(2rem, 6vw, 2.75rem); }
  .article__body { margin-top: clamp(2.25rem, 6.5vw, 3rem); }
}

/* ----------------------------------------------------------- responsive -- */

@media (max-width: 900px) {
  /* Il menu del Product Overview sta tutto nel suo blocco, più in alto: qui
     ne restava una seconda copia che rimetteva `flex: none` sulle voci e
     annullava la spartizione della riga. */
  .mock__body { grid-template-columns: 1fr; }
}

@media (max-width: 780px) {
  /* Aree toccabili: sotto i 44px il bersaglio è scomodo. Si allarga la zona
     sensibile senza spostare la grafica, con padding e margini compensati. */
  .logo {
    padding-block: 0.6rem;
    margin-block: -0.6rem;
  }

  .site-footer__privacy,
  .site-footer__legal a,
  .start__contact a,
  .overview__link {
    display: inline-block;
    padding-block: 0.7rem;
  }

  .site-footer__brand { gap: 0.25rem; }

  /* --- barra senza fondo --------------------------------------------------
     Da ferma la barra non è un oggetto: sono due segni appoggiati sulla
     pagina, il marchio e il tasto. Il fondo ce l'ha già il tasto — un disco
     pieno, opposto a quel che gli passa sotto — e il marchio prende
     l'inchiostro che il JS misura sotto la barra, quindi una fascia di vetro
     dietro non aggiungeva leggibilità: aggiungeva un rettangolo.

     Il vetro torna appena la pagina scorre (`.is-stuck`, oltre i novanta
     pixel): lì sotto passa contenuto vero, e il marchio senza un fondo che lo
     stacchi finirebbe sopra un titolo o una fotografia. La transizione fa il
     resto — la fascia entra, non compare.

     Nessun bordo sotto neanche da agganciata: una riga da bordo a bordo è
     esattamente il taglio di colore che si voleva togliere. */
  .site-header {
    --header-h: 84px;

    position: fixed;
    inset: 0 0 auto 0;
    /* Il token resta a 84 perché è la riserva che la pagina si tiene sotto la
       barra; la barra invece è più bassa, così fra il suo bordo e la prima
       riga resta dell'aria invece di un pareggio. */
    height: 64px;
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    transition: background-color 0.3s var(--ease);
  }

  body { --header-h: 84px; }

  .site-header__inner {
    --fg: var(--ink);
    --fg-muted: var(--ink-4);
    --accent: var(--brand-6);

    gap: 0.75rem;
    padding-block: 0;
    color: var(--fg);
    /* Il vetro sta sulla barra, non sul suo contenuto: qui si azzera quel che
       la variante a pillola metterebbe. */
    background: none;
    border-radius: 0;
    box-shadow: none;
  }

  /* Sopra le zone scure si inverte l'inchiostro, non il fondo: da ferma il
     fondo non c'è. Chi lo decide è il JS, che legge cosa passa davvero sotto
     la barra — un colore fisso sarebbe illeggibile sopra metà pagina. */
  .site-header.is-over-dark .site-header__inner {
    --fg: var(--steel-0);
    --fg-muted: var(--steel-2);
    --accent: var(--brand-2);
  }

  /* Agganciata, il vetro torna: è qui che sotto la barra passa contenuto e il
     marchio ha bisogno di qualcosa che lo stacchi. Ma resta una fascia a tutta
     larghezza, non la pillola sospesa del desktop — le regole di quella pesano
     fino a cinque classi contro l'unica di `.site-header`, e una media query
     non aggiunge specificità, quindi il selettore ripete `--pill` per
     pareggiare quel peso. */
  .site-header--pill.site-header--sticky.is-stuck {
    background: rgba(var(--surface-rgb), 0.7);
    backdrop-filter: saturate(180%) blur(18px);
    -webkit-backdrop-filter: saturate(180%) blur(18px);
    box-shadow: none;
  }

  /* La sfocatura va ridichiarata anche qui. La regola desktop che la spegne
     elenca `.is-stuck.is-over-dark` — quattro classi — mentre quella qui sopra
     ne ha tre: sulle pagine scure vinceva lei e la fascia agganciata restava
     una velatura piatta, senza vetro. */
  .site-header--pill.site-header--sticky.is-stuck.is-over-dark {
    background: rgba(var(--night-rgb), 0.62);
    backdrop-filter: saturate(180%) blur(18px);
    -webkit-backdrop-filter: saturate(180%) blur(18px);
    box-shadow: none;
  }

  /* Il contenitore torna la fascia di sempre: misura e margini della `.shell`,
     nessun vetro suo — quello sta sulla barra. */
  .site-header--pill.site-header--sticky.is-stuck .site-header__inner,
  .site-header--pill.site-header--sticky.is-stuck.is-over-dark .site-header__inner {
    max-width: var(--shell);
    padding: 0 clamp(1.25rem, 3vw, 2.25rem);
    background: none;
    border-radius: 0;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  /* Il tasto era 42px di lato con due barre da 18x1.5: su un vetro chiaro si
     leggeva appena. Qui è il bersaglio pieno da 44 e le barre crescono in
     larghezza e spessore, col fondo pieno che le stacca dal vetro. */
  .nav-toggle {
    display: inline-flex;
    width: 44px;
    height: 44px;
    border-radius: 999px;
    background: var(--neutral-11);
    color: var(--neutral-0);
  }

  .site-header.is-over-dark .nav-toggle {
    background: var(--neutral-0);
    color: var(--neutral-12);
  }

  .nav-toggle__bars { width: 20px; }

  .nav-toggle__bars span { height: 2px; }

  .nav-toggle__bars span + span { margin-top: 6px; }

  .nav-toggle[aria-expanded="true"] .nav-toggle__bars span:first-child {
    transform: translateY(4px) rotate(45deg);
  }

  .nav-toggle[aria-expanded="true"] .nav-toggle__bars span:last-child {
    transform: translateY(-4px) rotate(-45deg);
  }

  /* Il pannello scende dalla barra e ne continua la larghezza, chiudendosi in
     basso con due angoli tondi: appeso, non appoggiato. Pieno e non velato —
     a 0.92 il titolo dell'apertura si leggeva ancora attraverso, e un menu
     che lascia vedere quel che copre non si legge come uno strato ma come un
     difetto. Il vetro se lo può permettere la barra, che è alta sessanta
     pixel; un pannello che copre mezzo schermo, no. */
  .site-nav {
    position: fixed;
    top: 64px;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0.15rem;
    padding: 0.5rem clamp(1.25rem, 3vw, 2.25rem) 1.35rem;
    border-radius: 0 0 1.5rem 1.5rem;
    /* Pieno, non velato: a 0.92 il titolo dell'apertura si leggeva ancora
       attraverso il pannello, e un menu che lascia vedere quel che copre non
       si legge come uno strato ma come un difetto. La pillola può permettersi
       il vetro perché è alta sessanta pixel e sotto ci passa poco; un
       pannello che copre mezzo schermo, no. */
    background: var(--paper);
    backdrop-filter: saturate(180%) blur(18px);
    -webkit-backdrop-filter: saturate(180%) blur(18px);
    box-shadow:
      inset 0 0 0 1px rgba(var(--ink-rgb), 0.08),
      0 14px 40px rgba(var(--ink-rgb), 0.18);
    --fg: var(--ink);
    --fg-muted: var(--ink-4);
    --btn-bg: var(--neutral-11);
    --btn-bg-hover: var(--neutral-12);
    --btn-fg: var(--neutral-0);
    color: var(--fg);
    transform: translateY(-10px);
    opacity: 0;
    visibility: hidden;
    transition: transform 0.24s var(--ease), opacity 0.24s var(--ease), visibility 0.24s;
  }

  .site-nav.is-open { transform: translateY(0); opacity: 1; visibility: visible; }

  .site-nav__link {
    padding: 0.8rem 0;
    font-size: var(--text-md);
    border-bottom: 1px solid rgba(var(--ink-rgb), 0.08);
  }

  .site-nav__link:last-of-type { border-bottom: 0; }

  .site-nav__cta { margin-top: 0.85rem; }

  /* --- apertura: il portatile, poi il titolo e il resto -------------------
     Sul desktop il portatile sta a destra del claim, ancorato al testo. Sul
     telefono la colonna è tutta la pagina e «a destra» non esiste, quindi si
     impila — e va in testa, sopra il titolo, non in coda. Fra i 781 e i
     1199px la colonna di testo tiene una schermata intera e il portatile
     comincia dove quella finisce (vedi il blocco a 1199); sul telefono quella
     schermata era tutta testo e l'oggetto restava sotto la piega, dove non
     lo vedeva nessuno. Qui è la prima cosa che si vede, e il titolo gli sta
     subito sotto.

     Nel DOM resta fuori dalla `.shell` e dopo la colonna, perché sul desktop
     è l'elemento assoluto e spostarlo nel template costringerebbe a rifare
     l'ancoraggio. `.hero` è una colonna flex e il portatile, impilato, è un
     suo figlio in flusso: basta `order: -1` a metterlo davanti a
     `.hero__main`. La colonna di testo perde il `min-height` di una
     schermata che aveva sotto i 1200 — serviva a non far tagliare dalla
     piega un portatile messo in coda, e in testa non c'è più niente da
     tagliare — e torna alta quanto il suo contenuto; il `flex: 1 0 auto` che
     ha di suo le fa assorbire quel che avanza sugli schermi alti, e il
     centraggio lo distribuisce attorno al testo.

     `display: contents` scioglie la shell e promuove i quattro pezzi di
     testo a figli di `.hero__main`, dove `order` li tiene in fila. La
     `.shell` così sparisce, quindi i suoi margini laterali passano a
     `.hero__main`, e lo `z-index: 2` che teneva il testo sopra la grana va
     ridichiarato sui pezzi: erano proprietà della scatola che non c'è più. */
  .hero {
    /* La rampa è la stessa del blocco a 1199, e il titolo ci sta sotto da sé:
       comincia dopo la riserva della barra e dopo il portatile — la cima
       visibile a 100px, il pieno alto 0.4914 volte la larghezza, poi i 2.5rem
       di stacco: 277px su un 320, 313 su un 393, 444 su un 780 — e la discesa
       chiude al 70% della sua altezza, che qui vale da 300 a 460px: la prima
       riga cade sempre sul blu-950 piatto, misurato 18:1 dai 320 ai 780. Il
       termine in `svh` non morde più — serviva quando ad
       aprire la colonna era il titolo — e resta perché su uno schermo basso
       tiene la rampa dentro la prima schermata. Sulla discesa adesso siede il
       portatile: il cielo chiaro resta dietro la barra e l'oggetto sta sulla
       parte che scurisce, come sul desktop. */
    background-size: 100% clamp(300px, min(92vw, 46svh), 460px);
  }

  /* Il portatile apre la colonna. La misura è quella dell'impilato a 1199 —
     tutta la colonna meno i margini, al massimo 620px — con un terzo termine
     per gli schermi bassi e larghi (un telefono di traverso): la scatola è
     alta 0.6927 volte la larghezza, e 72svh la tiene sotto metà schermo. Sui
     telefoni dritti non morde mai: 613px su un 393x852 contro i 353 che
     entrano.

     I margini scontano le fasce trasparenti del PNG, misurate sull'alfa: il
     pieno comincia al 9.99% dell'altezza della scatola, cioè 0.0692 volte la
     larghezza, e finisce all'80.86%, con sotto il 19.14% di ombra — 0.1326
     volte la larghezza, la stessa fascia che il desktop sconta in `bottom`.
     Così è la cima *visibile* a stare a riserva-più-respiro dal bordo, dove
     prima stava la prima riga del titolo, ed è la base visibile a stare a
     2.5rem dal titolo — lo stesso stacco che separa gli altri pezzi del
     blocco — invece di lasciare sotto l'oggetto un vuoto d'ombra che cresce
     con la larghezza (47px su un 393, 82 su un 780). Il margine sotto è
     negativo: tira su il titolo dentro la fascia d'ombra, che a schermo non
     si vede e che il testo, dipinto dopo, copre. */
  .hero__mock {
    --mock-w: min(calc(100% - 2 * clamp(1.25rem, 3vw, 2.25rem)), 620px, 72svh);
    order: -1;
    width: var(--mock-w);
    margin-block:
      calc(var(--header-h) + 1rem - var(--mock-w) * 0.0692)
      calc(2.5rem - var(--mock-w) * 0.1326);
  }

  /* I margini laterali sono quelli della `.shell`, alla lettera: è la stessa
     misura, solo spostata di una scatola in su. Niente riserva in cima —
     adesso è del portatile — e il `min-height` di una schermata è azzerato
     per la ragione detta sopra. */
  .hero__main {
    --hero-gutter: clamp(1.25rem, 3vw, 2.25rem);
    min-height: 0;
    padding-top: 0;
    padding-bottom: clamp(2rem, 6vw, 3rem);
    padding-inline: var(--hero-gutter);
  }

  .hero__inner { display: contents; }

  .hero__headline    { order: 1; }
  .hero__standards   { order: 3; }
  .hero__description { order: 4; }
  .hero__actions     { order: 5; }

  /* Sopra la grana, che è `.hero::after` a z-index 1. Lo teneva `.hero__inner`
     con una riga sola; sciolta lei, va detto a ognuno. */
  .hero__headline,
  .hero__standards,
  .hero__description,
  .hero__actions {
    position: relative;
    z-index: 2;
  }

  .hero__standards { margin-bottom: 0; }

  /* --- aria fra gli elementi ----------------------------------------------
     Gli stacchi interni ai blocchi sono tarati sul desktop, dove una riga di
     testo è lunga il doppio e le righe di un paragrafo sono la metà: lo
     stesso valore che lì separa due elementi, qui, fra due paragrafi già
     fitti di righe, non si legge più come uno stacco. Cresce quel che sta
     dentro un blocco — non quel che sta fra due sezioni, che era troppo e
     s'è già stretto. */
  .hero__description { margin-top: 2.5rem; }
  .hero__actions { margin-top: 2.5rem; gap: 1rem; }
  .hero__standards { margin-top: 3rem; }

  .section__lead { margin-top: 1.4rem; }

  /* --- tasti ---------------------------------------------------------------
     Misure da desktop: 0.9rem di padding verticale su un corpo da 16 fanno
     cinquanta pixel di altezza, che su una colonna larga trecentocinquanta
     diventano un blocco. Qui scendono a quarantadue — la misura che usa
     vercel.com sul telefono, dove il bersaglio non è più il puntatore ma il
     pollice e a fare l'area è la larghezza, non lo spessore.

     E si allineano a tutta colonna invece di seguire ciascuno la propria
     etichetta: due tasti larghi uno il doppio dell'altro, incolonnati, non si
     leggono come una coppia. */
  .btn {
    /* L'interlinea del corpo — 1.6 — dentro un tasto è aria che non serve a
       nessuno: da sola valeva nove pixel dei cinquanta. Qui la riga torna
       alta quanto le lettere e l'altezza la decide il padding. */
    line-height: 1.15;
    padding: 0.6rem 1.05rem;
    font-size: var(--text-sm);
  }

  .btn--lg {
    padding: 0.72rem 1.25rem;
    font-size: var(--text-sm);
  }

  .hero__actions,
  .page-hero__actions {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.7rem;
  }

  /* La banda di chiusura, invece, tiene i suoi due tasti affiancati e larghi
     quanto la propria etichetta — come fa linear.com sul telefono. In cima
     alla pagina il tasto a tutta colonna è l'invito principale e vuole tutto
     il bersaglio che può avere; quaggiù, a pagina già letta, due blocchi
     pieni prima del piede sono un muro. Due pillole in fila pesano il giusto.

     `wrap` è la via d'uscita quando la coppia non ci sta — schermi da 320,
     etichette lunghe: si incolonnano invece di uscire dal viewport. E i tasti
     non si stringono (`flex: 0 0 auto`), altrimenti invece di andare a capo
     si schiaccerebbero entrambi sotto la misura del testo. */
  .cta-band__actions {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.7rem;
  }

  .cta-band__actions .btn { flex: 0 0 auto; }

  /* Un tasto singolo dentro un piede centrato: `inline-flex` lo lascia della
     misura del testo, e in colonna con gli altri stonava. */
  .section__foot .btn { display: flex; }

  .cards { gap: 1.5rem; }
  .team__grid { --team-gap: 1.5rem; }

  .bento__title { margin-bottom: 0.7rem; }
  .manifesto__title { margin-bottom: 0.9rem; }
  .split__title { margin-bottom: 1rem; }

  .roadmap__title { margin-bottom: 0.85rem; }

  /* --- niente parole orfane ------------------------------------------------
     A schermo stretto ogni titolo rischia l'ultima riga da una parola sola —
     «ISO» da solo sotto tre righe piene — e ogni paragrafo la sua vedova.
     `balance` distribuisce le parole fra le righe di un titolo, `pretty`
     impedisce che l'ultima riga di un paragrafo ne contenga una soltanto.
     Dove non sono supportati si torna al ritorno a capo di sempre: è una
     rifinitura, non una stampella. */
  .hero__headline,
  .page-hero__title,
  .page-hero__lead,
  .section__title,
  .cta-band__title,
  .split__title,
  .faq__h,
  .card__title,
  .archive__title { text-wrap: balance; }

  .hero__description,
  .section__lead,
  .page-hero__note,
  .split__body,
  .manifesto__item p,
  .standards__card p,
  .card__excerpt,
  .faq__a p,
  .team__role,
  .bento__body,
  .roadmap__body { text-wrap: pretty; }
  .site-footer__inner {
    flex-direction: column;
    /* `flex-end` incolonna il marchio contro il bordo destro mentre il blocco
       legale, allineato a sinistra qui sotto, resta dall'altra parte: due
       allineamenti opposti nello stesso footer. In riga quel valore serviva
       ad appoggiare il marchio sulla base del blocco più alto; in colonna non
       ha più niente da appoggiare. */
    align-items: flex-start;
    gap: 2.25rem;
  }

  .site-footer__legal { text-align: left; }

}

/* --------------------------------------------------------- design system -- */

/* Pagina di riferimento: mostra i token, non ne introduce di nuovi. Tutto
   quello che segue usa i semantici come qualsiasi altro componente — se un
   token cambia, questa pagina cambia con lui e resta veritiera. */

.ds-section + .ds-section { padding-top: 0; }

.ds-section code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: var(--text-xs);
  letter-spacing: -0.01em;
}

/* --- scale ---------------------------------------------------------------
   Una riga per passo invece di una griglia di quadrati: il nome del token e
   l'uso stanno accanto al colore, ed è quella coppia a servire davvero. */

.ds-scale {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
}

.ds-swatch {
  display: grid;
  grid-template-columns: 4.5rem 9.5rem 6rem 1fr;
  align-items: center;
  gap: 1.25rem;
  padding: 0.6rem 1.1rem;
}

.ds-swatch + .ds-swatch { border-top: 1px solid var(--line); }

.ds-swatch__chip {
  height: 2.75rem;
  border-radius: 8px;
  /* Filo interno: i passi chiari su superficie bianca sparirebbero. */
  box-shadow: inset 0 0 0 1px rgba(var(--ink-rgb), 0.14);
}

.ds-swatch__token { font-weight: 600; }
.ds-swatch__hex { color: var(--fg-muted); text-transform: uppercase; }
.ds-swatch__use { color: var(--fg-muted); font-size: var(--text-sm); }

@media (max-width: 780px) {
  .ds-swatch {
    grid-template-columns: 3rem 1fr;
    grid-template-areas:
      "chip token"
      "chip hex"
      "use  use";
    gap: 0.15rem 1rem;
  }
  .ds-swatch__chip  { grid-area: chip; height: 100%; min-height: 3.25rem; }
  .ds-swatch__token { grid-area: token; }
  .ds-swatch__hex   { grid-area: hex; }
  .ds-swatch__use   { grid-area: use; margin-top: 0.4rem; }
}

/* --- token semantici -----------------------------------------------------
   Chiaro e scuro affiancati: il punto della tabella è proprio la coppia, e
   separarli in due blocchi costringerebbe a saltare avanti e indietro. */

.ds-table {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
}

.ds-table__row {
  display: grid;
  grid-template-columns: 9.5rem 1fr 1fr 1.2fr;
  align-items: center;
  gap: 1.25rem;
  padding: 0.7rem 1.1rem;
  font-size: var(--text-sm);
  color: var(--fg-muted);
}

.ds-table__row + .ds-table__row { border-top: 1px solid var(--line); }

.ds-table__row--head {
  background: var(--surface-2);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-faint);
}

.ds-table__row > code { color: var(--fg); font-weight: 600; }

.ds-cell { display: flex; align-items: center; gap: 0.55rem; min-width: 0; }
.ds-cell code { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.ds-dot {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
}

/* I token traslucidi vanno letti sopra il fondo a cui appartengono: su un
   fondo qualsiasi un `rgba` al 12% non dice niente. */
.ds-dot--light { box-shadow: inset 0 0 0 1px rgba(var(--ink-rgb), 0.18), 0 0 0 3px var(--surface); }
.ds-dot--dark  { box-shadow: inset 0 0 0 1px rgba(var(--paper-rgb), 0.22), 0 0 0 3px var(--neutral-11); }

@media (max-width: 900px) {
  .ds-table { border: 0; border-radius: 0; background: none; }
  .ds-table__row--head { display: none; }
  .ds-table__row {
    grid-template-columns: 1fr;
    gap: 0.4rem;
    padding: 1.1rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
  }
  .ds-table__row + .ds-table__row { margin-top: 0.6rem; border-top: 1px solid var(--line); }
}

/* La griglia del set Lucide. Celle a larghezza fissa e non fluide: i nomi
   variano da `x` a `chart-no-axes-column` e una colonna elastica li farebbe
   ballare. Il nome sta sotto perché è lui che si copia nel template. */
.ds-icons {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: 1px;
  list-style: none;
  padding: 0;
  margin: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--line);
}

.ds-icon {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  padding: 1.4rem 0.6rem;
  background: var(--surface);
  color: var(--fg);
  text-align: center;
}

.ds-icon__name {
  font-size: var(--text-xs);
  color: var(--fg-muted);
  word-break: break-word;
  line-height: 1.3;
}

/* Dither per le code scure. In `overlay` il rumore su un fondo quasi nero si
   moltiplica verso lo zero e sparisce, proprio dove servirebbe; `plus-lighter`
   somma, quindi lì continua a esistere. Serve sopra --grad-glow. */
.grain-lift {
  position: relative;
  isolation: isolate;
}

.grain-lift::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0.055;
  mix-blend-mode: plus-lighter;
  background-image: var(--grain);
  background-repeat: repeat;
  background-size: 200px 200px;
}

/* --- gradienti -----------------------------------------------------------
   Il tassello è alto: un gradiente su una striscia sottile non si giudica,
   e questi tre vivono su fondi a piena pagina. */

.ds-grads {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1.25rem;
}

.ds-grad {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: clamp(1.5rem, 3vw, 2.5rem);
  align-items: center;
}

.ds-grad__swatch {
  display: block;
  /* Alto: un gradiente su una striscia sottile non si giudica, e il foco dal
     basso ha bisogno di spazio verticale per mostrare la sua coda. */
  min-height: 21rem;
  border-radius: var(--radius-lg);
  box-shadow: inset 0 0 0 1px rgba(var(--ink-rgb), 0.12);
}

.ds-grad__name { font-size: var(--text-h4); letter-spacing: var(--tracking-h4); line-height: var(--leading-h4); margin-bottom: 0.45rem; }
.ds-grad__lead { color: var(--fg-muted); margin: 0.8rem 0 1rem; }

.ds-grad__css {
  display: block;
  padding: 0.8rem 0.95rem;
  border-radius: 10px;
  background: var(--surface-2);
  color: var(--fg-muted);
  font-size: var(--text-xs);
  line-height: 1.55;
  /* Il valore va copiato per intero: si manda a capo invece di troncare. */
  overflow-wrap: anywhere;
}

@media (max-width: 780px) {
  .ds-grad { grid-template-columns: 1fr; }
  .ds-grad__swatch { min-height: 11rem; }


  .page-hero__actions {
      display: flex;
      flex-direction: column;
      align-items: stretch;
      gap: 0.7rem;
    }
}

/* ============================================================== moto — 2 ==
   Tre aggiunte che non portano un byte di JavaScript: la transizione fra una
   pagina e l'altra, lo svelamento delle foto agganciato alle entrate che già
   ci sono, e il filo di avanzamento sull'articolo.
   ========================================================================= */

/* -------------------------------------------------- transizioni di pagina -- */

/* Fra una pagina e l'altra il browser interpola due istantanee invece di
   ridipingere da bianco. È nativo: nessuno script, nessun router, nessun peso
   aggiunto. Dove non c'è supporto la regola viene semplicemente ignorata e la
   navigazione resta quella di oggi — non c'è nulla da cui degradare. */
@view-transition { navigation: auto; }

/* La barra non prende un nome proprio, ed è voluto. Un nome serve a chi resta
   fermo mentre la pagina cambia — cioè a un elemento fisso. Questa è in
   `position: absolute` in cima al documento e scorre via col resto: nominarla
   la farebbe interpolare fra la posizione che aveva a pagina scorsa (fuori
   campo, sopra) e quella nuova, e si vedrebbe calare dall'alto a ogni
   navigazione. Dentro l'istantanea della pagina si comporta come si deve. */

/* Il corpo della pagina. La vecchia sfuma prima che la nuova finisca di
   salire: se si sovrapponessero a piena opacità le due istantanee si
   sommerebbero e al centro dell'incrocio l'immagine schiarirebbe. */
::view-transition-old(root) {
  animation: vt-out 0.2s cubic-bezier(0.4, 0, 1, 1) both;
}

::view-transition-new(root) {
  animation: vt-in 0.36s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes vt-out { to { opacity: 0; } }

/* Salita corta: 8px bastano a dare una direzione. Di più scoprirebbe una
   striscia vuota sotto il bordo basso dell'istantanea. */
@keyframes vt-in {
  from { opacity: 0; transform: translateY(8px); }
}

/* Il morphing di default dura 0.25s: su una foto che attraversa mezzo schermo
   si legge come uno scatto. */
::view-transition-group(*) { animation-duration: 0.42s; }

/* La copertina viaggia dalla scheda all'articolo, e le due inquadrature non
   hanno la stessa proporzione: 16/10 nella scheda, 16/9 nell'articolo. Le
   istantanee, di loro, vengono stirate sul riquadro del gruppo — quindi per
   mezzo secondo la foto si schiaccia e poi si assesta. Ritagliate invece che
   stirate si comportano come si comporta una foto vera.

   `view-transition-class` per dirlo a tutte in una riga: i nomi sono uno per
   articolo e in un foglio di stile non si possono elencare. Dove la classe
   non è supportata la regola cade e resta il comportamento di prima. */
.card__media img,
.article__cover img { view-transition-class: foto; }

::view-transition-old(.foto),
::view-transition-new(.foto) {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Chi ha chiesto meno moto naviga come prima: la pagina cambia e basta. */
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

/* --------------------------------------------- svelamento delle immagini -- */

/* La foto non compare: si scopre. Una tendina sale dal basso mentre
   l'immagine rientra dal 6% di ingrandimento — è la contro-scala a farla
   leggere come un'inquadratura che si assesta invece che come un ritaglio.
   Sale nella stessa direzione della scheda che entra, così le due cose si
   sommano in un gesto solo.

   Il ritaglio sta sulla CORNICE e non sull'immagine, e questa è la
   correzione: prima stava sull'immagine, e la cornice — superficie, raggio,
   filo di contorno, sfumature — restava disegnata dal primo fotogramma
   mentre la foto arrivava dentro un riquadro già pronto. Si leggeva come
   un'immagine che non ha ancora caricato, cioè come un guasto, non come
   un'entrata. Ritagliando la cornice non c'è prima niente e poi compaiono
   insieme superficie, foto e filo.

   `inset()` ritaglia sul rettangolo di bordo e ignora il raggio: va ripetuto
   dentro la forma o a fine corsa gli angoli tornano squadrati.

   È un'animazione e non una transizione, e nemmeno questo è un dettaglio.
   `backwards` tiene il primo fotogramma durante il ritardo ma non l'ultimo:
   a corsa finita l'elemento torna al suo stile di base e il ritaglio sparisce
   del tutto. Serviva a .showcase__shot, che portava tre ombre fuori dal bordo —
   un `clip-path` permanente gliele taglierebbe via. Durante la corsa l'ombra
   cresce insieme alla forma, che è come dev'essere.

   Lo stato di partenza non serve dichiararlo: finché manca `.is-in` il
   contenitore sta dentro un `[data-enter]` a opacità zero, quindi non c'è
   niente da nascondere e niente che possa lampeggiare. */
.js-motion [data-enter] .card__media   { --reveal-round: var(--radius-lg); }
.js-motion [data-enter].team__card     { --reveal-round: var(--radius-lg); }
.js-motion [data-enter].feature__stage { --reveal-round: 0; }

.js-motion [data-enter].is-in .card__media,
.js-motion [data-enter].is-in.team__card,
.js-motion [data-enter].is-in.feature__stage {
  /* Più lunga dell'entrata del testo che l'accompagna: una superficie grande
     che si assesta alla stessa velocità di una riga sembra uno scatto. Il
     ritardo è quello già calcolato per la scheda, ereditato per cascata. */
  animation: frame-reveal 0.85s cubic-bezier(0.16, 1, 0.3, 1)
             var(--enter-delay, 0ms) backwards;
}

@keyframes frame-reveal {
  from { clip-path: inset(100% 0 0 0 round var(--reveal-round, 0)); }
  to   { clip-path: inset(0 0 0 0 round var(--reveal-round, 0)); }
}

/* La contro-scala sta sull'immagine, che è l'unica cosa che deve muoversi
   dentro la cornice ferma. Si anima `scale` e non `transform`: sono proprietà
   distinte e si compongono, quindi l'ingrandimento al passaggio del puntatore
   — che sta su `transform` — continua a funzionare senza sovrascriverla. */
.js-motion [data-enter].is-in .card__media img,
.js-motion [data-enter].is-in.feature__stage img {
  animation: frame-zoom 1s cubic-bezier(0.16, 1, 0.3, 1)
             var(--enter-delay, 0ms) backwards;
}

@keyframes frame-zoom {
  from { scale: 1.06; }
  to   { scale: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .js-motion [data-enter].is-in .card__media,
  .js-motion [data-enter].is-in.team__card,
  .js-motion [data-enter].is-in.feature__stage,
  .js-motion [data-enter].is-in .card__media img,
  .js-motion [data-enter].is-in.feature__stage img {
    animation: none;
  }
}

/* ------------------------------------------------- avanzamento di lettura -- */

/* Un filo in cima che si riempie mentre si scende. La misura la dà il
   browser: `scroll()` lega l'animazione alla posizione della pagina invece
   che al tempo, e il conto resta sul compositore — nessun listener, nessuna
   lettura di layout, niente sul filo principale.
   Tutto sta dentro `@supports` per un motivo preciso: senza timeline,
   un'animazione dichiarata senza durata vale zero secondi e con `both`
   scatterebbe subito all'ultimo fotogramma, cioè mostrerebbe una barra piena
   e ferma — una lettura sempre completata. Meglio nessuna barra. */
.reading-progress { display: none; }

@supports (animation-timeline: scroll()) {
  .reading-progress {
    display: block;
    position: fixed;
    inset: 0 0 auto 0;
    height: 2px;
    /* Sopra la pillola dell'header, che sta a 100. */
    z-index: 110;
    background: linear-gradient(90deg, var(--steel-6), var(--teal-500));
    transform-origin: 0 50%;
    transform: scaleX(0);
    animation: reading-progress linear both;
    animation-timeline: scroll(root block);
  }

  @keyframes reading-progress {
    to { transform: scaleX(1); }
  }
}

@media (prefers-reduced-motion: reduce) {
  .reading-progress { display: none; }
}

/* ================================================ apertura del prodotto ==
   L'apertura non si muoveva affatto: titolo, sommario, tasto e portatile
   comparivano già finiti. Qui entrano in sequenza, e sullo schermo passa un
   riflesso. Tutto in CSS, all'avvio: non c'è osservatore da far scattare né
   soglia da attendere, perché questa roba sta sopra la piega e si guarda una
   volta sola. Nessuna riga di JavaScript.
   ========================================================================= */

.page-hero__mock img {
  display: block;
  width: 100%;
  height: auto;
}

/* ---------------------------------------------------- entrata in sequenza -- */

/* L'ordine è quello della lettura — titolo, sommario, tasto — col portatile
   che parte quasi insieme al titolo perché è grande e lento: aspettare il suo
   turno lo farebbe arrivare buon ultimo, a scena già ferma.
   `both` tiene il primo fotogramma prima del ritardo e l'ultimo dopo: senza,
   il testo lampeggerebbe a piena opacità nell'attesa. */
.page-hero--dark .page-hero__title--display {
  animation: hero-rise 0.9s cubic-bezier(0.16, 1, 0.3, 1) 0.05s both;
}

.page-hero--dark .page-hero__note {
  animation: hero-rise-sm 0.7s cubic-bezier(0.16, 1, 0.3, 1) 0.22s both;
}

.page-hero--dark .page-hero__actions {
  animation: hero-rise-sm 0.7s cubic-bezier(0.16, 1, 0.3, 1) 0.34s both;
}

/* La sfocatura è quel che distingue un'entrata di adesso da una di dieci anni
   fa: il titolo non scivola soltanto, si mette a fuoco. Costa un filtro su
   un'area grande, e per questo si paga una volta sola all'avvio e mai in un
   ciclo o legata allo scorrimento. */
@keyframes hero-rise {
  from { opacity: 0; transform: translateY(22px); filter: blur(11px); }
  to   { opacity: 1; transform: translateY(0);    filter: blur(0); }
}

@keyframes hero-rise-sm {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Il portatile arriva da destra e da sotto, come se scivolasse in campo dal
   fuori quadro verso la sua posizione. Si animano `translate` e `scale`, non
   `transform`: sono proprietà distinte e si compongono, così la deriva allo
   scorrimento più sotto — che sta su `transform` — non cancella l'entrata.
   Lo stesso portatile apre anche la home, e lì entra allo stesso modo: è lo
   stesso oggetto e deve muoversi uguale ovunque compaia. Il testo della home
   però resta fermo com'era. */
.page-hero--dark .page-hero__mock,
.hero__mock {
  animation: mock-in 1.15s cubic-bezier(0.16, 1, 0.3, 1) 0.1s both;
}

@keyframes mock-in {
  from { opacity: 0; translate: 54px 26px; scale: 1.035; }
  to   { opacity: 1; translate: 0 0;       scale: 1; }
}

/* ------------------------------------------------- riflesso sullo schermo -- */

/* Una lama di luce attraversa il display una volta, a portatile posato.
   Il ritaglio è la sagoma vera dello schermo, misurata sul file cercando i
   pixel chiari sopra la cerniera: senza, la lama uscirebbe sulla scocca e sul
   fondo, e si vedrebbe che è un rettangolo appoggiato sopra. */
/* Il ritaglio è la sagoma vera dello schermo, misurata sul file cercando i
   pixel chiari sopra la cerniera: senza, la lama uscirebbe sulla scocca e sul
   fondo, e si vedrebbe che è un rettangolo appoggiato sopra. */
.page-hero__glare,
.hero__glare {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

/* Sulla home il portatile è in prospettiva e lo schermo è un trapezio.
   L'inclinazione non è a occhio: i fianchi scendono spostandosi di 133px in
   orizzontale su 548 in verticale, cioè 13.6 gradi, e la lama porta la stessa,
   così corre parallela ai bordi invece di tagliarli. */
.hero__glare {
  clip-path: polygon(54.73% 19.83%, 94.93% 11.65%, 86.33% 72.57%, 45.87% 72.57%);
}

/* Su /product il portatile è di fronte e lo schermo è un rettangolo: la corsa
   di pixel bianchi più lunga lungo la colonna centrale, e la riga a metà di
   quella corsa — la base è chiara anche lei, ma la cerniera nera la separa.
   La lama tiene la stessa inclinazione della home: non ha bordi da seguire,
   ma un riflesso dritto in piedi sembrerebbe una scansione. */
.page-hero__glare {
  clip-path: inset(13.81% 12.77% 16.11% 15.27%);
}


.page-hero__glare::before,
.hero__glare::before {
  content: "";
  position: absolute;
  /* Sborda sopra e sotto: inclinata, una lama alta quanto il ritaglio
     lascerebbe scoperti i due angoli che l'inclinazione porta fuori. */
  top: -25%;
  bottom: -25%;
  left: 0;
  width: 20%;
  /* Trasparente ai bordi e non un bianco pieno: è un riflesso, e un riflesso
     non ha contorno. La sfocatura toglie l'ultimo spigolo. */
  background: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.42) 50%,
    rgba(255, 255, 255, 0) 100%
  );
  filter: blur(7px);
  animation: glare-sweep 1.25s cubic-bezier(0.36, 0, 0.28, 1) 0.95s both;
}

/* Il percorso è in multipli della propria larghezza: la lama è il 20% del
   portatile, quindi per attraversarlo interamente — da tutta fuori a sinistra
   a tutta fuori a destra — le servono (100 + 20 + 20) / 20 = 700 punti. */
@keyframes glare-sweep {
  from { transform: translateX(-120%) skewX(-13.6deg); opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  to   { transform: translateX(580%) skewX(-13.6deg); opacity: 0; }
}

/* ------------------------------------------------- deriva allo scorrimento -- */

/* Lasciando l'apertura il portatile sale un po' più in fretta della pagina:
   è la parallasse, e basta a dare profondità a un blocco che altrimenti
   scorre tutto d'un pezzo. Il conto lo fa il browser sul compositore, legato
   alla posizione della pagina invece che al tempo — nessun listener.
   `exit` copre l'uscita dal bordo alto: finché il portatile è fermo in campo
   il progresso è zero e la trasformazione non esiste, quindi non tocca
   l'entrata. */
@supports (animation-timeline: view()) {
  .page-hero--dark .page-hero__mock,
  .hero__mock {
    animation:
      mock-in 1.15s cubic-bezier(0.16, 1, 0.3, 1) 0.1s both,
      mock-drift linear both;
    animation-timeline: auto, view();
    animation-range: normal, exit 0% exit 100%;
  }

  @keyframes mock-drift {
    to { transform: translateY(-56px); }
  }
}

@media (prefers-reduced-motion: reduce) {
  .page-hero--dark .page-hero__title--display,
  .page-hero--dark .page-hero__note,
  .page-hero--dark .page-hero__actions,
  .page-hero--dark .page-hero__mock,
  .hero__mock,
  .page-hero__glare::before,
  .hero__glare::before {
    animation: none;
  }

  /* Il riflesso non ha uno stato di riposo utile: senza animazione resterebbe
     una banda chiara ferma in mezzo allo schermo. */
  .page-hero__glare,
  .hero__glare { display: none; }
}

/* --- archivio della palette ---------------------------------------------
   Le tavole ritirate. Stanno in fondo alla pagina e si distinguono per essere
   spente, non per essere piccole: i colori vanno visti alla stessa misura
   degli altri, altrimenti l'archivio non serve a confrontare. A separarli è
   la fascia più scura e il filo sopra, non una scala ridotta. */
.ds-section--archive {
  margin-top: clamp(3rem, 6vw, 5rem);
  padding-top: clamp(3rem, 6vw, 5rem);
  border-top: 1px solid var(--line);
}

.ds-archive + .ds-archive {
  margin-top: clamp(2.5rem, 5vw, 4rem);
  padding-top: clamp(2.5rem, 5vw, 4rem);
  border-top: 1px solid var(--line);
}

.ds-archive__head { margin-bottom: 1.6rem; }

.ds-archive__name {
  font-family: var(--font-display);
  font-size: var(--text-h4);
  letter-spacing: var(--tracking-h4);
  line-height: var(--leading-h4);
  margin: 0;
}

.ds-archive__date {
  color: var(--fg-faint);
  font-size: var(--text-xs);
  margin-top: 0.2rem;
}

/* Il motivo è la parte utile della scheda: prende la misura di lettura e non
   la larghezza della colonna, che qui è quella larga delle campiture. */
.ds-archive__reason {
  color: var(--fg-muted);
  max-width: 62ch;
  margin-top: 0.75rem;
}

/* Le campiture ritirate su griglia invece che in colonna: sono tredici, e in
   colonna l'archivio diventava più lungo della palette in uso. */
.ds-swatches--archive {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 0.6rem 1.4rem;
  list-style: none;
  margin: 0 0 2rem;
  padding: 0;
}

.ds-swatches--archive .ds-swatch { border: 0; padding: 0.35rem 0; }
.ds-swatches--archive .ds-swatch + .ds-swatch { border-top: 0; }

.ds-grads--archive .ds-grad__swatch { min-height: 130px; }

/* --------------------------------------------------- stati: la sezione -- */

/* Le tre rampe di stato e i componenti che le usano, una volta sul chiaro e
   una dentro `.on-dark`. Stanno qui e non in cima con la blu perché il loro
   punto è la coppia di temi: sono le uniche rampe del sito a dover reggere
   due fondi con gradini diversi, ed è questa pagina a dire quali.

   I rapporti sotto le campiture li misura public/js/main.js sul fondo reale:
   lo stesso gradino segna AA in un blocco e non nell'altro, ed è quel
   confronto la ragione della sezione. */

.ds-section--cmp {
  margin-top: clamp(3rem, 6vw, 5rem);
  padding-top: clamp(3rem, 6vw, 5rem);
  border-top: 1px solid var(--line);
}

/* Un blocco per tema. Quello scuro porta `.on-dark` e nient'altro: i token si
   ribaltano da soli, che è precisamente la proprietà da verificare. */
.ds-cmp__ctx {
  padding: clamp(1.25rem, 2.5vw, 2rem);
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
}

.ds-cmp__ctx + .ds-cmp__ctx { margin-top: 1.25rem; }

.ds-cmp__ctx-name {
  font-family: var(--font-display);
  font-size: var(--text-h5);
  letter-spacing: var(--tracking-h5);
  line-height: var(--leading-h5);
  margin: 0 0 0.3rem;
}

.ds-cmp__ctx-lead {
  color: var(--fg-muted);
  font-size: var(--text-sm);
  max-width: 72ch;
  margin: 0 0 1.4rem;
}

.ds-cmp__ramp + .ds-cmp__ramp { margin-top: 1.5rem; }

.ds-cmp__ramp-head {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
}

.ds-cmp__ramp-name { font-weight: 600; font-size: var(--text-sm); }
.ds-cmp__ramp-count { color: var(--fg-faint); font-size: var(--text-xs); }

.ds-cmp__ramp-note {
  color: var(--fg-muted);
  font-size: var(--text-xs);
  max-width: 72ch;
  margin: 0 0 0.7rem;
}

.ds-cmp__strip {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
  gap: 0.35rem;
}

/* Ogni passo prende la stessa frazione della riga: le tre rampe hanno dieci
   gradini e restano incolonnate, e se un giorno ne avessero di più
   resterebbero allineate ai capi invece che al gradino. */
.ds-cmp__step { flex: 1 1 0; min-width: 0; }

.ds-cmp__chip {
  display: block;
  height: 3.25rem;
  border-radius: 7px;
  /* Il filo segue --line e non un grigio scritto a mano: dentro `.on-dark` si
     ribalta da solo, altrimenti i passi scuri sparirebbero sul fondo scuro
     come quelli chiari sparivano sul bianco. */
  box-shadow: inset 0 0 0 1px var(--line);
}

.ds-cmp__meta {
  display: block;
  margin-top: 0.35rem;
  font-size: 0.62rem;
  line-height: 1.45;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ds-cmp__token { color: var(--fg-muted); }
.ds-cmp__hex { color: var(--fg-faint); text-transform: uppercase; }

.ds-cmp__ratio {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}

/* Il segno di soglia è una parola, non un colore: un pallino verde direbbe
   «passa» solo a chi lo vede, e questa è una pagina che parla di contrasto. */
.ds-cmp__aa {
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 0.05rem 0.22rem;
  border-radius: 3px;
  border: 1px solid currentColor;
}

.ds-cmp__step.is-pass .ds-cmp__ratio { color: var(--fg); font-weight: 600; }

@media (max-width: 900px) {
  /* Sotto questa larghezza dieci colonne danno 50px a testa e le etichette
     spariscono. Le rampe smettono di incolonnarsi — è il prezzo — ma una riga
     illeggibile non si confronta comunque con niente. */
  .ds-cmp__strip {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
    gap: 0.5rem;
  }
}

/* --- i componenti di stato -------------------------------------------------
   Le caselle e l'avviso di modulo, dipinti coi token del sito e basta: sono
   gli stessi che si scrivono nei componenti veri, e dentro il blocco scuro
   si ribaltano senza una regola in più. Un colore scritto qui a mano
   confronterebbe questa pagina con se stessa. */

.ds-demo {
  margin-top: 1.75rem;
  padding-top: 1.5rem;
  border-top: 1px dashed var(--line);
  display: grid;
  gap: 1rem;
}

.ds-demo__label {
  display: block;
  font-size: var(--text-2xs);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--fg-faint);
}

.ds-demo__badges { display: flex; gap: 0.45rem; flex-wrap: wrap; }

.ds-demo__badge {
  display: inline-flex;
  align-items: center;
  padding: 0.25rem 0.6rem;
  border-radius: 999px;
  font-size: var(--text-2xs);
  font-weight: 600;
  background: var(--badge-sutil);
  color: var(--badge-testo);
  border: 1px solid var(--badge-tinta);
}

/* Per famiglia: fondo tenue, testo, filo. Il neutro non è uno stato e prende i
   token generici del sito — sta in fila per far vedere quanto pesa un tinta
   di stato accanto a una casella che non ne ha. */
.ds-demo__badge--ok     { --badge-sutil: var(--ok-sutil);    --badge-testo: var(--ok-texto);    --badge-tinta: var(--ok); }
.ds-demo__badge--warn   { --badge-sutil: var(--warn-sutil);  --badge-testo: var(--warn-texto);  --badge-tinta: var(--warn); }
.ds-demo__badge--error  { --badge-sutil: var(--error-sutil); --badge-testo: var(--error-texto); --badge-tinta: var(--error); }
.ds-demo__badge--neutro { --badge-sutil: var(--surface-2);   --badge-testo: var(--fg-muted);    --badge-tinta: var(--line); }

/* L'avviso è il componente vero, con la sua classe: qui perde solo il margine
   sotto, che serve al modulo e non a una fila di campioni. */
.ds-demo .notice { margin-bottom: 0; max-width: 62ch; }

/* --- le regole d'uso ------------------------------------------------------
   Stanno in pagina e non solo nel file dei dati perché una rampa senza le sue
   regole d'uso è un elenco di colori: il vincolo che dice di non accoppiare i
   gradini per indice vale più della tavola stessa. */
.ds-cmp__rules {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.1rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.ds-cmp__rule {
  padding: 1.1rem 1.2rem;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--surface);
}

.ds-cmp__rule-title {
  font-family: var(--font-display);
  font-size: var(--text-h6);
  letter-spacing: var(--tracking-h6);
  line-height: var(--leading-h6);
  margin: 0 0 0.45rem;
}

.ds-cmp__rule-body {
  color: var(--fg-muted);
  font-size: var(--text-sm);
  margin: 0;
}

.ds-cmp__part { margin-top: clamp(2.5rem, 5vw, 4rem); }

.ds-cmp__part-title {
  font-family: var(--font-display);
  font-size: var(--text-h4);
  letter-spacing: var(--tracking-h4);
  line-height: var(--leading-h4);
  margin: 0 0 0.4rem;
}

.ds-cmp__part-lead {
  color: var(--fg-muted);
  max-width: 72ch;
  margin: 0 0 1.5rem;
}

.ds-cmp__notes {
  margin: 1.4rem 0 0;
  padding: 0;
  list-style: none;
  color: var(--fg-muted);
  font-size: var(--text-xs);
  max-width: 78ch;
}

.ds-cmp__notes li + li { margin-top: 0.5rem; }

/* La capa semantica di una tavola archiviata: stessa tabella dei semantici in
   uso, sotto le campiture e sopra i gradienti, se ce ne sono. */
.ds-table--archive { margin-bottom: 2rem; }

/* ------------------------------------------------ tipografia (DS) -- */

/* La tavola dei gradini: campione a sinistra, token e uso a destra, sulla
   stessa linea di base. Il campione non ha un corpo suo: lo prende dal token
   in uno stile inline scritto in data/design-system.js. */
.ds-type {
  border-top: 1px solid var(--line);
}

.ds-type__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 18rem;
  gap: 2rem;
  align-items: baseline;
  padding: 1.25rem 0;
  border-bottom: 1px solid var(--line);
}

.ds-type__sample {
  margin: 0;
  overflow-wrap: anywhere;
  text-wrap: balance;
}

.ds-type__sample--display {
  font-family: var(--font-display);
  font-weight: 600;
}

.ds-type__sample--text { font-family: var(--font-text); }

.ds-type__meta {
  display: grid;
  gap: 0.25rem;
  font-size: var(--text-xs);
  color: var(--fg-muted);
}

.ds-type__size { font-variant-numeric: tabular-nums; color: var(--fg); }

@media (max-width: 780px) {
  .ds-type__row { grid-template-columns: 1fr; gap: 0.5rem; }
}
