[call-me] - feat: UX overhaul — calling overlays, sign-in redesign, empty states, i18n updates, update dep
This commit is contained in:
+22
-7
@@ -3,12 +3,16 @@
|
||||
"appTitle": "Call-me - مكالمات فيديو فورية",
|
||||
"appDescription": "خيارك للمكالمات المرئية الفورية!",
|
||||
"signIn": {
|
||||
"title": "تسجيل الدخول",
|
||||
"username": "أدخل اسم المستخدم",
|
||||
"button": "تسجيل الدخول",
|
||||
"title": "انضمام",
|
||||
"username": "اختر اسم العرض",
|
||||
"button": "انضمام",
|
||||
"camera": "الكاميرا",
|
||||
"microphone": "الميكروفون",
|
||||
"enterUsername": "يرجى إدخال اسم المستخدم"
|
||||
"enterUsername": "يرجى إدخال اسم العرض",
|
||||
"subtitle": "مكالمات فيديو فورية. لا حاجة للتسجيل.",
|
||||
"directCallLabel": "أو اتصل بشخص مباشرة",
|
||||
"directCallPlaceholder": "أدخل اسمًا للاتصال",
|
||||
"directCallButton": "اتصل الآن"
|
||||
},
|
||||
"room": {
|
||||
"sessionTime": "مدة الجلسة",
|
||||
@@ -27,7 +31,18 @@
|
||||
"hangupWith": "إنهاء المكالمة مع __username__",
|
||||
"callUser": "اتصل بـ __username__",
|
||||
"videoOff": "الفيديو متوقف",
|
||||
"videoDisabled": "الفيديو معطّل"
|
||||
"videoDisabled": "الفيديو معطّل",
|
||||
"noUsersOnline": "لا يوجد مستخدمون آخرون متصلون بعد",
|
||||
"shareToInvite": "شارك رابط الاتصال لدعوة شخص ما!",
|
||||
"noChatMessages": "لا توجد رسائل بعد. ابدأ المحادثة بعد الاتصال!",
|
||||
"noActiveCall": "اختر مستخدمًا لبدء مكالمة فيديو",
|
||||
"callingOverlay": "جارٍ الاتصال...",
|
||||
"incomingCall": "مكالمة واردة",
|
||||
"cancelCall": "إلغاء",
|
||||
"callDeclined": "رفض __username__ المكالمة",
|
||||
"callBusy": "__username__ في مكالمة أخرى",
|
||||
"callTimeout": "لا يوجد رد من __username__",
|
||||
"usersOnline": "__count__ مستخدم(ين) متصل"
|
||||
},
|
||||
"controls": {
|
||||
"microphone": "الميكروفون",
|
||||
@@ -60,8 +75,8 @@
|
||||
"error": "حدث خطأ",
|
||||
"copied": "تم النسخ إلى الحافظة",
|
||||
"invalidPassword": "كلمة مرور غير صحيحة",
|
||||
"shareRoomText": "انضم إلى غرفة Call-me الخاصة بي!",
|
||||
"roomCopied": "تم نسخ الغرفة إلى الحافظة __text__",
|
||||
"shareRoomText": "اتصل بي على Call-me!",
|
||||
"roomCopied": "تم نسخ رابط الاتصال إلى الحافظة!",
|
||||
"devicesRefreshed": "تم تحديث الأجهزة بنجاح",
|
||||
"cameraChanged": "تم تغيير الكاميرا بنجاح",
|
||||
"microphoneChanged": "تم تغيير الميكروفون بنجاح",
|
||||
|
||||
+22
-7
@@ -3,12 +3,16 @@
|
||||
"appTitle": "Call-me - Sofortige Videoanrufe",
|
||||
"appDescription": "Ihre erste Wahl für sofortige Videoanrufe!",
|
||||
"signIn": {
|
||||
"title": "Anmelden",
|
||||
"username": "Benutzernamen eingeben",
|
||||
"button": "Anmelden",
|
||||
"title": "Beitreten",
|
||||
"username": "Anzeigename wählen",
|
||||
"button": "Beitreten",
|
||||
"camera": "Kamera",
|
||||
"microphone": "Mikrofon",
|
||||
"enterUsername": "Bitte geben Sie Ihren Benutzernamen ein"
|
||||
"enterUsername": "Bitte geben Sie Ihren Anzeigenamen ein",
|
||||
"subtitle": "Sofortige Videoanrufe. Keine Anmeldung nötig.",
|
||||
"directCallLabel": "Oder jemanden direkt anrufen",
|
||||
"directCallPlaceholder": "Namen zum Anrufen eingeben",
|
||||
"directCallButton": "Jetzt anrufen"
|
||||
},
|
||||
"room": {
|
||||
"sessionTime": "Sitzungszeit",
|
||||
@@ -27,7 +31,18 @@
|
||||
"hangupWith": "Anruf mit __username__ auflegen",
|
||||
"callUser": "__username__ anrufen",
|
||||
"videoOff": "Video aus",
|
||||
"videoDisabled": "Video deaktiviert"
|
||||
"videoDisabled": "Video deaktiviert",
|
||||
"noUsersOnline": "Noch keine anderen Benutzer online",
|
||||
"shareToInvite": "Teile deinen Anruflink, um jemanden einzuladen!",
|
||||
"noChatMessages": "Noch keine Nachrichten. Starte einen Chat nach der Verbindung!",
|
||||
"noActiveCall": "Wähle einen Benutzer, um einen Videoanruf zu starten",
|
||||
"callingOverlay": "Anruf läuft...",
|
||||
"incomingCall": "Eingehender Anruf",
|
||||
"cancelCall": "Abbrechen",
|
||||
"callDeclined": "__username__ hat den Anruf abgelehnt",
|
||||
"callBusy": "__username__ führt bereits ein anderes Gespräch",
|
||||
"callTimeout": "Keine Antwort von __username__",
|
||||
"usersOnline": "__count__ Benutzer online"
|
||||
},
|
||||
"controls": {
|
||||
"microphone": "Mikrofon",
|
||||
@@ -60,8 +75,8 @@
|
||||
"error": "Ein Fehler ist aufgetreten",
|
||||
"copied": "In die Zwischenablage kopiert",
|
||||
"invalidPassword": "Ungültiges Passwort",
|
||||
"shareRoomText": "Tritt meinem Call-me-Raum bei!",
|
||||
"roomCopied": "Raum in die Zwischenablage kopiert __text__",
|
||||
"shareRoomText": "Ruf mich auf Call-me an!",
|
||||
"roomCopied": "Anruflink in die Zwischenablage kopiert!",
|
||||
"devicesRefreshed": "Geräte erfolgreich aktualisiert",
|
||||
"cameraChanged": "Kamera erfolgreich gewechselt",
|
||||
"microphoneChanged": "Mikrofon erfolgreich gewechselt",
|
||||
|
||||
+23
-7
@@ -3,12 +3,16 @@
|
||||
"appTitle": "Call-me - Instant Video Calls",
|
||||
"appDescription": "Your Go-To for Instant Video Calls!",
|
||||
"signIn": {
|
||||
"title": "Sign In",
|
||||
"username": "Enter username",
|
||||
"button": "Sign In",
|
||||
"title": "Join",
|
||||
"username": "Choose a display name",
|
||||
"button": "Join",
|
||||
"camera": "Camera",
|
||||
"microphone": "Microphone",
|
||||
"enterUsername": "Please enter your username"
|
||||
"enterUsername": "Please enter your display name",
|
||||
"subtitle": "Instant Video Calls. No signup needed.",
|
||||
"directCallLabel": "Or call someone directly",
|
||||
"directCallPlaceholder": "Enter name to call",
|
||||
"directCallButton": "Call Now"
|
||||
},
|
||||
"room": {
|
||||
"sessionTime": "Session Time",
|
||||
@@ -27,7 +31,19 @@
|
||||
"hangupWith": "Hang up call with __username__",
|
||||
"callUser": "Call __username__",
|
||||
"videoOff": "Video Off",
|
||||
"videoDisabled": "Video Disabled"
|
||||
"videoDisabled": "Video Disabled",
|
||||
"noUsersOnline": "No other users online yet",
|
||||
"shareToInvite": "Share your call link to invite someone!",
|
||||
"noChatMessages": "No messages yet. Start chatting once connected!",
|
||||
"noActiveCall": "Select a user to start a video call",
|
||||
"callingOverlay": "Calling...",
|
||||
"incomingCall": "Incoming call",
|
||||
"cancelCall": "Cancel",
|
||||
"callDeclined": "__username__ declined the call",
|
||||
"callBusy": "__username__ is on another call",
|
||||
"callTimeout": "No answer from __username__",
|
||||
"connecting": "Connecting...",
|
||||
"usersOnline": "__count__ user(s) online"
|
||||
},
|
||||
"controls": {
|
||||
"microphone": "Microphone",
|
||||
@@ -60,8 +76,8 @@
|
||||
"error": "An error occurred",
|
||||
"copied": "Copied to clipboard",
|
||||
"invalidPassword": "Invalid password",
|
||||
"shareRoomText": "Join my Call-me room!",
|
||||
"roomCopied": "Room copied to clipboard __text__",
|
||||
"shareRoomText": "Call me on Call-me!",
|
||||
"roomCopied": "Call link copied to clipboard!",
|
||||
"devicesRefreshed": "Devices refreshed successfully",
|
||||
"cameraChanged": "Camera changed successfully",
|
||||
"microphoneChanged": "Microphone changed successfully",
|
||||
|
||||
+22
-7
@@ -3,12 +3,16 @@
|
||||
"appTitle": "Call-me - Videollamadas Instantáneas",
|
||||
"appDescription": "¡Tu opción para videollamadas instantáneas!",
|
||||
"signIn": {
|
||||
"title": "Iniciar Sesión",
|
||||
"username": "Ingrese nombre de usuario",
|
||||
"button": "Iniciar Sesión",
|
||||
"title": "Unirse",
|
||||
"username": "Elige un nombre para mostrar",
|
||||
"button": "Unirse",
|
||||
"camera": "Cámara",
|
||||
"microphone": "Micrófono",
|
||||
"enterUsername": "Por favor ingrese su nombre de usuario"
|
||||
"enterUsername": "Por favor ingrese su nombre para mostrar",
|
||||
"subtitle": "Videollamadas instantáneas. Sin registro.",
|
||||
"directCallLabel": "O llama a alguien directamente",
|
||||
"directCallPlaceholder": "Ingresa un nombre para llamar",
|
||||
"directCallButton": "Llamar ahora"
|
||||
},
|
||||
"room": {
|
||||
"sessionTime": "Tiempo de Sesión",
|
||||
@@ -27,7 +31,18 @@
|
||||
"hangupWith": "Colgar la llamada con __username__",
|
||||
"callUser": "Llamar a __username__",
|
||||
"videoOff": "Vídeo desactivado",
|
||||
"videoDisabled": "Vídeo deshabilitado"
|
||||
"videoDisabled": "Vídeo deshabilitado",
|
||||
"noUsersOnline": "Aún no hay otros usuarios en línea",
|
||||
"shareToInvite": "¡Comparte tu enlace de llamada para invitar a alguien!",
|
||||
"noChatMessages": "¡Aún no hay mensajes. Empieza a chatear una vez conectado!",
|
||||
"noActiveCall": "Selecciona un usuario para iniciar una videollamada",
|
||||
"callingOverlay": "Llamando...",
|
||||
"incomingCall": "Llamada entrante",
|
||||
"cancelCall": "Cancelar",
|
||||
"callDeclined": "__username__ rechazó la llamada",
|
||||
"callBusy": "__username__ está en otra llamada",
|
||||
"callTimeout": "Sin respuesta de __username__",
|
||||
"usersOnline": "__count__ usuario(s) en línea"
|
||||
},
|
||||
"controls": {
|
||||
"microphone": "Micrófono",
|
||||
@@ -60,8 +75,8 @@
|
||||
"error": "Ocurrió un error",
|
||||
"copied": "Copiado al portapapeles",
|
||||
"invalidPassword": "Contraseña inválida",
|
||||
"shareRoomText": "¡Únete a mi sala de Call-me!",
|
||||
"roomCopied": "Sala copiada al portapapeles __text__",
|
||||
"shareRoomText": "¡Llámame en Call-me!",
|
||||
"roomCopied": "¡Enlace de llamada copiado al portapapeles!",
|
||||
"devicesRefreshed": "Dispositivos actualizados correctamente",
|
||||
"cameraChanged": "Cámara cambiada correctamente",
|
||||
"microphoneChanged": "Micrófono cambiado correctamente",
|
||||
|
||||
+22
-7
@@ -3,12 +3,16 @@
|
||||
"appTitle": "Call-me - Appels Vidéo Instantanés",
|
||||
"appDescription": "Votre solution pour les appels vidéo instantanés!",
|
||||
"signIn": {
|
||||
"title": "Se Connecter",
|
||||
"username": "Entrez le nom d'utilisateur",
|
||||
"button": "Se Connecter",
|
||||
"title": "Rejoindre",
|
||||
"username": "Choisissez un nom d'affichage",
|
||||
"button": "Rejoindre",
|
||||
"camera": "Caméra",
|
||||
"microphone": "Microphone",
|
||||
"enterUsername": "Veuillez entrer votre nom d'utilisateur"
|
||||
"enterUsername": "Veuillez entrer votre nom d'affichage",
|
||||
"subtitle": "Appels vidéo instantanés. Aucune inscription nécessaire.",
|
||||
"directCallLabel": "Ou appelez quelqu'un directement",
|
||||
"directCallPlaceholder": "Entrez un nom à appeler",
|
||||
"directCallButton": "Appeler maintenant"
|
||||
},
|
||||
"room": {
|
||||
"sessionTime": "Temps de Session",
|
||||
@@ -27,7 +31,18 @@
|
||||
"hangupWith": "Raccrocher avec __username__",
|
||||
"callUser": "Appeler __username__",
|
||||
"videoOff": "Vidéo coupée",
|
||||
"videoDisabled": "Vidéo désactivée"
|
||||
"videoDisabled": "Vidéo désactivée",
|
||||
"noUsersOnline": "Aucun autre utilisateur en ligne pour le moment",
|
||||
"shareToInvite": "Partagez votre lien d'appel pour inviter quelqu'un !",
|
||||
"noChatMessages": "Pas encore de messages. Commencez à discuter une fois connecté !",
|
||||
"noActiveCall": "Sélectionnez un utilisateur pour démarrer un appel vidéo",
|
||||
"callingOverlay": "Appel en cours...",
|
||||
"incomingCall": "Appel entrant",
|
||||
"cancelCall": "Annuler",
|
||||
"callDeclined": "__username__ a refusé l'appel",
|
||||
"callBusy": "__username__ est déjà en ligne",
|
||||
"callTimeout": "Pas de réponse de __username__",
|
||||
"usersOnline": "__count__ utilisateur(s) en ligne"
|
||||
},
|
||||
"controls": {
|
||||
"microphone": "Microphone",
|
||||
@@ -60,8 +75,8 @@
|
||||
"error": "Une erreur s'est produite",
|
||||
"copied": "Copié dans le presse-papiers",
|
||||
"invalidPassword": "Mot de passe invalide",
|
||||
"shareRoomText": "Rejoignez ma salle Call-me !",
|
||||
"roomCopied": "Salle copiée dans le presse-papiers __text__",
|
||||
"shareRoomText": "Appelez-moi sur Call-me !",
|
||||
"roomCopied": "Lien d'appel copié dans le presse-papiers !",
|
||||
"devicesRefreshed": "Appareils actualisés avec succès",
|
||||
"cameraChanged": "Caméra changée avec succès",
|
||||
"microphoneChanged": "Microphone changé avec succès",
|
||||
|
||||
+23
-8
@@ -3,12 +3,16 @@
|
||||
"appTitle": "Call-me - तुरंत वीडियो कॉल",
|
||||
"appDescription": "तुरंत वीडियो कॉल के लिए आपका पसंदीदा विकल्प!",
|
||||
"signIn": {
|
||||
"title": "साइन इन",
|
||||
"username": "उपयोगकर्ता नाम दर्ज करें",
|
||||
"button": "साइन इन",
|
||||
"title": "शामिल हों",
|
||||
"username": "एक डिस्प्ले नाम चुनें",
|
||||
"button": "शामिल हों",
|
||||
"camera": "कैमरा",
|
||||
"microphone": "माइक्रोफ़ोन",
|
||||
"enterUsername": "कृपया अपना उपयोगकर्ता नाम दर्ज करें"
|
||||
"microphone": "माइक्रोવोन",
|
||||
"enterUsername": "कृपया अपना डिस्प्ले नाम दर्ज करें",
|
||||
"subtitle": "तुरंत वीडियो कॉल। साइनअप की ज़रूरत नहीं।",
|
||||
"directCallLabel": "या किसी को सीधे कॉल करें",
|
||||
"directCallPlaceholder": "कॉल करने के लिए नाम दर्ज करें",
|
||||
"directCallButton": "अभी कॉल करें"
|
||||
},
|
||||
"room": {
|
||||
"sessionTime": "सत्र समय",
|
||||
@@ -27,7 +31,18 @@
|
||||
"hangupWith": "__username__ के साथ कॉल समाप्त करें",
|
||||
"callUser": "__username__ को कॉल करें",
|
||||
"videoOff": "वीडियो बंद",
|
||||
"videoDisabled": "वीडियो अक्षम"
|
||||
"videoDisabled": "वीडियो अक्षम",
|
||||
"noUsersOnline": "अभी कोई अन्य उपयोगकर्ता ऑनलाइन नहीं है",
|
||||
"shareToInvite": "किसी को आमंत्रित करने के लिए अपना कॉल लिंक साझा करें!",
|
||||
"noChatMessages": "अभी कोई संदेश नहीं। कनेक्ट होने के बाद चैट शुरू करें!",
|
||||
"noActiveCall": "वीडियो कॉल शुरू करने के लिए एक उपयोगकर्ता चुनें",
|
||||
"callingOverlay": "कॉल हो रहा है...",
|
||||
"incomingCall": "आने वाली कॉल",
|
||||
"cancelCall": "रद्द करें",
|
||||
"callDeclined": "__username__ ने कॉल अस्वीकार कर दी",
|
||||
"callBusy": "__username__ दूसरी कॉल पर है",
|
||||
"callTimeout": "__username__ से कोई जवाब नहीं",
|
||||
"usersOnline": "__count__ उपयोगकर्ता ऑनलाइन"
|
||||
},
|
||||
"controls": {
|
||||
"microphone": "माइक्रोफ़ोन",
|
||||
@@ -60,8 +75,8 @@
|
||||
"error": "एक त्रुटि हुई",
|
||||
"copied": "क्लिपबोर्ड पर कॉपी किया गया",
|
||||
"invalidPassword": "अमान्य पासवर्ड",
|
||||
"shareRoomText": "मेरे Call-me रूम में शामिल हों!",
|
||||
"roomCopied": "रूम क्लिपबोर्ड पर कॉपी किया गया __text__",
|
||||
"shareRoomText": "मुझे Call-me पर कॉल करें!",
|
||||
"roomCopied": "कॉल लिंक क्लिपबोर्ड पर कॉपी किया गया!",
|
||||
"devicesRefreshed": "डिवाइस सफलतापूर्वक रिफ्रेश किए गए",
|
||||
"cameraChanged": "कैमरा सफलतापूर्वक बदला गया",
|
||||
"microphoneChanged": "माइक्रोफ़ोन सफलतापूर्वक बदला गया",
|
||||
|
||||
+22
-7
@@ -3,12 +3,16 @@
|
||||
"appTitle": "Call-me - Trenutni video pozivi",
|
||||
"appDescription": "Vaš izbor za trenutne video pozive!",
|
||||
"signIn": {
|
||||
"title": "Prijava",
|
||||
"username": "Unesite korisničko ime",
|
||||
"button": "Prijavi se",
|
||||
"title": "Pridruži se",
|
||||
"username": "Odaberite ime za prikaz",
|
||||
"button": "Pridruži se",
|
||||
"camera": "Kamera",
|
||||
"microphone": "Mikrofon",
|
||||
"enterUsername": "Molimo unesite svoje korisničko ime"
|
||||
"enterUsername": "Molimo unesite svoje ime za prikaz",
|
||||
"subtitle": "Trenutni video pozivi. Bez registracije.",
|
||||
"directCallLabel": "Ili nazovite nekoga izravno",
|
||||
"directCallPlaceholder": "Unesite ime za poziv",
|
||||
"directCallButton": "Pozovi odmah"
|
||||
},
|
||||
"room": {
|
||||
"sessionTime": "Trajanje sesije",
|
||||
@@ -27,7 +31,18 @@
|
||||
"hangupWith": "Prekini poziv s __username__",
|
||||
"callUser": "Nazovi __username__",
|
||||
"videoOff": "Video isključen",
|
||||
"videoDisabled": "Video onemogućen"
|
||||
"videoDisabled": "Video onemogućen",
|
||||
"noUsersOnline": "Još nema drugih korisnika na mreži",
|
||||
"shareToInvite": "Podijelite svoj link za poziv da pozovete nekoga!",
|
||||
"noChatMessages": "Još nema poruka. Počnite razgovarati nakon povezivanja!",
|
||||
"noActiveCall": "Odaberite korisnika za pokretanje video poziva",
|
||||
"callingOverlay": "Pozivanje...",
|
||||
"incomingCall": "Dolazni poziv",
|
||||
"cancelCall": "Odustani",
|
||||
"callDeclined": "__username__ je odbio poziv",
|
||||
"callBusy": "__username__ je na drugom pozivu",
|
||||
"callTimeout": "Nema odgovora od __username__",
|
||||
"usersOnline": "__count__ korisnik(a) na mreži"
|
||||
},
|
||||
"controls": {
|
||||
"microphone": "Mikrofon",
|
||||
@@ -60,8 +75,8 @@
|
||||
"error": "Došlo je do pogreške",
|
||||
"copied": "Kopirano u međuspremnik",
|
||||
"invalidPassword": "Neispravna lozinka",
|
||||
"shareRoomText": "Pridruži se mojoj Call-me sobi!",
|
||||
"roomCopied": "Soba kopirana u međuspremnik __text__",
|
||||
"shareRoomText": "Nazovi me na Call-me!",
|
||||
"roomCopied": "Link za poziv kopiran u međuspremnik!",
|
||||
"devicesRefreshed": "Uređaji su uspješno osvježeni",
|
||||
"cameraChanged": "Kamera je uspješno promijenjena",
|
||||
"microphoneChanged": "Mikrofon je uspješno promijenjen",
|
||||
|
||||
+22
-7
@@ -3,12 +3,16 @@
|
||||
"appTitle": "Call-me - Videochiamate Istantanee",
|
||||
"appDescription": "La tua scelta per videochiamate istantanee!",
|
||||
"signIn": {
|
||||
"title": "Accedi",
|
||||
"username": "Inserisci nome utente",
|
||||
"button": "Accedi",
|
||||
"title": "Entra",
|
||||
"username": "Scegli un nome visualizzato",
|
||||
"button": "Entra",
|
||||
"camera": "Fotocamera",
|
||||
"microphone": "Microfono",
|
||||
"enterUsername": "Inserisci il tuo nome utente"
|
||||
"enterUsername": "Inserisci il tuo nome visualizzato",
|
||||
"subtitle": "Videochiamate istantanee. Nessuna registrazione necessaria.",
|
||||
"directCallLabel": "Oppure chiama qualcuno direttamente",
|
||||
"directCallPlaceholder": "Inserisci un nome da chiamare",
|
||||
"directCallButton": "Chiama ora"
|
||||
},
|
||||
"room": {
|
||||
"sessionTime": "Tempo di Sessione",
|
||||
@@ -27,7 +31,18 @@
|
||||
"hangupWith": "Riaggancia la chiamata con __username__",
|
||||
"callUser": "Chiama __username__",
|
||||
"videoOff": "Video disattivato",
|
||||
"videoDisabled": "Video disabilitato"
|
||||
"videoDisabled": "Video disabilitato",
|
||||
"noUsersOnline": "Nessun altro utente online al momento",
|
||||
"shareToInvite": "Condividi il tuo link di chiamata per invitare qualcuno!",
|
||||
"noChatMessages": "Nessun messaggio ancora. Inizia a chattare una volta connesso!",
|
||||
"noActiveCall": "Seleziona un utente per avviare una videochiamata",
|
||||
"callingOverlay": "Chiamata in corso...",
|
||||
"incomingCall": "Chiamata in arrivo",
|
||||
"cancelCall": "Annulla",
|
||||
"callDeclined": "__username__ ha rifiutato la chiamata",
|
||||
"callBusy": "__username__ è in un'altra chiamata",
|
||||
"callTimeout": "Nessuna risposta da __username__",
|
||||
"usersOnline": "__count__ utente/i online"
|
||||
},
|
||||
"controls": {
|
||||
"microphone": "Microfono",
|
||||
@@ -60,8 +75,8 @@
|
||||
"error": "Si è verificato un errore",
|
||||
"copied": "Copiato negli appunti",
|
||||
"invalidPassword": "Password non valida",
|
||||
"shareRoomText": "Unisciti alla mia stanza Call-me!",
|
||||
"roomCopied": "Stanza copiata negli appunti __text__",
|
||||
"shareRoomText": "Chiamami su Call-me!",
|
||||
"roomCopied": "Link di chiamata copiato negli appunti!",
|
||||
"devicesRefreshed": "Dispositivi aggiornati correttamente",
|
||||
"cameraChanged": "Fotocamera cambiata correttamente",
|
||||
"microphoneChanged": "Microfono cambiato correttamente",
|
||||
|
||||
+22
-7
@@ -3,12 +3,16 @@
|
||||
"appTitle": "Call-me - 即時ビデオ通話",
|
||||
"appDescription": "即時ビデオ通話に最適!",
|
||||
"signIn": {
|
||||
"title": "サインイン",
|
||||
"username": "ユーザー名を入力",
|
||||
"button": "サインイン",
|
||||
"title": "参加",
|
||||
"username": "表示名を選択",
|
||||
"button": "参加",
|
||||
"camera": "カメラ",
|
||||
"microphone": "マイク",
|
||||
"enterUsername": "ユーザー名を入力してください"
|
||||
"enterUsername": "表示名を入力してください",
|
||||
"subtitle": "即時ビデオ通話。登録不要。",
|
||||
"directCallLabel": "または直接電話をかける",
|
||||
"directCallPlaceholder": "通話相手の名前を入力",
|
||||
"directCallButton": "今すぐ電話"
|
||||
},
|
||||
"room": {
|
||||
"sessionTime": "セッション時間",
|
||||
@@ -27,7 +31,18 @@
|
||||
"hangupWith": "__username__ との通話を終了",
|
||||
"callUser": "__username__ に発信",
|
||||
"videoOff": "ビデオオフ",
|
||||
"videoDisabled": "ビデオ無効"
|
||||
"videoDisabled": "ビデオ無効",
|
||||
"noUsersOnline": "他のユーザーはまだオンラインではありません",
|
||||
"shareToInvite": "通話リンクを共有して招待しましょう!",
|
||||
"noChatMessages": "まだメッセージはありません。接続後にチャットを始めましょう!",
|
||||
"noActiveCall": "ユーザーを選択してビデオ通話を開始",
|
||||
"callingOverlay": "発信中...",
|
||||
"incomingCall": "着信",
|
||||
"cancelCall": "キャンセル",
|
||||
"callDeclined": "__username__ が通話を拒否しました",
|
||||
"callBusy": "__username__ は他の通話中です",
|
||||
"callTimeout": "__username__ から応答がありません",
|
||||
"usersOnline": "__count__ 人のユーザーがオンライン"
|
||||
},
|
||||
"controls": {
|
||||
"microphone": "マイク",
|
||||
@@ -60,8 +75,8 @@
|
||||
"error": "エラーが発生しました",
|
||||
"copied": "クリップボードにコピーしました",
|
||||
"invalidPassword": "パスワードが無効です",
|
||||
"shareRoomText": "私のCall-meルームに参加して!",
|
||||
"roomCopied": "ルームをクリップボードにコピーしました __text__",
|
||||
"shareRoomText": "Call-meで電話してね!",
|
||||
"roomCopied": "通話リンクをクリップボードにコピーしました!",
|
||||
"devicesRefreshed": "デバイスを正常に更新しました",
|
||||
"cameraChanged": "カメラを正常に変更しました",
|
||||
"microphoneChanged": "マイクを正常に変更しました",
|
||||
|
||||
+20
-5
@@ -4,11 +4,15 @@
|
||||
"appDescription": "Sua opção para chamadas de vídeo instantâneas!",
|
||||
"signIn": {
|
||||
"title": "Entrar",
|
||||
"username": "Digite o nome de usuário",
|
||||
"username": "Escolha um nome de exibição",
|
||||
"button": "Entrar",
|
||||
"camera": "Câmera",
|
||||
"microphone": "Microfone",
|
||||
"enterUsername": "Por favor, digite seu nome de usuário"
|
||||
"enterUsername": "Por favor, digite seu nome de exibição",
|
||||
"subtitle": "Chamadas de vídeo instantâneas. Sem cadastro.",
|
||||
"directCallLabel": "Ou ligue para alguém diretamente",
|
||||
"directCallPlaceholder": "Digite um nome para ligar",
|
||||
"directCallButton": "Ligar agora"
|
||||
},
|
||||
"room": {
|
||||
"sessionTime": "Tempo da sessão",
|
||||
@@ -27,7 +31,18 @@
|
||||
"hangupWith": "Encerrar chamada com __username__",
|
||||
"callUser": "Ligar para __username__",
|
||||
"videoOff": "Vídeo desligado",
|
||||
"videoDisabled": "Vídeo desativado"
|
||||
"videoDisabled": "Vídeo desativado",
|
||||
"noUsersOnline": "Nenhum outro usuário online ainda",
|
||||
"shareToInvite": "Compartilhe seu link de chamada para convidar alguém!",
|
||||
"noChatMessages": "Nenhuma mensagem ainda. Comece a conversar após conectar!",
|
||||
"noActiveCall": "Selecione um usuário para iniciar uma chamada de vídeo",
|
||||
"callingOverlay": "Ligando...",
|
||||
"incomingCall": "Chamada recebida",
|
||||
"cancelCall": "Cancelar",
|
||||
"callDeclined": "__username__ recusou a chamada",
|
||||
"callBusy": "__username__ está em outra chamada",
|
||||
"callTimeout": "Sem resposta de __username__",
|
||||
"usersOnline": "__count__ usuário(s) online"
|
||||
},
|
||||
"controls": {
|
||||
"microphone": "Microfone",
|
||||
@@ -60,8 +75,8 @@
|
||||
"error": "Ocorreu um erro",
|
||||
"copied": "Copiado para a área de transferência",
|
||||
"invalidPassword": "Senha inválida",
|
||||
"shareRoomText": "Entre na minha sala Call-me!",
|
||||
"roomCopied": "Sala copiada para a área de transferência __text__",
|
||||
"shareRoomText": "Me ligue no Call-me!",
|
||||
"roomCopied": "Link de chamada copiado para a área de transferência!",
|
||||
"devicesRefreshed": "Dispositivos atualizados com sucesso",
|
||||
"cameraChanged": "Câmera alterada com sucesso",
|
||||
"microphoneChanged": "Microfone alterado com sucesso",
|
||||
|
||||
+22
-7
@@ -3,12 +3,16 @@
|
||||
"appTitle": "Call-me — мгновенные видеозвонки",
|
||||
"appDescription": "Ваш выбор для мгновенных видеозвонков!",
|
||||
"signIn": {
|
||||
"title": "Войти",
|
||||
"username": "Введите имя пользователя",
|
||||
"button": "Войти",
|
||||
"title": "Присоединиться",
|
||||
"username": "Выберите отображаемое имя",
|
||||
"button": "Присоединиться",
|
||||
"camera": "Камера",
|
||||
"microphone": "Микрофон",
|
||||
"enterUsername": "Пожалуйста, введите имя пользователя"
|
||||
"enterUsername": "Пожалуйста, введите отображаемое имя",
|
||||
"subtitle": "Мгновенные видеозвонки. Регистрация не нужна.",
|
||||
"directCallLabel": "Или позвоните кому-то напрямую",
|
||||
"directCallPlaceholder": "Введите имя для звонка",
|
||||
"directCallButton": "Позвонить сейчас"
|
||||
},
|
||||
"room": {
|
||||
"sessionTime": "Время сессии",
|
||||
@@ -27,7 +31,18 @@
|
||||
"hangupWith": "Завершить звонок с __username__",
|
||||
"callUser": "Позвонить __username__",
|
||||
"videoOff": "Видео выключено",
|
||||
"videoDisabled": "Видео отключено"
|
||||
"videoDisabled": "Видео отключено",
|
||||
"noUsersOnline": "Пока нет других пользователей онлайн",
|
||||
"shareToInvite": "Поделитесь ссылкой для приглашения!",
|
||||
"noChatMessages": "Сообщений пока нет. Начните общение после подключения!",
|
||||
"noActiveCall": "Выберите пользователя для начала видеозвонка",
|
||||
"callingOverlay": "Вызов...",
|
||||
"incomingCall": "Входящий звонок",
|
||||
"cancelCall": "Отмена",
|
||||
"callDeclined": "__username__ отклонил звонок",
|
||||
"callBusy": "__username__ уже на другом звонке",
|
||||
"callTimeout": "Нет ответа от __username__",
|
||||
"usersOnline": "__count__ пользователь(ей) онлайн"
|
||||
},
|
||||
"controls": {
|
||||
"microphone": "Микрофон",
|
||||
@@ -60,8 +75,8 @@
|
||||
"error": "Произошла ошибка",
|
||||
"copied": "Скопировано в буфер обмена",
|
||||
"invalidPassword": "Неверный пароль",
|
||||
"shareRoomText": "Присоединяйтесь к моей комнате Call-me!",
|
||||
"roomCopied": "Комната скопирована в буфер обмена __text__",
|
||||
"shareRoomText": "Позвоните мне на Call-me!",
|
||||
"roomCopied": "Ссылка для звонка скопирована в буфер обмена!",
|
||||
"devicesRefreshed": "Устройства успешно обновлены",
|
||||
"cameraChanged": "Камера успешно изменена",
|
||||
"microphoneChanged": "Микрофон успешно изменён",
|
||||
|
||||
+22
-7
@@ -3,12 +3,16 @@
|
||||
"appTitle": "Call-me - Trenutni video pozivi",
|
||||
"appDescription": "Vaš izbor za trenutne video pozive!",
|
||||
"signIn": {
|
||||
"title": "Prijava",
|
||||
"username": "Unesite korisničko ime",
|
||||
"button": "Prijavi se",
|
||||
"title": "Pridruži se",
|
||||
"username": "Izaberite ime za prikaz",
|
||||
"button": "Pridruži se",
|
||||
"camera": "Камера",
|
||||
"microphone": "Микрофон",
|
||||
"enterUsername": "Molimo unesite svoje korisničko ime"
|
||||
"enterUsername": "Molimo unesite svoje ime za prikaz",
|
||||
"subtitle": "Trenutni video pozivi. Bez registracije.",
|
||||
"directCallLabel": "Ili pozovite nekoga direktno",
|
||||
"directCallPlaceholder": "Unesite ime za poziv",
|
||||
"directCallButton": "Pozovi odmah"
|
||||
},
|
||||
"room": {
|
||||
"sessionTime": "Trajanje sesije",
|
||||
@@ -27,7 +31,18 @@
|
||||
"hangupWith": "Prekini poziv sa __username__",
|
||||
"callUser": "Pozovi __username__",
|
||||
"videoOff": "Video isključen",
|
||||
"videoDisabled": "Video onemogućen"
|
||||
"videoDisabled": "Video onemogućen",
|
||||
"noUsersOnline": "Još nema drugih korisnika na mreži",
|
||||
"shareToInvite": "Podelite svoj link za poziv da pozovete nekoga!",
|
||||
"noChatMessages": "Još nema poruka. Počnite da ćaskate nakon povezivanja!",
|
||||
"noActiveCall": "Izaberite korisnika za pokretanje video poziva",
|
||||
"callingOverlay": "Pozivanje...",
|
||||
"incomingCall": "Dolazni poziv",
|
||||
"cancelCall": "Otkaži",
|
||||
"callDeclined": "__username__ je odbio poziv",
|
||||
"callBusy": "__username__ je na drugom pozivu",
|
||||
"callTimeout": "Nema odgovora od __username__",
|
||||
"usersOnline": "__count__ korisnik(a) na mreži"
|
||||
},
|
||||
"controls": {
|
||||
"microphone": "Mikrofon",
|
||||
@@ -60,8 +75,8 @@
|
||||
"error": "Došlo je do greške",
|
||||
"copied": "Kopirano u ostavu",
|
||||
"invalidPassword": "Neispravna lozinka",
|
||||
"shareRoomText": "Pridruži se mojoj Call-me sobi!",
|
||||
"roomCopied": "Soba kopirana u ostavu __text__",
|
||||
"shareRoomText": "Pozovi me na Call-me!",
|
||||
"roomCopied": "Link za poziv kopiran u ostavu!",
|
||||
"devicesRefreshed": "Uređaji su uspešno osveženi",
|
||||
"cameraChanged": "Kamera je uspešno promenjena",
|
||||
"microphoneChanged": "Mikrofon je uspešno promenjen",
|
||||
|
||||
+22
-7
@@ -3,12 +3,16 @@
|
||||
"appTitle": "Call-me - 即时视频通话",
|
||||
"appDescription": "即时视频通话的首选!",
|
||||
"signIn": {
|
||||
"title": "登录",
|
||||
"username": "请输入用户名",
|
||||
"button": "登录",
|
||||
"title": "加入",
|
||||
"username": "选择显示名称",
|
||||
"button": "加入",
|
||||
"camera": "摄像头",
|
||||
"microphone": "麦克风",
|
||||
"enterUsername": "请输入您的用户名"
|
||||
"enterUsername": "请输入您的显示名称",
|
||||
"subtitle": "即时视频通话。无需注册。",
|
||||
"directCallLabel": "或直接呼叫某人",
|
||||
"directCallPlaceholder": "输入要呼叫的名称",
|
||||
"directCallButton": "立即通话"
|
||||
},
|
||||
"room": {
|
||||
"sessionTime": "会话时长",
|
||||
@@ -27,7 +31,18 @@
|
||||
"hangupWith": "挂断与 __username__ 的通话",
|
||||
"callUser": "呼叫 __username__",
|
||||
"videoOff": "视频已关闭",
|
||||
"videoDisabled": "视频已禁用"
|
||||
"videoDisabled": "视频已禁用",
|
||||
"noUsersOnline": "目前没有其他用户在线",
|
||||
"shareToInvite": "分享您的通话链接来邀请他人!",
|
||||
"noChatMessages": "还没有消息。连接后开始聊天!",
|
||||
"noActiveCall": "选择一个用户开始视频通话",
|
||||
"callingOverlay": "正在呼叫...",
|
||||
"incomingCall": "来电",
|
||||
"cancelCall": "取消",
|
||||
"callDeclined": "__username__ 拒绝了通话",
|
||||
"callBusy": "__username__ 正在其他通话中",
|
||||
"callTimeout": "__username__ 没有应答",
|
||||
"usersOnline": "__count__ 个用户在线"
|
||||
},
|
||||
"controls": {
|
||||
"microphone": "麦克风",
|
||||
@@ -60,8 +75,8 @@
|
||||
"error": "发生错误",
|
||||
"copied": "已复制到剪贴板",
|
||||
"invalidPassword": "密码无效",
|
||||
"shareRoomText": "加入我的 Call-me 房间!",
|
||||
"roomCopied": "房间已复制到剪贴板 __text__",
|
||||
"shareRoomText": "在 Call-me 上呼叫我!",
|
||||
"roomCopied": "通话链接已复制到剪贴板!",
|
||||
"devicesRefreshed": "设备刷新成功",
|
||||
"cameraChanged": "摄像头切换成功",
|
||||
"microphoneChanged": "麦克风切换成功",
|
||||
|
||||
+8
-4
@@ -261,9 +261,9 @@ app.get('/join/', (req, res) => {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
const isValidCall = isValidUsername(user);
|
||||
const isValidCall = call ? isValidUsername(call) : true;
|
||||
log.debug('isValidCall', { call: call, valid: isValidCall });
|
||||
if (!isValidCall) {
|
||||
if (call && !isValidCall) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
@@ -515,11 +515,15 @@ function handleConnection(socket) {
|
||||
break;
|
||||
case 'offerDecline':
|
||||
log.warn(`User ${name} declined your call`);
|
||||
sendError(recipientSocket || socket, `User ${name} declined your call`);
|
||||
if (recipientSocket) {
|
||||
sendMsgTo(recipientSocket, { type: 'offerDecline', from: name });
|
||||
}
|
||||
break;
|
||||
case 'offerBusy':
|
||||
log.warn(`User ${name} busy in another call`);
|
||||
sendError(recipientSocket || socket, `User ${name} busy in another call.`);
|
||||
if (recipientSocket) {
|
||||
sendMsgTo(recipientSocket, { type: 'offerBusy', from: name });
|
||||
}
|
||||
break;
|
||||
default:
|
||||
log.warn(`Unknown offer type: ${type}`);
|
||||
|
||||
Generated
+4
-4
@@ -13,7 +13,7 @@
|
||||
"axios": "^1.14.0",
|
||||
"colors": "^1.4.0",
|
||||
"cors": "^2.8.6",
|
||||
"dotenv": "^17.4.0",
|
||||
"dotenv": "^17.4.1",
|
||||
"express": "^5.2.1",
|
||||
"helmet": "^8.1.0",
|
||||
"httpolyglot": "0.1.2",
|
||||
@@ -609,9 +609,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.4.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.0.tgz",
|
||||
"integrity": "sha512-kCKF62fwtzwYm0IGBNjRUjtJgMfGapII+FslMHIjMR5KTnwEmBmWLDRSnc3XSNP8bNy34tekgQyDT0hr7pERRQ==",
|
||||
"version": "17.4.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz",
|
||||
"integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
"axios": "^1.14.0",
|
||||
"colors": "^1.4.0",
|
||||
"cors": "^2.8.6",
|
||||
"dotenv": "^17.4.0",
|
||||
"dotenv": "^17.4.1",
|
||||
"express": "^5.2.1",
|
||||
"helmet": "^8.1.0",
|
||||
"httpolyglot": "0.1.2",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.8 KiB |
+224
-32
@@ -61,6 +61,18 @@ const remoteVideoDisabled = document.getElementById('remoteVideoDisabled');
|
||||
const localUsername = document.getElementById('localUsername');
|
||||
const remoteUsername = document.getElementById('remoteUsername');
|
||||
const remoteVideo = document.getElementById('remoteVideo');
|
||||
const directCallInput = document.getElementById('directCallInput');
|
||||
const directCallBtn = document.getElementById('directCallBtn');
|
||||
|
||||
const callingOverlay = document.getElementById('callingOverlay');
|
||||
const callingUsername = document.getElementById('callingUsername');
|
||||
const callingTimer = document.getElementById('callingTimer');
|
||||
const cancelCallBtn = document.getElementById('cancelCallBtn');
|
||||
const incomingCallOverlay = document.getElementById('incomingCallOverlay');
|
||||
const incomingCallUsername = document.getElementById('incomingCallUsername');
|
||||
const incomingCallTimer = document.getElementById('incomingCallTimer');
|
||||
const acceptCallBtn = document.getElementById('acceptCallBtn');
|
||||
const declineCallBtn = document.getElementById('declineCallBtn');
|
||||
|
||||
// Ensure app is defined, even if config.js is not loaded
|
||||
const app = window.myAppConfig || {};
|
||||
@@ -70,6 +82,10 @@ let userInfo;
|
||||
let userName;
|
||||
let connectedUser;
|
||||
let pendingUser; // Track outgoing call target
|
||||
let callingTimerId = null; // Timer for calling overlay
|
||||
let callingElapsed = 0; // Seconds elapsed while calling
|
||||
let incomingCallData = null; // Pending incoming call data
|
||||
let incomingCallTimerId = null; // Auto-decline timer for incoming call
|
||||
let thisConnection;
|
||||
let camera = 'user';
|
||||
let stream;
|
||||
@@ -449,6 +465,12 @@ function handleMessage(data) {
|
||||
}
|
||||
offerCreate();
|
||||
break;
|
||||
case 'offerDecline':
|
||||
handleOfferDecline(data);
|
||||
break;
|
||||
case 'offerBusy':
|
||||
handleOfferBusy(data);
|
||||
break;
|
||||
case 'offer':
|
||||
handleOffer(data);
|
||||
break;
|
||||
@@ -534,6 +556,14 @@ function handleListeners() {
|
||||
emojiBtn.addEventListener('click', handleEmojiClick);
|
||||
saveChatBtn.addEventListener('click', handleSaveChatClick);
|
||||
clearChatBtn.addEventListener('click', handleClearChatClick);
|
||||
|
||||
// Direct call listeners
|
||||
directCallBtn.addEventListener('click', handleDirectCallClick);
|
||||
directCallInput.addEventListener('keyup', (e) => handleKeyUp(e, handleDirectCallClick));
|
||||
cancelCallBtn.addEventListener('click', handleCancelCall);
|
||||
acceptCallBtn.addEventListener('click', handleAcceptIncomingCall);
|
||||
declineCallBtn.addEventListener('click', handleDeclineIncomingCall);
|
||||
|
||||
// Language change event listener - reapply config after translation
|
||||
window.addEventListener('languageChanged', () => {
|
||||
handleConfig();
|
||||
@@ -645,7 +675,7 @@ function handleUserClickToCall(user) {
|
||||
from: userName,
|
||||
to: user,
|
||||
});
|
||||
toast(t('room.callingUser', { username: user }));
|
||||
showCallingOverlay(user);
|
||||
if (userSidebar.classList.contains('active')) {
|
||||
userSidebar.classList.remove('active');
|
||||
}
|
||||
@@ -676,6 +706,75 @@ function handleSignInClick() {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle direct call button click (sign in + auto-call)
|
||||
function handleDirectCallClick() {
|
||||
const myName = usernameIn.value.trim();
|
||||
const callTarget = directCallInput ? directCallInput.value.trim() : '';
|
||||
if (!myName) {
|
||||
handleError(t('signIn.enterUsername'));
|
||||
usernameIn.focus();
|
||||
return;
|
||||
}
|
||||
if (!callTarget) {
|
||||
handleError(t('errors.noUserSelected'));
|
||||
if (directCallInput) directCallInput.focus();
|
||||
return;
|
||||
}
|
||||
// Store call target for after sign-in
|
||||
userName = myName;
|
||||
localStorage.callMeUsername = myName;
|
||||
sendMsg({
|
||||
type: 'signIn',
|
||||
name: myName,
|
||||
});
|
||||
// After sign-in completes, auto-call the target user
|
||||
const waitForSignIn = setInterval(() => {
|
||||
if (userSignedIn) {
|
||||
clearInterval(waitForSignIn);
|
||||
setTimeout(() => handleUserClickToCall(callTarget), 1500);
|
||||
}
|
||||
}, 200);
|
||||
// Safety timeout to avoid infinite loop
|
||||
setTimeout(() => clearInterval(waitForSignIn), 15000);
|
||||
}
|
||||
|
||||
// Show calling overlay
|
||||
function showCallingOverlay(targetUser) {
|
||||
if (!callingOverlay) return;
|
||||
callingElapsed = 0;
|
||||
if (callingUsername) callingUsername.textContent = targetUser;
|
||||
if (callingTimer) callingTimer.textContent = '0s';
|
||||
callingOverlay.style.display = 'flex';
|
||||
|
||||
if (callingTimerId) clearInterval(callingTimerId);
|
||||
callingTimerId = setInterval(() => {
|
||||
callingElapsed++;
|
||||
if (callingTimer) callingTimer.textContent = callingElapsed + 's';
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Hide calling overlay
|
||||
function hideCallingOverlay() {
|
||||
if (!callingOverlay) return;
|
||||
callingOverlay.style.display = 'none';
|
||||
if (callingTimerId) {
|
||||
clearInterval(callingTimerId);
|
||||
callingTimerId = null;
|
||||
}
|
||||
callingElapsed = 0;
|
||||
}
|
||||
|
||||
// Handle cancel call button click
|
||||
function handleCancelCall() {
|
||||
hideCallingOverlay();
|
||||
if (pendingUser) {
|
||||
// Notify the remote user that the call was cancelled
|
||||
sendMsg({ type: 'leave', name: pendingUser });
|
||||
pendingUser = null;
|
||||
}
|
||||
toast(t('messages.callEnded'), 'info', 'top', 2000);
|
||||
}
|
||||
|
||||
// Share Room click handler
|
||||
async function handleShareRoomClick() {
|
||||
const roomUrl = window.location.origin;
|
||||
@@ -1231,6 +1330,7 @@ function handlePing(data) {
|
||||
// Handle user not found from the server
|
||||
function handleNotFound(data) {
|
||||
const { username } = data;
|
||||
hideCallingOverlay();
|
||||
handleError(t('errors.userNotFound', { username }));
|
||||
// Remove from user list if present
|
||||
allConnectedUsers = allConnectedUsers.filter((u) => u !== username);
|
||||
@@ -1238,6 +1338,24 @@ function handleNotFound(data) {
|
||||
updateParticipantCount();
|
||||
}
|
||||
|
||||
// Handle call declined by remote user
|
||||
function handleOfferDecline(data) {
|
||||
const { from } = data;
|
||||
hideCallingOverlay();
|
||||
pendingUser = null;
|
||||
toast(t('room.callDeclined', { username: from }), 'warning', 'top', 4000);
|
||||
sound('notify');
|
||||
}
|
||||
|
||||
// Handle remote user busy in another call
|
||||
function handleOfferBusy(data) {
|
||||
const { from } = data;
|
||||
hideCallingOverlay();
|
||||
pendingUser = null;
|
||||
toast(t('room.callBusy', { username: from }), 'warning', 'top', 4000);
|
||||
sound('notify');
|
||||
}
|
||||
|
||||
// Handle sign-in response from the server
|
||||
async function handleSignIn(data) {
|
||||
const { success, message } = data;
|
||||
@@ -1499,39 +1617,69 @@ function offerAccept(data) {
|
||||
return;
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
heightAuto: false,
|
||||
scrollbarPadding: false,
|
||||
position: 'top',
|
||||
imageUrl: 'assets/ring.png',
|
||||
imageWidth: 284,
|
||||
imageHeight: 120,
|
||||
text: t('room.acceptCallFrom', { username: data.from }),
|
||||
showDenyButton: true,
|
||||
confirmButtonText: t('common.yes'),
|
||||
denyButtonText: t('common.no'),
|
||||
timerProgressBar: true,
|
||||
timer: 10000,
|
||||
showClass: { popup: 'animate__animated animate__fadeInDown' },
|
||||
hideClass: { popup: 'animate__animated animate__fadeOutUp' },
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
// Apply caller's media status before accepting the call
|
||||
if (data.callerMediaStatus) {
|
||||
applyCallerMediaStatus(data.callerMediaStatus);
|
||||
}
|
||||
|
||||
data.type = 'offerCreate';
|
||||
socket.recipient = data.from;
|
||||
} else {
|
||||
data.type = 'offerDecline';
|
||||
}
|
||||
sendMsg({ ...data });
|
||||
});
|
||||
|
||||
incomingCallData = data;
|
||||
showIncomingCallOverlay(data.from);
|
||||
sound('ring');
|
||||
}
|
||||
|
||||
// Show incoming call overlay
|
||||
function showIncomingCallOverlay(callerName) {
|
||||
if (!incomingCallOverlay) return;
|
||||
if (incomingCallUsername) incomingCallUsername.textContent = callerName;
|
||||
|
||||
// Reset timer bar animation
|
||||
if (incomingCallTimer) {
|
||||
incomingCallTimer.style.animation = 'none';
|
||||
incomingCallTimer.offsetHeight; // Force reflow
|
||||
incomingCallTimer.style.animation = '';
|
||||
}
|
||||
|
||||
incomingCallOverlay.style.display = 'flex';
|
||||
|
||||
// Auto-decline after 10 seconds
|
||||
if (incomingCallTimerId) clearTimeout(incomingCallTimerId);
|
||||
incomingCallTimerId = setTimeout(() => {
|
||||
handleDeclineIncomingCall();
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
// Hide incoming call overlay
|
||||
function hideIncomingCallOverlay() {
|
||||
if (!incomingCallOverlay) return;
|
||||
incomingCallOverlay.style.display = 'none';
|
||||
if (incomingCallTimerId) {
|
||||
clearTimeout(incomingCallTimerId);
|
||||
incomingCallTimerId = null;
|
||||
}
|
||||
incomingCallData = null;
|
||||
}
|
||||
|
||||
// Handle accept incoming call
|
||||
function handleAcceptIncomingCall() {
|
||||
if (!incomingCallData) return;
|
||||
const data = incomingCallData;
|
||||
hideIncomingCallOverlay();
|
||||
|
||||
// Apply caller's media status before accepting the call
|
||||
if (data.callerMediaStatus) {
|
||||
applyCallerMediaStatus(data.callerMediaStatus);
|
||||
}
|
||||
|
||||
data.type = 'offerCreate';
|
||||
socket.recipient = data.from;
|
||||
sendMsg({ ...data });
|
||||
}
|
||||
|
||||
// Handle decline incoming call
|
||||
function handleDeclineIncomingCall() {
|
||||
if (!incomingCallData) return;
|
||||
const data = incomingCallData;
|
||||
hideIncomingCallOverlay();
|
||||
|
||||
data.type = 'offerDecline';
|
||||
sendMsg({ ...data });
|
||||
}
|
||||
|
||||
// Handle incoming offer
|
||||
async function handleOffer(data) {
|
||||
const { offer, name } = data;
|
||||
@@ -1562,6 +1710,7 @@ async function handleAnswer(data) {
|
||||
const { answer } = data;
|
||||
try {
|
||||
await thisConnection.setRemoteDescription(new RTCSessionDescription(answer));
|
||||
hideCallingOverlay();
|
||||
// Set connectedUser from pendingUser after call is accepted
|
||||
if (pendingUser) {
|
||||
connectedUser = pendingUser;
|
||||
@@ -1570,6 +1719,7 @@ async function handleAnswer(data) {
|
||||
renderUserList(); // Update UI to show hang-up button for caller
|
||||
}
|
||||
} catch (error) {
|
||||
hideCallingOverlay();
|
||||
handleError(t('errors.remoteDescriptionFailed'), error);
|
||||
}
|
||||
}
|
||||
@@ -1723,6 +1873,8 @@ function disconnectConnection() {
|
||||
|
||||
// Handle leaving the room
|
||||
function handleLeave(disconnect = true) {
|
||||
hideCallingOverlay();
|
||||
hideIncomingCallOverlay();
|
||||
if (disconnect) {
|
||||
// Stop screen sharing if active
|
||||
if (isScreenSharing) {
|
||||
@@ -2169,6 +2321,22 @@ function renderUserList() {
|
||||
if (tip) tip.dispose();
|
||||
});
|
||||
userList.innerHTML = '';
|
||||
|
||||
// Show empty state if no users (only after sign-in)
|
||||
if (filteredUsers.length === 0 && userSignedIn) {
|
||||
const emptyLi = document.createElement('li');
|
||||
emptyLi.className = 'user-list-empty';
|
||||
emptyLi.innerHTML = `
|
||||
<div>
|
||||
<i class="fas fa-users"></i>
|
||||
<p>${t('room.noUsersOnline')}</p>
|
||||
<p>${t('room.shareToInvite')}</p>
|
||||
</div>
|
||||
`;
|
||||
userList.appendChild(emptyLi);
|
||||
return;
|
||||
}
|
||||
|
||||
filteredUsers.forEach((user) => {
|
||||
const li = document.createElement('li');
|
||||
li.tabIndex = 0;
|
||||
@@ -2309,6 +2477,10 @@ function switchTab(tabName) {
|
||||
// Clear unread messages when switching to chat
|
||||
unreadMessages = 0;
|
||||
updateChatNotification();
|
||||
// Show empty state if no messages
|
||||
if (chatMessages && chatMessages.children.length === 0) {
|
||||
showChatEmptyState();
|
||||
}
|
||||
} else if (tabName === 'settings') {
|
||||
settingsTab.classList.add('active');
|
||||
// Refresh devices when switching to settings
|
||||
@@ -2376,6 +2548,10 @@ if (chatForm && chatInput) {
|
||||
}
|
||||
|
||||
function addChatMessage(msg, isSelf = false) {
|
||||
// Remove empty state if present
|
||||
const emptyState = chatMessages.querySelector('.chat-empty-state');
|
||||
if (emptyState) emptyState.remove();
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.className = 'chat-message';
|
||||
if (isSelf) {
|
||||
@@ -2790,7 +2966,23 @@ function handleClearChatClick() {
|
||||
}
|
||||
|
||||
function thereAreChatMessages() {
|
||||
return chatMessages.children.length > 0;
|
||||
// Exclude the empty state element from the count
|
||||
const children = Array.from(chatMessages.children);
|
||||
return children.some((el) => !el.classList.contains('chat-empty-state'));
|
||||
}
|
||||
|
||||
// Show chat empty state placeholder
|
||||
function showChatEmptyState() {
|
||||
if (!chatMessages) return;
|
||||
const existing = chatMessages.querySelector('.chat-empty-state');
|
||||
if (existing) return;
|
||||
const emptyDiv = document.createElement('div');
|
||||
emptyDiv.className = 'chat-empty-state';
|
||||
emptyDiv.innerHTML = `
|
||||
<i class="fas fa-comments"></i>
|
||||
<p>${t('room.noChatMessages')}</p>
|
||||
`;
|
||||
chatMessages.appendChild(emptyDiv);
|
||||
}
|
||||
|
||||
// Device Management Functions
|
||||
|
||||
+64
-3
@@ -68,7 +68,12 @@
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><h1 id="appName" data-i18n="appName">Call-me</h1></div>
|
||||
<div class="card-header">
|
||||
<h1 id="appName" data-i18n="appName">Call-me</h1>
|
||||
<p class="sign-in-subtitle" data-i18n="signIn.subtitle">
|
||||
Instant Video Calls. No signup needed.
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Sign-in Form -->
|
||||
<div class="mb-3">
|
||||
@@ -76,7 +81,7 @@
|
||||
<input
|
||||
id="usernameIn"
|
||||
type="text"
|
||||
placeholder="Enter username"
|
||||
placeholder="Choose a display name"
|
||||
data-i18n-placeholder="signIn.username"
|
||||
required
|
||||
/>
|
||||
@@ -105,7 +110,27 @@
|
||||
</label>
|
||||
</div>
|
||||
<!-- Sign-in button -->
|
||||
<button id="signInBtn" class="btn btn-primary" data-i18n="signIn.button">Sign In</button>
|
||||
<button id="signInBtn" class="btn btn-primary" data-i18n="signIn.button">Join</button>
|
||||
|
||||
<!-- Direct call section -->
|
||||
<div class="direct-call-section">
|
||||
<div class="direct-call-divider">
|
||||
<span data-i18n="signIn.directCallLabel">Or call someone directly</span>
|
||||
</div>
|
||||
<div class="direct-call-row">
|
||||
<input
|
||||
id="directCallInput"
|
||||
type="text"
|
||||
placeholder="Enter name to call"
|
||||
data-i18n-placeholder="signIn.directCallPlaceholder"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button id="directCallBtn" class="btn btn-call-now">
|
||||
<i class="fas fa-phone"></i>
|
||||
<span data-i18n="signIn.directCallButton">Call Now</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -117,6 +142,42 @@
|
||||
<!-- Session time -->
|
||||
<span id="sessionTime">0s</span>
|
||||
|
||||
<!-- Calling overlay (outgoing) -->
|
||||
<div id="callingOverlay" class="calling-overlay">
|
||||
<div class="calling-overlay-content">
|
||||
<div class="calling-pulse-ring"></div>
|
||||
<i class="fas fa-phone calling-icon"></i>
|
||||
<p class="calling-status" data-i18n="room.callingOverlay">Calling...</p>
|
||||
<p id="callingUsername" class="calling-username"></p>
|
||||
<span id="callingTimer" class="calling-timer">0s</span>
|
||||
<button id="cancelCallBtn" class="btn btn-danger btn-cancel-call" data-i18n="room.cancelCall">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Incoming call overlay -->
|
||||
<div id="incomingCallOverlay" class="incoming-call-overlay">
|
||||
<div class="incoming-call-content">
|
||||
<div class="incoming-call-ring-outer"></div>
|
||||
<div class="incoming-call-ring-inner"></div>
|
||||
<div class="incoming-call-avatar">
|
||||
<i class="fas fa-user"></i>
|
||||
</div>
|
||||
<p class="incoming-call-label" data-i18n="room.incomingCall">Incoming call</p>
|
||||
<p id="incomingCallUsername" class="incoming-call-username"></p>
|
||||
<div id="incomingCallTimer" class="incoming-call-timer"></div>
|
||||
<div class="incoming-call-actions">
|
||||
<button id="declineCallBtn" class="btn-call-action btn-decline" title="Decline">
|
||||
<i class="fas fa-phone-slash"></i>
|
||||
</button>
|
||||
<button id="acceptCallBtn" class="btn-call-action btn-accept" title="Accept">
|
||||
<i class="fas fa-phone"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Remote video container (video from the other user) -->
|
||||
<div id="remoteVideoContainer">
|
||||
<!-- Local video container (your video) -->
|
||||
|
||||
+464
-2
@@ -1812,7 +1812,7 @@ input {
|
||||
|
||||
/* Settings Section */
|
||||
.settings-section {
|
||||
padding: var(--spacing-lg);
|
||||
padding: var(--spacing-lg) var(--spacing-xl);
|
||||
background: rgba(15, 20, 25, 0.4);
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
@@ -2070,7 +2070,7 @@ input {
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
padding: 16px;
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
}
|
||||
|
||||
.settings-title {
|
||||
@@ -2484,4 +2484,466 @@ z-index:
|
||||
- 5. userSidebar
|
||||
- 6. file-transfer-status
|
||||
- 7. SweetAlert2
|
||||
- 8. callingOverlay
|
||||
*/
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Sign-In Subtitle
|
||||
--------------------------------------------------------------*/
|
||||
|
||||
.sign-in-subtitle {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-normal);
|
||||
color: var(--text-secondary);
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Direct Call Section (Sign-In Page)
|
||||
--------------------------------------------------------------*/
|
||||
|
||||
.direct-call-section {
|
||||
margin-top: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.direct-call-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
margin-bottom: var(--spacing-md);
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.direct-call-divider::before,
|
||||
.direct-call-divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--glass-border);
|
||||
}
|
||||
|
||||
.direct-call-row {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.direct-call-row input {
|
||||
flex: 1;
|
||||
padding: 12px 16px;
|
||||
font-size: 15px;
|
||||
color: var(--text-color);
|
||||
border: 2px solid var(--glass-border);
|
||||
border-radius: var(--border-radius);
|
||||
background: rgba(30, 35, 50, 0.5);
|
||||
transition: all var(--transition-base);
|
||||
font-weight: var(--font-weight-medium);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.direct-call-row input::placeholder {
|
||||
color: var(--text-muted);
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
.direct-call-row input:focus {
|
||||
border-color: var(--primary-color);
|
||||
background: rgba(30, 35, 50, 0.8);
|
||||
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
|
||||
.btn-call-now {
|
||||
background: linear-gradient(135deg, var(--success-color), var(--success-hover)) !important;
|
||||
color: #fff !important;
|
||||
border: none !important;
|
||||
padding: 12px 20px !important;
|
||||
border-radius: var(--border-radius) !important;
|
||||
font-weight: var(--font-weight-semibold) !important;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base) !important;
|
||||
white-space: nowrap;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.btn-call-now:hover {
|
||||
background: linear-gradient(135deg, var(--success-hover), #047857) !important;
|
||||
transform: translateY(-2px) !important;
|
||||
box-shadow:
|
||||
var(--shadow-md),
|
||||
0 0 16px rgba(16, 185, 129, 0.4) !important;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Calling Overlay
|
||||
--------------------------------------------------------------*/
|
||||
|
||||
.calling-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 150;
|
||||
background: rgba(15, 20, 25, 0.92);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--border-radius-lg);
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.calling-overlay-content {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.calling-pulse-ring {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid var(--primary-color);
|
||||
animation: callingPulse 1.5s ease-out infinite;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.calling-icon {
|
||||
font-size: 2.5rem;
|
||||
color: var(--primary-light);
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(59, 130, 246, 0.15);
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--primary-color);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.calling-status {
|
||||
color: var(--text-color);
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.calling-username {
|
||||
color: var(--primary-light);
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.calling-timer {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.btn-cancel-call {
|
||||
margin-top: var(--spacing-md);
|
||||
padding: 10px 32px !important;
|
||||
border-radius: 24px !important;
|
||||
font-size: var(--font-size-base) !important;
|
||||
}
|
||||
|
||||
@keyframes callingPulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
opacity: 0.8;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1.8);
|
||||
opacity: 0;
|
||||
border-color: var(--primary-light);
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Incoming Call Overlay
|
||||
--------------------------------------------------------------*/
|
||||
|
||||
.incoming-call-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 160;
|
||||
background: rgba(10, 12, 18, 0.95);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--border-radius-lg);
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.incoming-call-content {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.incoming-call-avatar {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(16, 185, 129, 0.2));
|
||||
border: 2px solid rgba(59, 130, 246, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2.2rem;
|
||||
color: var(--primary-light);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.incoming-call-ring-outer,
|
||||
.incoming-call-ring-inner {
|
||||
position: absolute;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--success-color);
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.incoming-call-ring-outer {
|
||||
animation: incomingRing 2s ease-out infinite;
|
||||
}
|
||||
|
||||
.incoming-call-ring-inner {
|
||||
animation: incomingRing 2s ease-out 0.5s infinite;
|
||||
}
|
||||
|
||||
.incoming-call-label {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
margin: var(--spacing-md) 0 0 0;
|
||||
}
|
||||
|
||||
.incoming-call-username {
|
||||
color: var(--text-color);
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.incoming-call-timer {
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 2px;
|
||||
margin: var(--spacing-sm) 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.incoming-call-timer::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: linear-gradient(90deg, var(--success-color), var(--primary-color));
|
||||
animation: incomingTimerBar 10s linear forwards;
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
.incoming-call-actions {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
margin-top: var(--spacing-md);
|
||||
}
|
||||
|
||||
.btn-call-action {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
color: #fff;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn-call-action:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.btn-call-action:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.btn-decline {
|
||||
background: linear-gradient(135deg, #ef4444, #dc2626);
|
||||
box-shadow: 0 4px 20px rgba(239, 68, 68, 0.4);
|
||||
}
|
||||
|
||||
.btn-decline:hover {
|
||||
box-shadow: 0 6px 28px rgba(239, 68, 68, 0.5);
|
||||
}
|
||||
|
||||
.btn-accept {
|
||||
background: linear-gradient(135deg, #10b981, #059669);
|
||||
box-shadow: 0 4px 20px rgba(16, 185, 129, 0.4);
|
||||
animation: acceptPulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.btn-accept:hover {
|
||||
box-shadow: 0 6px 28px rgba(16, 185, 129, 0.5);
|
||||
animation: none;
|
||||
}
|
||||
|
||||
@keyframes incomingRing {
|
||||
0% {
|
||||
transform: translateX(-50%) scale(1);
|
||||
opacity: 0.6;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-50%) scale(2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes acceptPulse {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 4px 20px rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 4px 30px rgba(16, 185, 129, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes incomingTimerBar {
|
||||
0% {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
100% {
|
||||
transform: scaleX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Empty States
|
||||
--------------------------------------------------------------*/
|
||||
|
||||
.user-list-empty {
|
||||
text-align: center;
|
||||
padding: 40px 20px !important;
|
||||
color: var(--text-muted);
|
||||
cursor: default !important;
|
||||
border-left: none !important;
|
||||
display: flex !important;
|
||||
justify-content: center !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.user-list-empty:hover {
|
||||
background: none !important;
|
||||
transform: none !important;
|
||||
border-left-color: transparent !important;
|
||||
}
|
||||
|
||||
.user-list-empty div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.user-list-empty i {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.user-list-empty p {
|
||||
margin: 4px 0;
|
||||
font-size: var(--font-size-sm);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.user-list-empty p:last-child {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.chat-empty-state {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.chat-empty-state i {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.chat-empty-state p {
|
||||
margin: 4px 0;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Online User Count (Sign-In Page)
|
||||
--------------------------------------------------------------*/
|
||||
|
||||
.online-count {
|
||||
margin-top: var(--spacing-sm);
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--success-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.online-count .online-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--success-color);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.direct-call-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.direct-call-row .btn-call-now {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user