Use 100vh and the viewport-fit toggle for the installed app
Researched it instead of guessing again. Launched from the homescreen, WebKit resolves the DYNAMIC viewport and every env() inset once against a provisional geometry and does not revisit them until the geometry is "exercised" — a rotation. That is the whole bug, and it also explains why listening for a signal could never fix it: nothing fires, so there is nothing to observe. Two known workarounds, both applied. Standalone has no toolbars, so 100vh is correct AND stable from the first frame — dvh only exists for the browser's disappearing toolbars, so it now applies only there. And toggling viewport-fit off and back on at boot forces WebKit to recalculate, without the user rotating anything. Countdown moved up off the ad boards — the strip's own projected line is its ceiling now, so it stays in the stands whatever the phone does to the layout, with a 1px shadow to carry it over the crowd. Sources: gist.github.com/fozzedout/5e77925381991a9570151550992baf14 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9a4d921706
commit
614e1ee469
3 files changed
+33
-7
No files matched your search
@@ -633,6 +633,7 @@ function drawModal(m) {
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ frame ----
|
||||
const AD_H = 14; // px tall the courtside ad strip is drawn — also the ceiling for centred HUD text
|
||||
export function render() {
|
||||
ctx.setTransform(RES, 0, 0, RES, 0, 0); // logical W x H coordinates at RES density
|
||||
// ease the aim camera FIRST, before anything projects — one camera per frame (see proj)
|
||||
@@ -655,7 +656,7 @@ export function render() {
|
||||
// pinning to that projected line keeps the boards clear of the lawn in BOTH orientations
|
||||
// (a fixed baseline offset over-/under-shoots because camF differs portrait vs landscape).
|
||||
const standsBottom = proj(0, 0, -6).y;
|
||||
const adH = 14, crowdH = standsBottom - adH + 2;
|
||||
const crowdH = standsBottom - AD_H + 2;
|
||||
const crowdImg = SPR['x_crowd'];
|
||||
if (crowdImg) {
|
||||
// Applause is a real TWO-FRAME animation now: x_crowd is the seated sheet, x_crowd2 the same
|
||||
@@ -684,9 +685,9 @@ export function render() {
|
||||
// courtside advertising: tiled sponsor panels at native aspect (never half-cut text)
|
||||
const ads = SPR['x_ads'];
|
||||
if (ads) {
|
||||
const tw = ads.width * (adH / ads.height);
|
||||
for (let x = 0; x < W; x += tw) ctx.drawImage(ads, x, standsBottom - adH, tw, adH);
|
||||
} else { ctx.fillStyle = pal.board; ctx.fillRect(0, standsBottom - adH, W, adH); }
|
||||
const tw = ads.width * (AD_H / ads.height);
|
||||
for (let x = 0; x < W; x += tw) ctx.drawImage(ads, x, standsBottom - AD_H, tw, AD_H);
|
||||
} else { ctx.fillStyle = pal.board; ctx.fillRect(0, standsBottom - AD_H, W, AD_H); }
|
||||
// the rot dims the stadium: one tint over crowd + boards, fading home during duels
|
||||
// the rot dims the stadium — but 0.5 on top of an already-dark crowd sheet buried the stands in
|
||||
// splatter mode, where you spend most of the game. 0.26 still reads as "the lights went wrong".
|
||||
@@ -943,8 +944,14 @@ export function render() {
|
||||
G.phase === 'over' ? G.overT : 0,
|
||||
G.interlude ? G.interlude.introT : 0);
|
||||
if (waitT > 0.5) {
|
||||
ctx.font = F(10); ctx.fillStyle = hexA(UI.warm, 0.85);
|
||||
ctx.fillText('RESUMING IN ' + Math.ceil(waitT), W / 2, Math.round(H * 0.4)); // out in the open, not tucked under the HUD
|
||||
// Kept clear of the ad strip — centred on H alone it landed right on the boards, which is the
|
||||
// busiest band on the screen. The strip's own projected line is the ceiling, so it stays legible
|
||||
// whatever the phone's height does to the layout. A 1px shadow, not a box, carries it over the crowd.
|
||||
const txt = 'RESUMING IN ' + Math.ceil(waitT);
|
||||
const y = Math.min(Math.round(H * 0.34), Math.round(proj(0, 0, -6).y) - AD_H - 16);
|
||||
ctx.font = F(10);
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.55)'; ctx.fillText(txt, W / 2 + 1, y + 1);
|
||||
ctx.fillStyle = hexA(UI.warm, 0.92); ctx.fillText(txt, W / 2, y);
|
||||
}
|
||||
if (G.phase === 'pretoss' && score.server === 'P' && G.mode !== 'eaten' && !G.horde && !(G.mgT > 0) && !G.aim && waitT <= 0.5) { // while pulling, the draw meter owns this slot
|
||||
ctx.fillStyle = UI.warm; ctx.font = F(8);
|
||||
|
||||
Reference in new issue
Block a user