Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/client/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
335 changes: 335 additions & 0 deletions frontend/client/styles/callScreenTheme.css
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading