|
5 | 5 | <script>if(window.self===window.top)document.documentElement.classList.add('standalone')</script> |
6 | 6 | <style>html.standalone .back-btn,html.standalone #back,html.standalone #info,html.standalone #ui,html.standalone #mode-btn,html.standalone .panel,html.standalone .controls,html.standalone #hint,html.standalone #hud{display:revert!important}</style> |
7 | 7 | <meta charset="UTF-8"> |
8 | | -<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover"> |
| 8 | +<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content"> |
9 | 9 | <title>Logistic Map</title> |
10 | 10 | <style> |
11 | 11 | * { margin: 0; padding: 0; box-sizing: border-box; } |
12 | | -html, body { width: 100%; height: 100%; } |
| 12 | +html, body { width: 100%; min-height: 100svh; min-height: -webkit-fill-available; height: 100%; height: 100dvh; } |
13 | 13 | body { |
14 | 14 | background: radial-gradient(circle at top, #131a2a 0%, #080b13 46%, #040509 100%); |
15 | 15 | overflow: hidden; |
16 | 16 | font-family: ui-monospace, SFMono-Regular, Menlo, monospace; |
17 | 17 | color: #dfe7ff; |
18 | | -height:100dvh} |
| 18 | + overscroll-behavior: none; |
| 19 | + -webkit-text-size-adjust: 100%; |
| 20 | + touch-action: none; |
| 21 | + padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); |
| 22 | +} |
19 | 23 | canvas { display: block; width: 100%; height: 100%; touch-action: none; } |
20 | 24 | #back { |
21 | 25 | position: fixed; |
|
107 | 111 | #reseed:hover { |
108 | 112 | background: rgba(223,231,255,0.12); |
109 | 113 | } |
| 114 | +#ui, #ui * , #back { touch-action: manipulation; } |
110 | 115 | @media (max-width: 620px) { |
111 | 116 | #hint { |
112 | 117 | top: auto; |
|
213 | 218 |
|
214 | 219 | function resize() { |
215 | 220 | const dpr = Math.min(window.devicePixelRatio || 1, 2); |
216 | | - canvas.width = Math.floor(window.innerWidth * dpr); |
217 | | - canvas.height = Math.floor(window.innerHeight * dpr); |
218 | | - canvas.style.width = window.innerWidth + 'px'; |
219 | | - canvas.style.height = window.innerHeight + 'px'; |
| 221 | + const viewport = window.visualViewport; |
| 222 | + const width = Math.round(viewport?.width || window.innerWidth || document.documentElement.clientWidth || 0); |
| 223 | + const height = Math.round(viewport?.height || window.innerHeight || document.documentElement.clientHeight || 0); |
| 224 | + canvas.width = Math.max(1, Math.floor(width * dpr)); |
| 225 | + canvas.height = Math.max(1, Math.floor(height * dpr)); |
| 226 | + canvas.style.width = width + 'px'; |
| 227 | + canvas.style.height = height + 'px'; |
220 | 228 | ctx.setTransform(dpr, 0, 0, dpr, 0, 0); |
221 | 229 |
|
222 | | - W = window.innerWidth; |
223 | | - H = window.innerHeight; |
| 230 | + W = Math.max(width, 1); |
| 231 | + H = Math.max(height, 1); |
224 | 232 |
|
225 | 233 | const marginX = Math.min(56, W * 0.08); |
226 | 234 | const topPad = inIframe ? 18 : Math.min(86, H * 0.14); |
|
503 | 511 | resetOrbit(true); |
504 | 512 | }); |
505 | 513 |
|
506 | | -window.addEventListener('resize', resize); |
507 | | -if (window.visualViewport) window.visualViewport.addEventListener('resize', resize); |
508 | | -window.addEventListener('orientationchange', () => { setTimeout(resize, 200); }); |
| 514 | +let resizeQueued = false; |
| 515 | +function scheduleResize() { |
| 516 | + if (resizeQueued) return; |
| 517 | + resizeQueued = true; |
| 518 | + requestAnimationFrame(() => { |
| 519 | + resizeQueued = false; |
| 520 | + resize(); |
| 521 | + }); |
| 522 | +} |
| 523 | + |
| 524 | +window.addEventListener('resize', scheduleResize, { passive: true }); |
| 525 | +window.addEventListener('pageshow', scheduleResize, { passive: true }); |
| 526 | +document.addEventListener('visibilitychange', () => { |
| 527 | + if (!document.hidden) scheduleResize(); |
| 528 | +}, { passive: true }); |
| 529 | +if (window.visualViewport) { |
| 530 | + window.visualViewport.addEventListener('resize', scheduleResize, { passive: true }); |
| 531 | + window.visualViewport.addEventListener('scroll', scheduleResize, { passive: true }); |
| 532 | +} |
| 533 | +let dprQuery; |
| 534 | +function unwatchDpr() { |
| 535 | + if (!dprQuery) return; |
| 536 | + if (typeof dprQuery.removeEventListener === 'function') dprQuery.removeEventListener('change', watchDpr); |
| 537 | + else if (typeof dprQuery.removeListener === 'function') dprQuery.removeListener(watchDpr); |
| 538 | +} |
| 539 | +function watchDpr() { |
| 540 | + unwatchDpr(); |
| 541 | + dprQuery = matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`); |
| 542 | + if (typeof dprQuery.addEventListener === 'function') dprQuery.addEventListener('change', watchDpr, { once: true }); |
| 543 | + else if (typeof dprQuery.addListener === 'function') dprQuery.addListener(watchDpr); |
| 544 | + scheduleResize(); |
| 545 | +} |
| 546 | +watchDpr(); |
| 547 | +window.addEventListener('orientationchange', () => { setTimeout(scheduleResize, 200); }, { passive: true }); |
509 | 548 |
|
510 | 549 | requestAnimationFrame(() => { |
511 | 550 | resize(); |
|
0 commit comments