From 5b1a50f1486cdef9703a6d3adb528cbddc2e5fec Mon Sep 17 00:00:00 2001 From: Grzegorz Kucmierz Date: Fri, 27 Feb 2026 19:02:49 +0000 Subject: [PATCH] chore: prepare release; reintroduce front camera CSS mirror, stabilize QR Scanner --- src/components/tools/QrScanner.vue | 113 ++++++++++++++++++++++++++++- src/style.css | 2 + 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/src/components/tools/QrScanner.vue b/src/components/tools/QrScanner.vue index 0cde6b9..69df65b 100644 --- a/src/components/tools/QrScanner.vue +++ b/src/components/tools/QrScanner.vue @@ -9,12 +9,60 @@ const scannedCodes = ref([]) const hasMultipleCameras = ref(false) const isFullscreen = ref(false) const videoAspect = ref(1) +const isFront = computed(() => facingMode.value === 'user') +const wrapperRef = ref(null) +let frontMirrorObserver = null +const bgCanvas = ref(null) +let bgRafId = null const updateVideoAspect = () => { const videoEl = document.querySelector('.camera-wrapper video') if (videoEl && videoEl.videoWidth && videoEl.videoHeight) { videoAspect.value = videoEl.videoWidth / videoEl.videoHeight } } +const startBackgroundLoop = () => { + const draw = () => { + const videoEl = document.querySelector('.camera-wrapper video') + const canvas = bgCanvas.value + if (!videoEl || !canvas) { + bgRafId = requestAnimationFrame(draw) + return + } + const vw = videoEl.videoWidth || 0 + const vh = videoEl.videoHeight || 0 + if (!vw || !vh) { + bgRafId = requestAnimationFrame(draw) + return + } + const cw = Math.floor(window.innerWidth) + const ch = Math.floor(window.innerHeight * 0.5) + if (canvas.width !== cw || canvas.height !== ch) { + canvas.width = cw + canvas.height = ch + } + const ctx = canvas.getContext('2d') + if (ctx) { + // cover horizontally: scale by width, crop top/bottom + const scale = cw / vw + const srcH = ch / scale + const sx = 0 + const sy = Math.max(0, (vh - srcH) / 2) + ctx.clearRect(0, 0, cw, ch) + ctx.drawImage(videoEl, sx, sy, vw, srcH, 0, 0, cw, ch) + } + bgRafId = requestAnimationFrame(draw) + } + if (bgRafId) cancelAnimationFrame(bgRafId) + bgRafId = requestAnimationFrame(draw) +} + +// front mirror canvas removed to restore stable behavior +const stopBackgroundLoop = () => { + if (bgRafId) { + cancelAnimationFrame(bgRafId) + bgRafId = null + } +} const desktopFullscreenStyle = computed(() => { if (!isFullscreen.value) return {} const isDesktop = window.innerWidth >= 768 @@ -95,8 +143,26 @@ const onDetect = (detectedCodes) => { const onCameraOn = async (capabilities) => { // Camera is ready setTimeout(updateVideoAspect, 100) + setTimeout(startBackgroundLoop, 150) + // Flip is handled via global CSS; no JS flips needed } +const ensureFrontMirror = () => { + // No-op: mirror is applied via CSS selectors +} + +const startFrontMirrorObserver = () => { + // No-op: mirror is applied via CSS selectors +} + +const stopFrontMirrorObserver = () => { + // No-op +} + +watch(isFront, () => { + // CSS-based; nothing to do +}) + const paintDetections = (detectedCodes, ctx) => { try { ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height) @@ -171,10 +237,20 @@ onMounted(() => { checkCameras() loadHistory() window.addEventListener('resize', updateVideoAspect) + window.addEventListener('resize', startBackgroundLoop) + watch(isFullscreen, (fs) => { + if (fs) { + startBackgroundLoop() + } else { + stopBackgroundLoop() + } + }, { immediate: true }) }) onUnmounted(() => { window.removeEventListener('resize', updateVideoAspect) + window.removeEventListener('resize', startBackgroundLoop) + stopBackgroundLoop() }) const switchCamera = (event) => { @@ -244,11 +320,22 @@ const isUrl = (string) => {
+ -
+
{ border-radius: 0; border: none; margin: 0; + z-index: 1; +} + +.camera-bg { + position: absolute; + left: 0; + top: 0; + width: 100vw; + height: 50vh; + filter: blur(16px) saturate(110%); + opacity: 0.9; z-index: 0; } +/* front mirror canvas removed */ + .error-overlay { position: absolute; top: 0; @@ -614,6 +714,17 @@ const isUrl = (string) => { inset: 0 !important; } +/* Front camera mirror (CSS-only) */ +.camera-wrapper.is-front :deep(video) { + transform: scaleX(-1); + transform-origin: center; +} +.camera-wrapper.is-front :deep(#qrcode-stream-pause-frame), +.camera-wrapper.is-front :deep(#qrcode-stream-overlay) { + transform: scaleX(-1); + transform-origin: center; +} + @media (min-width: 768px) { :deep(.scanner-content.is-fullscreen .camera-wrapper video) { object-fit: contain !important; diff --git a/src/style.css b/src/style.css index 548947f..e3f91cd 100644 --- a/src/style.css +++ b/src/style.css @@ -125,6 +125,8 @@ body { } } +/* Removed global front camera mirror to restore stability */ + /* --- Shared styles for all tools (moved from tools.css) --- */ .tool-container {