🚀 Auto-deploy: melhoria no snap e medição AR em 25/05/2026 17:15:39
This commit is contained in:
+72
-48
@@ -115,27 +115,27 @@ const CHAIRS = [
|
|||||||
|
|
||||||
const AVATAR_PRESETS: AvatarPreset[] = [
|
const AVATAR_PRESETS: AvatarPreset[] = [
|
||||||
// Masculinos (1 a 10)
|
// Masculinos (1 a 10)
|
||||||
{ id: 1, name: 'Leo', desc: 'Negro, cabelo curto preto', url: '/avatars/avatar_1.png', color: '#ef4444' },
|
{ id: 1, name: 'Av. 01', desc: 'Avatar de Reunião', url: '/avatars/avatar_1.png', color: '#ef4444' },
|
||||||
{ id: 2, name: 'Marcus', desc: 'Loiro, cabelo curto', url: '/avatars/avatar_2.png', color: '#b45309' },
|
{ id: 2, name: 'Av. 02', desc: 'Avatar de Reunião', url: '/avatars/avatar_2.png', color: '#b45309' },
|
||||||
{ id: 3, name: 'Arthur', desc: 'Ruivo com barba', url: '/avatars/avatar_3.png', color: '#eab308' },
|
{ id: 3, name: 'Av. 03', desc: 'Avatar de Reunião', url: '/avatars/avatar_3.png', color: '#eab308' },
|
||||||
{ id: 4, name: 'Jin', desc: 'Asiático, cabelo preto liso', url: '/avatars/avatar_4.png', color: '#3b82f6' },
|
{ id: 4, name: 'Av. 04', desc: 'Avatar de Reunião', url: '/avatars/avatar_4.png', color: '#3b82f6' },
|
||||||
{ id: 5, name: 'Mateo', desc: 'Castanho, cabelo cacheado', url: '/avatars/avatar_5.png', color: '#f97316' },
|
{ id: 5, name: 'Av. 05', desc: 'Avatar de Reunião', url: '/avatars/avatar_5.png', color: '#f97316' },
|
||||||
{ id: 6, name: 'Malik', desc: 'Negro, careca com barba', url: '/avatars/avatar_6.png', color: '#84cc16' },
|
{ id: 6, name: 'Av. 06', desc: 'Avatar de Reunião', url: '/avatars/avatar_6.png', color: '#84cc16' },
|
||||||
{ id: 7, name: 'Oliver', desc: 'Cabelo grisalho com óculos', url: '/avatars/avatar_7.png', color: '#06b6d4' },
|
{ id: 7, name: 'Av. 07', desc: 'Avatar de Reunião', url: '/avatars/avatar_7.png', color: '#06b6d4' },
|
||||||
{ id: 8, name: 'Carlos', desc: 'Latino com bigode', url: '/avatars/avatar_8.png', color: '#6366f1' },
|
{ id: 8, name: 'Av. 08', desc: 'Avatar de Reunião', url: '/avatars/avatar_8.png', color: '#6366f1' },
|
||||||
{ id: 9, name: 'Yuki', desc: 'Cabelo espetado azul escuro', url: '/avatars/avatar_9.png', color: '#a855f7' },
|
{ id: 9, name: 'Av. 09', desc: 'Avatar de Reunião', url: '/avatars/avatar_9.png', color: '#a855f7' },
|
||||||
{ id: 10, name: 'Elijah', desc: 'Negro com dreadlocks', url: '/avatars/avatar_10.png', color: '#ec4899' },
|
{ id: 10, name: 'Av. 10', desc: 'Avatar de Reunião', url: '/avatars/avatar_10.png', color: '#ec4899' },
|
||||||
// Femininos (11 a 20)
|
// Femininos (11 a 20)
|
||||||
{ id: 11, name: 'Sophia', desc: 'Morena, cabelo longo castanho', url: '/avatars/avatar_11.png', color: '#10b981' },
|
{ id: 11, name: 'Av. 11', desc: 'Avatar de Reunião', url: '/avatars/avatar_11.png', color: '#10b981' },
|
||||||
{ id: 12, name: 'Aisha', desc: 'Negra com turbante/lenço', url: '/avatars/avatar_12.png', color: '#78350f' },
|
{ id: 12, name: 'Av. 12', desc: 'Avatar de Reunião', url: '/avatars/avatar_12.png', color: '#78350f' },
|
||||||
{ id: 13, name: 'Isabella', desc: 'Loira, cabelo longo ondulado', url: '/avatars/avatar_13.png', color: '#1e293b' },
|
{ id: 13, name: 'Av. 13', desc: 'Avatar de Reunião', url: '/avatars/avatar_13.png', color: '#1e293b' },
|
||||||
{ id: 14, name: 'Chloe', desc: 'Ruiva, corte canal/bob', url: '/avatars/avatar_14.png', color: '#ca8a04' },
|
{ id: 14, name: 'Av. 14', desc: 'Avatar de Reunião', url: '/avatars/avatar_14.png', color: '#ca8a04' },
|
||||||
{ id: 15, name: 'Mei', desc: 'Asiática, cabelo com franja', url: '/avatars/avatar_15.png', color: '#f43f5e' },
|
{ id: 15, name: 'Av. 15', desc: 'Avatar de Reunião', url: '/avatars/avatar_15.png', color: '#f43f5e' },
|
||||||
{ id: 16, name: 'Elena', desc: 'Castanha, cabelo afro volumoso', url: '/avatars/avatar_16.png', color: '#14b8a6' },
|
{ id: 16, name: 'Av. 16', desc: 'Avatar de Reunião', url: '/avatars/avatar_16.png', color: '#14b8a6' },
|
||||||
{ id: 17, name: 'Zara', desc: 'Cabelo roxo moderno', url: '/avatars/avatar_17.png', color: '#0ea5e9' },
|
{ id: 17, name: 'Av. 17', desc: 'Avatar de Reunião', url: '/avatars/avatar_17.png', color: '#0ea5e9' },
|
||||||
{ id: 18, name: 'Naomi', desc: 'Negra de óculos e rabo de cavalo', url: '/avatars/avatar_18.png', color: '#4f46e5' },
|
{ id: 18, name: 'Av. 18', desc: 'Avatar de Reunião', url: '/avatars/avatar_18.png', color: '#4f46e5' },
|
||||||
{ id: 19, name: 'Harper', desc: 'Corte pixie rosa', url: '/avatars/avatar_19.png', color: '#d946ef' },
|
{ id: 19, name: 'Av. 19', desc: 'Avatar de Reunião', url: '/avatars/avatar_19.png', color: '#d946ef' },
|
||||||
{ id: 20, name: 'Amara', desc: 'Negra com tranças', url: '/avatars/avatar_20.png', color: '#f59e0b' }
|
{ id: 20, name: 'Av. 20', desc: 'Avatar de Reunião', url: '/avatars/avatar_20.png', color: '#f59e0b' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const SKIN_COLORS = ['#fed7aa', '#fdba74', '#f59e0b', '#d97706', '#9a3412', '#ffedd5'];
|
const SKIN_COLORS = ['#fed7aa', '#fdba74', '#f59e0b', '#d97706', '#9a3412', '#ffedd5'];
|
||||||
@@ -178,6 +178,7 @@ function MiiAvatarBackup({
|
|||||||
isTalking,
|
isTalking,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
isLastChatSender = false,
|
isLastChatSender = false,
|
||||||
|
inLobby = false,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
avatarId: number;
|
avatarId: number;
|
||||||
@@ -185,6 +186,7 @@ function MiiAvatarBackup({
|
|||||||
isTalking?: boolean;
|
isTalking?: boolean;
|
||||||
scale?: number;
|
scale?: number;
|
||||||
isLastChatSender?: boolean;
|
isLastChatSender?: boolean;
|
||||||
|
inLobby?: boolean;
|
||||||
} & Record<string, unknown>) {
|
} & Record<string, unknown>) {
|
||||||
const groupRef = useRef<THREE.Group>(null);
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
const ringRef = useRef<THREE.Mesh>(null);
|
const ringRef = useRef<THREE.Mesh>(null);
|
||||||
@@ -258,15 +260,17 @@ function MiiAvatarBackup({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<group ref={groupRef} position={positionVector} rotation={rotationEuler} scale={scale}>
|
<group ref={groupRef} position={positionVector} rotation={rotationEuler} scale={scale}>
|
||||||
<group position={[0, 0.38, 0]}>
|
{!inLobby && (
|
||||||
<mesh position={[0, 0, -0.001]}>
|
<group position={[0, 0.32, 0]}>
|
||||||
<planeGeometry args={[0.44, 0.1]} />
|
<mesh position={[0, 0, -0.001]}>
|
||||||
<meshBasicMaterial color="#0f172a" transparent opacity={0.7} />
|
<planeGeometry args={[0.22, 0.05]} />
|
||||||
</mesh>
|
<meshBasicMaterial color="#0f172a" transparent opacity={0.7} />
|
||||||
<Text fontSize={0.048} color="#ffffff" anchorX="center" anchorY="middle" fontWeight="bold">
|
</mesh>
|
||||||
{username}
|
<Text fontSize={0.024} color="#ffffff" anchorX="center" anchorY="middle" fontWeight="bold">
|
||||||
</Text>
|
{username}
|
||||||
</group>
|
</Text>
|
||||||
|
</group>
|
||||||
|
)}
|
||||||
|
|
||||||
{(isTalking || isLastChatSender) && (
|
{(isTalking || isLastChatSender) && (
|
||||||
<mesh ref={ringRef} rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.01, 0]}>
|
<mesh ref={ringRef} rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.01, 0]}>
|
||||||
@@ -277,18 +281,33 @@ function MiiAvatarBackup({
|
|||||||
|
|
||||||
{/* Cabeça do Avatar: Toon Head do Dicebear em HTML Transformado em 3D Plano (olhando para a mesa) */}
|
{/* Cabeça do Avatar: Toon Head do Dicebear em HTML Transformado em 3D Plano (olhando para a mesa) */}
|
||||||
<group ref={headRef} position={[0, 0.16, 0]}>
|
<group ref={headRef} position={[0, 0.16, 0]}>
|
||||||
<Html transform center distanceFactor={1.2}>
|
{inLobby ? (
|
||||||
<div className="w-16 h-16 flex items-center justify-center pointer-events-none select-none">
|
<Html center distanceFactor={0.8}>
|
||||||
<img
|
<div className="w-20 h-20 flex items-center justify-center pointer-events-none select-none">
|
||||||
src={avatarUrl}
|
<img
|
||||||
alt={username}
|
src={avatarUrl}
|
||||||
className="w-full h-full object-contain drop-shadow-md"
|
alt={username}
|
||||||
onError={(e) => {
|
className="w-full h-full object-contain drop-shadow-md"
|
||||||
(e.currentTarget as HTMLImageElement).src = `https://ui-avatars.com/api/?name=${encodeURIComponent(username || 'U')}&background=475569&color=fff&size=128&rounded=true`;
|
onError={(e) => {
|
||||||
}}
|
(e.currentTarget as HTMLImageElement).src = `https://ui-avatars.com/api/?name=${encodeURIComponent(username || 'U')}&background=475569&color=fff&size=128&rounded=true`;
|
||||||
/>
|
}}
|
||||||
</div>
|
/>
|
||||||
</Html>
|
</div>
|
||||||
|
</Html>
|
||||||
|
) : (
|
||||||
|
<Html transform center distanceFactor={1.2}>
|
||||||
|
<div className="w-16 h-16 flex items-center justify-center pointer-events-none select-none">
|
||||||
|
<img
|
||||||
|
src={avatarUrl}
|
||||||
|
alt={username}
|
||||||
|
className="w-full h-full object-contain drop-shadow-md"
|
||||||
|
onError={(e) => {
|
||||||
|
(e.currentTarget as HTMLImageElement).src = `https://ui-avatars.com/api/?name=${encodeURIComponent(username || 'U')}&background=475569&color=fff&size=128&rounded=true`;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Html>
|
||||||
|
)}
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
{/* Base cônica para dar uma sustentação 3D estilizada */}
|
{/* Base cônica para dar uma sustentação 3D estilizada */}
|
||||||
@@ -332,6 +351,7 @@ function MiiAvatar(props: {
|
|||||||
isTalking?: boolean;
|
isTalking?: boolean;
|
||||||
scale?: number;
|
scale?: number;
|
||||||
isLastChatSender?: boolean;
|
isLastChatSender?: boolean;
|
||||||
|
inLobby?: boolean;
|
||||||
} & Record<string, unknown>) {
|
} & Record<string, unknown>) {
|
||||||
const preset = AVATAR_PRESETS.find((p) => p.id === props.avatarId);
|
const preset = AVATAR_PRESETS.find((p) => p.id === props.avatarId);
|
||||||
const isPng = preset?.url.endsWith('.png');
|
const isPng = preset?.url.endsWith('.png');
|
||||||
@@ -477,12 +497,12 @@ function RealMiiAvatar({
|
|||||||
return (
|
return (
|
||||||
<group ref={groupRef} position={positionVector} rotation={rotationEuler} scale={scale}>
|
<group ref={groupRef} position={positionVector} rotation={rotationEuler} scale={scale}>
|
||||||
{/* Balão de Nome acima da cabeça */}
|
{/* Balão de Nome acima da cabeça */}
|
||||||
<group position={[0, 0.9, 0]}>
|
<group position={[0, 0.76, 0]}>
|
||||||
<mesh position={[0, 0, -0.001]}>
|
<mesh position={[0, 0, -0.001]}>
|
||||||
<planeGeometry args={[0.52, 0.11]} />
|
<planeGeometry args={[0.26, 0.055]} />
|
||||||
<meshBasicMaterial color="#0f172a" transparent opacity={0.8} />
|
<meshBasicMaterial color="#0f172a" transparent opacity={0.8} />
|
||||||
</mesh>
|
</mesh>
|
||||||
<Text fontSize={0.052} color="#ffffff" anchorX="center" anchorY="middle" fontWeight="bold">
|
<Text fontSize={0.026} color="#ffffff" anchorX="center" anchorY="middle" fontWeight="bold">
|
||||||
{username}
|
{username}
|
||||||
</Text>
|
</Text>
|
||||||
</group>
|
</group>
|
||||||
@@ -1157,16 +1177,20 @@ export default function MeetingRoom() {
|
|||||||
|
|
||||||
// Escuta mensagens do Chat via Broadcast
|
// Escuta mensagens do Chat via Broadcast
|
||||||
channel.on('broadcast', { event: 'chat_msg' }, (payload) => {
|
channel.on('broadcast', { event: 'chat_msg' }, (payload) => {
|
||||||
|
const sender = payload.payload.sender;
|
||||||
|
// Se a mensagem veio do próprio usuário local, ignora para não duplicar
|
||||||
|
if (sender === name.substring(0, 10)) return;
|
||||||
|
|
||||||
setChatMessages((prev) => [
|
setChatMessages((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
id: Math.random().toString(),
|
id: Math.random().toString(),
|
||||||
sender: payload.payload.sender,
|
sender: sender,
|
||||||
text: payload.payload.text,
|
text: payload.payload.text,
|
||||||
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setLastChatSender(payload.payload.sender);
|
setLastChatSender(sender);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Escuta atualizações de Apresentação da Maquete via Broadcast (apenas para convidados)
|
// Escuta atualizações de Apresentação da Maquete via Broadcast (apenas para convidados)
|
||||||
@@ -1351,7 +1375,7 @@ export default function MeetingRoom() {
|
|||||||
<ambientLight intensity={0.8} />
|
<ambientLight intensity={0.8} />
|
||||||
<directionalLight position={[2, 3, 2]} intensity={1.5} />
|
<directionalLight position={[2, 3, 2]} intensity={1.5} />
|
||||||
<pointLight position={[0, 0.15, 0.2]} intensity={1} color={currentAvatar.color} />
|
<pointLight position={[0, 0.15, 0.2]} intensity={1} color={currentAvatar.color} />
|
||||||
<MiiAvatar avatarId={selectedAvatar} username={name || 'Você'} position={[0, -0.04, 0]} isTalking={true} scale={0.6} />
|
<MiiAvatar avatarId={selectedAvatar} username={name || 'Você'} position={[0, -0.04, 0]} isTalking={true} scale={0.6} inLobby={true} />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col items-center justify-center h-full w-full bg-slate-950/80 p-4 text-center">
|
<div className="flex flex-col items-center justify-center h-full w-full bg-slate-950/80 p-4 text-center">
|
||||||
@@ -1654,7 +1678,7 @@ export default function MeetingRoom() {
|
|||||||
rotation={chairConf.rot}
|
rotation={chairConf.rot}
|
||||||
isTalking={isLocalUser ? isTalking : (isTalking && Math.random() > 0.5)}
|
isTalking={isLocalUser ? isTalking : (isTalking && Math.random() > 0.5)}
|
||||||
isLastChatSender={peer.username === lastChatSender}
|
isLastChatSender={peer.username === lastChatSender}
|
||||||
scale={4.8}
|
scale={3.84}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user