feat: initial commit - Carinha Control Panel v1.0 com UI glassmorphism, watch-bar, emojis, braco robotico e conversa IA
This commit is contained in:
+459
@@ -0,0 +1,459 @@
|
||||
/**
|
||||
* dashboard.js — Shell global da Central de Controle do Carinha
|
||||
* Injeta barra lateral unificada, telemetria em tempo real no topo,
|
||||
* controle de áudio robótico e dock de comandos rápidos.
|
||||
*/
|
||||
(() => {
|
||||
if (window.location.search.includes('clean=1')) {
|
||||
document.documentElement.classList.add('is-clean-embed');
|
||||
return;
|
||||
}
|
||||
|
||||
// Carrega dinamicamente o sintetizador de áudio caso não esteja presente
|
||||
if (!window.RobotAudio) {
|
||||
const audioScript = document.createElement('script');
|
||||
audioScript.src = '/audio-robot.js';
|
||||
audioScript.defer = true;
|
||||
document.head.appendChild(audioScript);
|
||||
}
|
||||
|
||||
const sections = [
|
||||
{
|
||||
title: 'Principal',
|
||||
items: [
|
||||
['/dashboard', 'Visão geral', 'M3 3h7v7H3z M14 3h7v7h-7z M3 14h7v7H3z M14 14h7v7h-7z'],
|
||||
['/painel', 'Câmera e rosto', 'M3 6h12v12H3z M15 10l6-4v12l-6-4']
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Inteligência & IA',
|
||||
items: [
|
||||
['/conversa', 'Conversar com IA', 'M3 4h18v13H9l-6 4z'],
|
||||
['/emojis', 'Emojis & Reações', 'M12 3a9 9 0 1 0 0 18a9 9 0 1 0 0-18 M8 9h1 M15 9h1 M7 14q5 5 10 0'],
|
||||
['/treinamento', 'Treinamento', 'M3 8l9-5 9 5-9 5z M6 11v6q6 5 12 0v-6']
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Robótica & Hardware',
|
||||
items: [
|
||||
['/braco', 'Braço robótico', 'M3 21h18 M8 21v-5l7-5-3-6 M9 4h6v4H9z M14 10h4v4h-4z'],
|
||||
['/servos', 'Servos PCA9685', 'M5 3v18 M12 3v18 M19 3v18 M2 8h6 M9 16h6 M16 7h6']
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Configurações',
|
||||
items: [
|
||||
['/api', 'API Gemini & Modelo', 'M8 5L2 12l6 7 M16 5l6 7-6 7 M14 3l-4 18']
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const pathname = location.pathname === '/' ? '/dashboard' : location.pathname;
|
||||
document.body.classList.add('dashboard');
|
||||
|
||||
const frame = document.createElement('div');
|
||||
frame.className = 'app-frame';
|
||||
|
||||
const main = document.createElement('main');
|
||||
main.className = 'app-content';
|
||||
main.id = 'appContent';
|
||||
|
||||
// Move elementos existentes do body para o container principal
|
||||
for (const child of [...document.body.childNodes]) {
|
||||
if (!(child.nodeType === 1 && ['SCRIPT', 'STYLE', 'LINK'].includes(child.tagName))) {
|
||||
main.append(child);
|
||||
}
|
||||
}
|
||||
|
||||
// Sidebar moderna e estilizada
|
||||
const sidebar = document.createElement('aside');
|
||||
sidebar.className = 'app-sidebar';
|
||||
sidebar.innerHTML = `
|
||||
<a href="/dashboard" class="brand" title="Central Carinha">
|
||||
<span class="brand-face" aria-hidden="true"><i></i><i></i></span>
|
||||
<span>Carinha <span style="font-size:12px;color:var(--ciano);font-weight:600;letter-spacing:0.05em;vertical-align:middle;background:rgba(92,214,255,0.12);padding:2px 6px;border-radius:6px;border:1px solid rgba(92,214,255,0.25)">IA</span></span>
|
||||
</a>
|
||||
<nav aria-label="Navegação principal" id="sidebarNav"></nav>
|
||||
<div class="sidebar-footer">
|
||||
<div><strong>Orange Pi Zero 3</strong></div>
|
||||
<div style="font-family:'JetBrains Mono',monospace;font-size:11.5px;color:var(--muito-fraco)" id="shellServer">${location.host}</div>
|
||||
<div style="margin-top:6px;display:flex;align-items:center;gap:6px">
|
||||
<span class="badge-dot" style="background:#10b981;color:#10b981"></span>
|
||||
<span style="font-size:11px;color:var(--fraco)">Serviço operacional</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const nav = sidebar.querySelector('#sidebarNav');
|
||||
for (const sec of sections) {
|
||||
const title = document.createElement('div');
|
||||
title.className = 'nav-section-title';
|
||||
title.textContent = sec.title;
|
||||
nav.append(title);
|
||||
|
||||
for (const [url, label, path] of sec.items) {
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="${path}"/></svg><span>${label}</span>`;
|
||||
if (url === pathname) {
|
||||
a.setAttribute('aria-current', 'page');
|
||||
}
|
||||
a.addEventListener('click', () => {
|
||||
if (window.RobotAudio) window.RobotAudio.click();
|
||||
});
|
||||
nav.append(a);
|
||||
}
|
||||
}
|
||||
|
||||
// Barra de ferramentas superior (Topbar)
|
||||
const top = document.createElement('header');
|
||||
top.className = 'app-topbar';
|
||||
top.innerHTML = `
|
||||
<div class="topbar-left">
|
||||
<button class="menu-button" type="button" aria-label="Abrir menu" aria-expanded="false">☰</button>
|
||||
<span class="server-label" style="font-size:14px;font-weight:600;color:var(--texto);display:flex;align-items:center;gap:8px">
|
||||
<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--ciano);box-shadow:0 0 8px var(--ciano)"></span>
|
||||
Central de Controle
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="topbar-telemetry">
|
||||
<button id="pipToggleBtn" type="button" title="Mini-monitor flutuante (Picture-in-Picture) que fica fixo em qualquer página" style="padding:6px 12px;font-size:12.5px;border-radius:20px;border-color:var(--borda)">
|
||||
📺 Monitor PiP
|
||||
</button>
|
||||
<button id="quickRemoteBtn" type="button" title="Atalhos rápidos de expressão" style="padding:6px 12px;font-size:12.5px;border-radius:20px;border-color:var(--borda)">
|
||||
⚡ Gestos
|
||||
</button>
|
||||
<button id="sfxToggleBtn" type="button" title="Ativar/Desativar efeitos sonoros robóticos" style="padding:6px 12px;font-size:12.5px;border-radius:20px">
|
||||
🔊 Som: Ativo
|
||||
</button>
|
||||
<span class="badge" id="shellConnection">
|
||||
<span class="badge-dot"></span>
|
||||
<span id="shellConnectionText">Verificando celular…</span>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Mini-Monitor Flutuante (Picture-in-Picture)
|
||||
const pipWidget = document.createElement('div');
|
||||
pipWidget.id = 'floatingPipWidget';
|
||||
pipWidget.className = 'floating-pip-widget';
|
||||
pipWidget.style.display = 'none';
|
||||
pipWidget.innerHTML = `
|
||||
<div class="floating-pip-header">
|
||||
<div class="pip-title">
|
||||
<span class="badge-dot" style="background:var(--ciano)"></span>
|
||||
<span>Carinha Monitor</span>
|
||||
</div>
|
||||
<div class="floating-pip-actions">
|
||||
<button id="pipMinimizeBtn" type="button" title="Minimizar ou Expandir">_</button>
|
||||
<button id="pipCloseBtn" type="button" title="Fechar">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="floating-pip-body" id="pipBody">
|
||||
<iframe src="/carinha.html?espelho=1&audio=0" title="Monitor Flutuante do Carinha"></iframe>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(pipWidget);
|
||||
|
||||
const pipToggleBtn = top.querySelector('#pipToggleBtn');
|
||||
const pipMinBtn = pipWidget.querySelector('#pipMinimizeBtn');
|
||||
const pipCloseBtn = pipWidget.querySelector('#pipCloseBtn');
|
||||
|
||||
function togglePip(force) {
|
||||
const isVisible = pipWidget.style.display === 'flex';
|
||||
const next = force !== undefined ? force : !isVisible;
|
||||
pipWidget.style.display = next ? 'flex' : 'none';
|
||||
pipToggleBtn.style.color = next ? 'var(--ciano)' : 'var(--texto)';
|
||||
pipToggleBtn.style.borderColor = next ? 'var(--ciano)' : 'var(--borda)';
|
||||
localStorage.setItem('carinha_pip', next ? 'true' : 'false');
|
||||
if (next && window.RobotAudio) window.RobotAudio.click();
|
||||
}
|
||||
|
||||
pipToggleBtn.onclick = () => togglePip();
|
||||
pipCloseBtn.onclick = () => togglePip(false);
|
||||
pipMinBtn.onclick = () => {
|
||||
const isMin = pipWidget.classList.toggle('minimized');
|
||||
pipMinBtn.textContent = isMin ? '□' : '_';
|
||||
};
|
||||
|
||||
// Se o usuário já deixou o PiP ativo anteriormente
|
||||
if (localStorage.getItem('carinha_pip') === 'true') {
|
||||
pipWidget.style.display = 'flex';
|
||||
pipToggleBtn.style.color = 'var(--ciano)';
|
||||
pipToggleBtn.style.borderColor = 'var(--ciano)';
|
||||
}
|
||||
|
||||
// Menu móvel
|
||||
const menuBtn = top.querySelector('.menu-button');
|
||||
menuBtn.onclick = () => {
|
||||
const open = document.body.classList.toggle('menu-open');
|
||||
menuBtn.setAttribute('aria-expanded', String(open));
|
||||
};
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape') {
|
||||
document.body.classList.remove('menu-open');
|
||||
menuBtn.setAttribute('aria-expanded', 'false');
|
||||
fecharQuickRemote();
|
||||
}
|
||||
});
|
||||
main.addEventListener('click', () => {
|
||||
document.body.classList.remove('menu-open');
|
||||
menuBtn.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
|
||||
// Modal / Dropdown de Gestos Rápidos
|
||||
const quickRemote = document.createElement('div');
|
||||
quickRemote.id = 'quickRemoteBar';
|
||||
quickRemote.style.cssText = `
|
||||
position: fixed; top: 72px; right: 28px; z-index: 90;
|
||||
background: rgba(14, 24, 38, 0.95); backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--borda-brilho); border-radius: 14px;
|
||||
padding: 16px; box-shadow: var(--sombra); display: none;
|
||||
flex-direction: column; gap: 10px; width: 290px;
|
||||
`;
|
||||
quickRemote.innerHTML = `
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--borda);padding-bottom:8px">
|
||||
<strong style="font-size:14px;color:var(--texto)">⚡ Disparo Rápido</strong>
|
||||
<button id="closeQuickRemote" type="button" style="padding:3px 8px;font-size:12px;border:none;background:transparent;color:var(--fraco)">✕</button>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-top:4px">
|
||||
<button data-gesto="feliz" style="padding:8px;font-size:12px">😊 Feliz</button>
|
||||
<button data-gesto="surpreso" style="padding:8px;font-size:12px">😲 Surpreso</button>
|
||||
<button data-gesto="coracao" style="padding:8px;font-size:12px">❤️ Coração</button>
|
||||
<button data-gesto="bravo" style="padding:8px;font-size:12px">😠 Bravo</button>
|
||||
<button data-gesto="piscar" style="padding:8px;font-size:12px">😉 Piscar</button>
|
||||
<button data-gesto="piscadinha" style="padding:8px;font-size:12px">✨ Piscar 2</button>
|
||||
<button data-gesto="acordar" style="padding:8px;font-size:12px" class="primary">☀️ Acordar</button>
|
||||
<button data-gesto="dormir" style="padding:8px;font-size:12px">🌙 Dormir</button>
|
||||
</div>
|
||||
<button data-gesto="parar" class="danger" style="width:100%;font-size:12px;padding:8px;margin-top:4px">🛑 Parar Tudo (E-Stop)</button>
|
||||
`;
|
||||
document.body.appendChild(quickRemote);
|
||||
|
||||
function toggleQuickRemote() {
|
||||
const isVisible = quickRemote.style.display === 'flex';
|
||||
quickRemote.style.display = isVisible ? 'none' : 'flex';
|
||||
if (!isVisible && window.RobotAudio) window.RobotAudio.click();
|
||||
}
|
||||
function fecharQuickRemote() {
|
||||
quickRemote.style.display = 'none';
|
||||
}
|
||||
|
||||
top.querySelector('#quickRemoteBtn').onclick = toggleQuickRemote;
|
||||
quickRemote.querySelector('#closeQuickRemote').onclick = fecharQuickRemote;
|
||||
|
||||
quickRemote.addEventListener('click', async (e) => {
|
||||
const btn = e.target.closest('button[data-gesto]');
|
||||
if (!btn) return;
|
||||
const gesto = btn.getAttribute('data-gesto');
|
||||
try {
|
||||
if (window.RobotAudio) window.RobotAudio.tocarGesto(gesto);
|
||||
await fetch('/gesto/' + encodeURIComponent(gesto));
|
||||
btn.style.transform = 'scale(0.95)';
|
||||
setTimeout(() => btn.style.transform = '', 150);
|
||||
} catch (err) {
|
||||
console.error('Falha ao enviar gesto:', err);
|
||||
}
|
||||
});
|
||||
|
||||
// Botão de Áudio
|
||||
const sfxBtn = top.querySelector('#sfxToggleBtn');
|
||||
function atualizarBotaoSfx() {
|
||||
if (!window.RobotAudio) return;
|
||||
sfxBtn.textContent = window.RobotAudio.enabled ? '🔊 Som: Ativo' : '🔇 Som: Mudo';
|
||||
sfxBtn.style.color = window.RobotAudio.enabled ? 'var(--ciano)' : 'var(--fraco)';
|
||||
}
|
||||
sfxBtn.onclick = () => {
|
||||
if (window.RobotAudio) {
|
||||
window.RobotAudio.toggle();
|
||||
atualizarBotaoSfx();
|
||||
}
|
||||
};
|
||||
setTimeout(atualizarBotaoSfx, 400);
|
||||
|
||||
frame.append(top, main);
|
||||
document.body.prepend(sidebar, frame);
|
||||
|
||||
// Monitoramento periódico de status do celular e servidor
|
||||
async function checkStatus() {
|
||||
try {
|
||||
const r = await fetch('/app/status', { cache: 'no-store', signal: AbortSignal.timeout(3000) });
|
||||
if (!r.ok) throw Error();
|
||||
const s = await r.json();
|
||||
const badge = document.getElementById('shellConnection');
|
||||
const text = document.getElementById('shellConnectionText') || badge;
|
||||
|
||||
const bat = s.estado?.bateria?.nivel;
|
||||
const carregando = s.estado?.bateria?.carregando;
|
||||
let batInfo = '';
|
||||
if (bat != null) {
|
||||
batInfo = ` · ${bat}%${carregando ? ' ⚡' : ''}`;
|
||||
}
|
||||
|
||||
if (s.online) {
|
||||
text.textContent = `S10 Online${batInfo}`;
|
||||
badge.className = 'badge ativo';
|
||||
} else {
|
||||
text.textContent = 'S10 Desconectado';
|
||||
badge.className = 'badge offline';
|
||||
}
|
||||
} catch (e) {
|
||||
const badge = document.getElementById('shellConnection');
|
||||
if (badge) {
|
||||
badge.className = 'badge danger';
|
||||
const text = document.getElementById('shellConnectionText') || badge;
|
||||
text.textContent = 'Servidor Offline';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkStatus();
|
||||
setInterval(checkStatus, 4000);
|
||||
|
||||
// =========================================================================
|
||||
// BARRA DE MONITORAMENTO NO TOPO — câmera e rosto sempre visíveis
|
||||
// Com botão de expandir/recolher cada painel individualmente
|
||||
// =========================================================================
|
||||
const watchBar = document.createElement('div');
|
||||
watchBar.id = 'watchBar';
|
||||
watchBar.className = 'watch-bar';
|
||||
|
||||
// Lê estado salvo (expandido ou não) e tamanho
|
||||
const watchState = (() => {
|
||||
try { return JSON.parse(localStorage.getItem('carinha_watch') || '{}'); }
|
||||
catch { return {}; }
|
||||
})();
|
||||
|
||||
function saveWatchState() {
|
||||
localStorage.setItem('carinha_watch', JSON.stringify(watchState));
|
||||
}
|
||||
|
||||
// Altura dos painéis: compacto ou expandido
|
||||
const HEIGHTS = { compact: 90, medium: 200, full: 340 };
|
||||
|
||||
function makePanelHTML(id, icon, label, src) {
|
||||
const h = watchState[id + '_size'] || 'compact';
|
||||
const hidden = watchState[id + '_hidden'] === true;
|
||||
return `
|
||||
<div class="watch-panel ${hidden ? 'watch-panel-hidden' : ''}" id="wp_${id}" data-id="${id}" data-size="${h}">
|
||||
<div class="watch-panel-header">
|
||||
<span class="watch-panel-label">${icon} ${label}</span>
|
||||
<div class="watch-panel-actions">
|
||||
<button class="wp-btn wp-shrink" title="Recolher" data-id="${id}">−</button>
|
||||
<button class="wp-btn wp-expand" title="Expandir" data-id="${id}">+</button>
|
||||
<button class="wp-btn wp-hide" title="Ocultar painel" data-id="${id}">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="watch-panel-body" style="height:${HEIGHTS[h]}px">
|
||||
<iframe src="${src}" title="${label}" loading="lazy" sandbox="allow-scripts allow-same-origin"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
watchBar.innerHTML = `
|
||||
<div class="watch-bar-inner">
|
||||
<div class="watch-bar-grip" title="Arrastar">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="12" r="1"/><circle cx="9" cy="5" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="15" cy="19" r="1"/></svg>
|
||||
<span style="font-size:11px;font-weight:700;letter-spacing:0.06em;color:var(--ciano)">MONITOR</span>
|
||||
</div>
|
||||
<div class="watch-panels" id="watchPanels">
|
||||
${makePanelHTML('camera', '📷', 'Câmera ao Vivo', '/painel?clean=1')}
|
||||
${makePanelHTML('rosto', '🤖', 'Rosto do Carinha', '/carinha.html?espelho=1&audio=0&clean=1')}
|
||||
</div>
|
||||
<div class="watch-bar-tools">
|
||||
<button id="watchAddCamera" class="wp-tool-btn" title="Mostrar/ocultar Câmera">📷</button>
|
||||
<button id="watchAddRosto" class="wp-tool-btn" title="Mostrar/ocultar Rosto">🤖</button>
|
||||
<button id="watchBarToggle" class="wp-tool-btn" title="Ocultar barra inteira" style="margin-top:auto">☰</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(watchBar);
|
||||
|
||||
// Aplica estado inicial de visibilidade dos painéis
|
||||
['camera', 'rosto'].forEach(id => {
|
||||
const panel = watchBar.querySelector('#wp_' + id);
|
||||
if (!panel) return;
|
||||
if (watchState[id + '_hidden'] === true) panel.classList.add('watch-panel-hidden');
|
||||
});
|
||||
|
||||
// Oculta a barra inteira se o usuário fechou antes
|
||||
if (watchState['bar_hidden'] === true) {
|
||||
watchBar.classList.add('watch-bar-collapsed');
|
||||
}
|
||||
|
||||
// Toggle da barra inteira
|
||||
watchBar.querySelector('#watchBarToggle').addEventListener('click', () => {
|
||||
const collapsed = watchBar.classList.toggle('watch-bar-collapsed');
|
||||
watchState['bar_hidden'] = collapsed;
|
||||
saveWatchState();
|
||||
if (window.RobotAudio) window.RobotAudio.click();
|
||||
});
|
||||
|
||||
// Toggle de visibilidade dos painéis individuais
|
||||
watchBar.querySelector('#watchAddCamera').addEventListener('click', () => {
|
||||
const panel = watchBar.querySelector('#wp_camera');
|
||||
const hidden = panel.classList.toggle('watch-panel-hidden');
|
||||
watchState['camera_hidden'] = hidden;
|
||||
saveWatchState();
|
||||
});
|
||||
watchBar.querySelector('#watchAddRosto').addEventListener('click', () => {
|
||||
const panel = watchBar.querySelector('#wp_rosto');
|
||||
const hidden = panel.classList.toggle('watch-panel-hidden');
|
||||
watchState['rosto_hidden'] = hidden;
|
||||
saveWatchState();
|
||||
});
|
||||
|
||||
// Expandir / Recolher tamanho dos painéis
|
||||
watchBar.addEventListener('click', e => {
|
||||
const btn = e.target.closest('.wp-expand, .wp-shrink, .wp-hide');
|
||||
if (!btn) return;
|
||||
const id = btn.dataset.id;
|
||||
const panel = watchBar.querySelector('#wp_' + id);
|
||||
const body = panel.querySelector('.watch-panel-body');
|
||||
const sizes = Object.keys(HEIGHTS);
|
||||
|
||||
if (btn.classList.contains('wp-expand')) {
|
||||
const cur = sizes.indexOf(panel.dataset.size);
|
||||
const next = sizes[Math.min(cur + 1, sizes.length - 1)];
|
||||
panel.dataset.size = next;
|
||||
body.style.height = HEIGHTS[next] + 'px';
|
||||
watchState[id + '_size'] = next;
|
||||
} else if (btn.classList.contains('wp-shrink')) {
|
||||
const cur = sizes.indexOf(panel.dataset.size);
|
||||
const next = sizes[Math.max(cur - 1, 0)];
|
||||
panel.dataset.size = next;
|
||||
body.style.height = HEIGHTS[next] + 'px';
|
||||
watchState[id + '_size'] = next;
|
||||
} else if (btn.classList.contains('wp-hide')) {
|
||||
const hidden = panel.classList.toggle('watch-panel-hidden');
|
||||
watchState[id + '_hidden'] = hidden;
|
||||
}
|
||||
saveWatchState();
|
||||
if (window.RobotAudio) window.RobotAudio.click();
|
||||
});
|
||||
|
||||
// Ajusta padding do conteúdo principal para não sobrepor a watch-bar
|
||||
const appContent = document.getElementById('appContent');
|
||||
function syncWatchBarOffset() {
|
||||
const collapsed = watchBar.classList.contains('watch-bar-collapsed');
|
||||
const h = collapsed ? 0 : watchBar.offsetHeight;
|
||||
if (appContent) appContent.style.paddingTop = h > 0 ? (h + 12) + 'px' : '';
|
||||
document.body.classList.toggle('watch-bar-active', !collapsed);
|
||||
}
|
||||
const ro = new ResizeObserver(syncWatchBarOffset);
|
||||
ro.observe(watchBar);
|
||||
syncWatchBarOffset();
|
||||
|
||||
// Também atualiza o offset ao colapsar/expandir a barra
|
||||
const origToggle = watchBar.querySelector('#watchBarToggle');
|
||||
if (origToggle) {
|
||||
const prevHandler = origToggle.onclick;
|
||||
origToggle.addEventListener('click', () => {
|
||||
setTimeout(syncWatchBarOffset, 320); // após animação
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user