judu/ex-egopublic Fork
⑂ ae0c077
Commits
⬇ Clone ▾
git clone https://git.rickub.com/judu/ex-ego.git
git clone ssh://git@rickub.com/judu/ex-ego.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

fix(ios): coquille 100dvh + ancrage absolu (tab bar, timer, toast) — plus de zone grise ; bandeau timer décollé du menuUnverified

Julien Durillon committed 2026-08-08T09:17:57+02:00 Browse files
ae0c077 parent: 88a7a4e
modified assets/style.css +22 -16
@@ -47,15 +47,19 @@
4747 }
4848
4949 *, *::before, *::after { box-sizing: border-box; }
50-html, body { margin: 0; height: 100%; }
50+html { height: 100%; }
5151 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;
5257 background: var(--color-neutral-300);
5358 color: var(--color-text);
5459 font-family: var(--font-body);
5560 font-size: 15px; line-height: 1.55;
5661 /* 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
58- et fond gris visible dessous. */
62+ (PWA) fait rebondir les éléments flottants avec la page. */
5963 overflow: hidden;
6064 }
6165 h1, h2, h3, h4 {
@@ -130,6 +134,8 @@ input[type=number] { -moz-appearance: textfield; appearance: textfield; }
130134
131135 /* ═══ Couche applicative time-time ═══ */
132136
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. */
133139 .viewport { height: 100%; display: flex; justify-content: center; background: var(--color-neutral-300); }
134140 .shell {
135141 width: 100%; max-width: 430px; height: 100%; background: var(--color-bg);
@@ -148,18 +154,20 @@ input[type=number] { -moz-appearance: textfield; appearance: textfield; }
148154 .app-topline .who { margin-left: auto; font-size: 12px; opacity: .6; }
149155 .app-header h1 { margin: 0; font-size: 29px; letter-spacing: -0.02em; }
150156
151-/* 168px : la pill timer flotte au-dessus de la tab bar, le contenu passe
152- dessous. Seul conteneur scrollable de l'app (cf. body overflow hidden). */
157+/* Dégagement bas = tab bar + bandeau timer + marge, safe area comprise.
158+ Seul conteneur scrollable de l'app (cf. body overflow hidden). */
153159 .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));
155162 overflow-y: auto; -webkit-overflow-scrolling: touch;
156163 }
157164
158165 /* — timer — */
159166 .timer-slot {
160- position: fixed; left: 50%; transform: translateX(-50%); margin: 0;
161- width: 100%; max-width: 430px; padding: 0 var(--space-4);
162- bottom: calc(88px + env(safe-area-inset-bottom, 0px));
167+ position: absolute; left: 0; right: 0; margin: 0;
168+ padding: 0 var(--space-4);
169+ /* 104px : nettement décollé de la tab bar. */
170+ bottom: calc(104px + env(safe-area-inset-bottom, 0px));
163171 display: flex; justify-content: center; pointer-events: none; z-index: 4;
164172 }
165173 /* Bandeau pleine largeur (lancer / en cours / mesure en attente). */
@@ -218,8 +226,7 @@ input[type=number] { -moz-appearance: textfield; appearance: textfield; }
218226
219227 /* — tab bar — */
220228 .tabbar {
221- position: fixed; bottom: 0; left: 50%; transform: translateX(-50%);
222- width: 100%; max-width: 430px;
229+ position: absolute; bottom: 0; left: 0; right: 0;
223230 display: flex;
224231 padding: var(--space-2) var(--space-2)
225232 calc(var(--space-3) + 6px + env(safe-area-inset-bottom, 0px));
@@ -373,7 +380,7 @@ input[type=number] { -moz-appearance: textfield; appearance: textfield; }
373380 .col-bar a { display: block; min-height: 3px; width: 100%; }
374381
375382 /* — 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; }
377384 .sheet-wrap:target { display: flex; align-items: flex-end; justify-content: center; }
378385 .sheet-backdrop {
379386 position: absolute; inset: 0;
@@ -426,9 +433,8 @@ input[type=number] { -moz-appearance: textfield; appearance: textfield; }
426433 /* — toast — */
427434 /* En haut : le bas de l'écran est déjà occupé par la pill timer et la tab bar. */
428435 .toast {
429- position: fixed; top: calc(var(--space-4) + env(safe-area-inset-top, 0px));
430- left: 50%; transform: translateX(-50%);
431- width: calc(100% - 2 * var(--space-4)); max-width: calc(430px - 2 * var(--space-4));
436+ position: absolute; top: calc(var(--space-4) + env(safe-area-inset-top, 0px));
437+ left: var(--space-4); right: var(--space-4);
432438 background: var(--color-neutral-900); color: var(--color-neutral-100);
433439 padding: var(--space-3) var(--space-4); border-radius: 999px;
434440 font-size: 14px; text-align: center; box-shadow: var(--shadow-md);
@@ -436,7 +442,7 @@ input[type=number] { -moz-appearance: textfield; appearance: textfield; }
436442 animation: tt-drop .18s ease-out, tt-fade .4s ease-in 5s forwards; z-index: 5;
437443 }
438444
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; } }
440446 @keyframes tt-fade { to { opacity: 0; visibility: hidden; } }
441447 @keyframes tt-rise-sheet { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
442448 /* Motion réduit : pas de glissements, mais le toast doit quand même
@@ -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, iOS61 /* 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 saute62+ (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 passe157+/* 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ême448 /* Motion réduit : pas de glissements, mais le toast doit quand même