import {useEffect, useRef, useState} from 'react' import { MAX_PARTICIPANTS, type ChatItem, type ParticipantInfo } from './p2p/network' import {VIDEO_QUALITIES, type VideoQuality} from './p2p/settings' import {TURN_CONFIGURED, type RelayStatus} from './p2p/turn' import {formatTranscript, type TranscriptItem} from './transcribe/store' import {useNetwork} from './useNetwork' // Screen capture is desktop-only in practice; hide the button where the API // doesn't exist (most mobile browsers). const canShareScreen = typeof navigator.mediaDevices?.getDisplayMedia === 'function' const initialRoomFromHash = (): string => { try { return decodeURIComponent(location.hash.slice(1)).replace(/\s/g, '') } catch { return '' } } // ---- styles --------------------------------------------------------------- const lightBtn: React.CSSProperties = { padding: '0.45rem 1.1rem', borderRadius: 6, border: '1px solid #888', background: '#fff', cursor: 'pointer', fontSize: '1rem' } const primaryBtn: React.CSSProperties = { ...lightBtn, background: '#1a7f37', borderColor: '#1a7f37', color: '#fff' } const disabledStyle: React.CSSProperties = { opacity: 0.4, cursor: 'not-allowed' } const inputStyle: React.CSSProperties = { padding: '0.5rem', fontSize: '1rem', borderRadius: 6, border: '1px solid #bbb', width: '100%', boxSizing: 'border-box' } // Dark in-room controls. const darkBtn: React.CSSProperties = { padding: '0.5rem 0.9rem', borderRadius: 8, border: '1px solid #555', background: '#2a2a2a', color: '#eee', cursor: 'pointer', fontSize: '0.95rem', display: 'inline-flex', alignItems: 'center', gap: '0.4rem' } // Square icon-only buttons for the control bar. Their meaning is carried by // the icon plus a title tooltip and aria-label. const iconBtn: React.CSSProperties = { ...darkBtn, padding: '0.65rem', justifyContent: 'center' } // A mute button while muted — red, the universal "you are muted" signal. const mutedIconBtn: React.CSSProperties = { ...iconBtn, background: '#c62828', borderColor: '#c62828', color: '#fff' } // The screen-share button while sharing. const sharingIconBtn: React.CSSProperties = { ...iconBtn, background: '#1a7f37', borderColor: '#1a7f37', color: '#fff' } const leaveBtn: React.CSSProperties = { ...darkBtn, background: '#c62828', borderColor: '#c62828', color: '#fff' } // Chip overlaid on a tile (name label, mute badges). const tileChip: React.CSSProperties = { background: 'rgba(0, 0, 0, 0.65)', color: '#fff', borderRadius: 6, padding: '0.2rem 0.5rem', fontSize: '0.85rem', display: 'inline-flex', alignItems: 'center', gap: '0.3rem', maxWidth: '90%' } // ---- icons (stroke-style paths from Feather icons, MIT) -------------------- const ICONS = { mic: ( <> ), micOff: ( <> ), video: ( <> ), videoOff: ( <> ), monitor: ( <> ), link: ( <> ), logout: ( <> ), chat: ( ), x: ( <> ), send: ( <> ), transcript: ( <> ), copy: ( <> ), download: ( <> ) } as const function Icon({ name, size = 18, style }: { name: keyof typeof ICONS size?: number style?: React.CSSProperties }) { return ( {ICONS[name]} ) } // ---- video tile ------------------------------------------------------------- function VideoView({ stream, muted, mirror, fit }: { stream: MediaStream | null muted: boolean mirror: boolean fit: 'cover' | 'contain' }) { const ref = useRef(null) useEffect(() => { if (ref.current && ref.current.srcObject !== stream) { ref.current.srcObject = stream } }, [stream]) return (