From a1c18cc404fecc3d2ae009f60a15028490c89210 Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 17:17:31 +0300 Subject: [PATCH 1/2] Polish call screen layout on desktop and mobile --- frontend/client/styles/callScreenTheme.css | 335 +++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 frontend/client/styles/callScreenTheme.css diff --git a/frontend/client/styles/callScreenTheme.css b/frontend/client/styles/callScreenTheme.css new file mode 100644 index 00000000..5714f189 --- /dev/null +++ b/frontend/client/styles/callScreenTheme.css @@ -0,0 +1,335 @@ +/* Dedicated visual layer for the WebRTC call panel. + * Keep call signaling/media logic in callPanelRuntime.jsx untouched. + */ + +.fixed.inset-0[class~='z-[80]'] { + --call-safe-top: max(16px, env(safe-area-inset-top)); + --call-safe-bottom: max(20px, env(safe-area-inset-bottom)); +} + +/* Audio/no-remote state: keep the avatar truly centered and let the caller + * identity/status block below provide the only name/status rendering. */ +.fixed.inset-0[class~='z-[80]'] + > .absolute.inset-0.flex.flex-col.items-center.justify-center + > img { + width: clamp(136px, 11vw, 164px); + height: clamp(136px, 11vw, 164px); + border: 3px solid rgba(56, 189, 248, 0.2); + box-shadow: + 0 0 0 7px rgba(255, 255, 255, 0.035), + 0 20px 55px rgba(0, 0, 0, 0.34), + 0 0 54px rgba(14, 165, 233, 0.1); +} + +.fixed.inset-0[class~='z-[80]'] + > .absolute.inset-0.flex.flex-col.items-center.justify-center + > h2 { + display: none; +} + +/* The old top-left caller/debug block becomes the single centered caller + * identity/status area. Call ID and quality debug text stay out of end-user UI. */ +.fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.top-0 + > div:first-child { + position: fixed; + z-index: 5; + left: 50%; + top: calc(50% + 96px); + width: min(88vw, 420px); + transform: translateX(-50%); + text-align: center; + pointer-events: none; +} + +.fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.top-0 + > div:first-child + > h1 { + margin: 0; + font-size: clamp(1.35rem, 2.4vw, 1.8rem); + line-height: 1.2; + font-weight: 700; + letter-spacing: -0.02em; + color: #fff; + text-shadow: 0 2px 20px rgba(0, 0, 0, 0.32); +} + +.fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.top-0 + > div:first-child + > p:first-of-type { + margin-top: 7px; + font-size: 0.92rem; + line-height: 1.35; + font-weight: 500; + color: rgba(255, 255, 255, 0.68); +} + +.fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.top-0 + > div:first-child + > p:not(:first-of-type) { + display: none; +} + +/* With a remote video feed, avoid covering the subject: caller name/status + * moves beneath the Secure badge instead of sitting in the center of video. */ +.fixed.inset-0[class~='z-[80]']:has(> video.absolute.inset-0) + > .absolute.inset-x-0.top-0 + > div:first-child { + top: calc(var(--call-safe-top) + 56px); +} + +.fixed.inset-0[class~='z-[80]']:has(> video.absolute.inset-0) + > .absolute.inset-x-0.top-0 + > div:first-child + > h1 { + font-size: 1rem; +} + +.fixed.inset-0[class~='z-[80]']:has(> video.absolute.inset-0) + > .absolute.inset-x-0.top-0 + > div:first-child + > p:first-of-type { + margin-top: 3px; + font-size: 0.78rem; +} + +/* Remove the old top-left visual treatment but retain a subtle top fade for + * close/security controls. */ +.fixed.inset-0[class~='z-[80]'] > .absolute.inset-x-0.top-0 { + min-height: 92px; + padding: 0; + background: linear-gradient( + to bottom, + rgba(0, 0, 0, 0.42) 0%, + rgba(0, 0, 0, 0.12) 54%, + transparent 100% + ); + pointer-events: none; +} + +/* Secure badge centered at the top. */ +.fixed.inset-0[class~='z-[80]'] > .absolute.inset-x-0.top-0::before { + content: 'Secure'; + position: absolute; + z-index: 7; + left: 50%; + top: var(--call-safe-top); + transform: translateX(-50%); + min-height: 34px; + display: flex; + align-items: center; + padding: 0 14px 0 31px; + border: 1px solid rgba(255, 255, 255, 0.09); + border-radius: 999px; + background: rgba(13, 26, 33, 0.64); + box-shadow: 0 8px 28px rgba(0, 0, 0, 0.18); + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); + color: rgba(255, 255, 255, 0.9); + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.01em; + white-space: nowrap; +} + +.fixed.inset-0[class~='z-[80]'] > .absolute.inset-x-0.top-0::after { + content: ''; + position: absolute; + z-index: 8; + left: calc(50% - 31px); + top: calc(var(--call-safe-top) + 12px); + width: 10px; + height: 10px; + border-radius: 999px; + background: #22c55e; + box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.5); + animation: syncchat-call-secure-pulse 1.8s ease-out infinite; +} + +@keyframes syncchat-call-secure-pulse { + 0% { + box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.48); + } + 65% { + box-shadow: 0 0 0 7px rgba(34, 197, 94, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); + } +} + +/* Close stays top-right and respects phone safe areas. */ +.fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.top-0 + > button[aria-label='Close'] { + position: fixed; + z-index: 9; + top: var(--call-safe-top); + right: max(16px, env(safe-area-inset-right)); + width: 44px; + height: 44px; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.1); + box-shadow: 0 8px 26px rgba(0, 0, 0, 0.18); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + pointer-events: auto; +} + +.fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.top-0 + > button[aria-label='Close']:hover { + background: rgba(255, 255, 255, 0.16); +} + +.fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.top-0 + > button[aria-label='Close'] + svg { + width: 24px; + height: 24px; +} + +/* Local video preview should never collide with the Secure/close row. */ +.fixed.inset-0[class~='z-[80]'] > video.absolute.right-4.top-20 { + top: calc(var(--call-safe-top) + 62px); + right: max(14px, env(safe-area-inset-right)); + border-radius: 16px; + box-shadow: 0 12px 34px rgba(0, 0, 0, 0.28); +} + +/* Bottom control dock: fixed, centered and safe-area aware on desktop/mobile. */ +.fixed.inset-0[class~='z-[80]'] > .absolute.inset-x-0.bottom-6, +.fixed.inset-0[class~='z-[80]'] > .absolute.inset-x-0.bottom-8 { + z-index: 10; + bottom: calc(var(--call-safe-bottom) + 2px); + align-items: center; + gap: 12px; + padding: 0 14px; +} + +.fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.bottom-6 + > button, +.fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.bottom-8 + > button { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + border: 1px solid rgba(255, 255, 255, 0.08); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); + transition: + transform 150ms ease, + background 150ms ease, + border-color 150ms ease; +} + +.fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.bottom-6 + > button:hover, +.fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.bottom-8 + > button:hover { + transform: translateY(-2px); +} + +.fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.bottom-6 + > button + svg, +.fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.bottom-8 + > button + svg { + width: 21px; + height: 21px; +} + +@media (max-width: 640px) { + .fixed.inset-0[class~='z-[80]'] + > .absolute.inset-0.flex.flex-col.items-center.justify-center + > img { + width: 146px; + height: 146px; + } + + .fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.top-0 + > div:first-child { + top: calc(50% + 94px); + width: calc(100vw - 36px); + } + + .fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.top-0 + > div:first-child + > h1 { + font-size: 1.45rem; + } + + .fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.bottom-6, + .fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.bottom-8 { + gap: 10px; + padding-inline: 10px; + } + + .fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.bottom-6 + > button:not([aria-label='End']), + .fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.bottom-8 + > button:not([aria-label='Reject']):not([aria-label='Accept']) { + width: 50px; + height: 50px; + } + + .fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.bottom-6 + > button[aria-label='End'] { + width: 58px; + height: 58px; + } + + .fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.bottom-8 + > button[aria-label='Reject'], + .fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.bottom-8 + > button[aria-label='Accept'] { + width: 62px; + height: 62px; + } +} + +@media (max-height: 620px) { + .fixed.inset-0[class~='z-[80]'] + > .absolute.inset-0.flex.flex-col.items-center.justify-center + > img { + width: 118px; + height: 118px; + } + + .fixed.inset-0[class~='z-[80]'] + > .absolute.inset-x-0.top-0 + > div:first-child { + top: calc(50% + 80px); + } +} + +@media (prefers-reduced-motion: reduce) { + .fixed.inset-0[class~='z-[80]'] > .absolute.inset-x-0.top-0::after { + animation: none; + } +} From 1094a4a0a78a2affa6a010f5ed9e983f2cde60cf Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 17:17:56 +0300 Subject: [PATCH 2/2] Load responsive call screen theme --- frontend/client/index.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/client/index.jsx b/frontend/client/index.jsx index 9dd17798..13e88137 100644 --- a/frontend/client/index.jsx +++ b/frontend/client/index.jsx @@ -19,6 +19,7 @@ import installImageFallbacks from './helpers/imageFallbacks'; import installRuntimeBranding from './helpers/runtimeBranding'; import './styles/chatToolsTheme.css'; import './styles/layoutFixes.css'; +import './styles/callScreenTheme.css'; import { registerServiceWorker } from './pwa/registerSW'; import { requestNotificationPermission } from './pwa/notifications';