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 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( );