# La sfumatura sopra il contenuto nelle PWA, iOS 26 e 27

Appunti presi risolvendo il problema su Faraway Segnapunti (PWA senza framework,
agosto 2026), su iPhone 16 Pro Max con iOS 27 beta. Il difetto e la soluzione non
dipendono dal framework.

Questo file integra e in parte corregge un appunto precedente, quello che indicava
come rimedio il solo cambio del meta tag della barra di stato. Su iOS 27 quel
cambio da solo non basta.

## Il sintomo

Nella web app aggiunta alla schermata home, il contenuto in cima alla pagina appare
sfocato, come dietro a un vetro smerigliato. Da noi era il logo dell'intestazione.
La sfumatura non si ferma all'altezza della barra di stato: scende sotto, e sfoca
quello che trova per un centinaio di punti.

Due segni per riconoscerlo:

- dal browser non si vede, solo dalla versione installata sulla home;
- non c'era prima, ed è comparso senza toccare quel codice.

## La causa

Da iOS 26 il sistema disegna sopra la cima della pagina il proprio *scroll edge
effect*, lo stesso delle app native. Per decidere che colore dare alla barra di
stato, Safari cerca gli elementi `position: fixed` o `sticky` vicini ai bordi del
viewport e ne legge `background-color` e `backdrop-filter`.

Due dettagli che spiegano i comportamenti apparentemente incoerenti:

- **il meta tag `theme-color` viene ignorato** per le web app installate;
- **gli elementi vengono campionati anche se invisibili**: `opacity: 0` e
  `pointer-events: none` non bastano a farsi ignorare, solo `display: none`.

## Cosa non ha funzionato

Provato in quest'ordine, su iOS 27 beta, e nessuno di questi ha risolto:

1. **`apple-mobile-web-app-status-bar-style: default`** al posto di
   `black-translucent`. Nessun cambiamento sulla sfumatura.
2. **`... : black`**. Nessun cambiamento sulla sfumatura. Da notare che il nome
   inganna: la barra non diventa nera, il colore Safari continua a campionarlo
   dalla pagina.
3. **Togliere l'elemento fisso che stava in cima**. Peggiora: senza un campione
   Safari applica comunque l'effetto.
4. **Togliere `background-attachment: fixed` dal body**, rendere non fissa una
   texture a schermo intero, togliere lo scorrimento in eccesso della pagina.
   Nessuno dei tre c'entrava.

## La soluzione

Un elemento fisso, trasparente, sopra la parte alta della pagina. Non deve avere
aspetto: serve solo a esistere e a farsi campionare.

```html
<body>
  <div id="scudo-barra" aria-hidden="true"></div>
  ...
```

```css
#scudo-barra{
  position: fixed; top: 0; left: 0; right: 0;
  height: 220px;              /* deve coprire tutta la zona sfumata, non solo l'inset */
  z-index: 39;                /* sotto a dialoghi e sovrapposizioni */
  pointer-events: none;       /* non deve intercettare i tocchi */
  background: linear-gradient(transparent, transparent);
}
```

Trovando lassù un elemento fisso da campionare, Safari smette di disegnare per conto
suo la sfumatura sul contenuto della pagina.

Due punti su cui non ho una spiegazione documentata, quindi vanno presi come
osservazioni e non come teoria:

- il fondo è **un gradiente trasparente e non un colore**. È la condizione in cui
  l'abbiamo visto funzionare, replicando un elemento della nostra diagnostica.
  Con un `background-color` vero l'effetto tornava, disegnato sopra quell'elemento;
- l'altezza di 220px è generosa di proposito. La zona sfumata scendeva a circa 110
  punti dal bordo, ma non è documentata e può cambiare con le versioni.

### Come ci siamo arrivati

Per caso, ed è il pezzo più utile di questo appunto. L'app aveva una diagnostica
attivabile con cinque tocchi sull'intestazione, che disegnava bande colorate e un
righello sopra tutta la pagina. **Con la diagnostica accesa la sfumatura spariva**,
spenta tornava. L'unica cosa che quella aggiungeva in cima era un elemento fisso
trasparente.

Se vi trovate a indovinare più di due volte di fila, conviene costruire uno
strumento che mostri sul dispositivo i numeri veri, invece di continuare a
ipotizzare. Su questo problema ha risolto in un colpo quello che tre ipotesi
ragionevoli avevano sbagliato.

## Le conseguenze, se cambiate anche il meta tag

Se insieme allo scudo passate la barra di stato da `black-translucent` a `black`
o `default`, cambiano parecchie misure. Da controllare:

**`env(safe-area-inset-top)` diventa 0.** La barra è opaca, la pagina comincia sotto
e non c'è più niente da schivare. Ogni margine calcolato su quell'inset si riduce.

**La finestra è più corta dello schermo, ma la parte mancante è IN ALTO.** Sul 16 Pro
Max `innerHeight` vale 894 su uno schermo di 956: quei 62 punti sono la barra di
stato sopra, non una fascia sotto. Se avete codice che calcola il margine inferiore
sottraendo `screen.height - innerHeight`, va corretto, altrimenti il contenuto in
basso finisce a filo del bordo. Da noi:

```js
const BARRA_OPACA = document.querySelector('meta[name="apple-mobile-web-app-status-bar-style"]')
                      .content !== 'black-translucent';
```

**Leggete la configurazione, non misuratela.** Dedurre lo stato della barra
dall'inset misurato non funziona: appena aperta l'app quel valore non è ancora
definitivo, e la prima passata decide male. Il sintomo è caratteristico: il margine
è sbagliato all'avvio e si sistema da solo appena qualcosa provoca una nuova misura,
per esempio uno scorrimento.

**Meglio ancora: quel margine non calcolatelo in JavaScript.** Con la barra opaca
non c'è niente da compensare, quindi basta `padding-bottom: env(safe-area-inset-bottom)`
nel CSS, che il browser aggiorna da sé. Ogni valore che JavaScript legge una volta
sola all'avvio è un valore che può essere ancora zero.

## La trappola peggiore: il meta tag si legge all'installazione

**iOS legge `apple-mobile-web-app-status-bar-style` quando l'utente aggiunge l'app
alla schermata home, e per quella copia resta com'era.** Aggiornare i file, svuotare
la cache e riavviare non cambia niente: la vecchia installazione continua a
comportarsi come prima.

Quindi chi ha già l'app sulla home deve toglierla e rimetterla. E siccome **ogni
installazione ha i suoi dati**, separati da Safari e dalle altre copie, questo
significa perdere tutto quello che c'è in `localStorage`.

Se l'app conserva dati che contano, **prima di pubblicare il cambio del meta tag
aggiungete un modo per esportarli e reimportarli.** Da noi: un pulsante che serializza
tutto in JSON, lo codifica in base64 e lo mette negli appunti, e uno che lo rilegge.
Un testo e non un file, così si passa via messaggio o nota e funziona anche fra
telefoni diversi. Due accortezze che valgono la pena:

- l'importazione aggiunge invece di sostituire, e salta quello che è già presente
  riconoscendolo dall'id, così incollare due volte non crea doppioni;
- se `navigator.clipboard` viene negato, il testo va mostrato in una textarea da
  copiare a mano, invece di fallire in silenzio.

## Come verificare di stare guardando l'ultima versione

Con un service worker di mezzo si rischia di provare per mezz'ora un file vecchio e
concludere che le correzioni non funzionano. Vale la pena mettere da qualche parte
un numero di versione preso dal file JavaScript che sta girando, e accanto il nome
della cache attiva letto da `caches.keys()`. Se i due non coincidono, il problema non
è il codice.

## Fonti

- pavel 1ar.ionov, *Safari 26 Liquid Glass: toolbar tinting, white bars, viewport
  bugs*: https://1ar.io/updates/safari-26-liquid-glass-web/
  (documenta il campionamento degli elementi fissi, `theme-color` ignorato, e il
  rimedio di spostare lo stile su un figlio `position: absolute`)
- Ben Frain, *iOS26 Safari theme-color/tab-tinting with fixed position elements is a
  mess*: https://benfrain.com/ios26-safari-theme-color-tab-tinting-with-fixed-position-elements/
