reforma da interface da página de projetos
Deploy / Deploy (push) Successful in 1m33s Details

This commit is contained in:
JotaChina 2025-12-15 19:22:02 -03:00
parent 9d2aa44851
commit 8ef56ce9f7
1 changed files with 60 additions and 67 deletions

View File

@ -342,10 +342,8 @@ document.addEventListener('DOMContentLoaded', () => {
const filterContent = document.getElementById('filter-content');
const filterChevron = document.getElementById('filter-chevron');
// Define estado inicial baseado na largura da tela
let isFilterOpen = window.innerWidth > 1024;
// Função para aplicar o estado visual
function updateFilterState() {
if(isFilterOpen) {
filterContent.style.display = 'block';
@ -356,15 +354,12 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
// Aplica estado inicial ao carregar
if (filterContent && filterChevron) {
updateFilterState();
}
if (filterContent && filterChevron) updateFilterState();
if(filterBtn && filterContent) {
filterBtn.addEventListener('click', () => {
isFilterOpen = !isFilterOpen; // Inverte o estado
updateFilterState(); // Aplica a mudança
isFilterOpen = !isFilterOpen;
updateFilterState();
});
}
@ -418,6 +413,7 @@ document.addEventListener('DOMContentLoaded', () => {
const genreContainer = document.getElementById('genre-filters');
const sortSelect = document.getElementById('sort-select');
const starFilterSelect = document.getElementById('star-filter');
const keyFilterSelect = document.getElementById('key-filter'); // CORREÇÃO 1: Pegar referência
const projectsContainer = document.getElementById('projects-container');
let currentGenre = 'all';
@ -431,39 +427,22 @@ document.addEventListener('DOMContentLoaded', () => {
})
.catch(console.warn);
// Função atualizada para Badge de Dificuldade
function renderizarDificuldade(num) {
const n = Math.round(num) || 1;
let label = "Básico";
let icon = "fa-leaf"; // Ícone padrão
let colorClass = "is-success"; // Verde
let icon = "fa-leaf";
let colorClass = "is-success";
if (n >= 4.5) {
label = "Expert";
icon = "fa-fire";
colorClass = "is-danger"; // Vermelho
} else if (n >= 3.5) {
label = "Avançado";
icon = "fa-bolt";
colorClass = "is-warning"; // Amarelo/Laranja
} else if (n >= 2.5) {
label = "Intermediário";
icon = "fa-layer-group";
colorClass = "is-info"; // Azul
}
if (n >= 4.5) { label = "Expert"; icon = "fa-fire"; colorClass = "is-danger"; }
else if (n >= 3.5) { label = "Avançado"; icon = "fa-bolt"; colorClass = "is-warning"; }
else if (n >= 2.5) { label = "Intermediário"; icon = "fa-layer-group"; colorClass = "is-info"; }
// Retorna uma tag HTML bonita e visível
return `
<span class="tag ${colorClass} is-light is-rounded" title="Complexidade: ${n}/5" style="border: 1px solid rgba(0,0,0,0.1);">
<i class="fa-solid ${icon} mr-1"></i> ${label}
</span>
`;
return `<span class="tag ${colorClass} is-light is-rounded" title="Complexidade: ${n}/5" style="border: 1px solid rgba(0,0,0,0.1);"><i class="fa-solid ${icon} mr-1"></i> ${label}</span>`;
}
function enrichCards(data) {
const mapDados = {};
// Mapeamento dos dados
data.forEach(item => {
const k1 = normalizarChaveJS(item.arquivo.replace('.wav','').replace('.mp3',''));
mapDados[k1] = item;
@ -473,66 +452,57 @@ document.addEventListener('DOMContentLoaded', () => {
cards.forEach(card => {
const info = mapDados[normalizarChaveJS(card.dataset.title)];
// --- 1. Definição de Valores Padrão (Safety First) ---
let genero = "Unknown";
let subgenerosLista = []; // CORREÇÃO 2: Array para guardar subgêneros
let estrelas = 0;
let intensidade = -100;
let bpm = 0;
let tomHtml = "";
let tomRaw = "all"; // Valor padrão para o filtro
let subTagsHTML = ""; // Para os subgêneros
let tomRaw = "all";
let subTagsHTML = "";
// --- 2. Só tenta extrair dados SE 'info' existir ---
if (info) {
// Dados IA
if (info.analise_ia) {
genero = info.analise_ia.genero_macro || "Unknown";
// Lógica de Subgêneros (Top 2)
// Captura subgêneros para o Filtro e Visual
if (Array.isArray(info.analise_ia.nuvem_tags)) {
// Filtro: Pega todas as tags relevantes
info.analise_ia.nuvem_tags.forEach(t => {
if(t.tag) subgenerosLista.push(t.tag);
});
// Visual: Pega top 2 para mostrar
const topTags = info.analise_ia.nuvem_tags.slice(0, 2);
topTags.forEach(t => {
// Multiplica por 100 para porcentagem legível
const scorePercent = Math.round((t.score || 0) * 100);
subTagsHTML += `
<span class="tag is-white is-rounded border-tag"
title="Confiança IA: ${scorePercent}%"
style="font-size: 0.65rem; color: #666; margin-right: 2px;">
#${t.tag}
</span>`;
subTagsHTML += `<span class="tag is-white is-rounded border-tag" title="Confiança IA: ${scorePercent}%" style="font-size: 0.65rem; color: #666; margin-right: 2px;">#${t.tag}</span>`;
});
}
}
// Dados Técnicos
if (info.analise_tecnica) {
intensidade = parseFloat(info.analise_tecnica.intensidade_db || -100);
bpm = parseFloat(info.analise_tecnica.bpm || 0);
// Correção: Usando Math.round para bater com o visual
estrelas = Math.round(info.analise_tecnica.complexidade?.estrelas || 0);
// Lógica do Tom (Visual + Filtro)
if (info.analise_tecnica.tom) {
const t = info.analise_tecnica.tom;
const e = info.analise_tecnica.escala === 'minor' ? 'm' : '';
// HTML visual (Badge)
tomHtml = `<span class="tag is-white is-rounded border-tag" title="Tom detectado">🎹 ${t}${e}</span>`;
// Valor cru para o filtro (Ex: "C", "Cm", "Ab")
tomRaw = t + e;
}
}
}
// --- 3. Salva no Dataset para os Filtros ---
card.dataset.genre = genero;
// CORREÇÃO 3: Salva subgêneros no dataset como string separada por vírgula
card.dataset.subgenres = subgenerosLista.join(',').toLowerCase();
card.dataset.stars = estrelas;
card.dataset.intensity = intensidade;
card.dataset.bpm_real = bpm;
card.dataset.key = tomRaw; // Novo dataset para o filtro de Tom
card.dataset.key = tomRaw;
// --- 4. Renderização no DOM ---
const bpmContainer = card.querySelector('.bpm-container');
if (bpmContainer) {
const iaDiv = document.createElement('div');
@ -542,14 +512,12 @@ document.addEventListener('DOMContentLoaded', () => {
iaDiv.style.gap = "6px";
iaDiv.className = "mt-3";
// Dificuldade
const diffDiv = document.createElement('div');
diffDiv.innerHTML = renderizarDificuldade(estrelas);
iaDiv.appendChild(diffDiv);
// Tags (Gênero Macro + Tom)
const tagsRow = document.createElement('div');
tagsRow.className = "tags is-centered mb-1"; // mb-1 para dar espaço pros subgeneros
tagsRow.className = "tags is-centered mb-1";
tagsRow.style.gap = "4px";
if(genero !== "Unknown") {
@ -557,14 +525,12 @@ document.addEventListener('DOMContentLoaded', () => {
if(genero === 'Electronic') color = 'is-info';
if(genero === 'Hip Hop') color = 'is-warning';
if(genero === 'Rock') color = 'is-danger';
tagsRow.innerHTML += `<span class="tag ${color} is-light is-rounded" style="font-size: 0.7rem;">🤖 ${genero}</span>`;
}
tagsRow.innerHTML += tomHtml;
iaDiv.appendChild(tagsRow);
// Nova Linha: Subgêneros
if (subTagsHTML) {
const subTagsRow = document.createElement('div');
subTagsRow.className = "tags is-centered mb-0";
@ -573,7 +539,6 @@ document.addEventListener('DOMContentLoaded', () => {
iaDiv.appendChild(subTagsRow);
}
// Limpa e reinsere
bpmContainer.innerHTML = '';
if(card.dataset.bpm_real > 0) {
bpmContainer.innerHTML = `<span class="tag is-dark is-rounded is-light mb-1" style="font-size: 0.7rem; font-weight: bold; border: 1px solid #ccc;">🎵 ${Math.round(card.dataset.bpm_real)} BPM</span>`;
@ -586,14 +551,23 @@ document.addEventListener('DOMContentLoaded', () => {
function createGenreButtons(data) {
const genres = new Set();
data.forEach(i => {
// Macro Gênero
if(i.analise_ia?.genero_macro && i.analise_ia.genero_macro !== "Unknown")
genres.add(i.analise_ia.genero_macro);
// CORREÇÃO 4: Adicionar Subgêneros aos filtros
if(Array.isArray(i.analise_ia?.nuvem_tags)) {
i.analise_ia.nuvem_tags.forEach(t => {
// Adiciona se tiver tag e um score razoável (opcional, aqui pegamos tudo)
if(t.tag) genres.add(t.tag);
});
}
});
Array.from(genres).sort().forEach(g => {
const btn = document.createElement('button');
btn.className = 'button is-small is-rounded is-white';
btn.textContent = g;
btn.textContent = g; // Exibe o nome do gênero
btn.onclick = () => {
document.querySelectorAll('#genre-filters .button').forEach(b => {
b.classList.remove('is-active', 'is-info');
@ -607,7 +581,6 @@ document.addEventListener('DOMContentLoaded', () => {
genreContainer.appendChild(btn);
});
// Reset All Button
const allBtn = genreContainer.querySelector('[data-genre="all"]');
allBtn.onclick = () => {
document.querySelectorAll('#genre-filters .button').forEach(b => {
@ -623,13 +596,26 @@ document.addEventListener('DOMContentLoaded', () => {
function applyFilters() {
cards.forEach(c => {
const col = c.closest('.project-column');
const g = c.dataset.genre;
const s = parseInt(c.dataset.stars || 0);
const k = c.dataset.key || 'all'; // Pega o tom salvo no card
const currentKey = document.getElementById('key-filter')?.value || 'all';
const col = c.closest('.project-column');
const matchG = (currentGenre === 'all' || g === currentGenre);
const g = c.dataset.genre; // Gênero Macro
const sub = c.dataset.subgenres || ""; // Lista de subgêneros (string)
const s = parseInt(c.dataset.stars || 0);
const k = c.dataset.key || 'all';
// CORREÇÃO 5: Lógica de Filtro Aprimorada
// Verifica se o gênero selecionado bate com o Macro OU se está contido nos subgêneros
let matchG = false;
if (currentGenre === 'all') {
matchG = true;
} else {
// Verifica match exato com Macro OU include na lista de subs
if (g === currentGenre) matchG = true;
if (sub.includes(currentGenre.toLowerCase())) matchG = true;
}
const matchS = (currentMinStars === 'all' || s === parseInt(currentMinStars));
const matchK = (currentKey === 'all' || k === currentKey);
@ -644,6 +630,13 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
// CORREÇÃO 6: Adicionando o Event Listener para o Tom
if(keyFilterSelect) {
keyFilterSelect.addEventListener('change', () => {
applyFilters();
});
}
sortSelect.addEventListener('change', (e) => {
const crit = e.target.value;
const cols = Array.from(projectsContainer.children);