From a02facff1ae8c27095c298cef867bbf8d319e305 Mon Sep 17 00:00:00 2001 From: Ricky Stretch Date: Fri, 13 Mar 2026 10:02:04 -0400 Subject: [PATCH] v0.8.4 full refresh --- .env.example | 2 +- backend/package.json | 2 +- build.sh | 2 +- frontend/index.html | 2 +- frontend/package.json | 2 +- frontend/src/components/Message.css | 16 +++--- frontend/src/components/MessageInput.css | 2 +- frontend/src/index.css | 2 + frontend/src/main.jsx | 67 ++++++++++++++++++++---- 9 files changed, 73 insertions(+), 24 deletions(-) diff --git a/.env.example b/.env.example index 497dcb9..e5fa1a0 100644 --- a/.env.example +++ b/.env.example @@ -7,7 +7,7 @@ TZ=UTC # Copy this file to .env and customize # Image version to run (set by build.sh, or use 'latest') -JAMA_VERSION=0.8.2 +JAMA_VERSION=0.8.4 # Default admin credentials (used on FIRST RUN only) ADMIN_NAME=Admin User diff --git a/backend/package.json b/backend/package.json index 9d685f6..9793ab2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "jama-backend", - "version": "0.8.2", + "version": "0.8.4", "description": "TeamChat backend server", "main": "src/index.js", "scripts": { diff --git a/build.sh b/build.sh index ffc5d1a..011c847 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.8.2}" +VERSION="${1:-0.8.4}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="jama" diff --git a/frontend/index.html b/frontend/index.html index 4e6cbdc..2666525 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,7 +3,7 @@ - + diff --git a/frontend/package.json b/frontend/package.json index 551af86..0d7cb69 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jama-frontend", - "version": "0.8.2", + "version": "0.8.4", "private": true, "scripts": { "dev": "vite", diff --git a/frontend/src/components/Message.css b/frontend/src/components/Message.css index 1cbaa93..d1d67f5 100644 --- a/frontend/src/components/Message.css +++ b/frontend/src/components/Message.css @@ -78,7 +78,7 @@ .own .message-body { align-items: flex-end; } .msg-name { - font-size: 12px; + font-size: calc(0.75rem * var(--font-scale)); font-weight: 600; color: var(--text-secondary); margin-bottom: 3px; @@ -98,8 +98,8 @@ .reply-bar { width: 3px; background: var(--primary); border-radius: 2px; flex-shrink: 0; } -.reply-name { font-size: 11px; font-weight: 600; color: var(--primary); } -.reply-text { font-size: 12px; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px; } +.reply-name { font-size: calc(0.6875rem * var(--font-scale)); font-weight: 600; color: var(--primary); } +.reply-text { font-size: calc(0.75rem * var(--font-scale)); color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px; } /* Bubble row */ .msg-bubble-wrap { @@ -204,10 +204,10 @@ border: 1px dashed var(--border); } -.deleted-text { font-size: 13px; color: var(--text-tertiary); font-style: italic; } +.deleted-text { font-size: calc(0.8125rem * var(--font-scale)); color: var(--text-tertiary); font-style: italic; } .msg-text { - font-size: 14px; + font-size: calc(0.875rem * var(--font-scale)); line-height: 1.5; white-space: pre-wrap; } @@ -236,7 +236,7 @@ } .msg-time { - font-size: 11px; + font-size: calc(0.6875rem * var(--font-scale)); color: var(--text-tertiary); white-space: nowrap; flex-shrink: 0; @@ -260,11 +260,11 @@ border-radius: 12px; background: var(--surface); border: 1px solid var(--border); - font-size: 14px; + font-size: calc(0.875rem * var(--font-scale)); cursor: pointer; transition: var(--transition); } -.reaction-count { font-size: 12px; color: var(--text-secondary); } +.reaction-count { font-size: calc(0.75rem * var(--font-scale)); color: var(--text-secondary); } .reaction-btn.active { background: var(--primary-light); border-color: var(--primary); } .reaction-btn.active .reaction-count { color: var(--primary); } .reaction-btn:hover { background: var(--primary-light); } diff --git a/frontend/src/components/MessageInput.css b/frontend/src/components/MessageInput.css index bc3d9c9..94cfee5 100644 --- a/frontend/src/components/MessageInput.css +++ b/frontend/src/components/MessageInput.css @@ -151,7 +151,7 @@ padding: 10px 14px; border: 1px solid var(--border); border-radius: 20px; - font-size: 14px; + font-size: calc(0.875rem * var(--font-scale)); line-height: 1.4; font-family: var(--font); color: var(--text-primary); diff --git a/frontend/src/index.css b/frontend/src/index.css index b6452a8..3d00ac9 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -26,6 +26,7 @@ --shadow-lg: 0 4px 20px rgba(0,0,0,0.18); --transition: 150ms cubic-bezier(0.4, 0, 0.2, 1); --font: 'Google Sans', 'Roboto', sans-serif; + --font-scale: 1; } html, body, #root { height: 100%; min-width: 320px; font-family: var(--font); color: var(--text-primary); background: var(--background); } @@ -33,6 +34,7 @@ html, body, #root { height: 100%; min-width: 320px; font-family: var(--font); co /* Disable pull-to-refresh in PWA standalone mode — prevents viewport shift bug */ html { overscroll-behavior-y: none; + font-size: 100%; /* inherits system font size — allows Android accessibility font scaling */ } button { font-family: var(--font); cursor: pointer; border: none; background: none; } diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 7e3b45e..266566e 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -14,25 +14,72 @@ if ('serviceWorker' in navigator) { -// Disable pull-to-refresh in PWA standalone mode to prevent viewport shift bug. -// The CSS overscroll-behavior-y:none handles most browsers; this catches the rest. +// ─── 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 = 'jama_font_scale'; + const MIN_SCALE = 0.8; + const MAX_SCALE = 2.0; + const isStandalone = window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true; - if (!isStandalone) return; - let startY = 0; - document.addEventListener('touchstart', e => { - startY = e.touches[0].clientY; + // 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', e => { - // Only block downward pull at the very top of the document - const dy = e.touches[0].clientY - startY; - if (dy > 0 && (document.documentElement.scrollTop === 0 || document.body.scrollTop === 0)) { + 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