# PWA su iPhone: la parte alta sfocata (iOS 26 e 27)

Guida comune ai progetti di Paolo con una PWA. Raccoglie quello che è stato provato
su iPhone vero in quattro progetti: Faraway, PromoGest, BG Collection (bgg-app) e
AmiCibi. Prima di inventare un rimedio nuovo, applicare la ricetta qui sotto.

Stato: bozza del 7 ottobre 2026, scritta da AmiCibi. Le sessioni degli altri
progetti la rivedono e mandano le correzioni, che vengono riportate qui. Un solo file,
un solo autore alla volta.

## Il sintomo

Nella web app **aggiunta alla schermata home** (non nel browser) la parte alta della
pagina appare sfocata, come dietro un vetro smerigliato: logo, pulsanti, quadretti
dell'intestazione. La sfocatura scende sotto l'orologio per circa 40-50 punti, fino a
circa 110 punti dal bordo dello schermo.

Si riconosce perché:

- in Safari, nella scheda normale, non si vede;
- è comparsa con iOS 26 senza toccare il codice.

Non si riproduce in Chrome, nel simulatore o in un iframe. Si prova solo sull'iPhone,
con l'app installata dalla home.

## La causa

Da iOS 26 il sistema disegna in cima alle web app installate il suo *scroll edge
effect* (Liquid Glass). Per decidere colore e sfocatura Safari campiona gli elementi
`position: fixed` o `position: sticky` vicini al bordo alto e ne legge
`background-color` e `backdrop-filter`. Se non trova niente di adatto, sfoca quello
che c'è sotto.

Regole del campionamento osservate o documentate:

- `theme-color` viene **ignorato** nelle web app installate;
- vengono campionati **anche gli elementi invisibili**: `opacity: 0` e
  `pointer-events: none` non bastano, solo `display: none` li esclude;
- **pseudo-elementi** (`::before`, `::after`) e **figli `position: absolute`** di un
  elemento fisso **non** vengono campionati;
- contano solo `fixed` e `sticky`: il contenuto normale che scorre non viene letto.

Nessun meta tag e nessuna proprietà CSS spengono l'effetto. Si può solo dargli
qualcosa da campionare.

## La ricetta

### 1. Lo scudo

Un elemento fisso, trasparente, come **primo figlio di `<body>`**. Non dentro un
wrapper con `display: contents`: in PromoGest lì non veniva campionato.

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

```css
.scudo-barra-ios {
  position: fixed; top: 0; left: 0; right: 0;
  height: 220px;              /* più della zona sfocata (~110pt), di proposito */
  z-index: 5;                 /* sotto intestazioni, dialoghi e sovrapposizioni */
  pointer-events: none;
  background: linear-gradient(transparent, transparent);   /* gradiente, NON un colore */
}
.scudo-barra-ios > div {
  position: absolute; top: 0; left: 0; right: 0;
  height: 62px;               /* altezza della barra di stato */
  background-color: transparent;
  -webkit-backdrop-filter: blur(0px);
  backdrop-filter: blur(0px); /* zero: non sfoca, dà a Safari un filtro da leggere */
}
```

Da copiare **così com'è**. È un comportamento non documentato: la forma è quella vista
funzionare sul dispositivo. Non accorpare le regole, non spostare il `backdrop-filter`
sul padre e non sostituire il gradiente con un `background-color`. In Faraway, con un
colore vero la sfocatura tornava.

Faraway usa la versione senza figlio: solo il padre con il gradiente, `z-index: 39`.
PromoGest ha avuto bisogno del figlio con `blur(0px)`. La forma con il figlio
funziona in entrambi i casi.

### 2. Niente altri fixed/sticky con uno sfondo in cima

Se in cima c'è un'intestazione `sticky` o una fascia `fixed` con `background-color` o
`backdrop-filter`, Safari campiona quella e lo scudo non serve a niente.

- il box `fixed`/`sticky` resta **trasparente**;
- colore, immagini e quadretti vanno su un **figlio `position: absolute`** o su un
  **`::before`**, che Safari non guarda;
- **mai `backdrop-blur`** (vetro smerigliato) su barre fisse in alto: oltre a farsi
  campionare, la sfocatura arriva fin sotto l'orologio (BG Collection).

Esempio (AmiCibi, intestazione sticky a quadretti):

```css
.gingham { position: relative; isolation: isolate; }       /* il box: niente sfondo */
.gingham::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  border-radius: inherit; pointer-events: none;
  background-color: var(--blue);
  background-image: /* quadretti */ ...;
}
```

### 3. La barra di stato

```html
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">  <!-- o "black" -->
```

Verificato funzionante, **insieme allo scudo**, con `default` (PromoGest) e con `black`
(Faraway). Da solo il cambio di meta **non** basta su iOS 27 (Faraway e dozzle).

Con `black-translucent` la pagina passa sotto l'orologio. È la combinazione in cui il
problema si vede di più. **In verifica su AmiCibi**: scudo più intestazione sticky
trasparente con `black-translucent`. Se funziona, la si potrà tenere anche lì; fino ad
allora usare `default` o `black`.

Scegliere fra `default` (fascia chiara del sistema) e `black` (fascia scura) in base al
colore della propria intestazione. BG Collection ha l'intestazione quasi nera
(`#0b1712`), quindi `black` sembra un blocco unico.

## Le trappole

### Il meta della barra di stato si legge solo all'installazione

iOS legge `apple-mobile-web-app-status-bar-style` quando l'app viene **aggiunta alla
home**, e per quella copia resta com'era. Aggiornare i file e svuotare la cache non
cambia niente: va **tolta dalla home e riaggiunta**.

Ogni installazione ha il **suo `localStorage`**, separato da Safari. Reinstallare
significa perdere i dati locali. Se l'app li usa, prima di cambiare il meta aggiungere
un'esportazione e un'importazione. In Faraway c'è "Copia/Incolla le partite": JSON in
base64 negli appunti, l'importazione aggiunge senza duplicare, e c'è una textarea di
riserva se gli appunti sono negati. Le app con i dati sul server (AmiCibi, PromoGest)
non hanno questo problema.

Lo scudo invece è CSS e HTML normale: arriva con il prossimo caricamento, senza
reinstallare.

### Con la barra opaca cambiano le misure

Con `default` o `black`:

- `env(safe-area-inset-top)` vale **0**: la pagina comincia sotto la barra;
- `innerHeight` è più corto dello schermo, ma i punti mancanti sono **in alto** (sul 16
  Pro Max: 894 su 956). Il codice che calcola il margine in basso con
  `screen.height - innerHeight` sbaglia;
- il margine in basso va fatto in CSS, `padding-bottom: env(safe-area-inset-bottom)`,
  non calcolato in JavaScript all'avvio;
- se serve sapere che barra c'è, leggere il meta dalla pagina, non dedurlo misurando
  l'inset: all'avvio non è ancora definitivo.

`env(safe-area-inset-top)` nel CSS va lasciato comunque: con `black-translucent` e in
orizzontale serve.

### Il service worker serve la versione vecchia

Prima di giudicare un tentativo, verificare che l'app installata stia girando i file
nuovi. In PromoGest era la prima causa di falsi negativi. Mettere in pagina un numero
di versione e, se c'è un service worker, il nome della cache attiva
(`caches.keys()`). Le app senza service worker (AmiCibi) basta chiuderle dal
multitasking e riaprirle.

## Come provare

Smettere di andare a ipotesi dopo due tentativi falliti e costruire una diagnostica
sul dispositivo. In tutti i progetti è stata la mossa che ha chiuso il problema.

- una pagina o un pannello nascosto con: inset reali (`env(safe-area-inset-*)` misurati
  con un elemento sonda), `display-mode`, valore del meta letto, versione in
  esecuzione, cache del service worker;
- un interruttore per accendere e spegnere le varianti dello scudo dal vivo,
  salvato in `localStorage` e applicato con un attributo su `<html>` prima del primo
  disegno.

In Faraway la soluzione è nata così: con la diagnostica accesa la sfocatura spariva, e
l'unica cosa che la diagnostica aggiungeva in cima era un elemento fisso trasparente.

## Cosa non ha funzionato

Per non riprovarlo:

| Tentativo | Esito | Dove |
|---|---|---|
| Solo `status-bar-style: default` o `black` | Sfocato uguale su iOS 27 | Faraway, dozzle |
| Togliere ogni elemento fisso in cima | Peggio: sfoca comunque | Faraway |
| Togliere `background-attachment: fixed`, texture fisse, scroll in eccesso | Nessun effetto | Faraway |
| Fascia `html::before` dietro la safe area | Ignorata (pseudo-elemento) | Faraway |
| Scudo dentro un wrapper `display: contents` | Non campionato | PromoGest |
| Scudo con `black-translucent` ma intestazione sticky con `background-color` | Sfocato: Safari campiona l'intestazione | AmiCibi |
| Fascia `fixed` blu piena alta quanto la barra di stato | Blocco blu, sotto ancora sfocato | AmiCibi |

## Fonti

- Pavel 1ar.ionov, *Safari 26 Liquid Glass: toolbar tinting, white bars, viewport bugs*:
  https://1ar.io/updates/safari-26-liquid-glass-web/
- Ben Frain, *iOS26 Safari theme-color/tab-tinting with fixed position elements*:
  https://benfrain.com/ios26-safari-theme-color-tab-tinting-with-fixed-position-elements/
- Discussioni di altri progetti: dozzle PR #5222 (il solo cambio di meta non basta),
  herdr-web-ui PR #106 (intestazione sticky piena).
- Codice di riferimento nei progetti:
  - Faraway: `pwa/index.html` (~riga 49), `pwa/app.css` (~righe 126-145),
    `docs/pwa-ios27-sfumatura-barra-di-stato.md` (la versione più lunga di questi
    appunti);
  - PromoGest: `valentina/promogest-v2/src/app.html`, `src/app.css` (~righe 31-85);
  - BG Collection: `bgg-app/src/app.html`, commit `662fcdd` e `fb61a02`;
  - AmiCibi: `amicibi/src/app/layout.tsx`, `src/app/globals.css`.
