96 lines
3.5 KiB
JavaScript
96 lines
3.5 KiB
JavaScript
import React from 'react';
|
|
import ReactDOM from 'react-dom/client';
|
|
import App from './App.jsx';
|
|
import './index.css';
|
|
|
|
// Register service worker
|
|
if ('serviceWorker' in navigator) {
|
|
window.addEventListener('load', () => {
|
|
navigator.serviceWorker.register('/sw.js')
|
|
.then(reg => console.log('[SW] Registered, scope:', reg.scope))
|
|
.catch(err => console.error('[SW] Registration failed:', err));
|
|
});
|
|
}
|
|
|
|
|
|
|
|
// ─── Touch gesture handler ───────────────────────────────────────────────────
|
|
// Handles two behaviours in one unified listener set to avoid conflicts:
|
|
//
|
|
// 1. PINCH → font scale only (not viewport zoom).
|
|
// viewport has user-scalable=no so the browser never zooms the layout.
|
|
// We intercept the pinch and adjust --font-scale on <html> instead,
|
|
// which scales only text (rem-based font sizes). Persisted to localStorage.
|
|
// On first launch, html { font-size: 100% } inherits the Android system
|
|
// font size as the 1rem baseline automatically.
|
|
//
|
|
// 2. PULL-TO-REFRESH → blocked in PWA standalone mode only.
|
|
(function () {
|
|
const LS_KEY = 'rosterchirp_font_scale';
|
|
const MIN_SCALE = 0.8;
|
|
const MAX_SCALE = 2.0;
|
|
|
|
const isStandalone = window.matchMedia('(display-mode: standalone)').matches
|
|
|| window.navigator.standalone === true;
|
|
|
|
// Restore saved font scale on launch
|
|
const saved = parseFloat(localStorage.getItem(LS_KEY));
|
|
let currentScale = (saved >= MIN_SCALE && saved <= MAX_SCALE) ? saved : 1.0;
|
|
document.documentElement.style.setProperty('--font-scale', currentScale);
|
|
|
|
let pinchStartDist = null;
|
|
let pinchStartScale = currentScale;
|
|
let singleStartY = 0;
|
|
|
|
function getTouchDist(e) {
|
|
const dx = e.touches[0].clientX - e.touches[1].clientX;
|
|
const dy = e.touches[0].clientY - e.touches[1].clientY;
|
|
return Math.sqrt(dx * dx + dy * dy);
|
|
}
|
|
|
|
document.addEventListener('touchstart', function (e) {
|
|
if (e.touches.length === 2) {
|
|
pinchStartDist = getTouchDist(e);
|
|
pinchStartScale = currentScale;
|
|
} else if (e.touches.length === 1) {
|
|
singleStartY = e.touches[0].clientY;
|
|
}
|
|
}, { passive: true });
|
|
|
|
document.addEventListener('touchmove', function (e) {
|
|
if (e.touches.length === 2 && pinchStartDist !== null) {
|
|
// Two-finger pinch: scale fonts, not viewport
|
|
e.preventDefault();
|
|
const ratio = getTouchDist(e) / pinchStartDist;
|
|
const newScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, pinchStartScale * ratio));
|
|
currentScale = Math.round(newScale * 100) / 100;
|
|
document.documentElement.style.setProperty('--font-scale', currentScale);
|
|
} else if (e.touches.length === 1 && isStandalone) {
|
|
// Single finger: block pull-to-refresh at top of page
|
|
const dy = e.touches[0].clientY - singleStartY;
|
|
if (dy > 0 && document.documentElement.scrollTop === 0 && document.body.scrollTop === 0) {
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
}, { passive: false });
|
|
|
|
document.addEventListener('touchend', function (e) {
|
|
if (e.touches.length < 2 && pinchStartDist !== null) {
|
|
pinchStartDist = null;
|
|
localStorage.setItem(LS_KEY, currentScale);
|
|
}
|
|
}, { passive: true });
|
|
})();
|
|
|
|
// Clear badge count when user focuses the app
|
|
window.addEventListener('focus', () => {
|
|
if (navigator.clearAppBadge) navigator.clearAppBadge().catch(() => {});
|
|
navigator.serviceWorker?.controller?.postMessage({ type: 'CLEAR_BADGE' });
|
|
});
|
|
|
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
<React.StrictMode>
|
|
<App />
|
|
</React.StrictMode>
|
|
);
|