| @@ -47,15 +47,19 @@ |
| 47 | } | 47 | } |
| 48 | | 48 | |
| 49 | *, *::before, *::after { box-sizing: border-box; } | 49 | *, *::before, *::after { box-sizing: border-box; } |
| 50 | -html, body { margin: 0; height: 100%; } | 50 | +html { height: 100%; } |
| 51 | body { | 51 | body { |
| | 52 | + margin: 0; |
| | 53 | + /* 100dvh, pas 100% : sur iOS (PWA), le viewport de layout s'arrête |
| | 54 | + au-dessus de la zone du geste home — 100% laisserait une bande non |
| | 55 | + peinte en bas de l'écran. */ |
| | 56 | + height: 100dvh; |
| 52 | background: var(--color-neutral-300); | 57 | background: var(--color-neutral-300); |
| 53 | color: var(--color-text); | 58 | color: var(--color-text); |
| 54 | font-family: var(--font-body); | 59 | font-family: var(--font-body); |
| 55 | font-size: 15px; line-height: 1.55; | 60 | font-size: 15px; line-height: 1.55; |
| 56 | /* Le body ne scrolle jamais : le scroll vit dans .app-main. Sans ça, iOS | 61 | /* Le body ne scrolle jamais : le scroll vit dans .app-main. Sans ça, iOS |
| 57 | - (PWA) fait rebondir les éléments fixed avec la page — tab bar qui saute | 62 | + (PWA) fait rebondir les éléments flottants avec la page. */ |
| 58 | - et fond gris visible dessous. */ | | |
| 59 | overflow: hidden; | 63 | overflow: hidden; |
| 60 | } | 64 | } |
| 61 | h1, h2, h3, h4 { | 65 | h1, h2, h3, h4 { |
| @@ -130,6 +134,8 @@ input[type=number] { -moz-appearance: textfield; appearance: textfield; } |
| 130 | | 134 | |
| 131 | /* ═══ Couche applicative time-time ═══ */ | 135 | /* ═══ Couche applicative time-time ═══ */ |
| 132 | | 136 | |
| | 137 | +/* La coquille couvre tout l'écran ; tab bar, timer et toast sont ancrés en |
| | 138 | + absolu DANS la coquille — position:fixed est piégeux en PWA iOS. */ |
| 133 | .viewport { height: 100%; display: flex; justify-content: center; background: var(--color-neutral-300); } | 139 | .viewport { height: 100%; display: flex; justify-content: center; background: var(--color-neutral-300); } |
| 134 | .shell { | 140 | .shell { |
| 135 | width: 100%; max-width: 430px; height: 100%; background: var(--color-bg); | 141 | width: 100%; max-width: 430px; height: 100%; background: var(--color-bg); |
| @@ -148,18 +154,20 @@ input[type=number] { -moz-appearance: textfield; appearance: textfield; } |
| 148 | .app-topline .who { margin-left: auto; font-size: 12px; opacity: .6; } | 154 | .app-topline .who { margin-left: auto; font-size: 12px; opacity: .6; } |
| 149 | .app-header h1 { margin: 0; font-size: 29px; letter-spacing: -0.02em; } | 155 | .app-header h1 { margin: 0; font-size: 29px; letter-spacing: -0.02em; } |
| 150 | | 156 | |
| 151 | -/* 168px : la pill timer flotte au-dessus de la tab bar, le contenu passe | 157 | +/* Dégagement bas = tab bar + bandeau timer + marge, safe area comprise. |
| 152 | - dessous. Seul conteneur scrollable de l'app (cf. body overflow hidden). */ | 158 | + Seul conteneur scrollable de l'app (cf. body overflow hidden). */ |
| 153 | .app-main { | 159 | .app-main { |
| 154 | - flex: 1; min-height: 0; padding: 0 var(--space-4) 168px; | 160 | + flex: 1; min-height: 0; |
| | 161 | + padding: 0 var(--space-4) calc(172px + env(safe-area-inset-bottom, 0px)); |
| 155 | overflow-y: auto; -webkit-overflow-scrolling: touch; | 162 | overflow-y: auto; -webkit-overflow-scrolling: touch; |
| 156 | } | 163 | } |
| 157 | | 164 | |
| 158 | /* — timer — */ | 165 | /* — timer — */ |
| 159 | .timer-slot { | 166 | .timer-slot { |
| 160 | - position: fixed; left: 50%; transform: translateX(-50%); margin: 0; | 167 | + position: absolute; left: 0; right: 0; margin: 0; |
| 161 | - width: 100%; max-width: 430px; padding: 0 var(--space-4); | 168 | + padding: 0 var(--space-4); |
| 162 | - bottom: calc(88px + env(safe-area-inset-bottom, 0px)); | 169 | + /* 104px : nettement décollé de la tab bar. */ |
| | 170 | + bottom: calc(104px + env(safe-area-inset-bottom, 0px)); |
| 163 | display: flex; justify-content: center; pointer-events: none; z-index: 4; | 171 | display: flex; justify-content: center; pointer-events: none; z-index: 4; |
| 164 | } | 172 | } |
| 165 | /* Bandeau pleine largeur (lancer / en cours / mesure en attente). */ | 173 | /* Bandeau pleine largeur (lancer / en cours / mesure en attente). */ |
| @@ -218,8 +226,7 @@ input[type=number] { -moz-appearance: textfield; appearance: textfield; } |
| 218 | | 226 | |
| 219 | /* — tab bar — */ | 227 | /* — tab bar — */ |
| 220 | .tabbar { | 228 | .tabbar { |
| 221 | - position: fixed; bottom: 0; left: 50%; transform: translateX(-50%); | 229 | + position: absolute; bottom: 0; left: 0; right: 0; |
| 222 | - width: 100%; max-width: 430px; | | |
| 223 | display: flex; | 230 | display: flex; |
| 224 | padding: var(--space-2) var(--space-2) | 231 | padding: var(--space-2) var(--space-2) |
| 225 | calc(var(--space-3) + 6px + env(safe-area-inset-bottom, 0px)); | 232 | calc(var(--space-3) + 6px + env(safe-area-inset-bottom, 0px)); |
| @@ -373,7 +380,7 @@ input[type=number] { -moz-appearance: textfield; appearance: textfield; } |
| 373 | .col-bar a { display: block; min-height: 3px; width: 100%; } | 380 | .col-bar a { display: block; min-height: 3px; width: 100%; } |
| 374 | | 381 | |
| 375 | /* — bottom sheet (zéro JS : :target) — */ | 382 | /* — bottom sheet (zéro JS : :target) — */ |
| 376 | -.sheet-wrap { position: fixed; inset: 0; display: none; z-index: 10; } | 383 | +.sheet-wrap { position: absolute; inset: 0; display: none; z-index: 10; } |
| 377 | .sheet-wrap:target { display: flex; align-items: flex-end; justify-content: center; } | 384 | .sheet-wrap:target { display: flex; align-items: flex-end; justify-content: center; } |
| 378 | .sheet-backdrop { | 385 | .sheet-backdrop { |
| 379 | position: absolute; inset: 0; | 386 | position: absolute; inset: 0; |
| @@ -426,9 +433,8 @@ input[type=number] { -moz-appearance: textfield; appearance: textfield; } |
| 426 | /* — toast — */ | 433 | /* — toast — */ |
| 427 | /* En haut : le bas de l'écran est déjà occupé par la pill timer et la tab bar. */ | 434 | /* En haut : le bas de l'écran est déjà occupé par la pill timer et la tab bar. */ |
| 428 | .toast { | 435 | .toast { |
| 429 | - position: fixed; top: calc(var(--space-4) + env(safe-area-inset-top, 0px)); | 436 | + position: absolute; top: calc(var(--space-4) + env(safe-area-inset-top, 0px)); |
| 430 | - left: 50%; transform: translateX(-50%); | 437 | + left: var(--space-4); right: var(--space-4); |
| 431 | - width: calc(100% - 2 * var(--space-4)); max-width: calc(430px - 2 * var(--space-4)); | | |
| 432 | background: var(--color-neutral-900); color: var(--color-neutral-100); | 438 | background: var(--color-neutral-900); color: var(--color-neutral-100); |
| 433 | padding: var(--space-3) var(--space-4); border-radius: 999px; | 439 | padding: var(--space-3) var(--space-4); border-radius: 999px; |
| 434 | font-size: 14px; text-align: center; box-shadow: var(--shadow-md); | 440 | font-size: 14px; text-align: center; box-shadow: var(--shadow-md); |
| @@ -436,7 +442,7 @@ input[type=number] { -moz-appearance: textfield; appearance: textfield; } |
| 436 | animation: tt-drop .18s ease-out, tt-fade .4s ease-in 5s forwards; z-index: 5; | 442 | animation: tt-drop .18s ease-out, tt-fade .4s ease-in 5s forwards; z-index: 5; |
| 437 | } | 443 | } |
| 438 | | 444 | |
| 439 | -@keyframes tt-drop { from { opacity: 0; transform: translate(-50%, -10px); } to { opacity: 1; transform: translate(-50%, 0); } } | 445 | +@keyframes tt-drop { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: none; } } |
| 440 | @keyframes tt-fade { to { opacity: 0; visibility: hidden; } } | 446 | @keyframes tt-fade { to { opacity: 0; visibility: hidden; } } |
| 441 | @keyframes tt-rise-sheet { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } } | 447 | @keyframes tt-rise-sheet { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } } |
| 442 | /* Motion réduit : pas de glissements, mais le toast doit quand même | 448 | /* Motion réduit : pas de glissements, mais le toast doit quand même |