primeiro commit

This commit is contained in:
isaac kauã 2025-09-15 02:15:44 -03:00
commit 7334423723
146 changed files with 104548 additions and 0 deletions

View File

@ -0,0 +1,234 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>HealthOne • Agendar Consulta</title>
<style>
:root{
--medicio-teal: #3fbbc0;
--medicio-teal-600: #34a3a8;
--medicio-dark: #2a2d34;
--medicio-muted: #6b7280;
--medicio-border: #e5e7eb;
--medicio-white: #ffffff;
--shadow: 0 8px 24px rgba(0,0,0,.06);
--radius: 12px;
}
body{
margin:0; font-family: Inter, sans-serif;
background: var(--medicio-white); color: var(--medicio-dark);
}
.appbar{
background:#ffffff; border-bottom:1px solid var(--medicio-border);
padding:18px 20px; display:flex; align-items:center; gap:12px;
}
.logo {
width: 80px;
height: auto;
border-radius: 50%; /* deixa redondo */
box-shadow: 0 0 20px rgba(63, 187, 188, 0.7), 0 0 40px rgba(63, 187, 188, 0.4);
}
main.wrap{
max-width:600px; margin:24px auto; padding:0 20px 80px;
}
.btn{
appearance:none; border:none; background: var(--medicio-teal); color:white;
padding:10px 16px; border-radius:12px; cursor:pointer; font-weight:600;
box-shadow:0 6px 16px rgba(63,187,192,.25); margin-right:8px;
}
.btn.secondary{ background:#ffffff; color:var(--medicio-dark); border:1px solid var(--medicio-border); box-shadow:none; }
#consultaMarcada{ margin-bottom:20px; }
ul{ padding-left:20px; margin-top:8px; }
li{ margin-bottom:4px; }
/* Calendário */
.calendar{ width:100%; border-collapse:collapse; margin-bottom:12px; }
.calendar th, .calendar td{ width:14.28%; text-align:center; padding:6px; cursor:pointer; border-radius:6px; }
.calendar th{ color:var(--medicio-muted); font-weight:500; }
.calendar td:hover{ background: var(--medicio-teal-100); }
.calendar .selected{ background: var(--medicio-teal); color:white; }
.calendar-nav{ display:flex; justify-content:space-between; margin-bottom:8px; }
</style>
</head>
<body>
<!-- Topbar -->
<div class="appbar">
<img src="logo_HealthOne.jpeg" alt="HealthOne" class="logo">
<div>
<small>Agendamento de Consulta</small>
</div>
</div>
<main class="wrap">
<!-- Médico selecionado e consultas -->
<div id="consultaMarcada"></div>
<!-- Calendário -->
<div class="calendar-nav">
<button id="prevMonth" class="btn secondary"></button>
<div id="mesAno"></div>
<button id="nextMonth" class="btn secondary"></button>
</div>
<table class="calendar" id="calendar">
<thead>
<tr>
<th>Dom</th><th>Seg</th><th>Ter</th><th>Qua</th><th>Qui</th><th>Sex</th><th>Sáb</th>
</tr>
</thead>
<tbody></tbody>
</table>
<!-- Hora -->
<div>
<label for="horaConsulta">Escolha a hora:</label>
<select id="horaConsulta">
<option value="07:00">07:00</option>
<option value="07:30">07:00</option>
<option value="08:00">08:00</option>
<option value="08:30">08:30</option>
<option value="09:00">09:00</option>
<option value="09:30">09:30</option>
<option value="10:00">10:00</option>
<option value="10:30">10:30</option>
<option value="11:00">11:00</option>
<option value="11:30">11:30</option>
<option value="12:00">12:00</option>
<option value="12:30">12:30</option>
<option value="13:00">13:00</option>
<option value="13:30">13:30</option>
<option value="14:00">14:00</option>
<option value="14:30">14:30</option>
<option value="15:00">15:00</option>
<option value="15:30">15:30</option>
<option value="16:00">16:00</option>
<option value="16:30">16:30</option>
<option value="17:00">17:00</option>
<option value="17:30">17:30</option>
<option value="18:00">18:00</option>
<option value="18:30">18:30</option>
<option value="19:00">19:00</option>
</select>
</div>
<!-- Botões -->
<div>
<button id="confirmarConsulta" class="btn">Confirmar Consulta</button>
<button id="voltarLista" class="btn secondary">Voltar para Médicos</button>
</div>
</main>
<script>
const medico = JSON.parse(localStorage.getItem('healthone_medico_escolhido'));
const consultaDiv = document.getElementById('consultaMarcada');
if(medico){
consultaDiv.innerHTML = `<strong>Médico:</strong> ${medico.nome} (${medico.especialidade})`;
}
document.getElementById('voltarLista').onclick = ()=>{
window.location.href = 'index.html';
};
let selectedDate = null;
const calendarBody = document.querySelector('#calendar tbody');
const mesAno = document.getElementById('mesAno');
let currentMonth = new Date().getMonth();
let currentYear = new Date().getFullYear();
function renderCalendar(month, year){
calendarBody.innerHTML = '';
const firstDay = new Date(year, month,1).getDay();
const daysInMonth = new Date(year, month+1,0).getDate();
mesAno.textContent = `${new Date(year, month).toLocaleString('pt-BR',{month:'long'})} ${year}`;
let row = document.createElement('tr');
for(let i=0;i<firstDay;i++){ row.appendChild(document.createElement('td')); }
for(let d=1; d<=daysInMonth; d++){
if(row.children.length===7){ calendarBody.appendChild(row); row=document.createElement('tr'); }
const td = document.createElement('td');
td.textContent = d;
td.onclick = ()=>{
selectedDate = new Date(year, month, d);
document.querySelectorAll('#calendar td').forEach(cell=>cell.classList.remove('selected'));
td.classList.add('selected');
// 🔥 verificar horários ocupados neste dia
marcarHorariosOcupados();
};
row.appendChild(td);
}
while(row.children.length<7){ row.appendChild(document.createElement('td')); }
calendarBody.appendChild(row);
}
document.getElementById('prevMonth').onclick = ()=>{
currentMonth--;
if(currentMonth<0){ currentMonth=11; currentYear--; }
renderCalendar(currentMonth,currentYear);
};
document.getElementById('nextMonth').onclick = ()=>{
currentMonth++;
if(currentMonth>11){ currentMonth=0; currentYear++; }
renderCalendar(currentMonth,currentYear);
};
renderCalendar(currentMonth,currentYear);
function marcarHorariosOcupados(){
if (!selectedDate) return;
const dataStr = selectedDate.toISOString().slice(0,10);
const consultas = JSON.parse(localStorage.getItem('healthone_consultas') || '[]');
// pega horários já usados neste dia para este médico
const ocupados = consultas
.filter(c => c.medicoId === medico.id && c.dataHora.startsWith(dataStr))
.map(c => c.dataHora.split(' ')[1]);
const horaInput = document.getElementById('horaConsulta');
// se o input estiver num horário ocupado, limpa e avisa
if (ocupados.includes(horaInput.value)) {
horaInput.value = '';
alert('Alguns horários deste dia já estão ocupados. Escolha outro.');
}
}
document.getElementById('confirmarConsulta').onclick = ()=>{
const hora = document.getElementById('horaConsulta').value;
if(!selectedDate || !hora){ alert('Escolha data e hora'); return; }
const dataStr = selectedDate.toISOString().slice(0,10);
const novaConsulta = {
medicoId: medico.id,
medicoNome: medico.nome,
especialidade: medico.especialidade,
dataHora: `${dataStr} ${hora}`
};
let consultas = JSON.parse(localStorage.getItem('healthone_consultas') || '[]');
// verificar se já existe consulta para o mesmo médico, data e hora
const ocupado = consultas.some(c =>
c.medicoId === medico.id && c.dataHora === `${dataStr} ${hora}`
);
if (ocupado) {
alert('Esse horário já está ocupado para este médico!');
return;
}
consultas.push(novaConsulta);
localStorage.setItem('healthone_consultas', JSON.stringify(consultas));
alert('Consulta marcada com sucesso!');
renderConsultas();
};
function renderConsultas(){
// Mantém apenas o nome do médico e sua especialidade
consultaDiv.innerHTML = `<strong>Médico:</strong> ${medico.nome} (${medico.especialidade})`;
}
renderConsultas();
</script>
</body>
</html>

View File

@ -0,0 +1,668 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>HealthOne • Médicos (CRUD)</title>
<style>
:root{
/* Paleta HealthOne (ajuste se quiser) */
--brand-primary:#2563eb;
--brand-primary-600:#1d4ed8;
--brand-accent:#22c55e;
--brand-surface:#0b1220;
--text:#e5e7eb;
--muted:#94a3b8;
--danger:#ef4444;
--warning:#f59e0b;
--bg:#0f172a;
--card:#111827;
--card-2:#0f172a;
--border:#1f2937;
--ring:#3b82f6;
--shadow: 0 10px 30px rgba(0,0,0,.35);
--radius: 16px;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;
background: radial-gradient(1200px 600px at 20% -10%, rgba(59,130,246,.15), transparent 60%),
radial-gradient(1200px 700px at 120% 20%, rgba(34,197,94,.10), transparent 55%),
var(--bg);
color:var(--text);
font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
line-height:1.35;
}
/* Header */
.appbar{
position:sticky; top:0; z-index:10;
backdrop-filter: saturate(120%) blur(8px);
background: rgba(15,23,42,.6);
border-bottom: 1px solid var(--border);
}
.appbar-inner{
max-width:1200px; margin:0 auto; padding:14px 20px;
display:flex; align-items:center; gap:16px; justify-content:space-between;
}
.brand{
display:flex; align-items:center; gap:12px;
}
.logo{
width:36px; height:36px; border-radius:10px;
background: linear-gradient(135deg, var(--brand-primary), var(--brand-accent));
box-shadow: 0 8px 20px rgba(37,99,235,.45), inset 0 0 10px rgba(255,255,255,.15);
}
.brand h1{
font-size:18px; margin:0; letter-spacing:.4px; font-weight:700;
}
.brand small{color:var(--muted); display:block; margin-top:2px; font-weight:500}
.wrap{
max-width:1200px; margin:24px auto; padding:0 20px 80px;
}
/* Top actions / filtros */
.toolbar{
display:grid; grid-template-columns: 1fr 220px 180px 160px auto;
gap:12px; margin:16px 0 12px;
}
@media (max-width: 980px){
.toolbar{ grid-template-columns: 1fr 1fr; }
}
.field{
display:flex; align-items:center; gap:10px; background: var(--card); border:1px solid var(--border);
padding:10px 12px; border-radius: 12px;
}
.field input,.field select{
width:100%; background:transparent; border:0; color:var(--text); outline:none; font-size:14px;
}
.btn{
appearance:none; border:1px solid var(--brand-primary);
background: linear-gradient(180deg, var(--brand-primary), var(--brand-primary-600));
color:white; padding:10px 14px; border-radius:12px; font-weight:600; cursor:pointer;
box-shadow: 0 8px 20px rgba(37,99,235,.35);
transition: transform .04s ease, filter .2s ease;
white-space:nowrap;
}
.btn.secondary{
background: transparent; border-color:var(--border); color:var(--text);
box-shadow:none;
}
.switch{
display:flex; align-items:center; gap:10px; padding:10px 12px; border-radius:12px;
border:1px solid var(--border); background:var(--card);
}
.switch input{ accent-color: var(--brand-primary); width:18px; height:18px; }
/* Card */
.card{
background: linear-gradient(180deg, rgba(255,255,255,.02), transparent 18%) , var(--card-2);
border:1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow);
}
.card-header{
display:flex; align-items:center; justify-content:space-between; padding:16px 16px 8px 16px;
border-bottom:1px solid var(--border);
}
.card-header h2{margin:0; font-size:18px}
.card-content{ padding: 8px 16px 16px 16px; }
/* Tabela */
.table{
width:100%; border-collapse:separate; border-spacing:0 8px;
}
.thead{ color:var(--muted); font-size:12px; text-transform:uppercase; letter-spacing:.6px; }
.thead th{ text-align:left; padding:0 12px 8px; }
.row{
background: var(--card); border:1px solid var(--border);
border-radius:12px; overflow:hidden;
}
.row td{
padding:12px; border-bottom:1px solid rgba(255,255,255,.04);
}
.row td:last-child{ border-bottom:0; }
.badge{
font-size:12px; padding:4px 8px; border-radius:999px; border:1px solid var(--border);
background: rgba(59,130,246,.12);
white-space: nowrap; /* Isso impede a quebra de linha dentro do badge */
}
.convenios {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.badge.ok{ background: rgba(34,197,94,.12); border-color: rgba(34,197,94,.4); color:#bbf7d0; }
.badge.warn{ background: rgba(245,158,11,.12); border-color: rgba(245,158,11,.4); color:#fde68a; }
.actions{
display:flex; gap:8px; flex-wrap:wrap;
}
.btn.icon{
padding:8px 10px; font-size:13px; border-radius:10px;
}
.btn.danger{
background: linear-gradient(180deg, #ef4444, #b91c1c);
border-color:#b91c1c; box-shadow: 0 8px 20px rgba(239,68,68,.25);
}
.btn.outline{
background: transparent; color: var(--text);
border-color: var(--border); box-shadow:none;
}
/* Paginação */
.pagination{
display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:16px;
}
.pagination .pages{
display:flex; gap:8px; align-items:center; flex-wrap:wrap;
}
.page{
border:1px solid var(--border); background:var(--card); color:var(--text);
padding:8px 10px; border-radius:10px; cursor:pointer;
}
.page.active{ border-color: var(--brand-primary); outline:2px solid rgba(37,99,235,.35); }
/* Modal (mantive CSS, mas o HTML foi removido) */
.modal-backdrop{ display:none; }
.empty{
text-align:center; color:var(--muted); padding:28px 12px;
}
/* Click sortable */
th.sortable{ cursor:pointer; user-select:none; }
th.sortable::after{
content:'↕'; margin-left:6px; opacity:.4; font-size:11px;
}
/* ===========================
THEME: Medicio-like
=========================== */
:root{
--medicio-teal: #3fbbc0;
--medicio-teal-600: #34a3a8;
--medicio-teal-100: #e5f7f8;
--medicio-dark: #2a2d34;
--medicio-muted: #6b7280;
--medicio-border: #e5e7eb;
--medicio-white: #ffffff;
}
:root{
--brand-primary: var(--medicio-teal);
--brand-primary-600: var(--medicio-teal-600);
--brand-accent: var(--medicio-teal);
--bg: var(--medicio-white);
--card: #ffffff;
--card-2: #ffffff;
--text: var(--medicio-dark);
--muted: var(--medicio-muted);
--border: var(--medicio-border);
--ring: var(--medicio-teal);
--shadow: 0 8px 24px rgba(0,0,0,.06);
--radius: 12px;
}
body{
background: var(--medicio-white) !important;
color: var(--medicio-dark) !important;
}
.topstrip-medicio{
background: var(--medicio-teal);
color: #eaffff;
font: 500 13px/1.2 Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
padding: 8px 20px;
display:flex; align-items:center; gap:10px;
}
.topstrip-medicio .ico{ opacity:.9 }
.appbar{
background: #ffffff !important;
backdrop-filter: none !important;
border-bottom: 1px solid var(--medicio-border) !important;
}
.appbar-inner{
padding: 18px 20px !important;
}
.brand h1{
color: var(--medicio-dark) !important;
letter-spacing:.2px;
}
.brand small{
color: var(--medicio-muted) !important;
}
.logo{
width:90px;
height:auto;
}
.toolbar{
gap: 14px !important;
margin: 24px 0 12px !important;
}
.field,
.switch{
background: #ffffff !important;
border: 1px solid var(--medicio-border) !important;
padding: 12px 14px !important;
border-radius: 10px !important;
}
.field input, .field select{
color: var(--medicio-dark) !important;
}
.btn{
background: var(--medicio-teal) !important;
border-color: var(--medicio-teal) !important;
box-shadow: 0 6px 16px rgba(63,187,192,.25) !important;
}
.btn:hover{ filter: brightness(.98); }
.btn:active{ transform: translateY(1px); }
.btn.secondary{
background: #ffffff !important;
color: var(--medicio-dark) !important;
border-color: var(--medicio-border) !important;
box-shadow: none !important;
}
.btn.outline{
background: #ffffff !important;
border-color: var(--medicio-border) !important;
}
.btn.danger{
background: linear-gradient(180deg, #ef4444, #d01e1e) !important;
border-color:#d01e1e !important;
box-shadow: 0 6px 16px rgba(239,68,68,.18) !important;
}
.card{
background: #ffffff !important;
border: 1px solid var(--medicio-border) !important;
border-radius: 12px !important;
box-shadow: var(--shadow) !important;
}
.card-header{
padding: 16px 18px 10px 18px !important;
}
.card-content{ padding: 8px 18px 18px 18px !important; }
.table{ border-spacing: 0 10px !important; }
.row{
background:#ffffff !important;
border:1px solid var(--medicio-border) !important;
border-radius:10px !important;
}
.row td{ border-bottom: 0 !important; }
.thead{ color: var(--medicio-muted) !important; }
.badge{
background: var(--medicio-teal-100) !important;
color: #20767a !important;
border: 1px solid #c8eef0 !important;
}
.badge.ok{
background: #e7f7ea !important;
color: #217a31 !important;
border-color:#cfeedd !important;
}
.badge.warn{
background: #fff6e6 !important;
color: #8a5b07 !important;
border-color: #fde8c3 !important;
}
.page{
background:#ffffff !important;
color: var(--medicio-dark) !important;
border:1px solid var(--medicio-border) !important;
border-radius: 999px !important;
padding: 8px 12px !important;
}
.page.active{
border-color: var(--medicio-teal) !important;
outline: 2px solid rgba(63,187,192,.18) !important;
}
.modal{ background:#ffffff !important; border:1px solid var(--medicio-border) !important; }
.modal header, .modal footer{ border-color: var(--medicio-border) !important; }
.input{
background:#ffffff !important; border:1px solid var(--medicio-border) !important;
border-radius:10px !important; padding: 12px 14px !important;
}
.input label{ color: var(--medicio-muted) !important; }
.input input, .input select, .input textarea{ color: var(--medicio-dark) !important; }
th.sortable::after{ opacity:.35 !important; }
.topnav{
position:sticky; top:0; z-index:10; background:var(--bg);
border-bottom:1px solid var(--line); padding:12px 24px;
display:flex; align-items:center; gap:22px;
}
.logo{display:flex; align-items:center; gap:10px; margin-right:8px}
.logo-badge{width:36px;height:36px;border-radius:12px;
background:linear-gradient(135deg,var(--brand),#64e4dc);
display:grid;place-items:center;color:#fff;font-weight:800;box-shadow:var(--soft)}
.logo span{font-weight:800;color:var(--brand-600);letter-spacing:.3px}
.tabs{display:flex; gap:16px; align-items:center; margin-left:600px}
.tabs a{color:var(--muted); text-decoration:none; font-weight:600}
.tabs a:hover{color:var(--brand-600, )}
.tabs .ativo{background:var(--bran, #64e4dc); color:#f8f8f8; padding:8px 18px;
border-radius:999px; box-shadow:0 4px 10px rgba(0, 211, 248, 0.25)}
</style>
</head>
<body>
<!-- Topbar -->
<div class="appbar">
<div class="appbar-inner">
<div class="brand">
<img src="logo_HealthOne.jpeg" alt="HealthOne" class="logo">
</div>
<div>
<small>Diretório de Médicos • CRUD</small>
</div>
<!-- ===== NAV SUPERIOR ===== -->
<!-- você pode colocar um campo de busca aqui se quiser -->
<nav class="tabs">
<a href="dash-pacientes.html" >Início</a>
<a href="agendamento.html" class="ativo">Marcar Consulta</a>
</nav>
</div>
<!-- Botão de novo médico removido -->
</div>
</div>
<main class="wrap">
<!-- Filtros -->
<div class="toolbar">
<div class="field" title="Pesquise por nome, CRM, cidade, especialidade">
<span>🔎</span>
<input id="searchInput" type="search" placeholder="Pesquisar (ex.: Neurologista, Ginecologista, Dr. Ana, CRM...)" />
</div>
<div class="field">
<select id="especialidadeFilter">
<option value="">Todas as especialidades</option>
<option>Cardiologista</option>
<option>Clínico Geral</option>
<option>Dermatologista</option>
<option>Endocrinologista</option>
<option>Ginecologista</option>
<option>Neurologista</option>
<option>Oftalmologista</option>
<option>Ortopedista</option>
<option>Pediatra</option>
<option>Psiquiatra</option>
<option>Urologista</option>
</select>
</div>
<div class="switch">
<input id="disponiveisToggle" type="checkbox" />
<label for="disponiveisToggle">Somente disponíveis</label>
</div>
<button id="limparFiltros" class="btn secondary">Limpar filtros</button>
</div>
<!-- Tabela -->
<section class="card" aria-label="Lista de médicos">
<div class="card-header">
<h2>Médicos</h2>
<div class="muted" style="color:var(--muted); font-size:13px">Clique no título das colunas para ordenar</div>
</div>
<div class="card-content">
<table class="table" aria-describedby="Lista de médicos">
<thead class="thead">
<tr>
<th class="sortable" data-sort="nome">Médico</th>
<th class="sortable" data-sort="especialidade">Especialidade</th>
<th class="sortable" data-sort="cidade">Cidade</th>
<th>Contato</th>
<th>Atende por</th>
<th class="sortable" data-sort="preco">Consulta</th>
<th class="sortable" data-sort="proxDisponivel">Próxima janela</th>
<th class="sortable" data-sort="disponivel">Status</th>
<th style="text-align:right">Ações</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
<div id="emptyState" class="empty" hidden>
Nenhum médico encontrado. Ajuste os filtros ou cadastre um novo.
</div>
<div class="pagination">
<div class="pages" id="pages"></div>
<div style="display:flex; align-items:center; gap:8px">
<button id="prevPage" class="page"></button>
<button id="nextPage" class="page"></button>
</div>
</div>
</div>
</section>
</main>
<!-- Modal Add/Edit REMOVIDO -->
<script>
// ---------- Util ----------
const $ = (sel, root = document) => root.querySelector(sel);
const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel));
const fmtMoney = v => (v == null || v === '') ? '-' : `R$ ${Number(v).toFixed(2).replace('.',',')}`;
const fmtDateTime = iso => {
if(!iso) return '-';
try{
const d = new Date(iso);
if(isNaN(d)) return '-';
return d.toLocaleString('pt-BR', { dateStyle:'short', timeStyle:'short' });
}catch{return '-'}
};
const debounce = (fn, ms=250)=>{ let t; return (...a)=>{ clearTimeout(t); t=setTimeout(()=>fn(...a), ms); }};
// ---------- Estado ----------
const STORAGE_KEY = 'healthone_medicos';
let medicos = [];
let page = 1;
const PAGE_SIZE = 10;
let sortBy = 'nome';
let sortDir = 'asc';
function loadSeedIfEmpty(){
const seed = [
{id:crypto.randomUUID(), nome:'Dra. Ana Clara Silva', especialidade:'Ginecologista', crm:'CRM 12345-AL', telefone:'(82) 98888-1111', email:'ana.silva@healthone.com', cidade:'Maceió/AL', preco:250, proxDisponivel: future(1), disponivel:true, atendePor:'Convênio X, Convênio Y'},
{id:crypto.randomUUID(), nome:'Dr. Bruno Mendes', especialidade:'Neurologista', crm:'CRM 98765-AL', telefone:'(82) 97777-2222', email:'bruno.mendes@healthone.com', cidade:'Maceió/AL', preco:300, proxDisponivel: future(2), disponivel:true, atendePor:'Particular, Convênio Z'},
{id:crypto.randomUUID(), nome:'Dra. Camila Rocha', especialidade:'Dermatologista', crm:'CRM 11223-AL', telefone:'(82) 96666-3333', email:'camila.rocha@healthone.com', cidade:'Rio Largo/AL', preco:220, proxDisponivel: future(5), disponivel:false, atendePor:'Somente particular'},
{id:crypto.randomUUID(), nome:'Dr. Daniel Souza', especialidade:'Cardiologista', crm:'CRM 33445-AL', telefone:'(82) 95555-4444', email:'daniel.souza@healthone.com', cidade:'Marechal/AL', preco:280, proxDisponivel: future(3), disponivel:true, atendePor:'Convênio W, Particular'},
{id:crypto.randomUUID(), nome:'Dra. Elisa Nunes', especialidade:'Clínico Geral', crm:'CRM 55667-AL', telefone:'(82) 94444-5555', email:'elisa.nunes@healthone.com', cidade:'Maceió/AL', preco:180, proxDisponivel: future(1), disponivel:true, atendePor:'SUS, Convênio X, Convênio Z'},
{id:crypto.randomUUID(), nome:'Dr. Felipe Barros', especialidade:'Ortopedista', crm:'CRM 77889-AL', telefone:'(82) 93333-6666', email:'felipe.barros@healthone.com', cidade:'Maceió/AL', preco:260, proxDisponivel: future(7), disponivel:false, atendePor:'Particular, Convênio W'},
{id:crypto.randomUUID(), nome:'Dra. Gabriela Lima', especialidade:'Pediatra', crm:'CRM 99887-AL', telefone:'(82) 92222-7777', email:'gabriela.lima@healthone.com', cidade:'Maceió/AL', preco:200, proxDisponivel: future(4), disponivel:true, atendePor:'Convênio X, Particular'},
{id:crypto.randomUUID(), nome:'Dr. Henrique Castro', especialidade:'Psiquiatra', crm:'CRM 66554-AL', telefone:'(82) 91111-8888', email:'henrique.castro@healthone.com', cidade:'Maceió/AL', preco:320, proxDisponivel: future(6), disponivel:true, atendePor:'Convênio X, Convênio Y'}
];
localStorage.setItem(STORAGE_KEY, JSON.stringify(seed));
return seed;
}
function future(days){
const d = new Date(); d.setDate(d.getDate()+days); d.setHours(9,0,0,0);
const iso = new Date(d.getTime() - d.getTimezoneOffset()*60000).toISOString().slice(0,16);
return iso;
}
function load(){
const raw = localStorage.getItem(STORAGE_KEY);
medicos = raw ? JSON.parse(raw) : loadSeedIfEmpty();
// 🔹 Garante que todos os médicos tenham "atendePor" sem sobrescrever os existentes
medicos = medicos.map(m => {
if(!('atendePor' in m) || !m.atendePor) {
return { ...m, atendePor: 'Particular' };
}
return m;
});
// salva de volta atualizado
localStorage.setItem(STORAGE_KEY, JSON.stringify(medicos));
}
// ---------- Filtros ----------
const searchInput = $('#searchInput');
const especialidadeFilter = $('#especialidadeFilter');
const disponiveisToggle = $('#disponiveisToggle');
function getFilters(){
return {
q: (searchInput.value || '').trim().toLowerCase(),
esp: especialidadeFilter.value,
onlyAvail: disponiveisToggle.checked
};
}
function applyFilters(list){
const {q, esp, onlyAvail} = getFilters();
return list.filter(m=>{
if(esp && m.especialidade !== esp) return false;
if(onlyAvail && !m.disponivel) return false;
if(!q) return true;
const hay = `${m.nome} ${m.especialidade} ${m.cidade} ${m.crm}`.toLowerCase();
return hay.includes(q);
});
}
// ---------- Ordenação ----------
function sortList(list){
const dir = sortDir === 'asc' ? 1 : -1;
return [...list].sort((a,b)=>{
let va = a[sortBy], vb = b[sortBy];
if(sortBy === 'preco'){ va = Number(va||0); vb = Number(vb||0); }
if(sortBy === 'proxDisponivel'){
va = va ? new Date(va).getTime() : 0;
vb = vb ? new Date(vb).getTime() : 0;
}
if(typeof va === 'string') va = va.toLowerCase();
if(typeof vb === 'string') vb = vb.toLowerCase();
if(va < vb) return -1*dir;
if(va > vb) return 1*dir;
return 0;
});
}
// ---------- Paginação ----------
function paginate(list){
const total = Math.max(1, Math.ceil(list.length / PAGE_SIZE));
if(page > total) page = total;
const start = (page-1)*PAGE_SIZE;
const end = start + PAGE_SIZE;
return { totalPages: total, items: list.slice(start, end) };
}
function renderPages(total){
const pages = $('#pages'); pages.innerHTML = '';
for(let i=1;i<=total;i++){
const b = document.createElement('button');
b.className = 'page' + (i===page ? ' active':'');
b.textContent = i;
b.addEventListener('click', ()=>{ page=i; render(); });
pages.appendChild(b);
}
$('#prevPage').onclick = ()=>{ if(page>1){ page--; render(); } };
$('#nextPage').onclick = ()=>{ if(page<total){ page++; render(); } };
}
// ---------- Render ----------
function render(){
const tbody = $('#tbody');
const empty = $('#emptyState');
let data = applyFilters(medicos);
data = sortList(data);
const { totalPages, items } = paginate(data);
tbody.innerHTML = '';
if(items.length === 0){
empty.hidden = false;
}else{
empty.hidden = true;
for(const m of items){
const tr = document.createElement('tr'); tr.className='row';
tr.innerHTML = `
<td>
<div style="display:flex; align-items:center; gap:10px;">
<div style="width:36px; height:36px; border-radius:10px; background:linear-gradient(135deg, var(--brand-primary), var(--brand-accent));"></div>
<div>
<div style="font-weight:600">${escapeHTML(m.nome)}</div>
<div style="color:var(--muted); font-size:12px">${escapeHTML(m.crm||'-')}</div>
</div>
</div>
</td>
<td><span class="badge">${escapeHTML(m.especialidade)}</span></td>
<td>${escapeHTML(m.cidade||'-')}</td>
<td>
<div style="font-size:13px; color:var(--muted)">
${escapeHTML(m.telefone||'-')} • ${escapeHTML(m.email||'-')}
</div>
</td>
<td>
<div class="convenios">
${(m.atendePor || '-').split(',').map(c => `<span class="badge">${escapeHTML(c.trim())}</span>`).join('')}
</div>
</td>
<td>${fmtMoney(m.preco)}</td>
<td>${fmtDateTime(m.proxDisponivel)}</td>
<td>
${m.disponivel ? '<span class="badge ok">Disponível</span>' : '<span class="badge warn">Indisponível</span>'}
</td>
<td style="text-align:right">
<div class="actions">
<button class="btn icon outline" title="Escolher / Marcar" data-act="marcar" data-id="${m.id}">Marcar</button>
</div>
</td>
`;
tbody.appendChild(tr);
}
}
renderPages(totalPages);
// Actions
$$('#tbody [data-act]').forEach(btn=>{
const id = btn.getAttribute('data-id');
const act = btn.getAttribute('data-act');
btn.onclick = ()=> onAction(act, id);
});
}
function escapeHTML(s){
if(s==null) return '';
return String(s).replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;', "'":'&#39;'}[m]));
}
function onAction(act, id){
const idx = medicos.findIndex(m=>m.id===id);
if(idx===-1) return;
const m = medicos[idx];
if(act==='marcar'){
localStorage.setItem('healthone_medico_escolhido', JSON.stringify(m));
window.location.href = 'agenda-consulta.html'; // redireciona para agenda
}
}
// ---------- Ordenação por cabeçalho ----------
$$('.thead th.sortable').forEach(th=>{
th.addEventListener('click', ()=>{
const key = th.getAttribute('data-sort');
if(sortBy === key){ sortDir = (sortDir === 'asc' ? 'desc' : 'asc'); }
else{ sortBy = key; sortDir = 'asc'; }
render();
});
});
// ---------- Filtros listeners ----------
searchInput.addEventListener('input', debounce(()=>{
page=1; render();
}, 250));
especialidadeFilter.addEventListener('change', ()=>{ page=1; render(); });
disponiveisToggle.addEventListener('change', ()=>{ page=1; render(); });
$('#limparFiltros').onclick = ()=>{
searchInput.value = '';
especialidadeFilter.value = '';
disponiveisToggle.checked = false;
page = 1; render();
};
// ---------- Init ----------
load();
render();
</script>
</body>
</html>

View File

@ -0,0 +1,263 @@
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>HealthOne — Dashboard do Paciente</title>
<style>
/* ====== TOKENS ====== */
:root{
--brand:#1db7ae; --brand-600:#12968f; --brand-100:#e7f7f6;
--ink:#24323f; --muted:#6b7a88; --line:#e6eaef;
--bg:#ffffff; --app:#f4f7fb; --radius:18px;
--shadow:0 12px 28px rgba(18,150,143,.12);
--soft:0 6px 18px rgba(36,50,63,.08);
}
*{box-sizing:border-box}
body{margin:0;background:var(--app);color:var(--ink);
font:14px/1.45 "Inter",system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif}
/* ====== TOP NAV ====== */
.topnav{
position:sticky; top:0; z-index:10; background:var(--bg);
border-bottom:1px solid var(--line); padding:12px 24px;
display:flex; align-items:center; gap:22px;
}
.logo{display:flex; align-items:center; gap:10px; margin-right:8px}
.logo-badge{width:36px;height:36px;border-radius:12px;
background:linear-gradient(135deg,var(--brand),#64e4dc);
display:grid;place-items:center;color:#fff;font-weight:800;box-shadow:var(--soft)}
.logo span{font-weight:800;color:var(--brand-600);letter-spacing:.3px}
.tabs{display:flex; gap:16px; align-items:center; margin-left:auto}
.tabs a{color:var(--muted); text-decoration:none; font-weight:600}
.tabs a:hover{color:var(--brand-600)}
.tabs .active{background:var(--brand); color:#fff; padding:8px 18px;
border-radius:999px; box-shadow:0 4px 10px rgba(18,150,143,.25)}
/* ====== LAYOUT ====== */
.wrap{padding:24px; max-width:1300px; margin:0 auto}
.grid{display:grid; grid-template-columns: 1fr 320px; gap:24px}
.stack{display:grid; gap:18px; grid-template-columns:1fr 1fr}
.card{background:var(--bg);border:1px solid var(--line);border-radius:var(--radius);
box-shadow:var(--soft); padding:16px}
.title{margin:0 0 10px 0; font-weight:800}
/* Perfil */
.profile{display:grid; grid-template-columns:64px 1fr; gap:14px; align-items:center}
.avatar{width:64px;height:64px;border-radius:16px;background:#dbe7f1;
display:grid;place-items:center;font-weight:800;color:#466;box-shadow:var(--soft)}
.kv{display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-top:12px}
.kv div{display:flex;justify-content:space-between;background:#fafcff;border:1px solid var(--line);
border-radius:10px;padding:8px 10px}
/* Anéis */
.circles{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}
.ring{display:grid;place-items:center;gap:8px}
.meter{width:120px;height:120px;border-radius:50%;
background:radial-gradient(closest-side,var(--bg) 60%,transparent 61% 100%),
conic-gradient(#ff5c89 var(--p,75%),#ffe3ec 0);
box-shadow:var(--soft)}
.ring.blue .meter{
background:radial-gradient(closest-side,var(--bg) 60%,transparent 61% 100%),
conic-gradient(#2f68ff var(--p,83%),#dfe7ff 0)}
.ring b{font-size:18px} .ring span{color:var(--muted);font-size:12px}
/* Atividade (barras) */
.bars{height:140px;display:flex;align-items:flex-end;gap:12px;padding:6px 8px}
.bar{flex:1;border-radius:10px;background:linear-gradient(180deg,#ff9fb9,#ffd6e3);box-shadow:var(--soft)}
.badge{background:var(--brand-100);color:var(--brand-600);padding:4px 8px;border-radius:999px;font-size:12px;font-weight:700}
/* Linha (SVG) */
.line-wrap{height:220px}
.line-svg{width:100%;height:160px;display:block}
.axis{stroke:#cfd7e3;stroke-width:1}
.stroke-brand{fill:none;stroke:#4b82ff;stroke-width:3;stroke-linecap:round}
/* Aside: calendário + consultas */
.calendar .grid{margin-top:10px;display:grid;grid-template-columns:repeat(7,1fr);gap:6px}
.cell{aspect-ratio:1/1;display:grid;place-items:center;background:#fff;border:1px solid var(--line);border-radius:10px}
.cell.h{background:transparent;border:0;color:var(--muted);font-weight:700}
.cell.m{background:var(--brand-100);border-color:transparent;color:var(--brand-600);font-weight:700}
.appt-list{display:flex;flex-direction:column;gap:10px}
.appt{display:flex;align-items:center;gap:10px;padding:10px;border-radius:12px;background:#fff;border:1px solid var(--line);box-shadow:var(--soft)}
.appt .ic{width:32px;height:32px;border-radius:10px;display:grid;place-items:center;color:#fff;font-size:16px}
.appt .time{margin-left:auto;font-weight:800;color:var(--muted)}
.brand{
display:flex; align-items:center; gap:12px;
}
.logo{
width:36px; height:36px; border-radius:10px;
background: linear-gradient(135deg, var(--brand-primary), var(--brand-accent));
box-shadow: 0 8px 20px rgba(37,99,235,.45), inset 0 0 10px rgba(255,255,255,.15);
}
.logo{
margin-left: 150px;
width:90px;
height:auto;
}
.ola{
margin-left: 130px;
grid-column: 1 / -1;
display:flex; align-items:center; justify-content:space-between;
margin-bottom:4px;
}
.ola .greet{font-size:24px; font-weight:700;}
.marca { display: flex; align-items: center; gap: .6rem; font-weight: 800; letter-spacing: .4px; }
.marca .M { width: 36px; height: 36px; border-radius: 50%; background: linear-gradient(135deg, var(--brand), #4dd7cf); display: grid; place-items: center; color: #fff; font-weight: 700; box-shadow: 0 6px 14px rgba(29,183,174,.35); }
/* Responsivo */
@media (max-width:1100px){ .grid{grid-template-columns:1fr} }
@media (max-width:880px){ .stack{grid-template-columns:1fr} .tabs{gap:10px} }
</style>
</head>
<body>
<!-- ===== NAV SUPERIOR ===== -->
<header class="topnav">
<div class="brand">
<img src="logo_HealthOne.jpeg" alt="HealthOne" class="logo">
<div class="marca">
<div class="M">IK</div>
<span>Isaac Kauã</span>
</div>
</div>
<!-- você pode colocar um campo de busca aqui se quiser -->
<nav class="tabs">
<a href="#" class="active">Início</a>
<a href="agendamento.html" >Marcar Consulta</a>
</nav>
</header>
<div class="ola">
<div class="greet">Olá, <span style="color:var(--brand-600)">Isaac Kauã</span> 👋</div>
</div>
<!-- ===== CONTEÚDO ===== -->
<div class="wrap">
<div class="grid">
<!-- coluna principal -->
<section class="stack">
<article class="card">
<h3 class="title">Perfil do Paciente</h3>
<div class="profile">
<div class="avatar">IK</div>
<div>
<strong>Isaac Kauã</strong>
<div style="color:var(--muted)">862.346.645-47</div>
</div>
</div>
<div class="kv">
<div><span>Idade</span><b>18 anos</b></div>
<div><span>Tipo Sanguíneo</span><b>O+</b></div>
<div><span>Altura</span><b>1,90 m</b></div>
<div><span>Peso</span><b>100 kg</b></div>
</div>
</article>
<article class="card">
<h3 class="title">Indicadores</h3>
<div class="circles">
<div class="ring" style="--p:75%">
<div class="meter" aria-label="Saúde Geral 75%"></div>
<b>75%</b><span>Saúde Geral</span>
</div>
<div class="ring blue" style="--p:83%">
<div class="meter" aria-label="Hidratação 83%"></div>
<b>83%</b><span>Frequencia de Presença</span>
</div>
</div>
</article>
<article class="card">
<h3 class="title">Taxa de Melhora</h3>
<div class="bars">
<div class="bar" style="height:40%"></div>
<div class="bar" style="height:65%"></div>
<div class="bar" style="height:55%"></div>
<div class="bar" style="height:80%"></div>
<div class="bar" style="height:50%"></div>
<div class="bar" style="height:35%"></div>
<div class="bar" style="height:60%"></div>
</div>
<div class="badge">Durante Tratamento</div>
</article>
<article class="card line-wrap">
<h3 class="title">Condições de Saúde</h3>
<svg class="line-svg" viewBox="0 0 600 180" aria-label="Linha de tendência">
<line x1="30" y1="150" x2="580" y2="150" class="axis"/>
<line x1="30" y1="30" x2="30" y2="150" class="axis"/>
<polyline class="stroke-brand"
points="30,120 90,140 150,95 210,110 270,70 330,95 390,80 450,120 510,60 570,130"/>
</svg>
<small style="color:var(--muted)">Jan → Dez</small>
</article>
</section>
<!-- aside direito -->
<aside>
<div class="card calendar">
<div style="display:flex;justify-content:space-between;align-items:center">
<h3 class="title" style="margin:0">Consultas</h3>
<span class="badge">Set 2025</span>
</div>
<div class="grid" id="cal"></div>
</div>
<div class="card">
<h3 class="title">Próximas Consultas</h3>
<div class="appt-list" id="appts">
<div class="appt">
<div class="ic" style="background:#2ec5ff">🦷</div>
<div><b>Dentista</b><div style="color:var(--muted)"> Quarta - Dra. Gorex Mathew</div></div>
<div class="time">09:00</div>
</div>
<div class="appt">
<div class="ic" style="background:#a55cff">❤️</div>
<div><b>Cardiologia</b><div style="color:var(--muted)">Quarta - Dr. Craig Gemx</div></div>
<div class="time">12:00</div>
</div>
<div class="appt">
<div class="ic" style="background:#ff7a59">🦴</div>
<div><b>Ortopedia</b><div style="color:var(--muted)">Quinta - Dr. Bruce Williams</div></div>
<div class="time">15:00</div>
</div>
<div class="appt">
<div class="ic" style="background:#22c55e">🩺</div>
<div><b>Clínico</b><div style="color:var(--muted)">Quinta - Dra. Kiera Knight</div></div>
<div class="time">16:00</div>
</div>
<div class="appt">
<div class="ic" style="background:#ffb020">🧪</div>
<div><b>Endocrino</b><div style="color:var(--muted)">Quinta - Dra. Anni Roy</div></div>
<div class="time">18:00</div>
</div>
</div>
</div>
</aside>
</div>
</div>
<script>
// mini calendário (exemplo: março com marcações)
(function(){
const el = document.getElementById('cal');
const heads = ['S','T','Q','Q','S','S','D'];
heads.forEach(h=>{
const c=document.createElement('div'); c.textContent=h; c.className='cell h'; el.appendChild(c);
});
const offset = 0; // domingo
const blanks = (7 + offset) % 7;
for(let i=0;i<blanks;i++){ const b=document.createElement('div'); b.className='cell'; b.style.visibility='hidden'; el.appendChild(b); }
for(let d=1; d<=31; d++){
const c=document.createElement('div'); c.className='cell'; c.textContent=d;
if([3,10,17,24].includes(d)) c.classList.add('m');
el.appendChild(c);
}
})();
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -0,0 +1,6 @@
Thanks for downloading this template!
Template Name: Medicio
Template URL: https://bootstrapmade.com/medicio-free-bootstrap-theme/
Author: BootstrapMade.com
License: https://bootstrapmade.com/license/

View File

@ -0,0 +1,388 @@
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Histórico de Pacientes • HealthOne</title>
<style>
:root{
--medicio-teal: #3fbbc0;
--medicio-teal-600: #34a3a8;
--medicio-muted: #6b7280;
--medicio-border: #e5e7eb;
--medicio-white: #ffffff;
--text-dark: #2a2d34;
--bg: #f8fafb;
--radius: 12px;
--shadow: 0 8px 24px rgba(0,0,0,.06);
}
*{box-sizing:border-box}
body{
margin:0;
font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial;
background: var(--bg);
color: var(--text-dark);
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
}
/* Header */
.appbar{
background: var(--medicio-white);
border-bottom: 1px solid var(--medicio-border);
position: sticky;
top: 0;
z-index: 20;
}
.appbar-inner{
max-width:1200px; margin:0 auto; padding:14px 20px;
display:flex; align-items:center; justify-content:space-between; gap:12px;
}
.brand{ display:flex; align-items:center; gap:12px; }
.logo{
width:44px; height:44px; border-radius:50%;
box-shadow: 0 0 20px rgba(63,187,192,0.12), 0 0 40px rgba(63,187,192,0.06);
background: linear-gradient(135deg, var(--medicio-teal), var(--medicio-teal-600));
}
.brand h1{ margin:0; font-size:16px; letter-spacing:.2px; }
.brand small{ display:block; color:var(--medicio-muted); font-size:12px; margin-top:2px; }
.wrap{ max-width:1200px; margin:20px auto; padding:0 20px 80px; }
/* Toolbar / filtros */
.toolbar{
display:grid;
grid-template-columns: 1fr 220px 180px 120px auto;
gap:12px; margin-bottom:18px;
align-items:center;
}
@media (max-width:980px){ .toolbar{ grid-template-columns: 1fr 1fr; } }
.field{
display:flex; align-items:center; gap:10px;
background: var(--medicio-white); border:1px solid var(--medicio-border);
padding:10px 12px; border-radius:10px;
}
.field input, .field select{
width:100%; border:0; outline:0; background:transparent; color:var(--text-dark); font-size:14px;
}
label{ font-weight:600; color:var(--medicio-muted); font-size:13px; margin-right:6px; }
.btn{
padding:10px 14px; border-radius:10px; font-weight:700; border:0; cursor:pointer;
background:var(--medicio-teal); color:#fff; box-shadow:0 6px 16px rgba(63,187,192,.16);
}
.btn.secondary{
background:#fff; color:var(--text-dark); border:1px solid var(--medicio-border); box-shadow:none;
}
/* Card / tabela estilo Medicio */
.card{ background:#fff; border:1px solid var(--medicio-border); border-radius:12px; box-shadow:var(--shadow); }
.card-header{ display:flex; align-items:center; justify-content:space-between; padding:16px 18px 10px 18px; border-bottom:1px solid var(--medicio-border); }
.card-content{ padding:12px 18px 18px 18px; }
.table-wrap{ overflow-x:auto; }
table{ width:100%; border-collapse:separate; border-spacing:0 10px; min-width:1000px; }
thead.thead th{ text-align:left; font-size:12px; color:var(--medicio-muted); padding:8px 12px; text-transform:uppercase; letter-spacing:.6px; }
tbody tr.row{ background:#fff; border:1px solid var(--medicio-border); border-radius:10px; }
tbody tr.row td{ padding:12px; vertical-align:middle; border-bottom:0; font-size:14px; color:var(--text-dark); }
tbody tr.row td.actions{ text-align:right; }
.badge{ font-size:12px; padding:6px 10px; border-radius:999px; display:inline-block; border:1px solid transparent; }
.badge.primary{ background: #eaf8f8; color: #1c7776; border-color:#cdeeea; }
.badge.ok{ background:#eaf6ea; color:#1f7a33; border-color:#d7eed5; }
.badge.warn{ background:#fff7e6; color:#8a5b07; border-color:#fde8c3; }
.convenios{ display:flex; gap:8px; flex-wrap:wrap; align-items:center; }
.actions .btn{ padding:8px 10px; border-radius:8px; font-size:13px; }
.page{ padding:8px 12px; border-radius:999px; border:1px solid var(--medicio-border); background:#fff; cursor:pointer; }
.empty{ text-align:center; color:var(--medicio-muted); padding:28px 12px; }
/* responsivo: compacta colunas pequenas */
@media (max-width:720px){
thead.thead th:nth-child(3), /* Telefone */
thead.thead th:nth-child(4), /* Cidade */
thead.thead th:nth-child(5), /* Estado */
thead.thead th:nth-child(6), /* Convênio */
thead.thead th:nth-child(7), /* VIP */
thead.thead th:nth-child(10), /* Último atendimento */
thead.thead th:nth-child(11) /* Próximo atendimento */ { display:none; }
tbody tr.row td:nth-child(3),
tbody tr.row td:nth-child(4),
tbody tr.row td:nth-child(5),
tbody tr.row td:nth-child(6),
tbody tr.row td:nth-child(7),
tbody tr.row td:nth-child(10),
tbody tr.row td:nth-child(11) { display:none; }
table{ min-width:700px; }
}
</style>
</head>
<body>
<div class="appbar">
<div class="appbar-inner">
<div class="brand">
<div class="logo" aria-hidden="true"></div>
<div>
<h1>HealthOne</h1>
<small>Histórico de Pacientes</small>
</div>
</div>
</div>
</div>
<main class="wrap">
<!-- filtros -->
<div class="toolbar">
<div class="field" style="min-width:220px;">
<label for="pacienteSelect" style="min-width:90px">Paciente</label>
<select id="pacienteSelect">
<option value="">-- Escolha --</option>
<option value="1">João da Silva</option>
<option value="2">Maria Ferreira</option>
<option value="3">Carlos Souza</option>
<option value="4">Ana Paula</option>
<option value="5">Lucas Lima</option>
</select>
</div>
<div class="field">
<label for="dataSelect">Dia</label>
<input type="date" id="dataSelect" />
</div>
<div class="field">
<label for="mesSelect">Mês</label>
<input type="month" id="mesSelect" />
</div>
<div style="display:flex; gap:8px; justify-content:flex-end;">
<button id="limparFiltros" class="btn secondary">Limpar filtros</button>
</div>
</div>
<!-- tabela -->
<section class="card" aria-label="Histórico de consultas">
<div class="card-header">
<h2>Histórico de Consultas</h2>
<div style="color:var(--medicio-muted); font-size:13px">Use os filtros acima para refinar.</div>
</div>
<div class="card-content">
<div class="table-wrap">
<table id="Consultas" aria-describedby="Tabela de histórico">
<thead class="thead">
<tr>
<th>CPF</th>
<th>Paciente</th>
<th>Telefone</th>
<th>Cidade</th>
<th>Estado</th>
<th>Convênio</th>
<th>VIP</th>
<th>Médico</th>
<th>Data da Consulta</th>
<th>Último atendimento</th>
<th>Próximo atendimento</th>
<th>Motivo da Consulta</th>
<th>Diagnóstico</th>
<th>Tratamento</th>
<th>Relatório</th>
</tr>
</thead>
<tbody>
<tr class="row" data-paciente="1" data-data="2025-09-03 09:30" data-medico="Dr. Almeida" data-cpf="123.456.789-00">
<td>123.456.789-00</td>
<td>João da Silva</td>
<td>(79) 99893-2499</td>
<td>Aracaju</td>
<td>Sergipe</td>
<td>Particular</td>
<td>-</td>
<td>Dr. Almeida</td>
<td>2025-09-03 09:30</td>
<td>2025-03-11 11:00</td>
<td>2025-09-19 15:00</td>
<td>Dor no peito</td>
<td>Angina estável</td>
<td>NTG conforme protocolo</td>
<td>Relatório: observou-se estabilização com medicação.</td>
</tr>
<tr class="row" data-paciente="1" data-data="2025-04-15 11:00" data-medico="Dra. Beatriz" data-cpf="123.456.789-00">
<td>123.456.789-00</td>
<td>João da Silva</td>
<td>(79) 99893-2499</td>
<td>Aracaju</td>
<td>Sergipe</td>
<td>Particular</td>
<td>-</td>
<td>Dra. Beatriz</td>
<td>2025-04-15 11:00</td>
<td>2024-12-20 09:00</td>
<td>2025-10-01 15:30</td>
<td>Consulta de rotina</td>
<td>Sem alterações</td>
<td>Recomendação: acompanhamento semestral</td>
<td>Relatório: sem intercorrências.</td>
</tr>
<tr class="row" data-paciente="2" data-data="2025-07-10 14:00" data-medico="Dr. Almeida" data-cpf="987.654.321-11">
<td>987.654.321-11</td>
<td>Maria Ferreira</td>
<td>(79) 99892-6363</td>
<td>Aracaju</td>
<td>Sergipe</td>
<td>SUS</td>
<td>-</td>
<td>Dr. Almeida</td>
<td>2025-07-10 14:00</td>
<td>2024-06-26 14:30</td>
<td>2025-08-19 15:00</td>
<td>Cansaço frequente</td>
<td>Anemia leve</td>
<td>Suplemento de ferro</td>
<td>Relatório: sugerido retorno em 3 meses.</td>
</tr>
<tr class="row" data-paciente="3" data-data="2025-11-18 10:00" data-medico="Dra. Beatriz" data-cpf="111.222.333-44">
<td>111.222.333-44</td>
<td>Carlos Souza</td>
<td>(79) 99983-0093</td>
<td>São Cristóvão</td>
<td>Sergipe</td>
<td>Unimed</td>
<td>-</td>
<td>Dra. Beatriz</td>
<td>2025-11-18 10:00</td>
<td>2024-12-30 08:40</td>
<td>2025-12-05 10:00</td>
<td>Febre alta</td>
<td>Infecção viral</td>
<td>Repouso e hidratação</td>
<td>Relatório: melhora após 5 dias.</td>
</tr>
<tr class="row" data-paciente="4" data-data="2025-09-22 16:30" data-medico="Dr. Carlos" data-cpf="555.666.777-88">
<td>555.666.777-88</td>
<td>Ana Paula</td>
<td>(79) 99890-9248</td>
<td>Itabaiana</td>
<td>Sergipe</td>
<td>Particular</td>
<td>-</td>
<td>Dr. Carlos</td>
<td>2025-09-22 16:30</td>
<td>2024-03-14 10:45</td>
<td>2025-10-10 09:00</td>
<td>Consulta de rotina</td>
<td>Hipertensão controlada</td>
<td>Manter medicação</td>
<td>Relatório: pressão arterial estável.</td>
</tr>
<tr class="row" data-paciente="5" data-data="2025-10-25 09:00" data-medico="Dr. Almeida" data-cpf="999.888.777-66">
<td>999.888.777-66</td>
<td>Lucas Lima</td>
<td>(79) 99888-7777</td>
<td>Aracaju</td>
<td>Sergipe</td>
<td>SUS</td>
<td>-</td>
<td>Dr. Almeida</td>
<td>2025-10-25 09:00</td>
<td>2024-09-21 11:50</td>
<td>2025-11-25 09:30</td>
<td>Dor de cabeça</td>
<td>Enxaqueca</td>
<td>Prescrição de analgésicos</td>
<td>Relatório: acompanhamento mensal recomendado.</td>
</tr>
</tbody>
</table>
</div>
<div class="empty note" id="note">Selecione um paciente, um dia ou um mês para filtrar o histórico de consultas.</div>
</div>
</section>
</main>
<script>
const pacienteSelect = document.getElementById("pacienteSelect");
const dataSelect = document.getElementById("dataSelect");
const mesSelect = document.getElementById("mesSelect");
const linhas = document.querySelectorAll("#Consultas tbody tr");
const note = document.getElementById('note');
function filtrarConsultas() {
const pacienteId = pacienteSelect.value;
const dataEscolhida = dataSelect.value; // yyyy-mm-dd
const mesEscolhido = mesSelect.value; // yyyy-mm
let anyVisible = false;
linhas.forEach(linha => {
const linhaPaciente = linha.getAttribute("data-paciente");
const dataConsulta = linha.getAttribute("data-data").slice(0, 10); // yyyy-mm-dd
let mostrar = true;
// Filtrar por paciente
if (pacienteId && linhaPaciente !== pacienteId) {
mostrar = false;
}
// Filtrar por dia (dataSelect)
if (dataEscolhida) {
if (dataConsulta !== dataEscolhida) {
mostrar = false;
}
}
// Filtrar por mês (mesSelect)
if (mesEscolhido) {
if (!dataConsulta.startsWith(mesEscolhido)) {
mostrar = false;
}
}
linha.style.display = mostrar ? "" : "none";
if(mostrar) anyVisible = true;
});
note.style.display = anyVisible ? 'none' : 'block';
}
// Eventos para disparar o filtro (mantendo comportamento original: limpar outros selects)
pacienteSelect.addEventListener("change", () => {
dataSelect.value = "";
mesSelect.value = "";
filtrarConsultas();
});
dataSelect.addEventListener("change", () => {
pacienteSelect.value = "";
mesSelect.value = "";
filtrarConsultas();
});
mesSelect.addEventListener("change", () => {
pacienteSelect.value = "";
dataSelect.value = "";
filtrarConsultas();
});
document.getElementById('limparFiltros').addEventListener('click', () => {
pacienteSelect.value = "";
dataSelect.value = "";
mesSelect.value = "";
filtrarConsultas();
});
// iniciar com todos visíveis
filtrarConsultas();
</script>
</body>
</html>

View File

@ -0,0 +1,304 @@
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Histórico de Pacientes • HealthOne</title>
<style>
:root{
--medicio-teal: #3fbbc0;
--medicio-teal-600: #34a3a8;
--medicio-muted: #6b7280;
--medicio-border: #e5e7eb;
--medicio-white: #ffffff;
--text-dark: #2a2d34;
--bg: #f8fafb;
--radius: 12px;
--shadow: 0 8px 24px rgba(0,0,0,.06);
}
*{box-sizing:border-box}
body{
margin:0;
font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial;
background: var(--bg);
color: var(--text-dark);
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
}
.appbar{
background: var(--medicio-white);
border-bottom: 1px solid var(--medicio-border);
position: sticky;
top: 0;
z-index: 20;
}
.appbar-inner{
max-width:1200px; margin:0 auto; padding:14px 20px;
display:flex; align-items:center; justify-content:space-between; gap:12px;
}
.brand{ display:flex; align-items:center; gap:12px; }
.logo{
width:44px; height:44px; border-radius:50%;
box-shadow: 0 0 20px rgba(63,187,192,0.12), 0 0 40px rgba(63,187,192,0.06);
background: linear-gradient(135deg, var(--medicio-teal), var(--medicio-teal-600));
}
.brand h1{ margin:0; font-size:16px; letter-spacing:.2px; }
.brand small{ display:block; color:var(--medicio-muted); font-size:12px; margin-top:2px; }
.wrap{ max-width:1200px; margin:20px auto; padding:0 20px 80px; }
.toolbar{
display:grid;
grid-template-columns: 1fr 220px 180px 120px auto;
gap:12px; margin-bottom:18px;
align-items:center;
}
@media (max-width:980px){ .toolbar{ grid-template-columns: 1fr 1fr; } }
.field{
display:flex; align-items:center; gap:10px;
background: var(--medicio-white); border:1px solid var(--medicio-border);
padding:10px 12px; border-radius:10px;
}
.field input, .field select{
width:100%; border:0; outline:0; background:transparent; color:var(--text-dark); font-size:14px;
}
label{ font-weight:600; color:var(--medicio-muted); font-size:13px; margin-right:6px; }
.btn{
padding:10px 14px; border-radius:10px; font-weight:700; border:0; cursor:pointer;
background:var(--medicio-teal); color:#fff; box-shadow:0 6px 16px rgba(63,187,192,.16);
}
.btn.secondary{
background:#fff; color:var(--text-dark); border:1px solid var(--medicio-border); box-shadow:none;
}
.card{ background:#fff; border:1px solid var(--medicio-border); border-radius:12px; box-shadow:var(--shadow); }
.card-header{ display:flex; align-items:center; justify-content:space-between; padding:16px 18px 10px 18px; border-bottom:1px solid var(--medicio-border); }
.card-content{ padding:12px 18px 18px 18px; }
.table-wrap{ overflow-x:auto; }
table{ width:100%; border-collapse:separate; border-spacing:0 10px; min-width:1000px; }
thead.thead th{ text-align:left; font-size:12px; color:var(--medicio-muted); padding:8px 12px; text-transform:uppercase; letter-spacing:.6px; }
tbody tr.row{ background:#fff; border:1px solid var(--medicio-border); border-radius:10px; }
tbody tr.row td{ padding:12px; vertical-align:middle; border-bottom:0; font-size:14px; color:var(--text-dark); }
tbody tr.row td.actions{ text-align:right; }
.empty{ text-align:center; color:var(--medicio-muted); padding:28px 12px; }
@media (max-width:720px){
thead.thead th:nth-child(3),
thead.thead th:nth-child(4),
thead.thead th:nth-child(5),
thead.thead th:nth-child(6),
thead.thead th:nth-child(7),
thead.thead th:nth-child(9),
thead.thead th:nth-child(10) { display:none; }
tbody tr.row td:nth-child(3),
tbody tr.row td:nth-child(4),
tbody tr.row td:nth-child(5),
tbody tr.row td:nth-child(6),
tbody tr.row td:nth-child(7),
tbody tr.row td:nth-child(9),
tbody tr.row td:nth-child(10) { display:none; }
table{ min-width:700px; }
}
</style>
</head>
<body>
<div class="appbar">
<div class="appbar-inner">
<div class="brand">
<div class="logo" aria-hidden="true"></div>
<div>
<h1>HealthOne</h1>
<small>Histórico de Pacientes</small>
</div>
</div>
</div>
</div>
<main class="wrap">
<div class="toolbar">
<div class="field" style="min-width:220px;">
<label for="pacienteSelect" style="min-width:90px">Paciente</label>
<select id="pacienteSelect">
<option value="">-- Escolha --</option>
<option value="1">João da Silva</option>
<option value="2">Maria Ferreira</option>
<option value="3">Carlos Souza</option>
<option value="4">Ana Paula</option>
<option value="5">Lucas Lima</option>
</select>
</div>
<div class="field">
<label for="dataSelect">Dia</label>
<input type="date" id="dataSelect" />
</div>
<div class="field">
<label for="mesSelect">Mês</label>
<input type="month" id="mesSelect" />
</div>
<div style="display:flex; gap:8px; justify-content:flex-end;">
<button id="limparFiltros" class="btn secondary">Limpar filtros</button>
</div>
</div>
<section class="card" aria-label="Histórico de consultas">
<div class="card-header">
<h2>Histórico de Consultas</h2>
<div style="color:var(--medicio-muted); font-size:13px">Use os filtros acima para refinar.</div>
</div>
<div class="card-content">
<div class="table-wrap">
<table id="Consultas" aria-describedby="Tabela de histórico">
<thead class="thead">
<tr>
<th>CPF</th>
<th>Paciente</th>
<th>Telefone</th>
<th>Cidade</th>
<th>Estado</th>
<th>VIP</th>
<th>Médico</th>
<th>Data da Consulta</th>
<th>Último atendimento</th>
<th>Próximo atendimento</th>
<th>Motivo da Consulta</th>
<th>Diagnóstico</th>
<th>Tratamento</th>
<th>Relatório</th>
</tr>
</thead>
<tbody id="consultasBody"></tbody>
</table>
</div>
<div class="empty note" id="note">Selecione um paciente, um dia ou um mês para filtrar o histórico de consultas.</div>
</div>
</section>
</main>
<script>
const consultasBody = document.getElementById('consultasBody');
const note = document.getElementById('note');
function carregarConsultas() {
const consultas = JSON.parse(localStorage.getItem('healthone_consultas') || '[]');
if (consultas.length === 0) {
consultasBody.innerHTML = '';
note.textContent = 'Nenhuma consulta agendada.';
note.style.display = 'block';
return;
}
const note = document.getElementById('note');
note.style.display = 'none';
consultasBody.innerHTML = consultas.map(c => {
// Dados fictícios para paciente, você pode adaptar conforme sua base
const pacienteNome = "Paciente não informado";
const cpf = "-";
const telefone = "-";
const cidade = "-";
const estado = "-";
const vip = "-";
const motivo = "-";
const diagnostico = "-";
const tratamento = "-";
const relatorio = "-";
return `
<tr class="row" data-paciente="" data-data="${c.dataHora}" data-medico="${c.medicoNome}" data-cpf="${cpf}">
<td>${cpf}</td>
<td>${pacienteNome}</td>
<td>${telefone}</td>
<td>${cidade}</td>
<td>${estado}</td>
<td>${vip}</td>
<td>${c.medicoNome}</td>
<td>${c.dataHora}</td>
<td>-</td>
<td>-</td>
<td>${motivo}</td>
<td>${diagnostico}</td>
<td>${tratamento}</td>
<td>${relatorio}</td>
</tr>
`;
}).join('');
}
carregarConsultas();
window.addEventListener('storage', (event) => {
if(event.key === 'healthone_consultas'){
carregarConsultas();
}
});
const pacienteSelect = document.getElementById("pacienteSelect");
const dataSelect = document.getElementById("dataSelect");
const mesSelect = document.getElementById("mesSelect");
function filtrarConsultas() {
const linhas = document.querySelectorAll("#Consultas tbody tr");
const pacienteId = pacienteSelect.value;
const dataEscolhida = dataSelect.value; // yyyy-mm-dd
const mesEscolhido = mesSelect.value; // yyyy-mm
let anyVisible = false;
linhas.forEach(linha => {
const linhaPaciente = linha.getAttribute("data-paciente");
const dataConsulta = linha.getAttribute("data-data").slice(0, 10); // yyyy-mm-dd
let mostrar = true;
if (pacienteId && linhaPaciente !== pacienteId) {
mostrar = false;
}
if (dataEscolhida && dataConsulta !== dataEscolhida) {
mostrar = false;
}
if (mesEscolhido && !dataConsulta.startsWith(mesEscolhido)) {
mostrar = false;
}
linha.style.display = mostrar ? "" : "none";
if(mostrar) anyVisible = true;
});
note.style.display = anyVisible ? 'none' : 'block';
}
pacienteSelect.addEventListener("change", () => {
dataSelect.value = "";
mesSelect.value = "";
filtrarConsultas();
});
dataSelect.addEventListener("change", () => {
pacienteSelect.value = "";
mesSelect.value = "";
filtrarConsultas();
});
mesSelect.addEventListener("change", () => {
pacienteSelect.value = "";
dataSelect.value = "";
filtrarConsultas();
});
document.getElementById('limparFiltros').addEventListener('click', () => {
pacienteSelect.value = "";
dataSelect.value = "";
mesSelect.value = "";
filtrarConsultas();
});
// iniciar com todos visíveis
filtrarConsultas();
</script>
</body>
</html>

View File

@ -0,0 +1,210 @@
:root {
--brand: #1db7ae;
--brand-600: #12968f;
--brand-100: #e7f7f6;
--ink: #24323f;
--muted: #6b7a88;
--line: #e6eaef;
--bg: #ffffff;
--danger: #e55353;
--warning: #f59e0b;
--success: #22c55e;
--shadow: 0 10px 20px rgba(15, 23, 42, .06);
--radius: 14px;
}
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"; color: var(--ink); background: #f6f8fb; }
a { color: inherit; text-decoration: none; }
button { font: inherit; }
.topbar { background: var(--brand); color: #fff; font-size: .9rem; }
.topbar .wrap { max-width: 1200px; margin: 0 auto; padding: .5rem 1rem; display: flex; gap: 1rem; align-items: center; justify-content: space-between; }
.topbar small { opacity: .95; }
header { background: #fff; box-shadow: var(--shadow); position: sticky; top: 0; z-index: 30; }
header .wrap { max-width: 1200px; margin: 0 auto; padding: .9rem 1rem; display: flex; align-items: center; justify-content: space-between; }
.brand { display: flex; align-items: center; gap: .6rem; font-weight: 800; letter-spacing: .4px; }
.brand .logo { width: 36px; height: 36px; border-radius: 50%; background: linear-gradient(135deg, var(--brand), #4dd7cf); display: grid; place-items: center; color: #fff; font-weight: 700; box-shadow: 0 6px 14px rgba(29,183,174,.35); }
nav { display: flex; gap: 1.2rem; color: var(--muted); font-weight: 600; }
.cta { background: var(--brand); color: #fff; padding: .6rem 1rem; border-radius: 999px; box-shadow: 0 10px 18px rgba(29,183,174,.25); transition: .2s ease; }
.cta:hover { background: var(--brand-600); transform: translateY(-1px); }
.container { max-width: 1200px; margin: 28px auto; padding: 0 1rem 4rem; }
.card { background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); }
.card .head { padding: 1rem 1.2rem; border-bottom: 1px solid var(--line); display: flex; gap: 1rem; align-items: center; justify-content: space-between; }
.title { display: flex; gap: .8rem; align-items: center; }
.title h1 { margin: 0; font-size: 1.25rem; }
.badge { background: var(--brand-100); color: var(--brand-600); font-weight: 700; padding: .2rem .5rem; border-radius: 6px; font-size: .75rem; }
.toolbar { display: grid; grid-template-columns: 1fr auto; gap: .8rem; width: 100%; }
.search { display: flex; align-items: center; gap: .6rem; background: #f9fbfd; border: 1px solid var(--line); padding: .55rem .8rem; border-radius: 999px; }
.search input { all: unset; width: 100%; color: var(--ink); }
.btn { display: inline-flex; gap: .5rem; align-items: center; border: 1px solid transparent; border-radius: 999px; padding: .65rem 1rem; font-weight: 700; cursor: pointer; transition: .2s ease; }
.btn.primary { background: var(--brand); color: #fff; box-shadow: 0 10px 18px rgba(29,183,174,.25); }
.btn.primary:hover { background: var(--brand-600); transform: translateY(-1px); }
.table { width: 100%; border-collapse: collapse; }
.table thead th { text-align: left; font-size: .8rem; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); background: #fbfdff; border-bottom: 1px solid var(--line); padding: .9rem .9rem; }
.table tbody td { padding: .9rem .9rem; border-bottom: 1px solid var(--line); vertical-align: middle; }
.table tbody tr:hover { background: #fcfefe; }
.actions { display: inline-flex; gap: .35rem; }
.icon-btn { --bg: #f2f6fb; --bd: var(--line); display: inline-grid; place-items: center; width: 34px; height: 34px; border-radius: 10px; border: 1px solid var(--bd); background: var(--bg); transition: .2s ease; }
.icon-btn:hover { transform: translateY(-1px); box-shadow: var(--shadow); }
.table-footer { display: flex; align-items: center; justify-content: space-between; padding: 1rem 1.2rem; }
.pagination { display: inline-flex; gap: .4rem; }
.page-btn, .page { min-width: 36px; height: 36px; border-radius: 10px; border: 1px solid var(--line); display: grid; place-items: center; padding: 0 .6rem; background:#fff; cursor:pointer }
.page-btn.active, .page.is-active { background: var(--brand); color: #fff; border-color: transparent; box-shadow: 0 10px 18px rgba(29,183,174,.25); }
.col-actions{ text-align:right }
@media (max-width: 900px) {
.toolbar { grid-template-columns: 1fr; }
.table thead { display: none; }
.table, .table tbody, .table tr, .table td { display: block; width: 100%; }
.table tbody tr { border-bottom: 1px solid var(--line); padding: .6rem 0; }
.table tbody td { border-bottom: none; display: grid; grid-template-columns: 30% 1fr; gap: .6rem; padding: .5rem .9rem; }
.actions { justify-content: flex-end; }
}
:root{
--brand: #1db7ae;
--brand-600:#12968f;
--brand-100:#e7f7f6;
--ink:#24323f;
--muted:#6b7a88;
--line:#e6eaef;
--bg:#ffffff;
--shadow: 0 20px 60px rgba(18,150,143,.22);
}
.overlay{
position: fixed; inset: 0;
background: rgba(14, 23, 29, .56);
display: none; align-items: center; justify-content: center;
padding: 24px;
z-index: 999;
}
.overlay[aria-hidden="false"]{ display:flex; }
.carteirinha-wrap{
width: min(820px, 96vw);
perspective: 1400px;
position: relative;
animation: overlayIn .18s ease-out;
}
@keyframes overlayIn {
from{ transform: translateY(8px); opacity:.0}
to{ transform: translateY(0); opacity:1}
}
.close-btn{
position: absolute;
top: -14px;
right: -14px;
width: 40px; height: 40px;
border: none; border-radius: 12px;
background: #fff;
box-shadow: 0 8px 20px rgba(0,0,0,.18);
font-size: 28px; line-height: 40px;
color: #6b7a88; cursor: pointer;
z-index: 1010; }
.close-btn:hover{ transform: translateY(-1px); }
.carteirinha{
background: var(--bg);
border-radius: 20px;
box-shadow: var(--shadow);
overflow: hidden;
transform: rotateX(14deg) rotateY(-8deg) scale(.96);
transform-origin: 50% 60%;
animation: cardEnter .35s cubic-bezier(.22,.9,.27,1.05) forwards;
}
@keyframes cardEnter{
to { transform: rotateX(0) rotateY(0) scale(1); }
}
.cart-header{
display:flex; align-items:center; gap:12px;
background: linear-gradient(135deg, var(--brand), #15a6a0);
color:#fff; padding: 20px 22px;
}
.logo-dot{ width:12px; height:12px; border-radius:50%; background:#fff; opacity:.9 }
.cart-header h3{ margin:0; font-weight:700; letter-spacing:.3px }
.tag{
margin-left:auto; background: rgba(255,255,255,.18);
border:1px solid rgba(255,255,255,.35);
padding:6px 10px; border-radius:999px; font-size:12px
}
.cart-body{ padding: 20px 22px; display:grid; grid-template-columns: 120px 1fr; gap: 18px; }
.avatar{
position: relative; width:110px; height:110px; border-radius:16px;
background: var(--brand-100);
display:grid; place-items:center; font-weight:700; color:var(--brand-600);
isolation:isolate;
}
.avatar-ring{
position:absolute; inset:-6px;
border-radius:20px; border: 2px dashed var(--brand-600);
opacity:.25; animation: spin 10s linear infinite;
z-index: -1;
}
@keyframes spin { to{ transform: rotate(360deg); } }
.avatar-iniciais{ font-size:28px }
.grid{
display:grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14px;
}
.field label{
display:block; font-size:12px; color: var(--muted); margin-bottom:4px
}
.field p{
background:#fff; border:1px solid var(--line);
border-radius:12px; padding:10px 12px; margin:0; color:var(--ink);
}
.obs{ grid-column: 1/-1; }
.obs label{ display:block; font-size:12px; color:var(--muted); margin:10px 0 6px; }
.obs p{
margin:0; background:#fff; border:1px dashed var(--line);
border-radius:12px; padding:12px; min-height:56px; color:var(--ink)
}
.cart-footer{
display:flex; align-items:center; gap:16px; justify-content:space-between;
padding: 12px 18px 18px;
border-top:1px solid var(--line);
background: #fafdfd;
}
.chips{ display:flex; gap:10px; flex-wrap:wrap }
.chip{
background: var(--brand-100); color: var(--brand-600);
border:1px solid #cbeeed; padding:6px 10px; border-radius:999px; font-size:12px;
}
.chip.outline{
background: #fff; border:1px dashed #cbeeed; color: var(--brand-600);
}
.actions{ display:flex; gap:10px }
button.primary, button.ghost{
border-radius:12px; padding:10px 14px; border:1px solid var(--line);
cursor:pointer; font-weight:600;
}
button.primary{ background: var(--brand); color:#fff; border-color: transparent; }
button.primary:hover{ background: var(--brand-600); }
button.ghost{ background:#fff; color: var(--ink); }
button.ghost:hover{ border-color: var(--brand-600); color: var(--brand-600); }
/* Responsivo */
@media (max-width: 720px){
.cart-body{ grid-template-columns: 1fr; }
.grid{ grid-template-columns: 1fr 1fr; }
}
@media (max-width: 440px){
.grid{ grid-template-columns: 1fr; }
}

View File

@ -0,0 +1,84 @@
body {
font-family: Arial, sans-serif;
background-color: #f7f9fb;
margin: 0;
padding: 20px;
color: #333;
}
h1 {
margin-bottom: 20px;
color: #000000;
}
.editor-toolbar {
margin-bottom: 10px;
}
.editor-toolbar button {
background: #00bcd4;
border: none;
padding: 8px;
margin-right: 5px;
color: white;
cursor: pointer;
border-radius: 6px;
}
#laudo-conteudo {
width: 100%;
height: 250px;
border: 1px solid #ccc;
padding: 10px;
background: white;
overflow-y: auto;
}
.opcoes {
margin-top: 20px;
}
.opcoes input,
.opcoes select,
.opcoes button {
padding: 8px;
margin-right: 10px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 6px;
}
.btn-salvar {
background: #4caf50;
color: white;
}
.btn-cancelar {
background: #f44336;
color: white;
}
label {
font-weight: bold;
display: block;
margin-top: 10px;
}
.status {
font-weight: bold;
margin-top: 5px;
padding: 5px 10px;
border-radius: 6px;
display: inline-block;
}
.status.ok {
background: #d4edda;
color: #155724;
}
.status.vencido {
background: #f8d7da;
color: #721c24;
}

View File

@ -0,0 +1,148 @@
body {
font-family: Arial, sans-serif;
background-color: #f7f9fb;
margin: 0;
padding: 0;
color: #333;
}
.brand { display: flex; align-items: center; gap: .6rem; font-weight: 800; letter-spacing: .4px; }
.brand .logo { width: 36px; height: 36px; border-radius: 50%; background: #4dd7cf; display: grid; place-items: center; color: #fff; font-weight: 700; box-shadow: 0 6px 14px rgba(29,183,174,.35); }
.top-bar {
background-color: #26bdbd;
color: white;
text-align: right;
padding: 5px 20px;
font-size: 13px;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
}
.phone {
color: #ff0080;
font-size: 14px;
margin-left: 10px;
}
.header {
display: flex;
justify-content: flex-end;
align-items: center;
background-color: white;
padding: 15px 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
gap: 20px;
}
.nav-links {
display: flex;
gap: 20px;
align-items: center;
}
.nav-links a {
text-decoration: none;
color: #555;
font-weight: 500;
padding: 10px 18px;
border-radius: 20px;
transition: background 0.3s ease;
}
.nav-links a.active {
background: #26bdbd;
color: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
font-weight: bold;
}
.btn-header {
background: none;
color: #555;
border: none;
padding: 10px 18px;
border-radius: 20px;
font-weight: 500;
cursor: pointer;
transition: background 0.3s ease;
}
.btn-header:hover {
background: #f0f0f0;
}
table {
width: 95%;
margin: 30px auto;
border-collapse: collapse;
background: white;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
border-radius: 8px;
overflow: hidden;
}
th, td {
padding: 12px;
border-bottom: 1px solid #ddd;
text-align: left;
}
th {
background: #00bcd4;
color: white;
}
tr:hover {
background: #f1f1f1;
}
.status-verde {
color: green;
font-weight: bold;
}
.status-vermelho {
color: red;
font-weight: bold;
}
.acoes button {
border: none;
padding: 6px 10px;
border-radius: 6px;
margin-right: 5px;
cursor: pointer;
color: white;
}
.btn-editar {
background: #2196f3;
}
.btn-revisar {
background: #607d8b;
}
.btn-excluir {
background: #f44336;
}
.novo-laudo {
margin: 20px;
}
.novo-laudo button {
background: #4caf50;
color: white;
border: none;
padding: 10px 15px;
border-radius: 6px;
cursor: pointer;
}
.user {
margin-right: 800px;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,77 @@
body {
font-family: Arial, sans-serif;
background-color: #f7f9fb;
margin: 0;
padding: 20px;
color: #333;
}
h1 {
color: #000000;
margin-bottom: 20px;
}
.editor-toolbar {
margin-bottom: 10px;
}
.editor-toolbar button {
background: #00bcd4;
border: none;
padding: 8px;
margin-right: 5px;
color: white;
cursor: pointer;
border-radius: 6px;
}
#laudo-conteudo {
width: 100%;
height: 250px;
border: 1px solid #ccc;
padding: 10px;
background: white;
overflow-y: auto;
}
.opcoes {
margin-top: 20px;
}
.opcoes input,
.opcoes select,
.opcoes button {
padding: 8px;
margin-right: 10px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 6px;
}
.btn-salvar {
background: #4caf50;
color: white;
}
.btn-cancelar {
background: #f44336;
color: white;
}
.preview {
border: 1px dashed #999;
padding: 10px;
margin-top: 20px;
background: #fff;
}
.hidden {
display: none;
}
label {
font-weight: bold;
display: block;
margin-top: 10px;
}

View File

@ -0,0 +1,81 @@
body {
font-family: Arial, sans-serif;
background-color: #f7f9fb;
margin: 0;
padding: 20px;
color: #333;
}
h1 {
margin-bottom: 10px;
}
.dados-paciente {
background: #fff;
padding: 12px;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.dados-paciente p {
margin: 4px 0;
}
.laudo-box {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.laudo-box h2 {
margin-top: 0;
text-align: center;
}
.acoes {
margin-top: 20px;
}
.acoes button {
padding: 10px 15px;
margin-right: 10px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
}
.btn-voltar {
background: #607d8b;
color: white;
}
.btn-imprimir {
background: #00bcd4;
color: white;
}
.btn-liberar {
background: #4caf50;
color: white;
}
.btn-excluir {
background: #f44336;
color: white;
}
/* Cores do status */
.status-verde {
color: green;
font-weight: bold;
}
.status-vermelho {
color: red;
font-weight: bold;
}

View File

@ -0,0 +1,189 @@
:root{
/* Paleta clara estilo print */
--brand-primary:#22c3b5; /* teal do topo/ações */
--brand-primary-600:#18ab9f;
--brand-accent:#22c55e; /* verde OK (pode manter) */
--bg:#f4f7fb; /* fundo da página claro */
--surface:#ffffff; /* cartões/painéis brancos */
--surface-2:#ffffff;
--text:#0f172a; /* texto principal escuro */
--muted:#64748b; /* texto secundário */
--border:#e5e9f0; /* linhas divisórias claras */
--warning:#f59e0b;
--danger:#ef4444;
--success:#10b981;
--radius:14px;
--shadow:0 8px 20px rgba(15,23,42,.08); /* sombra suave */
}
/* fundo geral claro */
html,body{height:100%}
body{
margin:0;
background: var(--bg);
color: var(--text);
font:500 14px/1.45 Inter, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial;
}
*{box-sizing:border-box}
html,body{height:100%}
body{margin:0;background:linear-gradient(180deg, #0b1220 0%, #0b1220 40%, #0a0f1a 100%);color:var(--text);font:500 14px/1.4 Inter, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial}
/* Header */
/* Header: faixa translúcida teal com blur (como no print) */
.app-header{
display:flex;align-items:center;justify-content:space-between;
padding:14px 22px;
border-bottom:1px solid var(--border);
position:sticky;top:0;z-index:40;
background:linear-gradient(0deg, rgba(45, 171, 209, 0.92), rgba(34,195,181,.92));
backdrop-filter:blur(6px);
}
/* Marca compacta */
.app-brand{display:flex;gap:10px;align-items:center}
.brand-mark{width:32px;height:32px;border-radius:999px;
background:#000000; box-shadow:0 2px 6px rgba(0,0,0,.08);}
.brand-title{font-weight:800;letter-spacing:.2px;color:#000000}
/* Subtítulo claro */
.app-brand .muted{color:rgba(0, 0, 0, 0.9)}
/* Navegação tipo “pill” */
.top-nav{display:flex;gap:8px;align-items:center}
.top-nav a{
display:inline-flex;align-items:center;gap:8px;
padding:10px 14px;border-radius:999px;
background:transparent;border:1px solid transparent;
text-decoration:none;color:#000000;font-weight:700;opacity:.95;
transition:.15s ease;
}
.top-nav a:hover{opacity:1; background:rgba(255,255,255,.12)}
.top-nav a.active{
background:#ffffff; color:#0f172a; border-color:#ffffff;
box-shadow:0 8px 22px rgba(15,23,42,.12);
}
.top-nav .pill{
font-size:11px;padding:2px 8px;border-radius:999px;
background:rgba(15,23,42,.08); color:#0f172a;border:1px solid rgba(15,23,42,.06)
}
.brand-mark{width:10px;height:10px;border-radius:10px;}
.brand-title{font-weight:800;letter-spacing:.5px}
/* cartão branco com borda clarinha e sombra suave */
.card{
background:var(--surface);
border:1px solid var(--border);
border-radius:18px;
box-shadow:var(--shadow);
overflow:hidden;
}
.card-header{
display:flex;flex-wrap:wrap;gap:10px;align-items:center;justify-content:space-between;
padding:18px 20px;border-bottom:1px solid var(--border); background:#fff;
}
.card-title{font-size:18px;font-weight:800;color:#0f172a}
.card-actions{display:flex;gap:10px}
/* botões em pill com sombra sutil */
.btn{
appearance:none;border:1px solid transparent;
background:var(--brand-primary);color:#fff;
padding:10px 16px;border-radius:999px;font-weight:800;cursor:pointer;
transition:.18s ease; box-shadow:0 6px 16px rgba(34,195,181,.26);
}
.btn:hover{background:var(--brand-primary-600); transform:translateY(-1px)}
.btn.secondary{
background:#ffffff;border-color:var(--border);color:#0f172a;
box-shadow:0 2px 8px rgba(15,23,42,.06);
}
.btn.secondary:hover{border-color:#dfe5ee}
/* seções com cabeçalho claro (sem degradê escuro) */
.section{border:1px solid var(--border);border-radius:14px;overflow:hidden;background:#fff}
.section-header{
display:flex;align-items:center;gap:12px;justify-content:space-between;
padding:14px 16px;background:#f8fafc;border-bottom:1px solid var(--border)
}
.section-title{font-weight:800;color:#0f172a}
.grid{display:grid;gap:12px}
.grid-cols-2{grid-template-columns:repeat(2, minmax(0,1fr))}
.grid-cols-3{grid-template-columns:repeat(3, minmax(0,1fr))}
.grid-cols-4{grid-template-columns:repeat(4, minmax(0,1fr))}
@media (max-width:900px){.grid-cols-4,.grid-cols-3{grid-template-columns:repeat(2, minmax(0,1fr))}}
@media (max-width:640px){.grid-cols-4,.grid-cols-3,.grid-cols-2{grid-template-columns:1fr}}
.field{display:flex;flex-direction:column;gap:6px}
/* campos: fundo branco, borda clara, foco teal */
.field label{font-size:12px;color:#64748b}
.field input[type="text"],
.field input[type="email"],
.field input[type="date"],
.field input[type="tel"],
.field input[type="number"],
.field select,
.field textarea{
background:#ffffff;border:1px solid var(--border);color:#0f172a;
padding:10px 12px;border-radius:12px;outline:none;transition:border-color .15s, box-shadow .15s;
}
.field textarea{min-height:120px;resize:vertical}
.field input:focus,.field select:focus,.field textarea:focus{
border-color:var(--brand-primary);
box-shadow:0 0 0 3px rgba(34,195,181,.18);
}
.inline{display:flex;gap:12px;align-items:center;flex-wrap:wrap}
.muted{color:var(--muted)}
.avatar-uploader{display:flex;gap:16px;align-items:center}
/* avatar com borda pontilhada bem clara */
.avatar{width:88px;height:88px;border-radius:16px;background:#fafbff;border:1px dashed #dfe5ee}
.avatar img{width:100%;height:100%;object-fit:cover}
.radio-group{display:flex;gap:12px;flex-wrap:wrap}
/* radios / toggles mantidos; só o toggle ganha leve borda clara */
.toggle{background:#f1f5f9;border:1px solid #dfe5ee}
.toggle::after{background:#ffffff}
.toggle.active{background:var(--brand-primary)}
.toggle.active::after{left:25px;background:white}
details.section summary{list-style:none;cursor:pointer}
details.section[open] .section-header{border-bottom-color:transparent}
/* attachments: divisórias clarinhas */
.attachments{border-top:1px dashed #e8edf3}
.attach-row{border-bottom:1px dashed #eef2f7}
.error{color:var(--danger);font-size:12px}
.hint{font-size:12px;color:var(--muted)}
.toast{position:fixed;right:16px;bottom:16px;background:#0b1220;border:1px solid var(--border);padding:12px 14px;border-radius:10px;box-shadow:var(--shadow);opacity:0;transform:translateY(10px);transition:.25s}
.toast.show{opacity:1;transform:translateY(0)}
.preview{background:rgba(255,255,255,.02);border:1px dashed var(--border);border-radius:12px;padding:12px;font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;font-size:12px;white-space:pre-wrap}
/* Se NÃO tiver CSS externo, você pode colar aqui um básico ou reutilizar o seu de antes */
/* Coloquei apenas pequenas classes usadas pelo toast/preview para não quebrar */
.muted{color:#64748b}
.error{color:#ef4444;font-size:12px}
.hint{font-size:12px;color:#64748b}
.toast{position:fixed;right:16px;bottom:16px;background:#fff;border:1px solid #e5e7eb;padding:12px 14px;border-radius:10px;box-shadow:0 8px 20px rgba(0,0,0,.12);opacity:0;transform:translateY(10px);transition:.25s;color:#0f172a}
.toast.show{opacity:1;transform:translateY(0)}
.avatar{width:88px;height:88px;border-radius:12px;border:1px dashed #dfe5ee;display:grid;place-items:center;overflow:hidden}
.avatar img{width:100%;height:100%;object-fit:cover}
.toggle{position:relative;width:50px;height:28px;background:#f1f5f9;border:1px solid #dfe5ee;border-radius:999px;cursor:pointer}
.toggle::after{content:"";position:absolute;top:3px;left:3px;width:22px;height:22px;border-radius:50%;background:#fff;transition:.2s}
.toggle.active{background:#22c3b5;border-color:transparent}
.toggle.active::after{left:25px}
.preview{background:#fff;border:1px dashed #e5e7eb;border-radius:12px;padding:12px;font-family:ui-monospace,monospace;font-size:12px;white-space:pre-wrap}
.btn{appearance:none;border:1px solid transparent;background:#22c3b5;color:#fff;padding:10px 14px;border-radius:999px;font-weight:700;cursor:pointer}
.btn.secondary{background:#fff;border-color:#e5e7eb;color:#0f172a}
.top-nav a.active{background:#22c3b5;color:#fff;border-radius:999px;padding:6px 10px}
.section{border:1px solid #e5e7eb;border-radius:12px;margin:12px 0}
.section-header{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-bottom:1px solid #e5e7eb;background:#f8fafc}
.grid{display:grid;gap:12px}
.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}
@media (max-width:900px){.grid-cols-4{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:640px){.grid-cols-4{grid-template-columns:1fr}}
.field{display:flex;flex-direction:column;gap:6px}
.field input,.field select,.field textarea{padding:10px 12px;border:1px solid #e5e7eb;border-radius:10px}

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,22 @@
// /js/apiClient.js
export const BASE_URL = 'https://mock.apidog.com/m1/1053378-0-default';
export async function api(path, { method='GET', data, token } = {}) {
const headers = { 'Content-Type': 'application/json' };
if (token) headers.Authorization = `Bearer ${token}`; // use se ativar auth no Apidog
const res = await fetch(`${BASE_URL}${path}`, {
method,
headers,
body: data ? JSON.stringify(data) : undefined,
});
const text = await res.text();
let payload; try { payload = text ? JSON.parse(text) : null; } catch { payload = text; }
if (!res.ok) {
const msg = (payload && (payload.message || payload.error)) || res.statusText;
throw new Error(`API ${method} ${path} falhou: ${msg}`);
}
return payload; // atenção: endpoints retornam { success, data, ... }
}

View File

@ -0,0 +1,340 @@
/* ========================= LocalStorage utils ========================= */
const LS_KEY_PACIENTES = 'healthone.pacientes';
function loadPacientes(){ try { return JSON.parse(localStorage.getItem(LS_KEY_PACIENTES) || '[]'); } catch { return []; } }
function setPacientes(arr){ localStorage.setItem(LS_KEY_PACIENTES, JSON.stringify(arr)); }
function ensureId(p){ p.id = p.id || (crypto?.randomUUID?.() || String(Date.now()+Math.floor(Math.random()*1000))); return p; }
function salvarPaciente(paciente){
ensureId(paciente);
const arr = loadPacientes();
const idx = arr.findIndex(x => String(x.id) === String(paciente.id));
if (idx >= 0) arr[idx] = paciente; else arr.push(paciente);
setPacientes(arr);
}
/* ========================= Helpers ========================= */
// datas: aceita DD/MM/YYYY, D/M/YYYY, YYYY-MM-DD, YYYY/M/D
function parseDateSmart(v){
if (!v) return null;
if (v instanceof Date) return v;
const s = String(v).trim();
let m = s.match(/^(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2,4})$/); // DD/MM/YYYY
if (m){
let dd = +m[1], mm = +m[2], yyyy = +m[3];
if (yyyy < 100) yyyy += 2000;
const d = new Date(Date.UTC(yyyy, mm-1, dd));
if (d.getUTCFullYear() === yyyy && d.getUTCMonth() === mm-1 && d.getUTCDate() === dd) return d;
return null;
}
m = s.match(/^(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})$/); // YYYY-MM-DD
if (m){
const yyyy = +m[1], mm = +m[2], dd = +m[3];
const d = new Date(Date.UTC(yyyy, mm-1, dd));
if (d.getUTCFullYear() === yyyy && d.getUTCMonth() === mm-1 && d.getUTCDate() === dd) return d;
return null;
}
const d = new Date(s);
return isNaN(d) ? null : d;
}
function toISODate(v){ // 'YYYY-MM-DD' p/ <input type="date">
const d = parseDateSmart(v);
if (!d) return '';
const yyyy = d.getUTCFullYear();
const mm = String(d.getUTCMonth()+1).padStart(2,'0');
const dd = String(d.getUTCDate()).padStart(2,'0');
return `${yyyy}-${mm}-${dd}`;
}
const $ = (s,p=document)=>p.querySelector(s);
const $$ = (s,p=document)=>[...p.querySelectorAll(s)];
function onlyDigits(v){ return (v||'').replace(/\D+/g,''); }
function maskCPF(v){ v = onlyDigits(v).slice(0,11); return v.replace(/(\d{3})(\d)/,'$1.$2').replace(/(\d{3})(\d)/,'$1.$2').replace(/(\d{3})(\d{1,2})$/,'$1-$2'); }
function maskCEP(v){ v = onlyDigits(v).slice(0,8); return v.replace(/(\d{5})(\d)/,'$1-$2'); }
function maskPhoneBRIntl(v){
v = onlyDigits(v);
if(!v.startsWith('55')) v = '55'+v;
v = v.slice(0,13);
const ddi=v.slice(0,2), ddd=v.slice(2,4), rest=v.slice(4);
if(rest.length>9) return `+${ddi} (${ddd}) ${rest.slice(0,5)}-${rest.slice(5,9)}`;
if(rest.length>4) return `+${ddi} (${ddd}) ${rest.slice(0,4)}-${rest.slice(4,8)}`;
if(ddd) return `+${ddi} (${ddd}) ${rest}`;
return `+${ddi}`;
}
function isValidCPF(raw){
const s = onlyDigits(raw);
if(s.length!==11) return false;
if(/^([0-9])\1+$/.test(s)) return false;
let sum=0; for(let i=0;i<9;i++) sum+=parseInt(s[i])*(10-i);
let d1=(sum*10)%11; if(d1===10) d1=0; if(d1!==parseInt(s[9])) return false;
sum=0; for(let i=0;i<10;i++) sum+=parseInt(s[i])*(11-i);
let d2=(sum*10)%11; if(d2===10) d2=0; if(d2!==parseInt(s[10])) return false;
return true;
}
function toast(msg, ok=true){
const t = $('#toast'); t.textContent = msg;
t.style.borderColor = ok? '#10b981':'#ef4444';
t.classList.add('show'); setTimeout(()=> t.classList.remove('show'), 2200);
}
/* ========================= Upload de avatar ========================= */
const photoInput = $('#photo');
const avatar = $('#avatarPreview');
$('#btnUpload').addEventListener('click', ()=> photoInput.click());
photoInput.addEventListener('change', ()=>{
const f = photoInput.files?.[0]; if(!f) return;
const reader = new FileReader();
reader.onload = e => { avatar.innerHTML = `<img alt="Foto do paciente" src="${e.target.result}"/>`; };
reader.readAsDataURL(f);
});
/* ========================= Interações de campos ========================= */
const rnToggle = $('#rnToggle');
rnToggle?.addEventListener('click', ()=> rnToggle.classList.toggle('active'));
rnToggle?.addEventListener('keydown', (e)=>{ if(e.key==='Enter'||e.key===' '){ e.preventDefault(); rnToggle.click(); } });
const docTipo = $('#docTipo');
const docNumero = $('#docNumero');
docTipo.addEventListener('change', ()=>{
docNumero.disabled = !docTipo.value;
docNumero.placeholder = docTipo.value ? `Número do ${docTipo.value}` : 'Preencha após selecionar o tipo';
});
const temResp = $('#temResponsavel');
const respNome = $('#responsavel');
const respCpf = $('#cpfResponsavel');
temResp.addEventListener('change', ()=>{
const on = temResp.value==='sim';
respNome.disabled = respCpf.disabled = !on;
if(!on){ respNome.value=''; respCpf.value=''; }
});
// máscaras
const cpf = $('#cpf'); cpf.addEventListener('input', ()=> cpf.value = maskCPF(cpf.value));
const cpfResp = $('#cpfResponsavel'); cpfResp.addEventListener('input', ()=> cpfResp.value = maskCPF(cpfResp.value));
const celular = $('#celular'); const tel1=$('#tel1'); const tel2=$('#tel2');
;[celular,tel1,tel2].forEach(el=> el.addEventListener('input', ()=> el.value = maskPhoneBRIntl(el.value)));
const cep = $('#cep'); cep.addEventListener('input', ()=> cep.value = maskCEP(cep.value));
// validações
const email = $('#email');
email.addEventListener('blur', ()=> $('#err-email').textContent = (email.value && !email.checkValidity()) ? 'Formato de e-mail inválido.' : '');
cpf.addEventListener('blur', ()=> $('#err-cpf').textContent = (cpf.value && !isValidCPF(cpf.value)) ? 'CPF inválido.' : '');
// ViaCEP
async function buscarCEP(v){
const s = onlyDigits(v); if(s.length!==8) return;
try{
const res = await fetch(`https://viacep.com.br/ws/${s}/json/`);
const data = await res.json();
if(data.erro){ $('#err-cep').textContent='CEP não encontrado.'; return; }
$('#err-cep').textContent='';
$('#logradouro').value = data.logradouro || '';
$('#bairro').value = data.bairro || '';
$('#cidade').value = data.localidade || '';
$('#uf').value = data.uf || '';
}catch{ $('#err-cep').textContent='Falha ao consultar CEP.'; }
}
cep.addEventListener('blur', ()=> buscarCEP(cep.value));
/* ========================= Coletar dados / validar ========================= */
const form = $('#patientForm');
function getFormData(){
return {
foto: photoInput.files?.[0]?.name || null,
nome: $('#nome').value.trim(),
nomeSocial: $('#nomeSocial').value.trim(),
cpf: $('#cpf').value.trim(),
rg: $('#rg').value.trim(),
doc:{ tipo: $('#docTipo').value, numero: $('#docNumero').value.trim() },
sexo: (form.querySelector('input[name="sexo"]:checked')||{}).value || '',
nasc: $('#nasc').value, // <input type="date"> fornece YYYY-MM-DD
raca: $('#raca').value,
etnia: $('#etnia').value.trim(),
naturalidade: $('#naturalidade').value.trim(),
nacionalidade: $('#nacionalidade').value,
profissao: $('#profissao').value.trim(),
estadoCivil: $('#estadoCivil').value,
filiacao:{ mae: $('#mae').value.trim(), profMae: $('#profMae').value.trim(), pai: $('#pai').value.trim(), profPai: $('#profPai').value.trim() },
responsavel:{ ativo: $('#temResponsavel').value==='sim', nome: $('#responsavel').value.trim(), cpf: $('#cpfResponsavel').value.trim() },
esposo: $('#esposo').value.trim(),
rnGuia: rnToggle?.classList.contains('active') || false,
codigoLegado: $('#codigoLegado').value.trim(),
obs: $('#obs').value.trim(),
contato:{ email: $('#email').value.trim(), celular: $('#celular').value.trim(), tel1: $('#tel1').value.trim(), tel2: $('#tel2').value.trim() },
endereco:{ cep: $('#cep').value.trim(), logradouro: $('#logradouro').value.trim(), numero: $('#numero').value.trim(), complemento: $('#complemento').value.trim(), bairro: $('#bairro').value.trim(), cidade: $('#cidade').value.trim(), uf: $('#uf').value.trim(), referencia: $('#referencia').value.trim() },
// ✅ NOVOS CAMPOS — salva no formato ISO (YYYY-MM-DD)
ultimaConsulta: toISODate($('#ultimaConsulta').value),
proximaConsulta: toISODate($('#proximaConsulta').value),
};
}
function validateBeforeSave(data){
let ok=true;
if(!data.nome){ $('#err-nome').textContent='Informe o nome.'; ok=false; } else { $('#err-nome').textContent=''; }
if(data.cpf && !isValidCPF(data.cpf)){ $('#err-cpf').textContent='CPF inválido.'; ok=false; } else { $('#err-cpf').textContent=''; }
if(data.contato.email && !email.checkValidity()){ $('#err-email').textContent='Formato de e-mail inválido.'; ok=false; } else { $('#err-email').textContent=''; }
if(data.responsavel.ativo){
if(!data.responsavel.nome){ ok=false; toast('Informe o nome do responsável.', false); }
if(data.responsavel.cpf && !isValidCPF(data.responsavel.cpf)){ ok=false; toast('CPF do responsável inválido.', false); }
}
return ok;
}
function renderPreview(obj){
const el = document.querySelector('#jsonPreview');
if (!el) return;
el.textContent = JSON.stringify(obj, null, 2);
}
/* ========================= Edição via ?id= ========================= */
let editingId = null;
(function hydrateIfEditing(){
const params = new URLSearchParams(location.search);
const idParam = params.get('id');
if(!idParam) return; // sem id = novo
const id = String(idParam); // mantém string
const lista = loadPacientes();
const p = lista.find(x => String(x.id) === id);
if(!p) return;
editingId = id;
// Preenche campos
$('#nome').value = p.nome||'';
$('#nomeSocial').value = p.nomeSocial||'';
$('#cpf').value = p.cpf||'';
$('#rg').value = p.rg||'';
$('#docTipo').value = p.doc?.tipo||'';
$('#docNumero').value = p.doc?.numero||''; $('#docNumero').disabled = !p.doc?.tipo;
if(p.sexo){ const el = document.querySelector(`input[name="sexo"][value="${p.sexo}"]`); el && (el.checked=true); }
$('#nasc').value = toISODate(p.nasc || p.dataNascimento) || '';
$('#raca').value = p.raca||'';
$('#etnia').value = p.etnia||'';
$('#naturalidade').value = p.naturalidade||'';
$('#nacionalidade').value = p.nacionalidade||'';
$('#profissao').value = p.profissao||'';
$('#estadoCivil').value = p.estadoCivil||'';
$('#mae').value = p.filiacao?.mae||'';
$('#profMae').value = p.filiacao?.profMae||'';
$('#pai').value = p.filiacao?.pai||'';
$('#profPai').value = p.filiacao?.profPai||'';
$('#temResponsavel').value = p.responsavel?.ativo ? 'sim' : 'nao';
const on = p.responsavel?.ativo; $('#responsavel').disabled = $('#cpfResponsavel').disabled = !on;
$('#responsavel').value = p.responsavel?.nome||'';
$('#cpfResponsavel').value = p.responsavel?.cpf||'';
if(p.rnGuia) $('#rnToggle')?.classList.add('active');
$('#codigoLegado').value = p.codigoLegado||'';
$('#obs').value = p.obs||'';
$('#email').value = p.contato?.email||'';
$('#celular').value = p.contato?.celular||'';
$('#tel1').value = p.contato?.tel1||'';
$('#tel2').value = p.contato?.tel2||'';
$('#cep').value = p.endereco?.cep||'';
$('#logradouro').value = p.endereco?.logradouro||'';
$('#numero').value = p.endereco?.numero||'';
$('#complemento').value = p.endereco?.complemento||'';
$('#bairro').value = p.endereco?.bairro||'';
$('#cidade').value = p.endereco?.cidade||'';
$('#uf').value = p.endereco?.uf||'';
$('#referencia').value = p.endereco?.referencia||'';
// ✅ Consultas (pré-preencher)
$('#ultimaConsulta').value = toISODate(p.ultimaConsulta ?? p.ultima_consulta ?? p.ultConsulta) || '';
$('#proximaConsulta').value = toISODate(p.proximaConsulta ?? p.proxima_consulta ?? p.proxConsulta) || '';
renderPreview(p);
})();
/* ========================= Salvar / Cancelar ========================= */
$('#btnSave').addEventListener('click', ()=>{
const paciente = getFormData();
if(!validateBeforeSave(paciente)) return;
if (editingId){
paciente.id = editingId; // mantém o mesmo id (update)
}
salvarPaciente(paciente); // cria ou atualiza
toast(editingId ? 'Paciente atualizado!' : 'Paciente salvo!', true);
renderPreview(paciente);
setTimeout(()=> location.href = 'crud-pacientes.html', 500);
});
$('#btnCancel').addEventListener('click', ()=>{
if(confirm('Cancelar e voltar à lista?')){
form.reset(); avatar.innerHTML = '<span class="muted">Sem foto</span>'; renderPreview({});
location.href = 'crud-pacientes.html';
}
});
/* ========================= UI ========================= */
(function highlightActive(){
const cur = 'cadastro';
document.querySelectorAll('.top-nav a').forEach(a=>{
if(a.dataset.page===cur) a.classList.add('active');
});
})();
/* ========================= Anexos (mock local) ========================= */
const anexosLista = $('#anexosLista');
const anexosInput = $('#anexosInput');
$('#btnAddAnexos').addEventListener('click', ()=> anexosInput.click());
anexosInput.addEventListener('change', ()=>{
[...anexosInput.files].forEach(f=> addAnexo(f));
anexosInput.value='';
});
function addAnexo(file){
const row = document.createElement('div');
row.style.cssText='display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px dashed #eef2f7';
const left = document.createElement('div');
left.innerHTML = `<strong>${file.name}</strong><div class="muted" style="font-size:12px">${new Date().toLocaleString()}</div>`;
const right = document.createElement('div');
const btn = document.createElement('button');
btn.className='btn secondary'; btn.type='button'; btn.textContent='Excluir';
btn.addEventListener('click', ()=> row.remove());
right.appendChild(btn);
row.append(left, right);
anexosLista.appendChild(row);
}
// /js/cadastro.js
import { getPaciente, updatePaciente, createPaciente } from './pacientesService.js';
const $form = document.querySelector('#patientForm');
const params = new URLSearchParams(location.search);
const id = params.get('id');
function fillForm(obj = {}) {
Object.entries(obj).forEach(([k, v]) => {
const el = $form.elements.namedItem(k);
if (el) el.value = v ?? '';
});
}
async function load() {
if (!id) return;
try {
const p = await getPaciente(id);
if (!p) return alert('Paciente não encontrado');
fillForm(p);
} catch (e) {
console.error(e);
alert('Erro ao carregar paciente');
}
}
$form.addEventListener('submit', async (ev) => {
ev.preventDefault();
const data = Object.fromEntries(new FormData($form));
try {
if (id) await updatePaciente(id, data);
else await createPaciente(data);
alert('Salvo com sucesso!');
location.href = 'crud-pacientes.html';
} catch (e) {
console.error(e);
alert(`Erro ao salvar: ${e.message}`);
}
});
load();

View File

@ -0,0 +1,368 @@
// /js/crud-pacientes.js
/* ========================= Imports (API) ========================= */
import { listPacientes, deletePaciente, createPaciente, getPaciente } from './pacientesService.js';
/* ========================= Seletores / Estado ========================= */
const SEL = {
tbody: '#tbody',
pager: '#pager',
search: '#q',
count: '#countLabel',
btnNew: '#btnNew',
topNavLinks: '.top-nav a'
};
const $ = (s, p=document)=>p.querySelector(s);
const $$ = (s, p=document)=>[...p.querySelectorAll(s)];
const byId = (id)=>document.getElementById(id);
const tbody = $(SEL.tbody);
const pager = $(SEL.pager);
const q = $(SEL.search);
const countLabel = $(SEL.count);
const btnNew = $(SEL.btnNew);
let pacientes = []; // cache do que veio da API (normalizado)
let filtro = '';
let page = 1;
const perPage = 10;
/* ========================= Helpers (strings/datas) ========================= */
function normalize(s){ return (s||'').toString().toLowerCase(); }
function fmtIndex(n){ return String(n).padStart(3,'0'); }
function parseDateSmart(v){
if (!v) return null;
if (v instanceof Date) return v;
const s = String(v).trim();
let m = s.match(/^(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2,4})$/); // DD/MM/YYYY
if (m) {
let dd = +m[1], mm = +m[2], yyyy = +m[3];
if (yyyy < 100) yyyy += 2000;
const d = new Date(Date.UTC(yyyy, mm - 1, dd));
if (d.getUTCFullYear() === yyyy && d.getUTCMonth() === mm - 1 && d.getUTCDate() === dd) return d;
return null;
}
m = s.match(/^(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})$/); // YYYY-MM-DD
if (m) {
const yyyy = +m[1], mm = +m[2], dd = +m[3];
const d = new Date(Date.UTC(yyyy, mm - 1, dd));
if (d.getUTCFullYear() === yyyy && d.getUTCMonth() === mm - 1 && d.getUTCDate() === dd) return d;
return null;
}
const d = new Date(s);
return isNaN(d) ? null : d;
}
function formatData(input){
const d = parseDateSmart(input);
return d
? new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()))
.toLocaleDateString('pt-BR', { timeZone: 'UTC' })
: '—';
}
function calcIdade(input){
const d = parseDateSmart(input);
if (!d) return null;
const hoje = new Date();
let idade = hoje.getUTCFullYear() - d.getUTCFullYear();
const m = hoje.getUTCMonth() - d.getUTCMonth();
if (m < 0 || (m === 0 && hoje.getUTCDate() < d.getUTCDate())) idade--;
return idade;
}
function normalizaGenero(g){
if (!g) return null;
const s = String(g).trim().toLowerCase();
if (['m','masc','masculino','homem'].includes(s)) return 'Masculino';
if (['f','fem','feminino','mulher'].includes(s)) return 'Feminino';
return String(g);
}
function formatCPF(v){
if(!v) return "—";
const only = String(v).replace(/\D/g,'').padStart(11,'0').slice(-11);
return only.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/,'$1.$2.$3-$4');
}
function iniciais(nome){
if(!nome) return "PT";
return nome.split(/\s+/).slice(0,2).map(p=>p[0]).join('').toUpperCase();
}
/* ========================= Normalização API -> View ========================= */
function fromApi(p) {
// garante campos para tabela/carteirinha independente do shape exato
return {
id: p.id,
nome: p.nome ?? p.nome_completo ?? '',
nomeSocial: p.nome_social ?? p.nomeSocial ?? '',
cpf: p.cpf ?? '',
contato: {
email: p.email ?? p.contato?.email ?? '',
celular: p.telefone ?? p.contato?.celular ?? p.contato?.tel1 ?? p.contato?.tel2 ?? ''
},
endereco: {
cidade: p.endereco?.cidade ?? p.cidade ?? '',
uf: p.endereco?.estado ?? p.uf ?? p.estado ?? ''
},
dataNascimento: p.data_nascimento ?? p.nasc ?? p.dataNascimento ?? '',
ultimaConsulta: p.ultima_consulta ?? p.ultimaConsulta ?? '',
proximaConsulta: p.proxima_consulta ?? p.proximaConsulta ?? '',
genero: p.genero ?? p.sexo ?? '',
observacoes: p.observacoes ?? p.obs ?? '',
status: p.status ?? ''
};
}
function blobPaciente(p){
return normalize(`
${p.nome||''} ${p.nomeSocial||''} ${p.cpf||''}
${p.contato?.email||''} ${p.contato?.celular||''}
${p.endereco?.cidade||''} ${p.endereco?.uf||''}
`);
}
/* ========================= Botão: Novo Paciente (cria na API) ========================= */
function gerarNovoPaciente() {
const hoje = new Date();
const yyyy = hoje.getFullYear() - 30;
const mm = String(hoje.getMonth() + 1).padStart(2, '0');
const dd = String(hoje.getDate()).padStart(2, '0');
return {
nome: 'Paciente Novo',
nome_social: 'Novo',
cpf: '111.222.333-44', // troque por CPF válido se sua API validar
rg: '00.000.000-0',
sexo: 'não informado',
data_nascimento: `${yyyy}-${mm}-${dd}`,
profissao: '—',
estado_civil: 'solteiro(a)',
contato: { email: 'novo@exemplo.com', celular: '+55 (00) 00000-0000' },
endereco: {
cep: '00000-000', logradouro: '—', numero: '—',
bairro: '—', cidade: '—', estado: '—'
},
observacoes: 'Registro criado automaticamente pelo botão "Novo paciente".'
};
}
async function criarEIrParaCadastro() {
try {
btnNew?.setAttribute('aria-busy', 'true');
btnNew?.classList.add('is-busy');
btnNew && (btnNew.style.pointerEvents = 'none');
const novo = await createPaciente(gerarNovoPaciente());
const newId = novo?.id;
if (!newId) throw new Error('A API não retornou o ID do novo paciente.');
location.href = `cadastro.html?id=${encodeURIComponent(newId)}`;
} catch (err) {
console.error(err);
alert(`Falha ao criar paciente: ${err.message}`);
location.href = 'cadastro.html';
} finally {
btnNew?.removeAttribute('aria-busy');
btnNew?.classList.remove('is-busy');
if (btnNew) btnNew.style.pointerEvents = '';
}
}
btnNew?.addEventListener('click', (ev)=>{ ev.preventDefault(); criarEIrParaCadastro(); });
/* ========================= Render (lista/paginação/contador) ========================= */
function render(){
// filtro local (client-side). Se quiser, mude para query no servidor.
const term = normalize(filtro);
const list = term ? pacientes.filter(p => blobPaciente(p).includes(term)) : pacientes.slice();
const total = list.length;
const pages = Math.max(1, Math.ceil(total / perPage));
if (page > pages) page = pages;
const start = (page - 1) * perPage;
const rows = list.slice(start, start + perPage);
// tabela
if (tbody) {
tbody.innerHTML = rows.length ? rows.map((p, i) => `
<tr>
<td>${fmtIndex(start + i + 1)}</td>
<td>
${p.nome || '—'}
${p.nomeSocial ? `<div style="font-size:12px;color:#64748b">(${p.nomeSocial})</div>` : ''}
</td>
<td>${p.cpf || '—'}</td>
<td>${p.contato?.email || '—'}</td>
<td>${p.contato?.celular || '—'}</td>
<td>${p.endereco?.cidade ? `${p.endereco.cidade}${p.endereco.uf ? '/' + p.endereco.uf : ''}` : '—'}</td>
<td class="col-actions" style="text-align:right; white-space:nowrap;">
<button class="page-btn btn-view" type="button" data-id="${p.id}" title="Ver carteirinha" aria-label="Ver carteirinha">👁</button>
<button class="page-btn btn-edit" type="button" data-id="${p.id}" title="Editar"></button>
<button class="page-btn btn-del" type="button" data-id="${p.id}" title="Excluir">🗑</button>
</td>
</tr>
`).join('') : `
<tr><td colspan="7" style="padding:16px;color:#64748b">Sem pacientes cadastrados.</td></tr>
`;
}
// ações (delegadas depois do render)
$$('[data-id].btn-edit').forEach(b => b.onclick = () => onEdit(b.dataset.id));
$$('[data-id].btn-del').forEach(b => b.onclick = () => onDelete(b.dataset.id));
$$('[data-id].btn-view').forEach(b => b.onclick = () => onView(b.dataset.id));
// paginação
if (pager) {
pager.innerHTML = `
<button class="page-btn" ${page<=1?'disabled':''} id="pgPrev">«</button>
${Array.from({length: pages}, (_, i) =>
`<button class="page-btn ${i+1===page?'active':''}" data-page="${i+1}">${i+1}</button>`
).join('')}
<button class="page-btn" ${page>=pages?'disabled':''} id="pgNext">»</button>
`;
$('#pgPrev')?.addEventListener('click', ()=>{ if(page>1){ page--; render(); } });
$('#pgNext')?.addEventListener('click', ()=>{ if(page<pages){ page++; render(); } });
$$('[data-page]').forEach(btn => btn.addEventListener('click', ()=>{ page = +btn.dataset.page; render(); }));
}
// contador
if (countLabel) {
const showing = rows.length;
countLabel.textContent = `Mostrando ${showing} de ${total} paciente(s)`;
}
}
/* ========================= Carregar dados da API ========================= */
async function fetchAndRender(initialQuery=''){
try {
// busca no servidor (seu service já desempacota { rows })
const { rows } = await listPacientes({ q: initialQuery });
// normaliza para o shape usado pela UI
pacientes = rows.map(fromApi);
// ordena por próxima consulta (opcional)
pacientes.sort((a,b) => new Date(a.proximaConsulta||'9999-12-31') - new Date(b.proximaConsulta||'9999-12-31'));
render();
} catch (err) {
console.error(err);
if (tbody) tbody.innerHTML = `<tr><td colspan="7" style="padding:16px;color:#ef4444">Erro ao carregar: ${err.message}</td></tr>`;
}
}
/* ========================= Ações ========================= */
function onEdit(id){ window.location.href = `cadastro.html?id=${encodeURIComponent(id)}`; }
async function onDelete(id){
if(!confirm('Deseja excluir este paciente?')) return;
try {
await deletePaciente(id);
// remove do cache local e re-renderiza
pacientes = pacientes.filter(p => String(p.id) !== String(id));
render();
} catch (e) {
console.error(e);
alert('Falha ao excluir paciente.');
}
}
async function onView(id){
try{
// tenta no cache (lista) primeiro
let p = pacientes.find(x => String(x.id) === String(id));
if (!p) {
const apiItem = await getPaciente(id);
p = fromApi(apiItem || {});
}
abrirCarteirinha(p);
}catch(e){
console.error(e);
alert('Não foi possível carregar os dados deste paciente.');
}
}
/* ========================= Eventos UI ========================= */
q?.addEventListener('input', () => { filtro = q.value || ''; page = 1; render(); });
/* ========================= Inicializa (carrega da API) ========================= */
fetchAndRender();
/* ========================= MODAL Carteirinha ========================= */
const overlay = document.getElementById('carteirinha-overlay');
const closeBtn = document.querySelector('.close-btn');
function cId(id){ return document.getElementById(id); }
function preencherCarteirinha(p){
if(!p){
alert('Não foi possível encontrar este paciente.');
return;
}
const nascimento =
p.dataNascimento ?? p.nascimento ?? p.dataNasc ?? p.dtNascimento ??
p.data_nascimento ?? p.dataDeNascimento ?? p.dt_nasc ?? p.nasc;
const genero =
p.genero ?? p.sexo ?? p.generoBiologico ?? p.genero_identidade ?? p['gênero'];
const obs =
p.observacoes ?? p.obs ?? p.anotacoes ?? p.observacao ?? p.notas;
const idadeCalc = nascimento ? calcIdade(nascimento) : null;
const idadeTxt = (idadeCalc != null) ? `${idadeCalc} anos` : '—';
cId('c-nome') && (cId('c-nome').textContent = p.nome || '—');
cId('c-idade') && (cId('c-idade').textContent = idadeTxt);
cId('c-genero') && (cId('c-genero').textContent = normalizaGenero(genero) || '—');
cId('c-cpf') && (cId('c-cpf').textContent = formatCPF(p.cpf));
cId('c-ultima') && (cId('c-ultima').textContent = formatData(p.ultimaConsulta ?? p.ultima_consulta ?? p.ultConsulta));
cId('c-proxima') && (cId('c-proxima').textContent = formatData(p.proximaConsulta ?? p.proxima_consulta ?? p.proxConsulta));
cId('c-observacoes')&& (cId('c-observacoes').textContent = obs || '—');
cId('c-iniciais') && (cId('c-iniciais').textContent = iniciais(p.nome));
const chipStatus = document.getElementById('chip-status');
if (chipStatus) chipStatus.textContent = p.status ? `Paciente ${String(p.status).toLowerCase()}` : 'Paciente ativo';
const chipProx = document.getElementById('chip-prox');
const dProx = p.proximaConsulta ?? p.proxima_consulta ?? p.proxConsulta;
if (chipProx) chipProx.textContent = dProx ? `Próx.: ${formatData(dProx)}` : '—';
}
function abrirCarteirinha(paciente){
if (!overlay) {
alert('Estrutura da carteirinha não está no HTML desta página.');
console.warn('⚠️ Elemento #carteirinha-overlay não encontrado no DOM.');
return;
}
preencherCarteirinha(paciente);
overlay.setAttribute('aria-hidden','false');
const card = document.getElementById('carteirinha');
if (card) {
card.style.animation = 'none';
void card.offsetWidth;
card.style.animation = 'cardEnter .35s cubic-bezier(.22,.9,.27,1.05) forwards';
}
}
function fecharCarteirinha(){ overlay?.setAttribute('aria-hidden','true'); }
closeBtn?.addEventListener('click', fecharCarteirinha);
overlay?.addEventListener('click', (e)=>{ if(e.target === overlay) fecharCarteirinha(); });
document.addEventListener('keydown', (e)=>{ if(e.key === 'Escape') fecharCarteirinha(); });
document.getElementById('btn-copiar')?.addEventListener('click', async ()=>{
const texto =
`Nome: ${byId('c-nome')?.textContent||''}
Idade: ${byId('c-idade')?.textContent||''}
Gênero: ${byId('c-genero')?.textContent||''}
CPF: ${byId('c-cpf')?.textContent||''}
Última consulta: ${byId('c-ultima')?.textContent||''}
Próxima consulta: ${byId('c-proxima')?.textContent||''}
Observações: ${byId('c-observacoes')?.textContent||''}`;
try{
await navigator.clipboard.writeText(texto);
alert('Dados copiados! ✅');
}catch{
alert('Não foi possível copiar.');
}
});
document.getElementById('btn-imprimir')?.addEventListener('click', ()=>{ window.print(); });

View File

@ -0,0 +1,195 @@
/**
* Template Name: Medicio
* Template URL: https://bootstrapmade.com/medicio-free-bootstrap-theme/
* Updated: Aug 07 2024 with Bootstrap v5.3.3
* Author: BootstrapMade.com
* License: https://bootstrapmade.com/license/
*/
(function() {
"use strict";
/**
* Apply .scrolled class to the body as the page is scrolled down
*/
function toggleScrolled() {
const selectBody = document.querySelector('body');
const selectHeader = document.querySelector('#header');
if (!selectHeader.classList.contains('scroll-up-sticky') && !selectHeader.classList.contains('sticky-top') && !selectHeader.classList.contains('fixed-top')) return;
window.scrollY > 100 ? selectBody.classList.add('scrolled') : selectBody.classList.remove('scrolled');
}
document.addEventListener('scroll', toggleScrolled);
window.addEventListener('load', toggleScrolled);
/**
* Mobile nav toggle
*/
const mobileNavToggleBtn = document.querySelector('.mobile-nav-toggle');
function mobileNavToogle() {
document.querySelector('body').classList.toggle('mobile-nav-active');
mobileNavToggleBtn.classList.toggle('bi-list');
mobileNavToggleBtn.classList.toggle('bi-x');
}
mobileNavToggleBtn.addEventListener('click', mobileNavToogle);
/**
* Hide mobile nav on same-page/hash links
*/
document.querySelectorAll('#navmenu a').forEach(navmenu => {
navmenu.addEventListener('click', () => {
if (document.querySelector('.mobile-nav-active')) {
mobileNavToogle();
}
});
});
/**
* Toggle mobile nav dropdowns
*/
document.querySelectorAll('.navmenu .toggle-dropdown').forEach(navmenu => {
navmenu.addEventListener('click', function(e) {
e.preventDefault();
this.parentNode.classList.toggle('active');
this.parentNode.nextElementSibling.classList.toggle('dropdown-active');
e.stopImmediatePropagation();
});
});
/**
* Preloader
*/
const preloader = document.querySelector('#preloader');
if (preloader) {
window.addEventListener('load', () => {
preloader.remove();
});
}
/**
* Scroll top button
*/
let scrollTop = document.querySelector('.scroll-top');
function toggleScrollTop() {
if (scrollTop) {
window.scrollY > 100 ? scrollTop.classList.add('active') : scrollTop.classList.remove('active');
}
}
scrollTop.addEventListener('click', (e) => {
e.preventDefault();
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
window.addEventListener('load', toggleScrollTop);
document.addEventListener('scroll', toggleScrollTop);
/**
* Animation on scroll function and init
*/
function aosInit() {
AOS.init({
duration: 600,
easing: 'ease-in-out',
once: true,
mirror: false
});
}
window.addEventListener('load', aosInit);
/**
* Auto generate the carousel indicators
*/
document.querySelectorAll('.carousel-indicators').forEach((carouselIndicator) => {
carouselIndicator.closest('.carousel').querySelectorAll('.carousel-item').forEach((carouselItem, index) => {
if (index === 0) {
carouselIndicator.innerHTML += `<li data-bs-target="#${carouselIndicator.closest('.carousel').id}" data-bs-slide-to="${index}" class="active"></li>`;
} else {
carouselIndicator.innerHTML += `<li data-bs-target="#${carouselIndicator.closest('.carousel').id}" data-bs-slide-to="${index}"></li>`;
}
});
});
/**
* Initiate glightbox
*/
const glightbox = GLightbox({
selector: '.glightbox'
});
/**
* Init swiper sliders
*/
function initSwiper() {
document.querySelectorAll(".init-swiper").forEach(function(swiperElement) {
let config = JSON.parse(
swiperElement.querySelector(".swiper-config").innerHTML.trim()
);
if (swiperElement.classList.contains("swiper-tab")) {
initSwiperWithCustomPagination(swiperElement, config);
} else {
new Swiper(swiperElement, config);
}
});
}
window.addEventListener("load", initSwiper);
/**
* Frequently Asked Questions Toggle
*/
document.querySelectorAll('.faq-item h3, .faq-item .faq-toggle').forEach((faqItem) => {
faqItem.addEventListener('click', () => {
faqItem.parentNode.classList.toggle('faq-active');
});
});
/**
* Correct scrolling position upon page load for URLs containing hash links.
*/
window.addEventListener('load', function(e) {
if (window.location.hash) {
if (document.querySelector(window.location.hash)) {
setTimeout(() => {
let section = document.querySelector(window.location.hash);
let scrollMarginTop = getComputedStyle(section).scrollMarginTop;
window.scrollTo({
top: section.offsetTop - parseInt(scrollMarginTop),
behavior: 'smooth'
});
}, 100);
}
}
});
/**
* Navmenu Scrollspy
*/
let navmenulinks = document.querySelectorAll('.navmenu a');
function navmenuScrollspy() {
navmenulinks.forEach(navmenulink => {
if (!navmenulink.hash) return;
let section = document.querySelector(navmenulink.hash);
if (!section) return;
let position = window.scrollY + 200;
if (position >= section.offsetTop && position <= (section.offsetTop + section.offsetHeight)) {
document.querySelectorAll('.navmenu a.active').forEach(link => link.classList.remove('active'));
navmenulink.classList.add('active');
} else {
navmenulink.classList.remove('active');
}
})
}
window.addEventListener('load', navmenuScrollspy);
document.addEventListener('scroll', navmenuScrollspy);
})();

View File

@ -0,0 +1,53 @@
// /js/pacientesService.js
import { api } from './apiClient.js';
// se você tiver token, carregue aqui: const token = localStorage.getItem('token');
export async function listPacientes({ q, page=1, per_page=20 } = {}) {
const params = new URLSearchParams();
if (q) params.set('q', q);
params.set('page', page);
params.set('per_page', per_page);
const payload = await api(`/pacientes${params.toString() ? `?${params}` : ''}`, {
method: 'GET',
// token
});
// payload: { success, data: [...], pagination: {...} }
return { rows: payload?.data || [], pagination: payload?.pagination || null };
}
export async function getPaciente(id) {
const payload = await api(`/pacientes/${encodeURIComponent(id)}`, {
method: 'GET',
// token
});
return payload?.data || null;
}
export async function createPaciente(data) {
const payload = await api(`/pacientes`, {
method: 'POST',
data,
// token
});
// retorna { success, message, data: { id, ... } }
return payload?.data || null;
}
export async function updatePaciente(id, data) {
const payload = await api(`/pacientes/${encodeURIComponent(id)}`, {
method: 'PUT',
data,
// token
});
return payload?.data || null;
}
export async function deletePaciente(id) {
await api(`/pacientes/${encodeURIComponent(id)}`, {
method: 'DELETE',
// token
});
return true;
}

View File

@ -0,0 +1,2 @@
The .scss (Sass) files are only available in the pro version.
You can buy it from: https://bootstrapmade.com/medicio-free-bootstrap-theme/

View File

@ -0,0 +1,614 @@
'use strict';
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var throttle = _interopDefault(require('lodash.throttle'));
var debounce = _interopDefault(require('lodash.debounce'));
var callback = function callback() {};
function containsAOSNode(nodes) {
var i = void 0,
currentNode = void 0,
result = void 0;
for (i = 0; i < nodes.length; i += 1) {
currentNode = nodes[i];
if (currentNode.dataset && currentNode.dataset.aos) {
return true;
}
result = currentNode.children && containsAOSNode(currentNode.children);
if (result) {
return true;
}
}
return false;
}
function check(mutations) {
if (!mutations) return;
mutations.forEach(function (mutation) {
var addedNodes = Array.prototype.slice.call(mutation.addedNodes);
var removedNodes = Array.prototype.slice.call(mutation.removedNodes);
var allNodes = addedNodes.concat(removedNodes);
if (containsAOSNode(allNodes)) {
return callback();
}
});
}
function getMutationObserver() {
return window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
}
function isSupported() {
return !!getMutationObserver();
}
function ready(selector, fn) {
var doc = window.document;
var MutationObserver = getMutationObserver();
var observer = new MutationObserver(check);
callback = fn;
observer.observe(doc.documentElement, {
childList: true,
subtree: true,
removedNodes: true
});
}
var observer = { isSupported: isSupported, ready: ready };
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
/**
* Device detector
*/
var fullNameRe = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i;
var prefixRe = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i;
var fullNameMobileRe = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i;
var prefixMobileRe = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i;
function ua() {
return navigator.userAgent || navigator.vendor || window.opera || '';
}
var Detector = function () {
function Detector() {
classCallCheck(this, Detector);
}
createClass(Detector, [{
key: 'phone',
value: function phone() {
var a = ua();
return !!(fullNameRe.test(a) || prefixRe.test(a.substr(0, 4)));
}
}, {
key: 'mobile',
value: function mobile() {
var a = ua();
return !!(fullNameMobileRe.test(a) || prefixMobileRe.test(a.substr(0, 4)));
}
}, {
key: 'tablet',
value: function tablet() {
return this.mobile() && !this.phone();
}
// http://browserhacks.com/#hack-acea075d0ac6954f275a70023906050c
}, {
key: 'ie11',
value: function ie11() {
return '-ms-scroll-limit' in document.documentElement.style && '-ms-ime-align' in document.documentElement.style;
}
}]);
return Detector;
}();
var detect = new Detector();
/**
* Adds multiple classes on node
* @param {DOMNode} node
* @param {array} classes
*/
var addClasses = function addClasses(node, classes) {
return classes && classes.forEach(function (className) {
return node.classList.add(className);
});
};
/**
* Removes multiple classes from node
* @param {DOMNode} node
* @param {array} classes
*/
var removeClasses = function removeClasses(node, classes) {
return classes && classes.forEach(function (className) {
return node.classList.remove(className);
});
};
var fireEvent = function fireEvent(eventName, data) {
var customEvent = void 0;
if (detect.ie11()) {
customEvent = document.createEvent('CustomEvent');
customEvent.initCustomEvent(eventName, true, true, { detail: data });
} else {
customEvent = new CustomEvent(eventName, {
detail: data
});
}
return document.dispatchEvent(customEvent);
};
/**
* Set or remove aos-animate class
* @param {node} el element
* @param {int} top scrolled distance
*/
var applyClasses = function applyClasses(el, top) {
var options = el.options,
position = el.position,
node = el.node,
data = el.data;
var hide = function hide() {
if (!el.animated) return;
removeClasses(node, options.animatedClassNames);
fireEvent('aos:out', node);
if (el.options.id) {
fireEvent('aos:in:' + el.options.id, node);
}
el.animated = false;
};
var show = function show() {
if (el.animated) return;
addClasses(node, options.animatedClassNames);
fireEvent('aos:in', node);
if (el.options.id) {
fireEvent('aos:in:' + el.options.id, node);
}
el.animated = true;
};
if (options.mirror && top >= position.out && !options.once) {
hide();
} else if (top >= position.in) {
show();
} else if (el.animated && !options.once) {
hide();
}
};
/**
* Scroll logic - add or remove 'aos-animate' class on scroll
*
* @param {array} $elements array of elements nodes
* @return {void}
*/
var handleScroll = function handleScroll($elements) {
return $elements.forEach(function (el, i) {
return applyClasses(el, window.pageYOffset);
});
};
/**
* Get offset of DOM element
* like there were no transforms applied on it
*
* @param {Node} el [DOM element]
* @return {Object} [top and left offset]
*/
var offset = function offset(el) {
var _x = 0;
var _y = 0;
while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
_x += el.offsetLeft - (el.tagName != 'BODY' ? el.scrollLeft : 0);
_y += el.offsetTop - (el.tagName != 'BODY' ? el.scrollTop : 0);
el = el.offsetParent;
}
return {
top: _y,
left: _x
};
};
/**
* Get inline option with a fallback.
*
* @param {Node} el [Dom element]
* @param {String} key [Option key]
* @param {String} fallback [Default (fallback) value]
* @return {Mixed} [Option set with inline attributes or fallback value if not set]
*/
var getInlineOption = (function (el, key, fallback) {
var attr = el.getAttribute('data-aos-' + key);
if (typeof attr !== 'undefined') {
if (attr === 'true') {
return true;
} else if (attr === 'false') {
return false;
}
}
return attr || fallback;
});
/**
* Calculate offset
* basing on element's settings like:
* - anchor
* - offset
*
* @param {Node} el [Dom element]
* @return {Integer} [Final offset that will be used to trigger animation in good position]
*/
var getPositionIn = function getPositionIn(el, defaultOffset, defaultAnchorPlacement) {
var windowHeight = window.innerHeight;
var anchor = getInlineOption(el, 'anchor');
var inlineAnchorPlacement = getInlineOption(el, 'anchor-placement');
var additionalOffset = Number(getInlineOption(el, 'offset', inlineAnchorPlacement ? 0 : defaultOffset));
var anchorPlacement = inlineAnchorPlacement || defaultAnchorPlacement;
var finalEl = el;
if (anchor && document.querySelectorAll(anchor)) {
finalEl = document.querySelectorAll(anchor)[0];
}
var triggerPoint = offset(finalEl).top - windowHeight;
switch (anchorPlacement) {
case 'top-bottom':
// Default offset
break;
case 'center-bottom':
triggerPoint += finalEl.offsetHeight / 2;
break;
case 'bottom-bottom':
triggerPoint += finalEl.offsetHeight;
break;
case 'top-center':
triggerPoint += windowHeight / 2;
break;
case 'center-center':
triggerPoint += windowHeight / 2 + finalEl.offsetHeight / 2;
break;
case 'bottom-center':
triggerPoint += windowHeight / 2 + finalEl.offsetHeight;
break;
case 'top-top':
triggerPoint += windowHeight;
break;
case 'bottom-top':
triggerPoint += windowHeight + finalEl.offsetHeight;
break;
case 'center-top':
triggerPoint += windowHeight + finalEl.offsetHeight / 2;
break;
}
return triggerPoint + additionalOffset;
};
var getPositionOut = function getPositionOut(el, defaultOffset) {
var windowHeight = window.innerHeight;
var anchor = getInlineOption(el, 'anchor');
var additionalOffset = getInlineOption(el, 'offset', defaultOffset);
var finalEl = el;
if (anchor && document.querySelectorAll(anchor)) {
finalEl = document.querySelectorAll(anchor)[0];
}
var elementOffsetTop = offset(finalEl).top;
return elementOffsetTop + finalEl.offsetHeight - additionalOffset;
};
/* Clearing variables */
var prepare = function prepare($elements, options) {
$elements.forEach(function (el, i) {
var mirror = getInlineOption(el.node, 'mirror', options.mirror);
var once = getInlineOption(el.node, 'once', options.once);
var id = getInlineOption(el.node, 'id');
var customClassNames = options.useClassNames && el.node.getAttribute('data-aos');
var animatedClassNames = [options.animatedClassName].concat(customClassNames ? customClassNames.split(' ') : []).filter(function (className) {
return typeof className === 'string';
});
if (options.initClassName) {
el.node.classList.add(options.initClassName);
}
el.position = {
in: getPositionIn(el.node, options.offset, options.anchorPlacement),
out: mirror && getPositionOut(el.node, options.offset)
};
el.options = {
once: once,
mirror: mirror,
animatedClassNames: animatedClassNames,
id: id
};
});
return $elements;
};
/**
* Generate initial array with elements as objects
* This array will be extended later with elements attributes values
* like 'position'
*/
var elements = (function () {
var elements = document.querySelectorAll('[data-aos]');
return Array.prototype.map.call(elements, function (node) {
return { node: node };
});
});
/**
* *******************************************************
* AOS (Animate on scroll) - wowjs alternative
* made to animate elements on scroll in both directions
* *******************************************************
*/
/**
* Private variables
*/
var $aosElements = [];
var initialized = false;
/**
* Default options
*/
var options = {
offset: 120,
delay: 0,
easing: 'ease',
duration: 400,
disable: false,
once: false,
mirror: false,
anchorPlacement: 'top-bottom',
startEvent: 'DOMContentLoaded',
animatedClassName: 'aos-animate',
initClassName: 'aos-init',
useClassNames: false,
disableMutationObserver: false,
throttleDelay: 99,
debounceDelay: 50
};
// Detect not supported browsers (<=IE9)
// http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805
var isBrowserNotSupported = function isBrowserNotSupported() {
return document.all && !window.atob;
};
var initializeScroll = function initializeScroll() {
// Extend elements objects in $aosElements with their positions
$aosElements = prepare($aosElements, options);
// Perform scroll event, to refresh view and show/hide elements
handleScroll($aosElements);
/**
* Handle scroll event to animate elements on scroll
*/
window.addEventListener('scroll', throttle(function () {
handleScroll($aosElements, options.once);
}, options.throttleDelay));
return $aosElements;
};
/**
* Refresh AOS
*/
var refresh = function refresh() {
var initialize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
// Allow refresh only when it was first initialized on startEvent
if (initialize) initialized = true;
if (initialized) initializeScroll();
};
/**
* Hard refresh
* create array with new elements and trigger refresh
*/
var refreshHard = function refreshHard() {
$aosElements = elements();
if (isDisabled(options.disable) || isBrowserNotSupported()) {
return disable();
}
refresh();
};
/**
* Disable AOS
* Remove all attributes to reset applied styles
*/
var disable = function disable() {
$aosElements.forEach(function (el, i) {
el.node.removeAttribute('data-aos');
el.node.removeAttribute('data-aos-easing');
el.node.removeAttribute('data-aos-duration');
el.node.removeAttribute('data-aos-delay');
if (options.initClassName) {
el.node.classList.remove(options.initClassName);
}
if (options.animatedClassName) {
el.node.classList.remove(options.animatedClassName);
}
});
};
/**
* Check if AOS should be disabled based on provided setting
*/
var isDisabled = function isDisabled(optionDisable) {
return optionDisable === true || optionDisable === 'mobile' && detect.mobile() || optionDisable === 'phone' && detect.phone() || optionDisable === 'tablet' && detect.tablet() || typeof optionDisable === 'function' && optionDisable() === true;
};
/**
* Initializing AOS
* - Create options merging defaults with user defined options
* - Set attributes on <body> as global setting - css relies on it
* - Attach preparing elements to options.startEvent,
* window resize and orientation change
* - Attach function that handle scroll and everything connected to it
* to window scroll event and fire once document is ready to set initial state
*/
var init = function init(settings) {
options = _extends(options, settings);
// Create initial array with elements -> to be fullfilled later with prepare()
$aosElements = elements();
/**
* Disable mutation observing if not supported
*/
if (!options.disableMutationObserver && !observer.isSupported()) {
console.info('\n aos: MutationObserver is not supported on this browser,\n code mutations observing has been disabled.\n You may have to call "refreshHard()" by yourself.\n ');
options.disableMutationObserver = true;
}
/**
* Observe [aos] elements
* If something is loaded by AJAX
* it'll refresh plugin automatically
*/
if (!options.disableMutationObserver) {
observer.ready('[data-aos]', refreshHard);
}
/**
* Don't init plugin if option `disable` is set
* or when browser is not supported
*/
if (isDisabled(options.disable) || isBrowserNotSupported()) {
return disable();
}
/**
* Set global settings on body, based on options
* so CSS can use it
*/
document.querySelector('body').setAttribute('data-aos-easing', options.easing);
document.querySelector('body').setAttribute('data-aos-duration', options.duration);
document.querySelector('body').setAttribute('data-aos-delay', options.delay);
/**
* Handle initializing
*/
if (['DOMContentLoaded', 'load'].indexOf(options.startEvent) === -1) {
// Listen to options.startEvent and initialize AOS
document.addEventListener(options.startEvent, function () {
refresh(true);
});
} else {
window.addEventListener('load', function () {
refresh(true);
});
}
if (options.startEvent === 'DOMContentLoaded' && ['complete', 'interactive'].indexOf(document.readyState) > -1) {
// Initialize AOS if default startEvent was already fired
refresh(true);
}
/**
* Refresh plugin on window resize or orientation change
*/
window.addEventListener('resize', debounce(refresh, options.debounceDelay, true));
window.addEventListener('orientationchange', debounce(refresh, options.debounceDelay, true));
return $aosElements;
};
/**
* Export Public API
*/
var aos = {
init: init,
refresh: refresh,
refreshHard: refreshHard
};
module.exports = aos;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,610 @@
import throttle from 'lodash.throttle';
import debounce from 'lodash.debounce';
var callback = function callback() {};
function containsAOSNode(nodes) {
var i = void 0,
currentNode = void 0,
result = void 0;
for (i = 0; i < nodes.length; i += 1) {
currentNode = nodes[i];
if (currentNode.dataset && currentNode.dataset.aos) {
return true;
}
result = currentNode.children && containsAOSNode(currentNode.children);
if (result) {
return true;
}
}
return false;
}
function check(mutations) {
if (!mutations) return;
mutations.forEach(function (mutation) {
var addedNodes = Array.prototype.slice.call(mutation.addedNodes);
var removedNodes = Array.prototype.slice.call(mutation.removedNodes);
var allNodes = addedNodes.concat(removedNodes);
if (containsAOSNode(allNodes)) {
return callback();
}
});
}
function getMutationObserver() {
return window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
}
function isSupported() {
return !!getMutationObserver();
}
function ready(selector, fn) {
var doc = window.document;
var MutationObserver = getMutationObserver();
var observer = new MutationObserver(check);
callback = fn;
observer.observe(doc.documentElement, {
childList: true,
subtree: true,
removedNodes: true
});
}
var observer = { isSupported: isSupported, ready: ready };
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
/**
* Device detector
*/
var fullNameRe = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i;
var prefixRe = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i;
var fullNameMobileRe = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i;
var prefixMobileRe = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i;
function ua() {
return navigator.userAgent || navigator.vendor || window.opera || '';
}
var Detector = function () {
function Detector() {
classCallCheck(this, Detector);
}
createClass(Detector, [{
key: 'phone',
value: function phone() {
var a = ua();
return !!(fullNameRe.test(a) || prefixRe.test(a.substr(0, 4)));
}
}, {
key: 'mobile',
value: function mobile() {
var a = ua();
return !!(fullNameMobileRe.test(a) || prefixMobileRe.test(a.substr(0, 4)));
}
}, {
key: 'tablet',
value: function tablet() {
return this.mobile() && !this.phone();
}
// http://browserhacks.com/#hack-acea075d0ac6954f275a70023906050c
}, {
key: 'ie11',
value: function ie11() {
return '-ms-scroll-limit' in document.documentElement.style && '-ms-ime-align' in document.documentElement.style;
}
}]);
return Detector;
}();
var detect = new Detector();
/**
* Adds multiple classes on node
* @param {DOMNode} node
* @param {array} classes
*/
var addClasses = function addClasses(node, classes) {
return classes && classes.forEach(function (className) {
return node.classList.add(className);
});
};
/**
* Removes multiple classes from node
* @param {DOMNode} node
* @param {array} classes
*/
var removeClasses = function removeClasses(node, classes) {
return classes && classes.forEach(function (className) {
return node.classList.remove(className);
});
};
var fireEvent = function fireEvent(eventName, data) {
var customEvent = void 0;
if (detect.ie11()) {
customEvent = document.createEvent('CustomEvent');
customEvent.initCustomEvent(eventName, true, true, { detail: data });
} else {
customEvent = new CustomEvent(eventName, {
detail: data
});
}
return document.dispatchEvent(customEvent);
};
/**
* Set or remove aos-animate class
* @param {node} el element
* @param {int} top scrolled distance
*/
var applyClasses = function applyClasses(el, top) {
var options = el.options,
position = el.position,
node = el.node,
data = el.data;
var hide = function hide() {
if (!el.animated) return;
removeClasses(node, options.animatedClassNames);
fireEvent('aos:out', node);
if (el.options.id) {
fireEvent('aos:in:' + el.options.id, node);
}
el.animated = false;
};
var show = function show() {
if (el.animated) return;
addClasses(node, options.animatedClassNames);
fireEvent('aos:in', node);
if (el.options.id) {
fireEvent('aos:in:' + el.options.id, node);
}
el.animated = true;
};
if (options.mirror && top >= position.out && !options.once) {
hide();
} else if (top >= position.in) {
show();
} else if (el.animated && !options.once) {
hide();
}
};
/**
* Scroll logic - add or remove 'aos-animate' class on scroll
*
* @param {array} $elements array of elements nodes
* @return {void}
*/
var handleScroll = function handleScroll($elements) {
return $elements.forEach(function (el, i) {
return applyClasses(el, window.pageYOffset);
});
};
/**
* Get offset of DOM element
* like there were no transforms applied on it
*
* @param {Node} el [DOM element]
* @return {Object} [top and left offset]
*/
var offset = function offset(el) {
var _x = 0;
var _y = 0;
while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
_x += el.offsetLeft - (el.tagName != 'BODY' ? el.scrollLeft : 0);
_y += el.offsetTop - (el.tagName != 'BODY' ? el.scrollTop : 0);
el = el.offsetParent;
}
return {
top: _y,
left: _x
};
};
/**
* Get inline option with a fallback.
*
* @param {Node} el [Dom element]
* @param {String} key [Option key]
* @param {String} fallback [Default (fallback) value]
* @return {Mixed} [Option set with inline attributes or fallback value if not set]
*/
var getInlineOption = (function (el, key, fallback) {
var attr = el.getAttribute('data-aos-' + key);
if (typeof attr !== 'undefined') {
if (attr === 'true') {
return true;
} else if (attr === 'false') {
return false;
}
}
return attr || fallback;
});
/**
* Calculate offset
* basing on element's settings like:
* - anchor
* - offset
*
* @param {Node} el [Dom element]
* @return {Integer} [Final offset that will be used to trigger animation in good position]
*/
var getPositionIn = function getPositionIn(el, defaultOffset, defaultAnchorPlacement) {
var windowHeight = window.innerHeight;
var anchor = getInlineOption(el, 'anchor');
var inlineAnchorPlacement = getInlineOption(el, 'anchor-placement');
var additionalOffset = Number(getInlineOption(el, 'offset', inlineAnchorPlacement ? 0 : defaultOffset));
var anchorPlacement = inlineAnchorPlacement || defaultAnchorPlacement;
var finalEl = el;
if (anchor && document.querySelectorAll(anchor)) {
finalEl = document.querySelectorAll(anchor)[0];
}
var triggerPoint = offset(finalEl).top - windowHeight;
switch (anchorPlacement) {
case 'top-bottom':
// Default offset
break;
case 'center-bottom':
triggerPoint += finalEl.offsetHeight / 2;
break;
case 'bottom-bottom':
triggerPoint += finalEl.offsetHeight;
break;
case 'top-center':
triggerPoint += windowHeight / 2;
break;
case 'center-center':
triggerPoint += windowHeight / 2 + finalEl.offsetHeight / 2;
break;
case 'bottom-center':
triggerPoint += windowHeight / 2 + finalEl.offsetHeight;
break;
case 'top-top':
triggerPoint += windowHeight;
break;
case 'bottom-top':
triggerPoint += windowHeight + finalEl.offsetHeight;
break;
case 'center-top':
triggerPoint += windowHeight + finalEl.offsetHeight / 2;
break;
}
return triggerPoint + additionalOffset;
};
var getPositionOut = function getPositionOut(el, defaultOffset) {
var windowHeight = window.innerHeight;
var anchor = getInlineOption(el, 'anchor');
var additionalOffset = getInlineOption(el, 'offset', defaultOffset);
var finalEl = el;
if (anchor && document.querySelectorAll(anchor)) {
finalEl = document.querySelectorAll(anchor)[0];
}
var elementOffsetTop = offset(finalEl).top;
return elementOffsetTop + finalEl.offsetHeight - additionalOffset;
};
/* Clearing variables */
var prepare = function prepare($elements, options) {
$elements.forEach(function (el, i) {
var mirror = getInlineOption(el.node, 'mirror', options.mirror);
var once = getInlineOption(el.node, 'once', options.once);
var id = getInlineOption(el.node, 'id');
var customClassNames = options.useClassNames && el.node.getAttribute('data-aos');
var animatedClassNames = [options.animatedClassName].concat(customClassNames ? customClassNames.split(' ') : []).filter(function (className) {
return typeof className === 'string';
});
if (options.initClassName) {
el.node.classList.add(options.initClassName);
}
el.position = {
in: getPositionIn(el.node, options.offset, options.anchorPlacement),
out: mirror && getPositionOut(el.node, options.offset)
};
el.options = {
once: once,
mirror: mirror,
animatedClassNames: animatedClassNames,
id: id
};
});
return $elements;
};
/**
* Generate initial array with elements as objects
* This array will be extended later with elements attributes values
* like 'position'
*/
var elements = (function () {
var elements = document.querySelectorAll('[data-aos]');
return Array.prototype.map.call(elements, function (node) {
return { node: node };
});
});
/**
* *******************************************************
* AOS (Animate on scroll) - wowjs alternative
* made to animate elements on scroll in both directions
* *******************************************************
*/
/**
* Private variables
*/
var $aosElements = [];
var initialized = false;
/**
* Default options
*/
var options = {
offset: 120,
delay: 0,
easing: 'ease',
duration: 400,
disable: false,
once: false,
mirror: false,
anchorPlacement: 'top-bottom',
startEvent: 'DOMContentLoaded',
animatedClassName: 'aos-animate',
initClassName: 'aos-init',
useClassNames: false,
disableMutationObserver: false,
throttleDelay: 99,
debounceDelay: 50
};
// Detect not supported browsers (<=IE9)
// http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805
var isBrowserNotSupported = function isBrowserNotSupported() {
return document.all && !window.atob;
};
var initializeScroll = function initializeScroll() {
// Extend elements objects in $aosElements with their positions
$aosElements = prepare($aosElements, options);
// Perform scroll event, to refresh view and show/hide elements
handleScroll($aosElements);
/**
* Handle scroll event to animate elements on scroll
*/
window.addEventListener('scroll', throttle(function () {
handleScroll($aosElements, options.once);
}, options.throttleDelay));
return $aosElements;
};
/**
* Refresh AOS
*/
var refresh = function refresh() {
var initialize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
// Allow refresh only when it was first initialized on startEvent
if (initialize) initialized = true;
if (initialized) initializeScroll();
};
/**
* Hard refresh
* create array with new elements and trigger refresh
*/
var refreshHard = function refreshHard() {
$aosElements = elements();
if (isDisabled(options.disable) || isBrowserNotSupported()) {
return disable();
}
refresh();
};
/**
* Disable AOS
* Remove all attributes to reset applied styles
*/
var disable = function disable() {
$aosElements.forEach(function (el, i) {
el.node.removeAttribute('data-aos');
el.node.removeAttribute('data-aos-easing');
el.node.removeAttribute('data-aos-duration');
el.node.removeAttribute('data-aos-delay');
if (options.initClassName) {
el.node.classList.remove(options.initClassName);
}
if (options.animatedClassName) {
el.node.classList.remove(options.animatedClassName);
}
});
};
/**
* Check if AOS should be disabled based on provided setting
*/
var isDisabled = function isDisabled(optionDisable) {
return optionDisable === true || optionDisable === 'mobile' && detect.mobile() || optionDisable === 'phone' && detect.phone() || optionDisable === 'tablet' && detect.tablet() || typeof optionDisable === 'function' && optionDisable() === true;
};
/**
* Initializing AOS
* - Create options merging defaults with user defined options
* - Set attributes on <body> as global setting - css relies on it
* - Attach preparing elements to options.startEvent,
* window resize and orientation change
* - Attach function that handle scroll and everything connected to it
* to window scroll event and fire once document is ready to set initial state
*/
var init = function init(settings) {
options = _extends(options, settings);
// Create initial array with elements -> to be fullfilled later with prepare()
$aosElements = elements();
/**
* Disable mutation observing if not supported
*/
if (!options.disableMutationObserver && !observer.isSupported()) {
console.info('\n aos: MutationObserver is not supported on this browser,\n code mutations observing has been disabled.\n You may have to call "refreshHard()" by yourself.\n ');
options.disableMutationObserver = true;
}
/**
* Observe [aos] elements
* If something is loaded by AJAX
* it'll refresh plugin automatically
*/
if (!options.disableMutationObserver) {
observer.ready('[data-aos]', refreshHard);
}
/**
* Don't init plugin if option `disable` is set
* or when browser is not supported
*/
if (isDisabled(options.disable) || isBrowserNotSupported()) {
return disable();
}
/**
* Set global settings on body, based on options
* so CSS can use it
*/
document.querySelector('body').setAttribute('data-aos-easing', options.easing);
document.querySelector('body').setAttribute('data-aos-duration', options.duration);
document.querySelector('body').setAttribute('data-aos-delay', options.delay);
/**
* Handle initializing
*/
if (['DOMContentLoaded', 'load'].indexOf(options.startEvent) === -1) {
// Listen to options.startEvent and initialize AOS
document.addEventListener(options.startEvent, function () {
refresh(true);
});
} else {
window.addEventListener('load', function () {
refresh(true);
});
}
if (options.startEvent === 'DOMContentLoaded' && ['complete', 'interactive'].indexOf(document.readyState) > -1) {
// Initialize AOS if default startEvent was already fired
refresh(true);
}
/**
* Refresh plugin on window resize or orientation change
*/
window.addEventListener('resize', debounce(refresh, options.debounceDelay, true));
window.addEventListener('orientationchange', debounce(refresh, options.debounceDelay, true));
return $aosElements;
};
/**
* Export Public API
*/
var aos = {
init: init,
refresh: refresh,
refreshHard: refreshHard
};
export default aos;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,601 @@
/*!
* Bootstrap Reboot v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
line-height: inherit;
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type=search]::-webkit-search-cancel-button {
cursor: pointer;
filter: grayscale(1);
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,598 @@
/*!
* Bootstrap Reboot v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
line-height: inherit;
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type=search]::-webkit-search-cancel-button {
cursor: pointer;
filter: grayscale(1);
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More