/*
    Asterisco sui campi obbligatori (DGWGEOWORK-9111, audit TxDOT §2.4 - WCAG 3.3.2 Labels or
    Instructions).

    IL FINDING: «Required fields are not clearly marked before submission. Users often discover
    missing fields only after clicking Continue.» E infatti, misurato prima del rimedio su un campo
    obbligatorio mai toccato della modale anagrafica: `aria-required` era **null** e `aria-invalid`
    era **"true"** - si diceva all'utente che il campo era *sbagliato* senza avergli mai detto che era
    *obbligatorio*.

    I TRE PEZZI, che vanno insieme e nessuno basta da solo (R8 di
    .claude/docs/frontend-accessibility.md):
      1. `aria-required="true"` sul campo -> direttiva
         App/common/directives/Accessibility/ariaRequiredFromNgRequired.js. NON ngAria: la sua
         opzione `ariaRequired` e' accesa ma si tira indietro proprio quando c'e' `ng-required`
         (Scripts/angular-aria.js:336), cioe' nel 100% dei nostri casi;
      2. l'asterisco visibile -> questo foglio;
      3. la frase «I campi contrassegnati con * sono obbligatori» -> nel form, chiave
         `RequiredFieldsInstruction`. L'asterisco da solo e' una convenzione, non una spiegazione.

    PERCHE' DUE GANCI, `aria-required` E `[required]` (DGWGEOWORK-9111, seconda passata).
    L'obbligatorieta' in GeoWorks si scrive in due modi e nessuno dei due copre l'altro:

      - CONDIZIONALE, `ng-required="vm.bigController.isRequired('X')"` - 449 occorrenze, la norma
        nei form di pratica. NON riflette l'attributo `required` nel DOM, quindi `[required]` non
        prende niente: qui serve `aria-required`, che la direttiva tiene aggiornato (l'asterisco
        appare e sparisce col cambiare della condizione).
      - STATICA, `<input required>` - 98 template (utenti, ruoli, unita' organizzative, dizionari,
        avvisi di portale, profilo). Qui `aria-required` NON c'e' e non deve esserci: la direttiva
        si tira indietro di proposito sui campi nativi, perche' il `required` statico e' gia' la
        semantica per le AT e duplicarlo sarebbe rumore. Il gancio e' l'attributo stesso.

    Agganciare solo il primo e' il difetto misurato dopo il merge di DGWGEOWORK-9110: quel ticket ha
    tolto il bordo rosso ai campi obbligatori mai toccati (giusto - «obbligatorio» non e' «in
    errore»), ma sui form a `required` statico l'asterisco non compariva, quindi il segnale non e'
    stato sostituito: e' sparito. A/B sulla stessa modale «Crea nuovo utente», pre-merge bordo
    `rgb(255,0,0)`, post-merge `rgb(194,202,216)` e nessun marcatore.

    PERCHE' `::before` E NON `::after`: `label::after` **e' gia' occupato da Metronic** - misurato:
    `.form-group.form-md-line-input .form-control ~ label::after` in components-md.css e' la barra
    animata del floating label, e vince anche per specificita' (0,3,1 contro 0,2,1). Scriverci
    `content: " *"` avrebbe messo l'asterisco dentro quella barra, posizionata in fondo al campo.
    `label::before` e' libero (misurato `content: none`), al costo che l'asterisco precede
    l'etichetta - «* Nome» invece di «Nome *». Convenzione diffusa quanto l'altra, e coerente su
    tutti gli stili di form dell'applicazione.

    PERCHE' `:has()`: nel repository l'etichetta sta PRIMA del controllo in certi file e DOPO in
    altri (floating label Metronic), quindi un selettore di fratellanza andrebbe bene solo in meta'
    dei casi. `:has()` e' gia' usato in Common/Styles/focusVisible.css.

    PERCHE' NON SI PRETENDE `label[for]` (DGWGEOWORK-9111, seconda passata). La prima stesura
    scriveva `> label[for]`, che sui form migrati va benissimo e altrove non prende niente: nei 98
    template a `required` statico l'etichetta e' un `<label>` nudo, senza `for` (misurato sul DOM
    vivo: 0 su 12 gruppi con etichetta associata, su tre schermate diverse). Pretendere il `for`
    faceva dipendere un segnale VISIVO da un legame PROGRAMMATICO che li' non c'e' - due requisiti
    distinti (3.3.2 vuole il primo, 1.3.1 il secondo), e legarli significa perdere l'uno per colpa
    dell'altro. Che quelle etichette vadano associate resta vero e resta da fare (R8b di
    .claude/docs/frontend-accessibility.md): e' un lavoro sul markup, file per file, non un
    prerequisito dell'asterisco.

    Togliere il `for` apre due modi di marcare l'etichetta SBAGLIATA, e non sono ipotetici: una
    passata sui 431 gruppi con un campo obbligatorio in GeoWorks.Web/App li ha trovati entrambi. Per
    questo il selettore ha due guardie, spiegate una per una sopra la regola:

      - un gruppo con PIU' etichette dirette non dice quale sia del campo obbligatorio, e CSS non
        puo' correlare `for` e `id` (map/.../create/measures.cshtml, modal/trunks/trunks.cshtml);
      - `:has()` non guarda solo il gruppo piu' vicino: fa match anche sugli ANTENATI, quindi un
        gruppo esterno che contiene un gruppo con la tendina obbligatoria si marcherebbe da se'
        (rftStatus/createRole.html, dove l'etichetta esterna e' quella di un radio).

    Dove una guardia scatta non compare nessun asterisco - esattamente come prima di questa passata,
    quindi nessuna regressione - e la verifica di .claude/scripts/a11y/browser-checks.js lo conta fra
    i «senza marcatore»: il caso resta visibile invece di essere risolto a caso. La stessa verifica
    conta i «marcatori spuri», cioe' le etichette marcate che NON appartengono al gruppo piu' interno
    di un campo obbligatorio: e' la guardia sulle guardie, e non dipende da come e' scritto questo
    selettore.

    L'ASTERISCO NON SI SCRIVE PIU' NEL TESTO DELL'ETICHETTA. Undici etichette lo avevano a mano
    (`Nome *`, o uno `<span class="text-danger">*</span>`): quattro erano gia' doppie prima di
    questa passata, le altre sette lo sarebbero diventate qui - `* Nome *`. Sono state ripulite nel
    markup, ed e' il foglio a metterlo. Se ne aggiungi uno a mano ottieni un doppione: la verifica
    di .claude/scripts/a11y/browser-checks.js lo segnala come «asterisco doppio».

    RICHIEDE `:has()`, E IL PAVIMENTO E' GIA' QUELLO. Su un browser che non conosce `:has()`
    (Chrome <105, Safari <15.4, Firefox <121) la regola cade INTERA e quei form restano senza
    marcatore - dove undici di loro, prima di questa passata, un asterisco scritto a mano ce
    l'avevano. Non e' un pavimento nuovo: Common/Styles/focusVisible.css usa `:has()` in 8 punti,
    quindi su quegli stessi browser manca gia' l'anello di focus, che e' 2.4.7 - un requisito di
    conformita' come questo. Chi volesse abbassare il pavimento deve rifare entrambi i fogli, non
    questo.

    QUANTO COSTA. La regola valuta due `:has()` per `.form-group`, e Blink invalida un `:has()`
    a ogni mutazione nel sottoalbero - che in AngularJS vuol dire a ogni tasto (`ng-touched`,
    `ng-dirty`, `edited`). Misurato su una pagina sintetica da 1.200 gruppi (il triplo del form
    reale piu' grande), 2.000 toggle di classe con reflow forzato: 0,001 ms per mutazione, identico
    alla regola precedente che di `:has()` ne aveva uno. La differenza sta sotto il rumore della
    misura - nella stessa prova il caso SENZA regola risulta il piu' lento. Misurato l'aggiornamento
    di classe, che e' la forma di churn del repository; non l'inserimento/rimozione di nodi
    (`ng-if`, `ng-repeat`), che invalida per altra via.

    Il colore e' `currentColor`: l'asterisco eredita quello dell'etichetta. Deliberatamente NON usa
    il rosso degli errori - «obbligatorio» e «sbagliato» sono due stati diversi, e confonderli e' il
    difetto che questo foglio corregge. Non essendo veicolato dal colore, non viola 1.4.1.
*/

/* Le due condizioni davanti (`:not(...)`) sono le GUARDIE, quella dietro (`:has(...)`) e' il
   bersaglio. Nell'ordine in cui si leggono:

     :not(:has(.form-group ...))   il campo obbligatorio non sta dentro un gruppo ANNIDATO. Senza,
                                   `:has()` fa match anche sugli antenati e l'asterisco compare su
                                   un'etichetta che non c'entra: misurato su
                                   rftStatus/createRole.html, dove il gruppo esterno tiene
                                   l'etichetta di un RADIO e quello annidato la tendina obbligatoria.
                                   La guardia nomina il CAMPO e non il gruppo annidato: escludere
                                   ogni gruppo che ne contiene un altro spegnerebbe l'asterisco di
                                   userAvailableTemplates/projectInformations.html, dove il gruppo
                                   «Owner» possiede la propria tendina obbligatoria E contiene due
                                   gruppi annidati («SameAs» e «OtherOwners», che obbligatori non
                                   sono, e il secondo e' `ng-hide` quindi sempre nel DOM).
     :not(.error)                  l'etichetta non e' un messaggio di validazione. E' un'ASSICURAZIONE,
                                   non la correzione di un difetto vivo, e la differenza va detta:
                                   i default di jQuery Validation inserirebbero l'errore come
                                   `<label class="error">` dentro lo stesso `.form-group`
                                   (libs/jquery-validation/js/jquery.validate.js:266,269), e lo
                                   farebbero proprio su Views/Account/*, che caricano
                                   common.min.css e chiamano `$form.validate()` senza opzioni. Ma
                                   questa applicazione quei default LI SOSTITUISCE:
                                   Common/Scripts/jquery-validation-custom.js:6-9 chiama
                                   `setDefaults({errorElement:'span', errorClass:'help-block ...'})`
                                   ed e' dentro login-forms.min.js, che Views/Account/_Layout.cshtml
                                   rende alla riga 99 - prima di ogni `validate()`. Quindi oggi
                                   l'elemento iniettato e' uno `<span>` e non conta come etichetta
                                   comunque. L'esclusione resta perche' costa un token e il giorno
                                   che quell'override sparisse il marcatore morirebbe in silenzio
                                   nel momento peggiore: l'etichetta d'errore resta nel DOM anche
                                   quando il campo torna valido, viene solo nascosta.

     > label:not([aria-hidden="true"])
                                   l'etichetta non e' un decoro. In project/views/modal/trunk e
                                   /trunks sei `<label class="control-label"><span>&nbsp;</span></label>`
                                   servono solo ad allineare in verticale la tendina obbligatoria
                                   che sta sotto: senza questa condizione ci finisce sopra un `*`
                                   solo, attaccato a niente, e il nome accessibile di quel `label`
                                   diventa «*». Marcarli `aria-hidden` era comunque dovuto - un
                                   `label` vuoto senza `for` non e' un'etichetta, e' un
                                   distanziatore - e qui l'attributo fa anche da esclusione.
     :not(:has(> label ~ label))   il gruppo ha UNA SOLA etichetta diretta CHE CONTI -> si sa quale
                                   marcare. Non contano quelle escluse sotto (decorative e di
                                   validazione): se contassero, basterebbe un messaggio d'errore a
                                   spegnere l'asterisco.
                                   Con piu' di una non si sa, e CSS non puo' correlare `for` e `id`:
                                   misurato su map/.../create/measures.cshtml (tre etichette,
                                   «Measurement 1», «Unit», «Location type», e un solo campo
                                   obbligatorio) e su modal/trunks/trunks.cshtml (la seconda
                                   etichetta e' un valore visualizzato). Li' si preferisce NESSUN
                                   asterisco a un asterisco sul campo sbagliato: un segnale mancante
                                   la verifica lo conta, un segnale falso disinforma.

   Il bersaglio e' una lista dentro un solo `:has()`: campi nativi con obbligatorieta' condizionale
   (`aria-required`), campi nativi con `required` statico, tendine marcate `.gw-required`.
   Caselle e radio sono escluse: la loro obbligatorieta' si esprime in gruppo (fieldset/legend) e i
   loro `label` hanno pseudo-elementi propri negli stili Metronic.

   Sulle tendine il marcatore e' la CLASSE e non `aria-required`: su un <div> senza ruolo
   l'attributo non e' ammesso (axe aria-allowed-attr, critical), e quello vero sta sui campi interni
   con ruolo combobox (vedi ariaRequiredFromNgRequired.js). La direttiva mette `.gw-required` sia
   per il `ng-required` sia per il `required` statico, quindi qui un ramo solo basta. */
.form-group:not(:has(> label:not(.error):not([aria-hidden="true"])
                   ~ label:not(.error):not([aria-hidden="true"]))):not(:has(.form-group :is(
    .form-control[aria-required="true"]:not([type="checkbox"]):not([type="radio"]),
    .form-control[required]:not([type="checkbox"]):not([type="radio"]),
    .ui-select-container.gw-required))):has(
    .form-control[aria-required="true"]:not([type="checkbox"]):not([type="radio"]),
    .form-control[required]:not([type="checkbox"]):not([type="radio"]),
    .ui-select-container.gw-required) > label:not(.error):not([aria-hidden="true"])::before {
    content: "* ";
    color: currentColor;
    font-weight: bold;
}

/* La frase che spiega la convenzione: sta in cima al form, non e' un errore, non urla. */
.__gw-required-instruction {
    margin-bottom: 10px;
    font-style: italic;
}

/* =====================================================================================
   ESTENSIONE (DGWGEOWORK-9111, seconda PR): le forme che NON hanno una `.form-group`.

   La regola sopra parte dal gruppo Bootstrap; nei form di pratica il gruppo spesso non c'e'
   e l'etichetta convive col campo dentro una `.row` o una colonna (`segment.html`,
   `edit.html`, `TrunkSetups.html`: `.row > label + div > input`, oppure
   `.col-md-12 > label + input`). Stessi bersagli, stesse guardie, contenitore diverso.
   La guardia sui gruppi annidati qui esclude il contenitore che ne racchiude un altro con una
   propria etichetta diretta: in `segment.html` il campo sta in una colonna accanto
   all'etichetta, e quella colonna un'etichetta non la possiede, quindi passa. NON si puo'
   scrivere «il campo obbligatorio dentro un gruppo annidato» come nella regola sopra: servirebbe
   un `:has()` dentro un `:has()`, che CSS vieta e che fa cadere l'intera regola in silenzio
   (misurato: 3 regole caricate su 5, nessun errore in console). */
:is(.row, [class*="col-"]):not(.form-group):not(:has(> label:not(.error):not([aria-hidden="true"]) ~ label:not(.error):not([aria-hidden="true"]))):not(:has(:is(.form-group, .row, [class*="col-"]) > label:not(.error):not([aria-hidden="true"]))):has(
    .form-control[aria-required="true"]:not([type="checkbox"]):not([type="radio"]),
    .form-control[required]:not([type="checkbox"]):not([type="radio"]),
    .ui-select-container.gw-required) > label:not(.error):not([aria-hidden="true"])::before,
/* Wizard nuova pratica: l'etichetta del titolo sta in un `div.input-title` insieme al tooltip
   (projectInformations.html). */
.form-group:not(:has(.form-group :is(
    .form-control[aria-required="true"]:not([type="checkbox"]):not([type="radio"]),
    .form-control[required]:not([type="checkbox"]):not([type="radio"]),
    .ui-select-container.gw-required))):has(
    .form-control[aria-required="true"]:not([type="checkbox"]):not([type="radio"]),
    .form-control[required]:not([type="checkbox"]):not([type="radio"]),
    .ui-select-container.gw-required) > .input-title > label::before,
/* Questionari (reviewAnswer.html): una risposta e' un `div.form-answer` con
   `div.label-answer > label.form-label` e il controllo in un fratello. I radio restano fuori
   dalla lista: la loro obbligatorieta' e' della DOMANDA (regola sotto). */
.form-answer:not(:has(.form-answer :is(
    .form-control[aria-required="true"]:not([type="checkbox"]):not([type="radio"]),
    .form-control[required]:not([type="checkbox"]):not([type="radio"]),
    .ui-select-container.gw-required))):has(
    .form-control[aria-required="true"]:not([type="checkbox"]):not([type="radio"]),
    .form-control[required]:not([type="checkbox"]):not([type="radio"]),
    .ui-select-container.gw-required) > .label-answer > label.form-label::before,
/* Questionari, CASELLA obbligatoria (DGWGEOWORK-9762): l'asterisco va sulla label della casella,
   quando c'e'. La prima stesura (9111) la trattava come i radio - «l'obbligo e' della domanda» -
   ma una casella singola («Per presa visione ed accettazione», 31 su 31 su Geopass Milano) ha il
   suo testo nella label della risposta, e la domanda madre e' spesso vuota o nascosta: misurato
   il 21/09/2026, un «*» orfano su una riga senza testo, oppure nessun asterisco da nessuna parte
   perche' `.question-label` sta sotto `ng-if="showDescription"`. */
.form-answer:not(:has(.form-answer input[type="checkbox"][aria-required="true"])):has(input[type="checkbox"][aria-required="true"]) > .label-answer > label.form-label::before,
/* Questionari: la domanda con risposte RADIO obbligatorie e' il gruppo, e l'asterisco va sul suo
   testo (`.question-label`), non su ogni opzione. Le domande dipendenti hanno il proprio
   `.Answer-container` annidato e non devono accendere quello del genitore. */
.Answer-container:not(:has(.Answer-container input[type="radio"][aria-required="true"])):has(input[type="radio"][aria-required="true"]) > div > .question-label::before,
/* Questionari, controllo obbligatorio SENZA label propria (DGWGEOWORK-9762): il testo che l'utente
   vede e' quello della domanda - «u) di essere appaltatori del comune» con una casella, «Indicare il
   deposito cauzionale» con un campo Decimal (template 294 di Geopass) - e l'asterisco va li'. Vale
   per ogni tipo di controllo, non solo radio e caselle: una risposta con descrizione vuota e' la norma
   nei questionari dove la domanda fa da etichetta. La guardia `:not(:has(.label-answer > label.form-label))`
   decide per l'intera domanda - se una qualunque risposta ha la sua label, la domanda non si marca -
   perche' CSS non permette un `:has()` dentro un altro `:has()` e quindi non puo' guardare il controllo
   e la sua label insieme. Nei dati reali le risposte di una domanda hanno tutte o nessuna la
   descrizione; il caso misto resta senza asterisco sul solo controllo senza label, visibile in
   .claude/scripts/a11y/browser-checks.js. */
.Answer-container:not(:has(.Answer-container :is(.form-control[aria-required="true"], .form-control[required], .ui-select-container.gw-required, input[type="radio"][aria-required="true"], input[type="checkbox"][aria-required="true"]))):not(:has(.label-answer > label.form-label)):has(.form-control[aria-required="true"], .form-control[required], .ui-select-container.gw-required, input[type="radio"][aria-required="true"], input[type="checkbox"][aria-required="true"]) > div > .question-label::before {
    content: "* ";
    color: currentColor;
    font-weight: bold;
}

/* Questionario con un controllo obbligatorio e NESSUNA etichetta visibile al suo interno
   (DGWGEOWORK-9762): domanda, pagina e gruppo nascosti, risposta senza descrizione. Succede quando
   il questionario e' un campo solo e il configuratore ha scritto il nome del campo nel titolo del
   template (Geopass, template 333 «INDICARE DATA DECORRENZA DELLA CONCESSIONE», un moment-picker).
   L'unico testo che l'utente vede e' il titolo (`treeNodeReview.html`, `h4 > strong > span`), quindi
   l'asterisco va li': senza, la frase «I campi contrassegnati con * sono obbligatori» resta senza
   nessun campo contrassegnato. La guardia e' stretta - nessuna `.question-label`, nessuna label di
   risposta, nessun titolo di pagina, nessuna descrizione di gruppo - perche' appena una di quelle e'
   visibile e' lei l'etichetta giusta (regole sopra) e il titolo non va toccato. */
.form-group.form-md-floating-label:has(review-question):not(:has(.question-label)):not(:has(.label-answer > label.form-label)):not(:has(.page > h4)):not(:has(.question-group > span)):has(.form-control[aria-required="true"], .form-control[required], .ui-select-container.gw-required, input[type="radio"][aria-required="true"], input[type="checkbox"][aria-required="true"]) > h4 > strong > span:last-child::before {
    content: "* ";
    color: currentColor;
    font-weight: bold;
}

/* La frase che spiega la convenzione, in versione AUTOMATICA: sta una volta sola nel guscio
   delle modali (`templateModalLayout.html`, `modal.html`) e si vede solo se nel dialogo c'e'
   almeno un campo obbligatorio. Cosi' non compare su una modale di sola lettura o senza
   obbligatori, e non va ricordata form per form (DGWGEOWORK-9111: era su 4 form su 95). */
.modal:not(:has(
    .form-control[aria-required="true"]:not([type="checkbox"]):not([type="radio"]),
    .form-control[required]:not([type="checkbox"]):not([type="radio"]),
    .ui-select-container.gw-required,
    input[type="radio"][aria-required="true"], input[type="checkbox"][aria-required="true"])) .__gw-required-instruction--auto {
    display: none;
}
