From a8269df0b485f06da35ea6182fe317418d2e4ba4 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Tue, 9 Dec 2025 23:58:04 +0000 Subject: [PATCH] Cleaner up registration link creation. Don't show the dialog until when there is a valid link. Implement a global blur backdrop with nicer effect and proper scrollbar handling (avoiding layout shifting a bit). Use the global backdrop to ensure consistent visuals between authentication and the modal being shown, along with in/out transitions. --- frontend/src/assets/style.css | 54 +++++++++++--- .../src/components/RegistrationLinkModal.vue | 74 ++++++++----------- frontend/src/utils/api.js | 28 +++++++ 3 files changed, 102 insertions(+), 54 deletions(-) diff --git a/frontend/src/assets/style.css b/frontend/src/assets/style.css index fbd8630..a8595d3 100644 --- a/frontend/src/assets/style.css +++ b/frontend/src/assets/style.css @@ -337,10 +337,13 @@ th { .global-status { position: fixed; top: 1.5rem; - left: 50%; - transform: translateX(-50%); + left: 0; + right: 0; + margin: 0 auto; z-index: 1200; - min-width: min(520px, calc(100vw - 2rem)); + width: fit-content; + min-width: min(520px, calc(100% - 2rem)); + max-width: calc(100% - 2rem); display: none; } @@ -377,10 +380,11 @@ th { .dialog-overlay { position: fixed; - inset: 0; + top: 0; + bottom: 0; + left: 0; + right: 0; background: transparent; - backdrop-filter: blur(.1rem) brightness(0.7); - -webkit-backdrop-filter: blur(.1rem) brightness(0.7); z-index: 1100; display: flex; align-items: center; @@ -683,9 +687,41 @@ th { } } -/* Auth iframe overlay styles */ +/* Global backdrop controlled by api.js ref-counting */ +body::before { + content: ''; + position: fixed; + inset: 0; + z-index: 1099; + background: transparent; + backdrop-filter: blur(0) brightness(1); + -webkit-backdrop-filter: blur(0) brightness(1); + pointer-events: none; + visibility: hidden; + transition: all 0.2s ease-out; +} + +body.has-backdrop::before { + -webkit-backdrop-filter: blur(.2rem) brightness(0.5); + backdrop-filter: blur(.2rem) brightness(0.5); + visibility: visible; +} + +/* Prevent scrolling via html to avoid scrollbar layout shift */ +html:has(body.has-backdrop), +html:has(#auth-iframe) { + overflow: clip; +} + +body.has-backdrop { + overflow: auto; +} + +/* Make fixed elements (toast, dialog) position relative to body, not viewport */ +/* This works because transform creates a new containing block for fixed children */ +body.has-backdrop, body:has(#auth-iframe) { - overflow: hidden; + transform: translateZ(0); } #auth-iframe { @@ -698,8 +734,6 @@ body:has(#auth-iframe) { z-index: 9999; color-scheme: auto; background: transparent; - backdrop-filter: blur(.1rem) brightness(0.7); - -webkit-backdrop-filter: blur(.1rem) brightness(0.7); } .slot-machine { diff --git a/frontend/src/components/RegistrationLinkModal.vue b/frontend/src/components/RegistrationLinkModal.vue index 9858b26..b862be1 100644 --- a/frontend/src/components/RegistrationLinkModal.vue +++ b/frontend/src/components/RegistrationLinkModal.vue @@ -1,5 +1,5 @@ diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index b9bf72b..d48d985 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -49,6 +49,29 @@ let authPromise = null let authResolve = null let authReject = null +// Global backdrop ref-count (works independently of Pinia store) +let backdropHolders = 0 + +/** + * Hold global backdrop (increment ref-count). + * Multiple callers can hold the backdrop; it only hides when all release. + */ +export function holdGlobalBackdrop() { + backdropHolders++ + document.body.classList.add('has-backdrop') +} + +/** + * Release global backdrop (decrement ref-count). + * Backdrop hides only when ref-count reaches zero. + */ +export function releaseGlobalBackdrop() { + backdropHolders = Math.max(0, backdropHolders - 1) + if (backdropHolders === 0) { + document.body.classList.remove('has-backdrop') + } +} + // Cache for auth iframe URL by mode const authIframeUrlCache = {} @@ -93,6 +116,7 @@ export function isAuthIframeOpen() { /** * Show the authentication iframe and return a promise that resolves on success. * If an auth iframe is already open (from any source), hooks into its completion. + * Uses global backdrop system to avoid flicker between auth and caller's UI. * @param {string} iframeUrl - The URL for the iframe src * @returns {Promise} * @throws {AuthCancelledError} - If authentication is cancelled by user @@ -119,6 +143,9 @@ export function showAuthIframe(iframeUrl) { // Remove existing iframe if any hideAuthIframe() + // Hold global backdrop for auth iframe + holdGlobalBackdrop() + // Create new iframe for authentication using src URL authIframe = document.createElement('iframe') authIframe.id = 'auth-iframe' @@ -134,6 +161,7 @@ function hideAuthIframe() { if (authIframe) { authIframe.remove() authIframe = null + releaseGlobalBackdrop() } }