/* =============================================================================
   Modulo: l'indice dei progetti, in griglia o in tabella.

   Nodi di design 513:860 (griglia) e 890:3122 (tabella) — due viste della stessa
   sezione. Una sezione ferma: nessun pin, nessun progresso, nessun `data-scene`.
   Il JavaScript del modulo fa due cose sole, e nessuna delle due è un'animazione:
   fa partire il filmato all'hover di una riga e decide se il suo riquadro va sopra
   o sotto.

   Le misure del design, per non doverle ricercare:

     testata     titolo display, 32 fra titolo e barra, 40 fra barra e risultati
     pastiglie   padding 8/12, angoli 16, distanziatore 4, etichetta a 10px
     vista       padding 6/12, angoli 16, icona 20x20
     griglia     card 838 e 594 alte 520, distanziatore 16, su una colonna da 1448
     card        angoli 24, padding interno 24, filetto sotto il titolo
     tabella     righe alte 56, padding 16, distanziatore fra colonne 16
     colonne     472 / 228 / 228 / 106 / 350
     riquadro    838x520, angoli 24, 40px sopra (o sotto) la riga
     paginazione cerchi da 56, distanziatore 8, 64 sopra
   ========================================================================== */

.projects-index {
    /* Metà stacco per lato: due moduli statici adiacenti fanno i 160 del design.
       Vedi `--space-section-half` in base.css.

       ⚠ Nel design questo modulo segue la hero, che è una scena e non mette padding
       in fondo — la sua sezione finisce al bordo del viewport — quindi lo stacco a
       schermo è 80 invece dei 128 che il frame dichiara. È lo stesso baratto di
       `flexible-content/claim-boxes`, che nella pagina di un servizio segue
       `heroes/hero-shrink-video` nella stessa posizione: un modulo non può contare
       sul vicino, e mezzo stacco per lato è la regola che tiene qualunque ordine. */
    padding-block: var(--space-section-half);

    /* La geometria della griglia. 838 + 16 + 594 fa esattamente i 1448 della colonna:
       **una coppia larga+stretta riempie la riga**, ed è quello che dà il ritmo. Le
       due frazioni sono 838 e 594 su 1432, cioè sulla larghezza al netto del
       distanziatore. Sono le stesse di `flexible-content/case-carousel`, perché nel
       design è la stessa coppia di card. */
    --card-gap: 1rem;
    --card-wide: 0.5852;
    --card-narrow: 0.4148;

    /* 520 su una colonna da 1512. Le due card hanno proporzioni diverse — 1,61 e
       1,14 — quindi è l'altezza a essere comune, non l'aspect-ratio.

       In `vw` e non in `vh` come nel carosello: là le card stanno in una scena alta
       una schermata, qui in una sezione che scorre, e legarne l'altezza a quella
       della finestra farebbe card diverse su due schermi della stessa larghezza. */
    --card-height: clamp(20rem, 34.39vw, 32.5rem);

    /* Il riquadro che compare all'hover di una riga: 838x520 dal nodo 890:2280,
       allineato al bordo destro della tabella, a 40px dalla riga. La larghezza è in
       percentuale della **riga**, che è larga quanto la tabella. */
    --preview-w: 57.87%;
    --preview-gap: 2.5rem;

    position: relative;
    color: var(--color-scene-text);
}

/* -----------------------------------------------------------------------------
   Testata: titolo, filtri, selettore di vista.
   -------------------------------------------------------------------------- */

.projects-index__head {
    display: grid;
    /* 32 fra titolo e barra (nodo 513:865). */
    gap: var(--space-l);
    /* 40 fra barra e risultati. Non è un passo della scala — che va da 32 a 48 — ed
       è lo stesso valore che `claim-boxes` e `cards-fullscreen` tengono fra titolo e
       contenuto. */
    margin-block-end: 2.5rem;
}

/* `title-upper-lg`: 72px su 64 di interlinea, peso 900, maiuscolo.

   ⚠ **Bianco pieno, non `.display-gradient`.** È un'eccezione alla regola di
   base.css («ce l'hanno tutte tranne la headline della hero»), e non è dedotta:
   campionando il render del nodo 513:866 riga per riga l'inchiostro è
   `rgb(255,255,255)` a ogni altezza, mentre lo stesso campionamento sul titolo del
   carosello della home (nodo 649:2384) dà una rampa da `rgb(188,210,237)` a
   `rgb(244,248,252)`. Due misure sullo stesso banco.

   Niente `margin` qui, e non serve: il reset di base.css lo azzera già. */
.projects-index__intro {
    font-size: var(--text-display);
    line-height: var(--leading-display);
    font-weight: var(--weight-black);
    text-transform: uppercase;
    font-feature-settings: "lnum", "pnum";
}

/* Filtri a sinistra, selettore di vista a destra. `flex-wrap` perché su uno schermo
   stretto le pastiglie sono più larghe della riga: il selettore va sotto invece di
   schiacciarle. */
.projects-index__bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-m);
}

.projects-index__filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    /* 32 fra l'etichetta e le pastiglie (nodo 513:868). */
    gap: var(--space-l);
}

/* `label-sm` a 10px: 10 su 16, peso 600, tracking 8%, maiuscolo. */
.projects-index__filter-label {
    font-size: var(--text-label-s);
    line-height: var(--leading-label-s);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-label-s);
    text-transform: uppercase;
    white-space: nowrap;
}

.projects-index__pills {
    display: flex;
    flex-wrap: wrap;
    /* 4 fra le pastiglie (nodo 513:870). */
    gap: var(--space-3xs);
}

/* ⚠ **`display: contents` sul contenitore del facet**, ed è la riga che tiene in
   piedi la fila. FacetWP avvolge le proprie caselle in un `<div class="facetwp-facet
   …">`: senza questa dichiarazione le pastiglie sarebbero un blocco a sé accanto ad
   *All*, con il proprio `gap` inesistente, e andrebbero a capo tutte insieme.

   Con `display: contents` le caselle diventano figli diretti del flex, quindi *All* e
   i servizi stanno sulla stessa fila e vanno a capo insieme, uno per uno, come nel
   design. È la stessa cosa che `base.css` fa col `<nav>` dell'header per la stessa
   ragione: un contenitore che deve esserci nel markup e non nel layout. */
.projects-index__pills .facetwp-facet {
    display: contents;
    /* FacetWP mette un `margin-bottom` sul contenitore dei facet: con
       `display: contents` non ha effetto, ma azzerarlo è ciò che rende la riga sopra
       indipendente dal foglio di stile del plugin. */
    margin: 0;
}

/* La pastiglia, in due sapori che devono essere identici: *All* è un `<a>` del tema
   e le altre sono i `<div>` di FacetWP. Lo stesso blocco di dichiarazioni per
   entrambi i selettori, e non due regole che si somigliano.

   Dal design (nodi 513:871 e 513:873): padding 8/12, angoli 16, etichetta a 10px in
   maiuscolo spaziato dell'8%, peso 600. A riposo bordo bianco di un pixel e testo
   bianco; attiva fondo bianco e testo nero. */
.projects-index__pill,
.projects-index__pills .facetwp-checkbox {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin: 0;

    /* ⚠ **Altezza dichiarata, non dedotta da padding e interlinea.** Nel design la
       pastiglia è alta **32** con il bordo dentro: Figma disegna la traccia senza
       aggiungerla alla misura, mentre in CSS il bordo si somma. Lasciando che
       l'altezza venga da 8 + 16 + 8 vengono 34, misurati — ed è la stessa trappola
       già documentata per `.button--outline` (48 che diventavano 50) e per il footer.
       Con `box-sizing: border-box` — che il reset applica a tutto — 2rem sono 32
       tutto compreso, e il padding verticale non serve più: centra `align-items`. */
    height: 2rem;
    padding: 0 var(--space-xs);
    background-color: transparent;

    /* ⚠ **`background-image: none` non è ridondante.** Il foglio di stile di FacetWP
       disegna la casella di spunta come immagine di fondo — `background:
       url(checkbox.png)` più 20px di padding a sinistra — e `front.css` è accodato nel
       **footer**, quindi dopo il CSS di questo modulo: a pari specificità vince lui.
       `padding` e `margin` qui sopra hanno un selettore più specifico del suo e
       passano; il fondo no, perché la sua è la scorciatoia `background` e la nostra
       solo `background-color`. Senza questa riga la pastiglia ha una spunta grigia
       dentro.

       Non si toglie dequeueando `front.css`: quel foglio serve agli altri facet
       dell'installazione — la lista del magazine ne usa tre — e il tema non spegne il
       CSS dei plugin (CLAUDE.md → Vincoli). Si sovrascrive, ed è quello che fanno
       questa riga e le tre marcate allo stesso modo più sotto. */
    background-image: none;
    /* Il bordo è dichiarato bianco e non `currentColor`: il testo diventa nero
       quando la pastiglia è attiva, e con `currentColor` il bordo lo seguirebbe
       disegnando un contorno scuro attorno al riempimento bianco. È la stessa
       trappola documentata per `.button--outline` in base.css. */
    border: 1px solid var(--color-text-primary);
    border-radius: var(--radius-m);
    color: var(--color-text-primary);
    font-size: var(--text-label-s);
    line-height: var(--leading-label-s);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-label-s);
    text-transform: uppercase;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition:
        background-color var(--duration-fast) var(--ease-out),
        color var(--duration-fast) var(--ease-out);
}

/* L'hover **non** riempie di bianco: quello è lo stato attivo, e usarlo anche per
   l'hover renderebbe indistinguibili «ci sto passando sopra» e «è selezionata». Un
   velo appena accennato basta, e sul bianco al 16% non c'è il problema del grigio
   perché non è il bianco a diventare trasparente ma il fondo a schiarirsi. */
.projects-index__pill:not(.is-active):is(:hover, :focus-visible),
.projects-index__pills .facetwp-checkbox:not(.checked):hover {
    background-color: var(--color-text-faint);
}

.projects-index__pill.is-active,
.projects-index__pills .facetwp-checkbox.checked {
    background-color: var(--color-text-primary);

    /* Come sopra, e serve una seconda volta: FacetWP ha una regola a sé per lo stato
       spuntato (`.facetwp-checkbox.checked`, due classi) che rimette l'immagine.
       Questo selettore ne ha tre, quindi vince. */
    background-image: none;
    /* `#000` crudo e non un token: è il nero del componente di design, e un token per
       il nero non esiste — come per i veli dei moduli, che usano `#000` per la stessa
       ragione. */
    color: #000;
}

/* Una scelta che non porterebbe nessun risultato: FacetWP le mette `.disabled`.
   Resta leggibile ma non invita — non si nasconde, perché una pastiglia che sparisce
   e ricompare a ogni filtro fa saltare la riga a ogni click. */
.projects-index__pills .facetwp-checkbox.disabled {
    opacity: 0.4;
    cursor: default;
}

/* Il conteggio fra parentesi che FacetWP aggiunge a ogni scelta. Nel design non c'è:
   le pastiglie sono solo nomi. */
.projects-index__pills .facetwp-counter {
    display: none;
}

/* L'etichetta dentro la pastiglia. FacetWP le mette 5px di padding a destra per
   staccarla dal conteggio, che qui non c'è: senza azzerarlo il testo è fuori centro
   di due pixel e mezzo, ed è il genere di scarto che si vede su una fila di pastiglie
   e non si sa spiegare. */
.projects-index__pills .facetwp-display-value {
    padding: 0;
}

/* Il tratto in cui FacetWP sta ricaricando. La sua regola — `opacity: 0.6` su
   `.facetwp-facet.is-loading` — qui non può funzionare, perché quel contenitore è
   `display: contents` e quindi non ha una scatola da rendere trasparente. Si sposta
   sulla fila, che ce l'ha: così le pastiglie restano l'unica cosa che si attenua, e
   non si attenua *All*, che è un link e non partecipa al caricamento. */
.projects-index__pills:has(.facetwp-facet.is-loading) .facetwp-checkbox {
    opacity: 0.6;
    pointer-events: none;
}

/* Il selettore di vista: due pastiglie con un'icona invece di un'etichetta. Nel design
   il riquadro è alto 32 come le pastiglie del filtro, con l'icona da 20 dentro (nodo
   513:882) — quindi stessa altezza dichiarata, per la stessa ragione scritta sopra. */
.projects-index__views {
    display: flex;
    gap: var(--space-3xs);
}

.projects-index__view {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 2rem;
    padding: 0 var(--space-xs);
    background-color: transparent;
    border: 1px solid var(--color-text-primary);
    border-radius: var(--radius-m);
    color: var(--color-text-primary);
    text-decoration: none;
    transition:
        background-color var(--duration-fast) var(--ease-out),
        color var(--duration-fast) var(--ease-out);

    /* L'icona è inlinata e con `fill="currentColor"`, quindi prende il colore del
       link in tutti e due gli stati senza una seconda dichiarazione. `display: block`
       toglie lo spazio della riga di testo sotto un elemento inline. */
    & > svg {
        display: block;
        width: 1.25rem;
        height: 1.25rem;
    }

    &:not(.is-active):is(:hover, :focus-visible) {
        background-color: var(--color-text-faint);
    }
}

/* ⚠ **L'icona attiva non è nera, è del colore della pagina.** Nel design il tracciato
   dell'icona attiva è `#05355A`, che è il fondo pagina di default — mentre il testo
   della pastiglia attiva è nero. Non è un'incoerenza da appianare: l'icona si legge
   come ritagliata nella pastiglia, e usando il token invece dell'esadecimale segue il
   colore che l'editor ha scelto per la pagina. */
.projects-index__view.is-active {
    background-color: var(--color-text-primary);
    color: var(--color-page-bg);
}

/* Lo stato di attesa, mentre la richiesta di FacetWP è in volo. Lo mette il JS del
   modulo su `facetwp-refresh` e lo toglie su `facetwp-loaded`, quindi copre tutte e tre
   le strade: filtro, cambio pagina, cambio vista.

   Serve perché la richiesta non è istantanea — su questa installazione è circa un
   secondo, ed è il boot di WordPress con venticinque plugin, non il modulo — e un
   secondo senza nessun segno è un secondo in cui la pagina sembra rotta.

   Si attenua e si spengono i click: i risultati a schermo sono quelli di prima, e
   cliccarne uno mentre stanno per essere sostituiti porta a un progetto che non è
   quello che si crede di aver cliccato. La transizione è solo in entrata — all'uscita
   il contenuto è nuovo e una dissolvenza lo farebbe comparire in ritardo. */
.projects-index__results {
    transition: opacity var(--duration-base) var(--ease-out);
}

.projects-index.is-refreshing .projects-index__results {
    opacity: 0.4;
    pointer-events: none;
    transition-duration: var(--duration-fast);
}

/* Anche la paginazione: i numeri sono quelli di prima finché la risposta non arriva. */
.projects-index.is-refreshing .projects-index__pager {
    opacity: 0.4;
    pointer-events: none;
}

/* -----------------------------------------------------------------------------
   Vista in griglia.
   -------------------------------------------------------------------------- */

.projects-index__grid {
    display: flex;
    flex-wrap: wrap;
    gap: var(--card-gap);
    list-style: none;
    margin: 0;
    padding: 0;
}

/* ⚠ **Il ritmo è un ciclo di quattro, non di due.** Nel design le righe si
   ribaltano: larga+stretta, stretta+larga, larga+stretta, stretta+larga (nodi
   513:889, 513:892, 513:895, 516:1085, 516:2263). Quindi le card larghe sono la 1ª,
   4ª, 5ª, 8ª… — cioè `4n+1` e `4n` — e le strette la 2ª, 3ª, 6ª, 7ª.

   Le classi non le mette il PHP: la larghezza dipende dalla **posizione**, non dal
   contenuto, e un `nth-child` la ricalcola da sé quando un filtro cambia quante card
   ci sono. Con una classe scritta dal server, la prima card filtrata via
   sfaserebbe tutte le successive. */
.projects-index__item {
    flex: 0 0 auto;
    height: var(--card-height);
}

.projects-index__item:nth-child(4n + 1),
.projects-index__item:nth-child(4n) {
    width: calc((100% - var(--card-gap)) * var(--card-wide));
}

.projects-index__item:nth-child(4n + 2),
.projects-index__item:nth-child(4n + 3) {
    width: calc((100% - var(--card-gap)) * var(--card-narrow));
}

/* ⚠ **L'ultima card di una pagina dispari resta stretta e la riga rimane a metà.**
   È voluto: allargarla per riempire la riga vorrebbe dire una card di una terza
   larghezza che il design non ha. Con `per_page` pari — il default è 12 — non
   succede. */

/* La card è tutta un link. `display: grid` con la riga centrale che si allarga tiene
   le pastiglie in alto e il blocco di testo in basso, senza posizionamenti.

   È lo stesso componente della card del carosello (nel design, l'istanza `card
   progetto`), e il CSS è ripetuto invece di importato: ogni modulo possiede i propri
   asset, che è quel che rende possibile il caricamento condizionale, e un `@import`
   fra CSS di moduli è vietato (CLAUDE.md → *Nessun build step*). Se un domani questa
   card servisse a un terzo modulo, è quello il momento di portarla in un file
   condiviso — non prima. */
.projects-index__card {
    position: relative;
    display: grid;
    grid-template-rows: auto 1fr auto;
    height: 100%;
    overflow: hidden;
    border-radius: var(--radius-l);
    color: inherit;
    text-decoration: none;
    isolation: isolate;
}

.projects-index__media {
    position: absolute;
    inset: 0;
    z-index: -1;
    background-color: var(--color-scene-bg);
}

.projects-index__poster,
.projects-index__video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
}

/* Il filmato sta sopra il poster e resta trasparente finché non parte: così il
   passaggio è una dissolvenza e non uno sfarfallio, e senza JavaScript resta
   semplicemente il poster. */
.projects-index__video {
    opacity: 0;
    transition: opacity var(--duration-base) var(--ease-out);
}

.projects-index__card.is-playing .projects-index__video {
    opacity: 1;
}

/* La sfumatura del design: nera al 60% in basso, trasparente a metà card. Serve a
   tenere leggibili titolo e dati sopra qualunque immagine. */
.projects-index__veil {
    position: absolute;
    inset: 0;
    background-image: linear-gradient(
        to top,
        rgb(0 0 0 / 0.6) 11.25%,
        rgb(0 0 0 / 0) 50%
    );
}

.projects-index__tags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3xs);
    padding: var(--space-2xs);
}

/* `label-sm` a 10px su una pastiglia nera al 32% con angoli a 16px. L'80% di opacità
   è quello del design: sul tag, non sul suo fondo. */
.projects-index__tag {
    padding: var(--space-2xs) var(--space-xs);
    border-radius: var(--radius-m);
    background-color: rgb(0 0 0 / 0.32);
    font-size: var(--text-label-s);
    line-height: var(--leading-label-s);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-label-s);
    text-transform: uppercase;
    opacity: 0.8;
}

.projects-index__body {
    display: grid;
    grid-row: 3;
    gap: var(--space-2xs);
    padding: var(--space-m);
}

/* `title-upper-md`: 40 su 40, peso 900, maiuscolo, nessun tracking. Il filetto sotto
   è l'unico bordo del design: rgba(255,255,255,.2), con 4px di respiro sopra e
   sotto. */
.projects-index__title {
    padding-block: var(--space-3xs);
    border-block-end: 1px solid var(--color-border);
    font-size: var(--text-title);
    line-height: var(--leading-solid);
    font-weight: var(--weight-black);
    text-transform: uppercase;
    font-feature-settings: "lnum", "pnum";
}

/* Nel design i primi due campi stanno vicini a 32px e la data è spinta al bordo
   opposto. Con l'ultimo campo a `margin-inline-start: auto` viene esattamente così,
   senza un contenitore in più nel markup.

   `:not(:first-child)` serve al caso di un campo solo: senza, quell'unico campo
   verrebbe spinto a destra invece di restare a sinistra col titolo. */
.projects-index__meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-s) var(--space-l);
}

.projects-index__field {
    display: grid;

    &:last-child:not(:first-child) {
        margin-inline-start: auto;
        text-align: end;
    }
}

.projects-index__label {
    font-size: var(--text-label-s);
    line-height: var(--leading-label-s);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-label-s);
    text-transform: uppercase;
    opacity: 0.8;
}

/* `body-xs`: 16 su 24, peso 500, tracking 1%. */
.projects-index__value {
    font-size: var(--text-body-s);
    line-height: var(--leading-body-s);
    font-weight: var(--weight-medium);
    letter-spacing: var(--tracking-body-s);
    font-feature-settings: "lnum", "pnum";
}

/* -----------------------------------------------------------------------------
   Vista in tabella.
   -------------------------------------------------------------------------- */

/* Una `<table>` vera con `display` intatto, non una griglia di `<div>`.

   ⚠ **Cambiare il `display` di un elemento di tabella gli toglie il ruolo ARIA**: un
   `<table>` messo a `display: block` non è più annunciato come tabella, e le
   intestazioni di colonna smettono di essere lette accanto ai valori. Sarebbe da
   rimettere a mano con `role="table"`, `role="row"`, `role="cell"` su ogni elemento —
   cioè riscrivere in attributi ciò che il markup già dice. `table-layout: fixed` più
   una larghezza per colonna dà lo stesso controllo senza toccare il `display`. */
.projects-index__table {
    width: 100%;
    border-collapse: collapse;
    table-layout: fixed;
    text-align: start;
}

/* Le colonne, nelle proporzioni del nodo 754:2504: 472 / 228 / 228 / 106 / 350.

   ⚠ **Sono proporzioni e non le misure**, perché le misure del design non stanno
   nella riga: 472+228+228+106+350 fa 1384, più i quattro distanziatori da 16 fa 1448,
   e con i 16 di padding della riga si arriva a 1480 su una colonna larga 1448. In
   Figma le larghezze sono dichiarate e non calcolate, quindi la riga sborda di 32 e
   il designer non l'ha visto. Qui le cinque larghezze sono le stesse proporzioni
   normalizzate a 100, e padding e distanziatori vivono dentro: la riga sta nella
   colonna a qualunque larghezza, e i rapporti fra le colonne sono quelli. */
.projects-index__cell--project  { width: 34.1%; }
.projects-index__cell--client   { width: 16.47%; }
.projects-index__cell--where    { width: 16.47%; }
.projects-index__cell--date     { width: 7.66%; }
.projects-index__cell--services { width: 25.3%; }

/* Padding 16 in verticale e 8 per lato, così fra due celle ci sono i 16 del
   distanziatore; ai due estremi della riga il padding torna 16, che è quello che il
   design mette sulla riga. Un `gap` non esiste per le celle di una tabella, e questo
   è il modo di ottenerlo senza. */
.projects-index__cell {
    padding: var(--space-s) var(--space-2xs);
    text-align: start;
    vertical-align: middle;

    &:first-child {
        padding-inline-start: var(--space-s);
    }

    &:last-child {
        padding-inline-end: var(--space-s);
    }
}

/* L'intestazione: `label-sm` a 12px — 12 su 16, peso 600, tracking 12%, maiuscolo —
   e un filetto **bianco pieno** sotto, non il 20% di `--color-border`: nel design
   questa riga è dichiarata `border-white`, e separa l'intestazione dai dati, non due
   righe di dati. */
.projects-index__row--head .projects-index__cell {
    border-block-end: 1px solid var(--color-text-primary);
    font-size: var(--text-label);
    line-height: var(--leading-label);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    white-space: nowrap;
}

/* Le righe di dati: `body-xs` 16 su 24, peso 500, tracking 1%, e un filetto al 32%
   di bianco.

   `position: relative` sulla riga, e non sulla cella: è il blocco contenitore del
   riquadro dell'hover, che deve essere allineato al bordo **destro della tabella** e
   largo una frazione di essa. Con la cella come contenitore quei due valori si
   riferirebbero alla prima colonna, che è un terzo della tabella. */
.projects-index__table tbody .projects-index__row {
    position: relative;
    /* Tutta la riga è un link — vedi `.projects-index__link` più sotto — quindi il
       puntatore lo deve dire: senza, il velo dell'hover invita e la freccia del mouse
       risponde che non c'è niente da cliccare. */
    cursor: pointer;
    border-block-end: 1px solid rgb(255 255 255 / 0.32);
    font-size: var(--text-body-s);
    line-height: var(--leading-body-s);
    font-weight: var(--weight-medium);
    letter-spacing: var(--tracking-body-s);
    font-feature-settings: "lnum", "pnum";
}

/* Il nome del progetto è un `<th scope="row">`, quindi il browser lo metterebbe in
   grassetto e centrato: sono i due user-agent styles che vanno annullati. Il link
   dentro non si sottolinea, come tutti i link-blocco del tema. */
.projects-index__cell--project {
    font-weight: inherit;
}

/* ⚠ **Tutta la riga è il link, e il link resta uno.** Una `<tr>` non può essere un
   `<a>`, e le due strade sbagliate sono note: avvolgere ogni cella in un `<a>` dà
   cinque link identici per riga da attraversare col tab, e agganciare un `click` in
   JavaScript alla riga non è un link — niente tasto centrale, niente cmd-click, niente
   tastiera, niente da leggere per un motore di ricerca.

   La terza strada è questa: il link vero è uno — il nome del progetto, che è anche il
   suo nome accessibile, l'unico dei cinque valori che identifichi la riga — e uno
   pseudo-elemento in assoluto lo allarga a tutta la riga. Il blocco contenitore è la
   `<tr>`, che è già `position: relative` per il riquadro dell'hover, quindi `inset: 0`
   è esattamente la riga.

   Niente `z-index`: uno pseudo-elemento posizionato dipinge dopo il contenuto in
   flusso di **tutte** le celle, non solo di quella che lo contiene, quindi intercetta
   il puntatore su tutta la riga anche sulle colonne che nel DOM vengono dopo. E resta
   sotto il riquadro del filmato, che sta a `--z-overlay` — e che comunque non
   intercetta niente.

   Il prezzo, che è il prezzo di ogni link-blocco: **il testo delle altre celle non si
   può più selezionare**, perché il velo sta sopra. Su una riga che è tutta un
   collegamento è il baratto giusto; se un domani servisse copiare il nome di un
   cliente da qui, è questa la regola da togliere. */
.projects-index__link {
    color: inherit;
    text-decoration: none;

    &::after {
        content: "";
        position: absolute;
        inset: 0;
    }
}

/* I servizi di un progetto, separati da 14px — che è il distanziatore del nodo
   754:2575, e non un passo della scala. Vanno a capo se sono tre. */
.projects-index__services {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3xs) 0.875rem;
}

/* La riga sotto il puntatore. Dal nodo 895:5078: fondo al 16% del colore pagina, una
   sfocatura di 4px dietro, e il testo che si accende con quattro aloni bianchi. I
   quattro valori degli aloni sono quelli del nodo, non arrotondati: sommati fanno una
   luce che si allarga invece di un contorno.

   Tutto e tre sulla **riga**, non sulle celle, e le tre ragioni sono diverse:

     - `text-shadow` si eredita, quindi una dichiarazione sola raggiunge il link, i
       valori e i nomi dei servizi;
     - il fondo su una `<tr>` con `border-collapse: collapse` dipinge sotto tutte le
       celle in una volta, senza giunzioni;
     - ⚠ e la sfocatura **deve** stare qui. Su una cella funzionerebbe, ma
       `backdrop-filter` rende l'elemento un **blocco contenitore per i discendenti in
       assoluto** — come `filter`, e come `contain` — quindi la cella diventerebbe il
       riferimento del riquadro dell'hover che sta dentro di lei. Il riquadro è larga
       il 57,87% *della riga*: risolto sulla prima colonna, che è il 34,1% della
       tabella, viene 286px invece di 838, e allineato al bordo destro **della cella**
       invece che della tabella. Misurato: 286x177 invece di 838x520.

       È un difetto che compare **solo mentre si passa sopra**, perché è la regola
       dell'hover a introdurre il blocco contenitore: con la classe aggiunta a mano il
       riquadro era giusto. Sulla riga il problema non esiste — la riga è già
       `position: relative`, quindi già il blocco contenitore, e `backdrop-filter`
       non cambia niente. */
.projects-index__table tbody .projects-index__row:is(:hover, :focus-within) {
    background-color: color-mix(in srgb, var(--color-page-bg) 16%, transparent);
    backdrop-filter: blur(4px);
    text-shadow:
        0 0 14.767px rgb(255 255 255 / 0.2),
        0 0 29.533px rgb(255 255 255 / 0.05),
        0 0 103.366px rgb(255 255 255 / 0.1),
        0 0 206.732px rgb(255 255 255 / 0.3);
}

/* -----------------------------------------------------------------------------
   Il riquadro del filmato all'hover di una riga.

   Dal nodo 890:2280: 838x520, angoli a 24, allineato al bordo destro della riga, a
   40px da essa. Nel design sta **sopra** la riga; il JS lo sposta sotto quando sopra
   non c'è spazio, aggiungendo `is-below` — e in entrambi i casi la riga su cui si sta
   passando resta interamente leggibile, che è il requisito.

   `display: none` a riposo, e non `opacity: 0`: un browser non scarica un'immagine
   `loading="lazy"` che non ha una scatola, quindi i poster delle righe non partono
   all'apertura della pagina. Con `opacity` partirebbero tutti.

   ⚠ **Due selettori lo mostrano, e servono entrambi.** `:hover` è quello che lo fa
   funzionare **senza JavaScript**: il riquadro compare sopra la riga, che è la
   posizione del design, e il poster si scarica in quel momento. `.is-previewing` è
   quello che lo mette il JS, e serve perché il JS possa **misurarlo**: per decidere
   se va sopra o sotto deve conoscerne l'altezza, e un elemento a `display: none` non
   ne ha una. Con la classe il riquadro è nel layout prima della misura, e i valori —
   larghezza, rapporto, distanza — restano scritti **solo qui**, invece di essere
   ricopiati in tre costanti nel JS che poi possono divergere.
   -------------------------------------------------------------------------- */

.projects-index__preview {
    display: none;
}

/* Solo dove c'è un puntatore vero, e solo dalla soglia in su: sotto quella larghezza
   la tabella scorre in orizzontale (vedi il blocco stretto in fondo) e un riquadro in
   assoluto dentro un contenitore che scorre verrebbe ritagliato. Su touch l'hover non
   esiste e la riga resta un link.

   ⚠ Questa **non** è la soglia delle scene: qui non c'è nessuna scena e nessun pin,
   è la larghezza sotto la quale la tabella cambia forma. Vale solo per questo modulo,
   e per questo non porta il commento `soglia scene`. */
@media (hover: hover) and (pointer: fine) and (min-width: 901px) {

    .projects-index__table tbody .projects-index__row:is(:hover, :focus-within, .is-previewing) .projects-index__preview {
        display: block;
        position: absolute;
        z-index: var(--z-overlay);
        inset-inline-end: 0;
        /* `calc(100% + gap)` sul bordo inferiore: il 100% è l'altezza della riga,
           quindi il fondo del riquadro finisce 40px sopra la cima della riga. Nel
           design è `top: -560` su una riga da 56 con un riquadro da 520, che è lo
           stesso punto detto in numeri assoluti. */
        inset-block-end: calc(100% + var(--preview-gap));
        width: var(--preview-w);
        aspect-ratio: 838 / 520;
        overflow: hidden;
        border-radius: var(--radius-l);
        background-color: var(--color-scene-bg);
        /* Non intercetta il puntatore: se lo facesse, il riquadro comparendo sopra
           un'altra riga ne rubberebbe l'hover, e le due righe si scambierebbero il
           riquadro a ripetizione. */
        pointer-events: none;
    }

    .projects-index__table tbody .projects-index__row.is-below:is(:hover, :focus-within, .is-previewing) .projects-index__preview {
        inset-block-end: auto;
        inset-block-start: calc(100% + var(--preview-gap));
    }

    .projects-index__preview-poster,
    .projects-index__preview-video {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        object-fit: cover;
        object-position: center;
    }

    .projects-index__preview-video {
        opacity: 0;
        transition: opacity var(--duration-base) var(--ease-out);
    }

    .projects-index__row.is-playing .projects-index__preview-video {
        opacity: 1;
    }
}

/* -----------------------------------------------------------------------------
   Paginazione.
   -------------------------------------------------------------------------- */

/* 64 fra i risultati e la paginazione (nodo 542:1808, `pt-[64px]`). Il 160 che quel
   frame ha sotto è lo stacco di fine sezione, e lo mette il padding del modulo. */
.projects-index__pager {
    margin-block-start: var(--space-2xl);
}

/* La fila di link che stampa FacetWP. Cerchi da 56 con 8 di distanziatore, etichetta
   `body-xs`; l'attivo ha il bordo bianco, gli altri niente (nodo 542:1809 contro
   542:1811). */
.projects-index__pager .facetwp-pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--space-2xs);
}

.projects-index__pager .facetwp-page {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3.5rem;
    height: 3.5rem;
    /* FacetWP mette `margin-right: 6px` fra le pagine: qui lo spazio lo fa il `gap`
       della fila, e sommarli darebbe 14 invece degli 8 del design. */
    margin: 0;
    border: 1px solid transparent;
    border-radius: var(--radius-full);
    color: var(--color-text-primary);
    font-size: var(--text-body-s);
    line-height: var(--leading-body-s);
    font-weight: var(--weight-medium);
    letter-spacing: var(--tracking-body-s);
    text-decoration: none;
    font-feature-settings: "lnum", "pnum";
    /* I link di FacetWP sono `<a>` senza `href` — è il suo JS a gestire il click —
       quindi il puntatore va dichiarato. */
    cursor: pointer;
    transition:
        border-color var(--duration-fast) var(--ease-out),
        background-color var(--duration-fast) var(--ease-out);

    /* Le due frecce: l'icona è inlinata da inc/facetwp.php al posto dell'etichetta,
       che resta come nome accessibile del link in un `.visually-hidden`. */
    & > svg {
        display: block;
        width: 1.5rem;
        height: 1.5rem;
    }

    &:is(:hover, :focus-visible) {
        background-color: var(--color-text-faint);
    }
}

/* La pagina corrente: nel design è un cerchio col bordo bianco, e il numero ha lo
   stesso peso degli altri (nodo 542:1809 contro 542:1811).

   `font-weight` va ridichiarato qui e non basta averlo sulla regola sopra: FacetWP
   mette `font-weight: bold` su `.facetwp-page.active`, che sono due classi come
   `.projects-index__pager .facetwp-page` — e a pari specificità vince il suo foglio,
   che è accodato dopo. Questo selettore ne ha tre. */
.projects-index__pager .facetwp-page.active {
    border-color: var(--color-text-primary);
    font-weight: var(--weight-medium);
}

/* I puntini non sono un link: né puntatore né hover. */
.projects-index__pager .facetwp-page.dots {
    cursor: default;
    pointer-events: none;
}

/* La freccia indietro è **la stessa icona specchiata**, non un secondo file: un
   chevron ribaltato sull'asse orizzontale *è* un chevron sinistro, quindi
   `assets/icons/chevron-right.svg` copre entrambi i versi e resta un asset solo da
   tenere allineato al design.

   `scale: -1 1` e non `rotate: 180deg`: la rotazione ribalta anche l'asse verticale,
   che su un chevron simmetrico non si vede — ma se un domani l'icona prendesse un
   dettaglio asimmetrico si vedrebbe, e lo specchio è quello che si intende. */
.projects-index__pager .facetwp-page.prev > svg {
    scale: -1 1;
}

/* -----------------------------------------------------------------------------
   Nessun risultato.
   -------------------------------------------------------------------------- */

/* `body-lg`: 24 su 40. Non è nel design — che non ha lo stato vuoto — ma un filtro
   che non trova niente deve dirlo, altrimenti la pagina sembra rotta. */
.projects-index__empty {
    padding-block: var(--space-2xl);
    font-size: var(--text-body-l);
    line-height: var(--leading-body-l);
    font-weight: var(--weight-medium);
    text-align: center;
    opacity: 0.7;
}

/* -----------------------------------------------------------------------------
   Layout stretto.

   Il design di questa larghezza non esiste, quindi qui si decide: la griglia va a
   una colonna e la tabella scorre in orizzontale dentro il proprio riquadro, invece
   di comprimere cinque colonne in 400px o di mandare la pagina a scorrere di lato.
   -------------------------------------------------------------------------- */

@media (max-width: 900px) {

    .projects-index__item,
    .projects-index__item:nth-child(4n + 1),
    .projects-index__item:nth-child(4n + 2),
    .projects-index__item:nth-child(4n + 3),
    .projects-index__item:nth-child(4n) {
        width: 100%;
        /* Il rapporto della card larga, che è quella che il design usa per prima: a
           una colonna un'altezza fissa su una card larga come lo schermo darebbe un
           riquadro sempre più schiacciato via che lo schermo si allarga. */
        height: auto;
        aspect-ratio: 838 / 520;
    }

    /* Il contenitore che scorre. `overflow-x: auto` **qui e non sulla pagina**: è la
       tabella a essere più larga della finestra, non il documento.

       `min-width` sulla tabella: senza, `table-layout: fixed` la comprimerebbe nella
       larghezza disponibile e le percentuali darebbero colonne da 40px. Con un minimo
       le proporzioni restano quelle e la tabella scorre. */
    .projects-index__results:has(.projects-index__table) {
        overflow-x: auto;
        /* Un po' di respiro sotto per la barra di scorrimento, così non copre il
           filetto dell'ultima riga. */
        padding-block-end: var(--space-2xs);
    }

    .projects-index__table {
        min-width: 45rem;
    }
}
