Compare commits

...

3 Commits

Author SHA1 Message Date
RafaelMTA13
024e730687 Finaliza as alterações e acessibilidade e inicio do chat suporte 2025-10-10 13:51:06 -03:00
RafaelMTA13
c9d1457775 Prototipo do chatsuporte 2025-10-09 23:18:15 -03:00
Rafael_Monteiro Alves
b2e2423978 Adição de mais opções de acessibilidade 2025-10-09 21:27:00 -03:00
10 changed files with 663 additions and 301 deletions

View File

@ -23,6 +23,6 @@
<!-- <script src="%PUBLIC_URL%/vendors/perfect-scrollbar/perfect-scrollbar.min.js"></script> <!-- <script src="%PUBLIC_URL%/vendors/perfect-scrollbar/perfect-scrollbar.min.js"></script>
<script src="%PUBLIC_URL%/js/bootstrap.bundle.min.js"></script> <script src="%PUBLIC_URL%/js/bootstrap.bundle.min.js"></script>
<script src="%PUBLIC_URL%/js/main.js"></script> --> <script src="%PUBLIC_URL%/js/main.js"></script> -->
<script src="https://website-widgets.pages.dev/dist/sienna.min.js" defer></script>
</body> </body>
</html> </html>

View File

@ -45,3 +45,50 @@ html[data-bs-theme="dark"] .App-header {
html[data-bs-theme="dark"] .App-link { html[data-bs-theme="dark"] .App-link {
color: #bb86fc; color: #bb86fc;
} }
.top-right-chat-button-wrapper {
position: fixed; /* 'fixed' faz ele flutuar mesmo com scroll */
top: 20px;
right: 20px;
z-index: 1001; /* Garante que fique acima de outros elementos */
}
/* App.css */
/* Container principal */
.app-wrapper {
display: flex; /* Organiza o conteúdo principal e o chat lado a lado */
}
.main-content {
position: relative; /* Essencial para ser o 'pai' */
flex-grow: 1;
transition: margin-right 0.4s ease-in-out;
}
.main-content.chat-open {
margin-right: 350px;
}
/* Posicionamento do botão que abre o chat */
.chat-button-container {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 999;
}
.floating-buttons-container {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
display: flex;
gap: 10px;
transition: right 0.4s ease-in-out;
}
.floating-buttons-container.chat-open {
right: 370px;
}

View File

@ -1,40 +1,58 @@
import React, { useState } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import './App.css';
// Suas páginas // Suas páginas
import LandingPage from './pages/LandingPage';
import Login from "./pages/Login"; import Login from "./pages/Login";
import Register from "./pages/Register"; import Register from "./pages/Register";
import Forgot from "./pages/ForgotPassword"; import Forgot from "./pages/ForgotPassword";
import PerfilSecretaria from "./perfis/perfil_secretaria/PerfilSecretaria"; import PerfilSecretaria from "./perfis/perfil_secretaria/PerfilSecretaria";
import LandingPage from './pages/LandingPage';
import PerfilFinanceiro from "./perfis/perfil_financeiro/PerfilFinanceiro"; import PerfilFinanceiro from "./perfis/perfil_financeiro/PerfilFinanceiro";
import Perfiladm from "./perfis/Perfil_adm/Perfiladm"; import Perfiladm from "./perfis/Perfil_adm/Perfiladm";
import PerfilMedico from "./perfis/Perfil_medico/PerfilMedico"; import PerfilMedico from "./perfis/Perfil_medico/PerfilMedico";
// Componentes globais de acessibilidade // Componentes globais
import VlibrasWidget from "./components/VlibrasWidget"; import VlibrasWidget from "./components/VlibrasWidget";
import BotaoAcessibilidade from "./components/botaoacessibilidade.jsx"; import BotaoAcessibilidade from "./components/botaoacessibilidade.jsx";
import ChatToggleButton from './components/ChatButton/ChatButton';
import ChatSidebar from './components/ChatSidebar/ChatSidebar';
function App() { function App() {
const [isChatOpen, setIsChatOpen] = useState(false);
const toggleChat = () => {
setIsChatOpen(!isChatOpen);
};
return ( return (
<Router> <Router>
<VlibrasWidget /> <div className="app-wrapper">
<BotaoAcessibilidade /> <div className={`main-content ${isChatOpen ? 'chat-open' : ''}`}>
<VlibrasWidget />
<Routes> <div className={`floating-buttons-container ${isChatOpen ? 'chat-open' : ''}`}>
<Route path="/" element={<LandingPage />} /> <BotaoAcessibilidade />
<Route path="/login" element={<Login />} /> <ChatToggleButton onClick={toggleChat} />
<Route path="/register" element={<Register />} /> </div>
<Route path="/forgotPassword" element={<Forgot />} />
<Route path="/secretaria/*" element={<PerfilSecretaria />} /> <Routes>
<Route path="/financeiro/*" element={<PerfilFinanceiro />} /> <Route path="/" element={<LandingPage />} />
<Route path="/medico/*" element={<PerfilMedico />} /> <Route path="/login" element={<Login />} />
<Route path="/admin/*" element={<Perfiladm />} /> <Route path="/register" element={<Register />} />
<Route path="*" element={<h2>Página não encontrada</h2>} /> <Route path="/forgotPassword" element={<Forgot />} />
</Routes> <Route path="/secretaria/*" element={<PerfilSecretaria />} />
<Route path="/financeiro/*" element={<PerfilFinanceiro />} />
<Route path="/medico/*" element={<PerfilMedico />} />
<Route path="/admin/*" element={<Perfiladm />} />
<Route path="*" element={<h2>Página não encontrada</h2>} />
</Routes>
</div>
<ChatSidebar isOpen={isChatOpen} onClose={toggleChat} />
</div>
</Router> </Router>
); );
} }
export default App; export default App;

View File

@ -0,0 +1,27 @@
.chat-toggle-button {
width: 60px;
height: 60px;
border-radius: 50%;
background-color: #007bff;
color: white;
border: none;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s ease;
}
.chat-toggle-button:hover {
background-color: #0056b3;
}
.chat-toggle-button svg {
width: 30px;
height: 30px;
fill: white;
}

View File

@ -0,0 +1,15 @@
import React from 'react';
import './ChatButton.css';
const ChatButton = ({ onClick }) => {
return (
<button className="chat-toggle-button" onClick={onClick} aria-label="Abrir chat">
{/* Ícone de Chat (SVG) */}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"></path>
</svg>
</button>
);
};
export default ChatButton;

View File

@ -0,0 +1,119 @@
.chat-sidebar {
height: 100vh;
width: 350px;
position: fixed;
top: 0;
right: 0;
background-color: #FFFFFF; /* Fundo branco limpo, como os cards */
color: #333; /* Texto escuro padrão */
font-family: Arial, sans-serif; /* Use a mesma fonte do seu site, se souber */
transform: translateX(100%);
transition: transform 0.4s ease-in-out;
box-shadow: -2px 0 15px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
z-index: 1000;
}
.chat-sidebar.open {
transform: translateX(0);
}
.chat-header {
padding: 15px 20px;
background-color: #F8F9FA; /* Cinza bem claro, como o fundo do site */
border-bottom: 1px solid #E9ECEF; /* Borda sutil */
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #212529; /* Cor do título dos cards */
}
.chat-close-button {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #6c757d; /* Cinza sutil para o ícone de fechar */
transition: color 0.2s;
}
.chat-close-button:hover {
color: #000;
}
/* Área das Mensagens */
.chat-messages {
flex-grow: 1;
padding: 20px;
overflow-y: auto;
background-color: #F8F9FA; /* Fundo igual ao header para consistência */
display: flex;
flex-direction: column;
gap: 12px; /* Espaçamento entre as mensagens */
}
/* Adicionando estilo para os balões de mensagem */
.message {
padding: 10px 15px;
border-radius: 18px;
max-width: 80%;
line-height: 1.4;
}
.message.received {
background-color: #E9ECEF; /* Cinza claro para mensagens recebidas */
color: #212529;
align-self: flex-start; /* Alinha à esquerda */
border-bottom-left-radius: 4px; /* Detalhe de estilo */
}
.message.sent {
background-color: #6C63FF; /* Cor principal do seu site */
color: white;
align-self: flex-end; /* Alinha à direita */
border-bottom-right-radius: 4px; /* Detalhe de estilo */
}
.chat-input-area {
padding: 15px 20px;
display: flex;
gap: 10px; /* Espaço entre o input e o botão */
border-top: 1px solid #E9ECEF;
background-color: #FFFFFF; /* Fundo branco para destacar */
}
.chat-input-area input {
flex-grow: 1;
border: 1px solid #DEE2E6; /* Borda padrão */
border-radius: 8px; /* Bordas arredondadas como os elementos do site */
padding: 12px 15px;
font-size: 15px;
color: #333;
transition: border-color 0.2s;
}
.chat-input-area input:focus {
outline: none;
border-color: #6C63FF; /* Destaque com a cor principal */
box-shadow: 0 0 0 2px rgba(108, 99, 255, 0.2);
}
.chat-input-area button {
background-color: #6C63FF; /* Cor principal do seu site */
border: none;
color: white;
padding: 0 25px;
border-radius: 8px; /* Mesma borda do input */
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s;
}
.chat-input-area button:hover {
background-color: #574ee6; /* Um tom um pouco mais escuro ao passar o mouse */
}

View File

@ -0,0 +1,79 @@
import React, { useState, useEffect, useRef } from 'react';
import './ChatSidebar.css';
const ChatSidebar = ({ isOpen, onClose }) => {
const [messages, setMessages] = useState([
{ id: 1, text: 'Olá! Como podemos ajudar você hoje?', sender: 'support' }
]);
const [inputValue, setInputValue] = useState('');
const messagesEndRef = useRef(null);
const sidebarClassName = `chat-sidebar ${isOpen ? 'open' : ''}`;
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const handleSendMessage = () => {
if (inputValue.trim() === '') return;
const newMessage = {
id: messages.length + 1,
text: inputValue,
sender: 'user'
};
setMessages(currentMessages => [...currentMessages, newMessage]);
setInputValue('');
setTimeout(() => {
const supportReply = {
id: messages.length + 2,
text: 'Obrigado por sua mensagem. Um de nossos atendentes responderá em breve.',
sender: 'support'
};
setMessages(currentMessages => [...currentMessages, supportReply]);
}, 1000);
};
const handleKeyPress = (event) => {
if (event.key === 'Enter') {
handleSendMessage();
}
};
return (
<div className={sidebarClassName}>
<div className="chat-header">
<h3>Suporte Online</h3>
<button onClick={onClose} className="chat-close-button">X</button>
</div>
<div className="chat-messages">
{/* Mapeia a lista de mensagens e cria um balão para cada uma */}
{messages.map(message => (
<div
key={message.id}
className={`message ${message.sender === 'user' ? 'sent' : 'received'}`}
>
{message.text}
</div>
))}
{/* Elemento invisível no final para o scroll automático */}
<div ref={messagesEndRef} />
</div>
<div className="chat-input-area">
<input
type="text"
placeholder="Digite sua mensagem..."
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={handleKeyPress}
/>
<button onClick={handleSendMessage}>Enviar</button>
</div>
</div>
);
};
export default ChatSidebar;

View File

@ -46,7 +46,7 @@ const TrocardePerfis = () => {
value={selectedProfile} value={selectedProfile}
onChange={handleSelectChange} onChange={handleSelectChange}
> >
<option value="">Selecionar perfil</option> <option value="" disabled invisible>Selecionar perfil</option>
{options.map((opt) => ( {options.map((opt) => (
<option key={opt.key} value={opt.route}> <option key={opt.key} value={opt.route}>
{opt.label} {opt.label}

View File

@ -1,272 +1,198 @@
/* --- ESTILO PARA ESCONDER O BOTÃO ORIGINAL DO VLIBRAS --- */ @import url('https://fonts.cdnfonts.com/css/open-dyslexic');
[vw-access-button] { [vw-access-button] {
display: none !important; display: none !important;
} }
/* --- ESTILOS GERAIS DO COMPONENTE --- */
.container-acessibilidade {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 99998;
display: flex;
flex-direction: column;
align-items: center;
pointer-events: none; /* Impede cliques no contêiner */
}
.botao-flutuante-acessibilidade { .botao-flutuante-acessibilidade {
position: relative;
z-index: 2; /* Acima do menu */
background: linear-gradient(45deg, #007bff, #0056b3);
color: white;
border: none;
border-radius: 50%;
width: 60px; width: 60px;
height: 60px; height: 60px;
border-radius: 50%;
background-color: #007bff;
color: white;
border: none;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
cursor: pointer; cursor: pointer;
box-shadow: 0 5px 15px rgba(0, 91, 179, 0.4);
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease; justify-content: center;
margin-top: 15px; /* Distância do menu */
pointer-events: auto; /* Permite que o botão seja clicável */
} }
.botao-flutuante-acessibilidade:hover {
transform: scale(1.1);
box-shadow: 0 8px 20px rgba(0, 91, 179, 0.5);
}
/* --- ESTILOS DO MENU "BALÃO" --- */
.menu-opcoes { .menu-opcoes {
background-color: #ffffff; position: absolute;
border-radius: 12px; bottom: 70px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); right: 0;
padding: 8px;
width: 280px; width: 280px;
z-index: 1; /* Abaixo do botão principal */ max-height: calc(100vh - 100px);
border: 1px solid #e9ecef; overflow-y: auto;
background-color: white;
/* Animação */ border-radius: 8px;
transform-origin: bottom center; box-shadow: 0 6px 12px rgba(0,0,0,0.3);
transform: translateY(10px) scale(0.95); display: flex;
flex-direction: column;
gap: 8px;
padding: 15px;
transform: scale(0.95);
transform-origin: bottom right;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
transition: all 0.2s ease; transition: transform 0.2s ease, opacity 0.2s ease, visibility 0.2s;
pointer-events: auto; /* Permite que o menu seja clicável */
} }
.menu-opcoes.aberto { .menu-opcoes.aberto {
transform: translateY(0) scale(1); transform: scale(1);
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
} }
.menu-titulo { .menu-titulo {
font-size: 14px; font-size: 18px;
font-weight: 600; font-weight: bold;
color: #6c757d; margin-bottom: 10px;
padding: 8px 12px; text-align: center;
border-bottom: 1px solid #f1f3f5; color: #333;
margin-bottom: 5px;
transition: color 0.2s ease, border-bottom-color 0.2s ease;
} }
/* --- ESTILOS DOS BOTÕES E DA CHECKBOX NO MENU --- */
.menu-opcoes button, .menu-opcoes button,
.checkbox-label-button { .checkbox-label-button {
width: 100%;
padding: 12px;
border-radius: 6px;
border: 1px solid #ddd;
background-color: #f9f9f9;
cursor: pointer;
text-align: left;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 10px;
background-color: transparent; font-size: 15px;
border: none; color: #333;
padding: 12px; transition: background-color 0.2s;
text-align: left;
cursor: pointer;
font-size: 16px;
color: #212529;
width: 100%;
border-radius: 8px;
transition: background-color 0.2s ease, color 0.2s ease;
} }
.menu-opcoes button:hover, .menu-opcoes button:hover,
.checkbox-label-button:hover { .checkbox-label-button:hover {
background-color: #f8f9fa; background-color: #f0f0f0;
} }
/* --- ESTILO DO INTERRUPTOR (CHECKBOX) --- */
.checkbox-label-button {
justify-content: space-between;
}
.checkbox-label-button input[type="checkbox"] { .checkbox-label-button input[type="checkbox"] {
appearance: none; margin-left: auto;
-webkit-appearance: none;
position: relative;
width: 44px;
height: 24px;
background-color: #ced4da;
border-radius: 12px;
cursor: pointer;
transition: background-color 0.3s ease-in-out;
}
.checkbox-label-button input[type="checkbox"]::before {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 20px; width: 20px;
height: 20px; height: 20px;
background-color: white; cursor: pointer;
border-radius: 50%;
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
transition: transform 0.3s ease-in-out;
} }
.checkbox-label-button input[type="checkbox"]:checked { /* Tamanho da Fonte */
background-color: #0d6efd;
}
.checkbox-label-button input[type="checkbox"]:checked::before {
transform: translateX(20px);
}
/* --- ✨ NOVOS ESTILOS PARA O CONTROLE DE FONTE ✨ --- */
.font-size-control { .font-size-control {
display: flex; display: flex;
align-items: center; flex-direction: column;
justify-content: space-between; padding: 10px;
padding: 8px 12px; background-color: rgba(0, 0, 0, 0.05);
color: #212529; border-radius: 8px;
transition: color 0.2s ease; margin-bottom: 5px;
border-top: 1px solid #f1f3f5; gap: 10px;
margin-top: 5px;
} }
.font-size-label { .font-size-label {
font-size: 16px;
display: flex;
align-items: center;
gap: 12px;
}
.font-size-buttons {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
}
.font-size-buttons button {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-weight: bold;
font-size: 16px;
background-color: #e9ecef;
color: #495057;
border: 1px solid #dee2e6;
border-radius: 6px;
width: 36px;
height: 32px;
padding: 0;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.2s ease, color 0.2s ease;
}
.font-size-buttons button:hover {
background-color: #dee2e6;
}
.font-size-buttons button:disabled {
background-color: #f8f9fa;
color: #adb5bd;
cursor: not-allowed;
border-color: #f1f3f5;
}
.font-size-display {
font-size: 14px;
font-weight: 600; font-weight: 600;
color: #495057; font-size: 15px;
min-width: 45px; }
.font-size-buttons {
display: flex;
justify-content: space-between;
align-items: center;
}
.font-size-buttons button {
border-radius: 50%;
width: 38px;
height: 38px;
border: 1px solid #ddd;
background-color: #fff;
font-size: 16px;
font-weight: bold;
cursor: pointer;
color: #333;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.font-size-buttons span {
font-size: 16px;
font-weight: 600;
min-width: 50px;
text-align: center; text-align: center;
transition: color 0.2s ease;
} }
/* Dark mode styles */ /* Fonte para Dislexia */
html[data-bs-theme="dark"] { .dyslexia-font-active * {
font-family: 'Open-Dyslexic', sans-serif !important;
/* Floating button */
.botao-flutuante-acessibilidade {
background: linear-gradient(45deg, #212529, #343a40);
color: #f8f9fa;
box-shadow: 0 5px 15px rgba(33, 37, 41, 0.4);
}
.botao-flutuante-acessibilidade:hover {
box-shadow: 0 8px 20px rgba(33, 37, 41, 0.5);
}
/* Menu balloon */
.menu-opcoes {
background-color: #23272b;
border: 1px solid #343a40;
box-shadow: 0 8px 25px rgba(0,0,0,0.4);
}
.menu-titulo {
color: #adb5bd;
border-bottom: 1px solid #343a40;
}
/* Menu buttons and checkbox */
.menu-opcoes button,
.checkbox-label-button {
color: #f8f9fa;
}
.menu-opcoes button:hover,
.checkbox-label-button:hover {
background-color: #343a40;
}
/* Checkbox switch */
.checkbox-label-button input[type="checkbox"] {
background-color: #495057;
}
.checkbox-label-button input[type="checkbox"]:checked {
background-color: #0d6efd;
}
.checkbox-label-button input[type="checkbox"]::before {
background-color: #f8f9fa;
}
/* Font size control */
.font-size-control {
color: #f8f9fa;
border-top: 1px solid #343a40;
}
.font-size-label {
color: #f8f9fa;
}
.font-size-buttons button {
background-color: #343a40;
color: #f8f9fa;
border: 1px solid #495057;
}
.font-size-buttons button:hover {
background-color: #495057;
}
.font-size-buttons button:disabled {
background-color: #23272b;
color: #6c757d;
border-color: #343a40;
}
.font-size-display {
color: #f8f9fa;
}
} }
/* Espaçamento e Altura */
.letter-spacing-active p, .letter-spacing-active li, .letter-spacing-active span, .letter-spacing-active a, .letter-spacing-active div, .letter-spacing-active td, .letter-spacing-active h1, .letter-spacing-active h2, .letter-spacing-active h3, .letter-spacing-active h4, .letter-spacing-active h5, .letter-spacing-active h6 {
letter-spacing: 1.5px !important;
}
.line-height-active p, .line-height-active li, .line-height-active span, .line-height-active a, .line-height-active div, .line-height-active td, .line-height-active h1, .line-height-active h2, .line-height-active h3, .line-height-active h4, .line-height-active h5, .line-height-active h6 {
line-height: 3 !important;
}
/* Guia de Leitura */
.reading-guide-mask {
position: fixed;
left: 0;
width: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 99999;
pointer-events: none;
transition: all 0.05s ease-out;
}
.reading-guide-top {
top: 0;
height: 0;
}
.reading-guide-bottom {
bottom: 0;
top: 100vh;
}
/* Cursor Grande */
.big-cursor-active, .big-cursor-active * {
cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="black" stroke="white" stroke-width="2" d="M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V4A1,1 0 0,1 7,3C7.24,3 7.45,3.08 7.62,3.22L16.39,10.22C16.78,10.5 16.88,11.05 16.6,11.45L13.64,21.97Z" /></svg>') 1 1, auto !important;
}
/* Filtros de Daltonismo */
.colorblind-protanopia { filter: url(#protanopia); }
.colorblind-deuteranopia { filter: url(#deuteranopia); }
.colorblind-tritanopia { filter: url(#tritanopia); }
.colorblind-achromatopsia { filter: url(#achromatopsia); }
.acessibilidade-select-control {
display: flex;
flex-direction: column;
gap: 8px;
background-color: rgba(0, 0, 0, 0.05);
padding: 10px;
border-radius: 8px;
margin-bottom: 5px;
}
.acessibilidade-select-control label {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
font-size: 15px;
}
.acessibilidade-select-control select {
width: 100%;
padding: 8px;
border-radius: 4px;
border: 1px solid #ccc;
font-size: 14px;
}
/* Dark Mode */
.dark-mode .menu-opcoes { background-color: #2d2d2d; color: #f1f1f1; }
.dark-mode .menu-titulo { color: #f1f1f1; }
.dark-mode .menu-opcoes button, .dark-mode .checkbox-label-button { background-color: #424242; border-color: #555; color: #f1f1f1; }
.dark-mode .menu-opcoes button:hover, .dark-mode .checkbox-label-button:hover { background-color: #535353; }
.dark-mode .font-size-control, .dark-mode .acessibilidade-select-control { background-color: rgba(255, 255, 255, 0.1); }
.dark-mode .font-size-buttons button { background-color: #535353; color: #f1f1f1; border-color: #666; }
.dark-mode .acessibilidade-select-control select { background-color: #535353; color: #f1f1f1; border-color: #666; }

View File

@ -2,41 +2,131 @@ import React, { useState, useEffect, useRef } from 'react';
import './botaoacessibilidade.css'; // Importando o CSS import './botaoacessibilidade.css'; // Importando o CSS
import { setTheme } from '../assets/static/js/components/dark'; import { setTheme } from '../assets/static/js/components/dark';
// Componente para o Guia de Leitura
function GuiaDeLeitura() {
const topMaskRef = useRef(null);
const bottomMaskRef = useRef(null);
useEffect(() => {
const handleMouseMove = (e) => {
if (topMaskRef.current && bottomMaskRef.current) {
const windowHeight = 40;
const offset = windowHeight / 2;
topMaskRef.current.style.height = `${e.clientY - offset}px`;
bottomMaskRef.current.style.top = `${e.clientY + offset}px`;
}
};
window.addEventListener('mousemove', handleMouseMove);
return () => { window.removeEventListener('mousemove', handleMouseMove); };
}, []);
return (
<>
<div ref={topMaskRef} className="reading-guide-mask reading-guide-top"></div>
<div ref={bottomMaskRef} className="reading-guide-mask reading-guide-bottom"></div>
</>
);
}
// --- COMPONENTE PRINCIPAL ---
function BotaoAcessibilidade() { function BotaoAcessibilidade() {
// Estados de todas as funcionalidades
const [isMenuOpen, setIsMenuOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false);
const [isReadOnHoverActive, setIsReadOnHoverActive] = useState(false);
const [isDarkMode, setIsDarkMode] = useState(false); const [isDarkMode, setIsDarkMode] = useState(false);
const [isReadOnHoverActive, setIsReadOnHoverActive] = useState(false);
const [fontSize, setFontSize] = useState(1);
const [isBigCursor, setIsBigCursor] = useState(false);
const [colorblindMode, setColorblindMode] = useState('none');
const [isReadingGuide, setIsReadingGuide] = useState(false);
const [isDyslexiaFont, setIsDyslexiaFont] = useState(false);
const [isLetterSpacing, setIsLetterSpacing] = useState(false);
const [isLineHeight, setIsLineHeight] = useState(false);
const lastSpokenTargetRef = useRef(null); const lastSpokenTargetRef = useRef(null);
// Efeitos para aplicar as funcionalidades na página
useEffect(() => { setTheme(isDarkMode ? "dark" : "light", true); }, [isDarkMode]);
useEffect(() => { useEffect(() => {
setTheme(isDarkMode ? "dark" : "light", true); const originalFontSize = document.documentElement.style.fontSize;
}, [isDarkMode]); document.documentElement.style.fontSize = `${fontSize * 100}%`;
return () => { document.documentElement.style.fontSize = originalFontSize; };
}, [fontSize]);
useEffect(() => { useEffect(() => {
if (!isReadOnHoverActive) { document.body.classList.toggle('big-cursor-active', isBigCursor);
window.speechSynthesis.cancel(); return () => { document.body.classList.remove('big-cursor-active'); };
return; }, [isBigCursor]);
useEffect(() => {
document.body.classList.toggle('dyslexia-font-active', isDyslexiaFont);
return () => { document.body.classList.remove('dyslexia-font-active'); };
}, [isDyslexiaFont]);
useEffect(() => {
document.body.classList.toggle('letter-spacing-active', isLetterSpacing);
return () => { document.body.classList.remove('letter-spacing-active'); };
}, [isLetterSpacing]);
useEffect(() => {
document.body.classList.toggle('line-height-active', isLineHeight);
return () => { document.body.classList.remove('line-height-active'); };
}, [isLineHeight]);
useEffect(() => {
const classesToRemove = ['colorblind-protanopia', 'colorblind-deuteranopia', 'colorblind-tritanopia', 'colorblind-achromatopsia'];
document.documentElement.classList.remove(...classesToRemove);
if (colorblindMode !== 'none') {
document.documentElement.classList.add(`colorblind-${colorblindMode}`);
} }
const handleMouseOver = (event) => { }, [colorblindMode]);
const target = event.target; // VERSÃO DEFINITIVA - Muito mais abrangente
if (target && target !== lastSpokenTargetRef.current && target.innerText) { useEffect(() => {
const text = target.innerText.trim(); if (!isReadOnHoverActive) {
if (text.length > 0 && ['P', 'H1', 'H2', 'H3', 'BUTTON', 'A', 'LI', 'LABEL'].includes(target.tagName)) { window.speechSynthesis.cancel();
lastSpokenTargetRef.current = target; return;
}
const handleMouseOver = (event) => {
// Seletor muito mais completo, incluindo DIVs, SPANs, células de tabela, imagens e papéis de acessibilidade
const selector = 'P, H1, H2, H3, H4, H5, H6, BUTTON, A, LI, LABEL, SPAN, TD, TH, DT, DD, FIGCAPTION, [role="button"], [role="link"], [role="menuitem"], IMG';
const relevantElement = event.target.closest(selector);
if (relevantElement && relevantElement !== lastSpokenTargetRef.current) {
let textToSpeak = '';
// Lógica especial para IMAGENS: lê o atributo "alt"
if (relevantElement.tagName === 'IMG') {
textToSpeak = relevantElement.getAttribute('alt');
}
// Lógica para outros elementos: prioriza aria-label, depois title, e por último o texto interno
else {
textToSpeak =
relevantElement.getAttribute('aria-label') ||
relevantElement.getAttribute('title') ||
relevantElement.innerText;
}
if (textToSpeak) {
textToSpeak = textToSpeak.trim();
// Evita ler elementos que só têm elementos filhos, mas nenhum texto próprio direto
const hasTextAndNoChildren = textToSpeak.length > 0 && relevantElement.children.length === 0;
const hasTextAndIsBlock = textToSpeak.length > 0 && !['SPAN', 'A', 'STRONG', 'EM'].includes(relevantElement.tagName);
if (hasTextAndNoChildren || hasTextAndIsBlock) {
lastSpokenTargetRef.current = relevantElement;
window.speechSynthesis.cancel(); window.speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(text); const utterance = new SpeechSynthesisUtterance(textToSpeak);
utterance.lang = 'pt-BR'; utterance.lang = 'pt-BR';
window.speechSynthesis.speak(utterance); window.speechSynthesis.speak(utterance);
} }
} }
}; }
document.body.addEventListener('mouseover', handleMouseOver); };
return () => {
document.body.removeEventListener('mouseover', handleMouseOver);
window.speechSynthesis.cancel();
};
}, [isReadOnHoverActive]);
document.body.addEventListener('mouseover', handleMouseOver);
return () => {
document.body.removeEventListener('mouseover', handleMouseOver);
window.speechSynthesis.cancel();
};
}, [isReadOnHoverActive]);
// Funções de controle
const handleIncreaseFontSize = () => setFontSize(prevSize => Math.min(prevSize + 0.1, 1.6));
const handleDecreaseFontSize = () => setFontSize(prevSize => Math.max(prevSize - 0.1, 0.8));
const handleVlibrasClick = () => { const handleVlibrasClick = () => {
const originalVlibrasButton = document.querySelector('[vw-access-button]'); const originalVlibrasButton = document.querySelector('[vw-access-button]');
if (originalVlibrasButton) { if (originalVlibrasButton) {
@ -47,53 +137,96 @@ function BotaoAcessibilidade() {
setIsMenuOpen(false); setIsMenuOpen(false);
}; };
const handleReadAloud = () => {
const selectedText = window.getSelection().toString().trim();
if (selectedText) {
window.speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(selectedText);
utterance.lang = 'pt-BR';
window.speechSynthesis.speak(utterance);
} else {
alert("Por favor, selecione um texto para ler em voz alta.");
}
setIsMenuOpen(false);
};
return ( return (
<div className={`container-acessibilidade ${isDarkMode ? 'dark-mode' : ''}`}> <div className={`container-acessibilidade ${isDarkMode ? 'dark-mode' : ''}`}>
{isReadingGuide && <GuiaDeLeitura />}
<svg style={{ position: 'absolute', height: 0, width: 0 }}>
<defs>
<filter id="protanopia"><feColorMatrix in="SourceGraphic" type="matrix" values="0.567, 0.433, 0, 0, 0, 0.558, 0.442, 0, 0, 0, 0, 0.242, 0.758, 0, 0, 0, 0, 0, 1, 0"/></filter>
<filter id="deuteranopia"><feColorMatrix in="SourceGraphic" type="matrix" values="0.625, 0.375, 0, 0, 0, 0.7, 0.3, 0, 0, 0, 0, 0.3, 0.7, 0, 0, 0, 0, 0, 1, 0"/></filter>
<filter id="tritanopia"><feColorMatrix in="SourceGraphic" type="matrix" values="0.95, 0.05, 0, 0, 0, 0, 0.433, 0.567, 0, 0, 0, 0.475, 0.525, 0, 0, 0, 0, 0, 1, 0"/></filter>
<filter id="achromatopsia"><feColorMatrix in="SourceGraphic" type="matrix" values="0.299, 0.587, 0.114, 0, 0, 0.299, 0.587, 0.114, 0, 0, 0.299, 0.587, 0.114, 0, 0, 0, 0, 0, 1, 0"/></filter>
</defs>
</svg>
<div className={`menu-opcoes ${isMenuOpen ? 'aberto' : ''}`}> <div className={`menu-opcoes ${isMenuOpen ? 'aberto' : ''}`}>
<div className="menu-titulo">Acessibilidade</div> <div className="menu-titulo">Acessibilidade</div>
<div className="font-size-control">
<div className="font-size-label">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="4 7 4 4 20 4 20 7"></polyline><line x1="9" y1="20" x2="15" y2="20"></line><line x1="12" y1="4" x2="12" y2="20"></line></svg>
Tamanho da Fonte
</div>
<div className="font-size-buttons">
<button onClick={handleDecreaseFontSize} title="Diminuir Fonte">A-</button>
<span>{`${Math.round(fontSize * 100)}%`}</span>
<button onClick={handleIncreaseFontSize} title="Aumentar Fonte">A+</button>
</div>
</div>
<div className="acessibilidade-select-control">
<label htmlFor="colorblind-select">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
Modo Daltonismo
</label>
<select id="colorblind-select" value={colorblindMode} onChange={(e) => setColorblindMode(e.target.value)}>
<option value="none">Desativado</option>
<option value="protanopia">Protanopia</option>
<option value="deuteranopia">Deuteranopia</option>
<option value="tritanopia">Tritanopia</option>
<option value="achromatopsia">Monocromático</option>
</select>
</div>
<label htmlFor="darkModeCheckbox" className="checkbox-label-button"> <label htmlFor="darkModeCheckbox" className="checkbox-label-button">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
Modo Escuro Modo Escuro
<input <input type="checkbox" id="darkModeCheckbox" checked={isDarkMode} onChange={() => setIsDarkMode(!isDarkMode)} />
type="checkbox"
id="darkModeCheckbox"
checked={isDarkMode}
onChange={() => setIsDarkMode(!isDarkMode)}
/>
</label> </label>
<label htmlFor="dyslexiaFontCheckbox" className="checkbox-label-button">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
Fonte para Dislexia
<input type="checkbox" id="dyslexiaFontCheckbox" checked={isDyslexiaFont} onChange={() => setIsDyslexiaFont(!isDyslexiaFont)} />
</label>
<label htmlFor="letterSpacingCheckbox" className="checkbox-label-button">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
Espaçamento entre Letras
<input type="checkbox" id="letterSpacingCheckbox" checked={isLetterSpacing} onChange={() => setIsLetterSpacing(!isLetterSpacing)} />
</label>
<label htmlFor="lineHeightCheckbox" className="checkbox-label-button">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
Altura da Linha
<input type="checkbox" id="lineHeightCheckbox" checked={isLineHeight} onChange={() => setIsLineHeight(!isLineHeight)} />
</label>
<label htmlFor="readingGuideCheckbox" className="checkbox-label-button">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg>
Guia de Leitura
<input type="checkbox" id="readingGuideCheckbox" checked={isReadingGuide} onChange={() => setIsReadingGuide(!isReadingGuide)} />
</label>
<label htmlFor="bigCursorCheckbox" className="checkbox-label-button">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"></path><path d="M13 13l6 6"></path></svg>
Cursor Grande
<input type="checkbox" id="bigCursorCheckbox" checked={isBigCursor} onChange={() => setIsBigCursor(!isBigCursor)} />
</label>
<label htmlFor="readOnHoverCheckbox" className="checkbox-label-button"> <label htmlFor="readOnHoverCheckbox" className="checkbox-label-button">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"></path><path d="M13 13l6 6"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"></path><path d="M13 13l6 6"></path></svg>
Leitura instantânea Leitura instantânea
<input <input type="checkbox" id="readOnHoverCheckbox" checked={isReadOnHoverActive} onChange={() => setIsReadOnHoverActive(!isReadOnHoverActive)} />
type="checkbox"
id="readOnHoverCheckbox"
checked={isReadOnHoverActive}
onChange={() => setIsReadOnHoverActive(!isReadOnHoverActive)}
/>
</label> </label>
{/* ADICIONADO DE VOLTA: Botão para LIBRAS */}
<button onClick={handleVlibrasClick}> <button onClick={handleVlibrasClick}>
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 11V6a2 2 0 0 0-2-2v0a2 2 0 0 0-2 2v0" /><path d="M14 10V4a2 2 0 0 0-2-2v0a2 2 0 0 0-2 2v2" /><path d="M10 10.5V6a2 2 0 0 0-2-2v0a2 2 0 0 0-2 2v8" /><path d="M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h2.3" /></svg> <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 11V6a2 2 0 0 0-2-2v0a2 2 0 0 0-2 2v0" /><path d="M14 10V4a2 2 0 0 0-2-2v0a2 2 0 0 0-2 2v2" /><path d="M10 10.5V6a2 2 0 0 0-2-2v0a2 2 0 0 0-2 2v8" /><path d="M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h2.3" /></svg>
Traduzir para LIBRAS Traduzir para LIBRAS
</button> </button>
</div> </div>
<button
className="botao-flutuante-acessibilidade" <button className="botao-flutuante-acessibilidade" onClick={() => setIsMenuOpen(!isMenuOpen)} title="Menu de Acessibilidade">
onClick={() => setIsMenuOpen(!isMenuOpen)}
title="Menu de Acessibilidade"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="30" height="30" fill="white"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="30" height="30" fill="white">
<path d="M12 2c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm9 7h-6v13h-2v-6h-2v6H9V9H3V7h18v2z" /> <path d="M12 2c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm9 7h-6v13h-2v-6h-2v6H9V9H3V7h18v2z" />
</svg> </svg>
@ -101,6 +234,4 @@ function BotaoAcessibilidade() {
</div> </div>
); );
} }
export default BotaoAcessibilidade; export default BotaoAcessibilidade;