Olá @Don, fiz uma pequena modificação pessoal no tema FKB pro para adicionar um botão flutuante de chat no canto inferior direito para tablet e desktop, e no canto inferior esquerdo no mobile, com o mesmo efeito no botão de criar tópico. Acho que existe algo melhor em código, mas isso funciona ![]()
aqui está meu código de componente: css
// 1. BOTÃO FLUTUANTE PRINCIPAL
.header-dropdown-toggle.chat-header-icon {
position: fixed !important;
bottom: 60px !important;
right: 20px !important;
z-index: 9999 !important;
/* Dimensões perfeitas (círculo) */
width: 60px !important;
height: 60px !important;
min-width: 60px !important;
min-height: 60px !important;
border-radius: 50% !important;
/* Cor azul vibrante e sombra colorida */
background: #0084ff !important;
box-shadow: 0 4px 14px rgba(0, 132, 255, 0.4), 0 2px 4px rgba(0, 0, 0, 0.1) !important;
/* Limpeza das margens */
display: flex !important;
align-items: center !important;
justify-content: center !important;
padding: 0 !important;
margin: 0 !important;
border: none !important;
/* Transição suave */
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.3s ease, box-shadow 0.2s ease !important;
}
/* Posição específica no Mobile (Telas <= 640px) */
@media (max-width: 640px) {
.header-dropdown-toggle.chat-header-icon {
bottom: 80px !important;
left: 20px !important; /* Mude para right: 20px se preferir colocá-lo à direita novamente */
}
}
/* Efeito ao passar o mouse (ampliação leve) */
.header-dropdown-toggle.chat-header-icon:hover {
transform: scale(1.08) !important;
box-shadow: 0 6px 20px rgba(0, 132, 255, 0.5), 0 4px 8px rgba(0, 0, 0, 0.15) !important;
}
/* 2. LIMPEZA DO FUNDO CINZA NATIVO DO DISCOURSE */
.header-dropdown-toggle.chat-header-icon .icon,
.header-dropdown-toggle.chat-header-icon span:not(.badge-notification):not(.chat-badge):not([class*="unread"]):not([class*="count"]) {
background-color: transparent !important;
box-shadow: none !important;
border: none !important;
}
/* 3. CENTRAGEM PERFEITA DO ÍCONE (A bolha) */
.header-dropdown-toggle.chat-header-icon .d-icon,
.header-dropdown-toggle.chat-header-icon svg {
position: absolute !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
margin: 0 !important;
padding: 0 !important;
width: 32px !important;
height: 32px !important;
color: #ffffff !important;
filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.1)) !important;
}
/* 4. BADGE DE NOTIFICAÇÃO (Contador verde + Número visível) */
.header-dropdown-toggle.chat-header-icon .badge-notification,
.header-dropdown-toggle.chat-header-icon .chat-badge,
.header-dropdown-toggle.chat-header-icon .unread-notifications,
.header-dropdown-toggle.chat-header-icon [class*="badge"],
.d-header-icons .header-dropdown-toggle.chat-header-icon .badge-notification {
position: absolute !important;
top: -4px !important;
right: -4px !important;
left: auto !important;
margin: 0 !important;
z-index: 10 !important;
/* Cor verde forçada */
background-color: #53A548 !important;
background: #53A548 !important;
color: #ffffff !important;
/* Tipografia forçada para tornar o número visível */
font-size: 13px !important;
font-weight: bold !important;
line-height: 18px !important;
letter-spacing: normal !important;
text-indent: 0 !important;
/* Dimensões e forma */
padding: 0 6px !important;
min-width: 22px !important;
height: 22px !important;
border-radius: 50px !important;
border: 2px solid #ffffff !important;
box-sizing: border-box !important;
/* Centralização do número */
display: flex !important;
align-items: center !important;
justify-content: center !important;
white-space: nowrap !important;
overflow: visible !important;
/* Animação Pulse Verde (ligada aos seus keyframes) */
animation: badge-pulse 2s infinite !important;
}
/* Força todos os elementos filhos (se o Discourse envolver o número em um span) a serem exibidos */
.header-dropdown-toggle.chat-header-icon .badge-notification *,
.header-dropdown-toggle.chat-header-icon .chat-badge *,
.header-dropdown-toggle.chat-header-icon [class*="badge"] * {
color: #ffffff !important;
background: transparent !important;
display: inline-block !important;
visibility: visible !important;
opacity: 1 !important;
font-size: inherit !important;
}
/* 5. ANIMAÇÕES CORRIGIDAS (Efeito Radar & Quicada) */
/* 5.1 - A onda de radar (pulso) ao redor do badge vermelho */
.header-dropdown-toggle.chat-header-icon .badge-notification,
.header-dropdown-toggle.chat-header-icon .chat-badge,
.header-dropdown-toggle.chat-header-icon .chat-channel-unread-indicator {
animation: badge-pulse 2s infinite !important;
transform-origin: center !important;
}
/* 5.2 - O efeito de quicada no botão azul inteiro quando há uma mensagem */
.header-dropdown-toggle.chat-header-icon:has(.badge-notification),
.header-dropdown-toggle.chat-header-icon:has(.chat-badge),
.header-dropdown-toggle.chat-header-icon:has(.chat-channel-unread-indicator) {
animation: chat-bounce 3s infinite !important;
}
/* --- DEFINIÇÃO DOS KEYFRAMES (Não modificar) --- */
/* Definição da onda: usamos apenas o box-shadow para evitar bugs de exibição */
@keyframes badge-pulse {
0% {
box-shadow: 0 0 0 0 rgba(250, 62, 62, 0.8);
}
70% {
box-shadow: 0 0 0 10px rgba(250, 62, 62, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(250, 62, 62, 0);
}
}
/* Definição da quicada: o botão salta levemente a cada 3 segundos */
@keyframes chat-bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-6px);
}
60% {
transform: translateY(-3px);
}
}
/* 6. PULSAÇÃO NA LISTA DE CANAIS E MENSAGENS PRIVADAS */
/* 6.1 - Permitimos o estouro nos contêineres de lista */
.chat-channel-row,
.chat-channel-list-item,
.chat-drawer-container .chat-channel-unread-indicator,
.chat-channel-row .chat-channel-unread-indicator {
overflow: visible !important;
}
/* 6.2 - Estilo e margens do ponto indicador */
.chat-channel-row .chat-channel-unread-indicator,
.chat-channel-row .badge-notification,
.chat-channel-row .unread-indicator,
.chat-drawer-container .chat-channel-unread-indicator,
.chat-full-page .chat-channel-unread-indicator,
.chat-channel-list-item .chat-channel-unread-indicator {
animation: list-dot-pulse 1.8s infinite !important;
border-radius: 50% !important;
position: relative !important;
overflow: visible !important;
margin: 0 6px !important; /* Espaço para não colar no texto e na data */
}
/* 6.3 - Animação ajustada (onda mais compacta de no máximo 6px) */
@keyframes list-dot-pulse {
0% {
box-shadow: 0 0 0 0 rgba(0, 132, 255, 0.7); /* Adapta-se à cor do ponto */
}
70% {
box-shadow: 0 0 0 6px rgba(0, 132, 255, 0); /* Raio reduzido para permanecer na linha */
}
100% {
box-shadow: 0 0 0 0 rgba(0, 132, 255, 0);
}
}
e o JS:
import { apiInitializer } from "discourse/lib/api";
export default apiInitializer((api) => {
let lastScrollTop = 0;
let isHiddenByScroll = false;
// 1. Manipulador de scroll (Ativo APENAS no mobile)
window.addEventListener("scroll", () => {
const isMobile = window.innerWidth <= 640;
const floatBtn = document.querySelector('.header-dropdown-toggle.chat-header-icon');
if (!floatBtn) return;
// Se estivermos no computador, garantimos que o botão permaneça sempre visível
if (!isMobile) {
if (isHiddenByScroll) {
floatBtn.style.setProperty('transform', 'translateY(0) scale(1)', 'important');
floatBtn.style.setProperty('opacity', '1', 'important');
isHiddenByScroll = false;
}
return;
}
const currentScrollTop = window.pageYOffset || document.documentElement.scrollTop;
// Se estivermos rolando para baixo (e tivermos passado de 100px)
if (currentScrollTop > lastScrollTop && currentScrollTop > 100) {
if (!isHiddenByScroll) {
floatBtn.style.setProperty('transform', 'translateY(100px) scale(0.8)', 'important');
floatBtn.style.setProperty('opacity', '0', 'important');
isHiddenByScroll = true;
}
}
// Se estivermos rolando para cima
else if (currentScrollTop < lastScrollTop) {
if (isHiddenByScroll) {
floatBtn.style.setProperty('transform', 'translateY(0) scale(1)', 'important');
floatBtn.style.setProperty('opacity', '1', 'important');
isHiddenByScroll = false;
}
}
lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop;
}, { passive: true });
// 2. MutationObserver para gerenciar a exibição base
const observer = new MutationObserver(() => {
const chatOpen = document.querySelector('.chat-drawer, .d-chat-floating-panel, .chat-widget, .chat-panel, .chat-out-of-container, .chat-full-page');
const isChatRoute = window.location.pathname.startsWith('/chat');
const hasFullPageClass = document.body.classList.contains('has-full-page-chat');
const isMobile = window.innerWidth <= 640;
const isTopicRoute = window.location.pathname.includes('/t/') || document.body.classList.contains('archetype-regular');
const hideOnMobileTopic = isMobile && isTopicRoute;
const floatBtn = document.querySelector('.header-dropdown-toggle.chat-header-icon');
if (floatBtn) {
if (chatOpen || isChatRoute || hasFullPageClass || hideOnMobileTopic) {
floatBtn.style.setProperty('display', 'none', 'important');
} else {
floatBtn.style.setProperty('display', 'flex', 'important');
// Gerenciamento limpo da posição mobile / desktop
if (isMobile) {
floatBtn.style.setProperty('left', '10px', 'important'); // Mude o valor se quiser deslocar mais
floatBtn.style.removeProperty('right');
} else {
floatBtn.style.setProperty('right', '20px', 'important');
floatBtn.style.removeProperty('left');
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true, attributes: true });
});

