mmpSearch/pattern.md

575 lines
21 KiB
Markdown

---
layout: default
title: Projetos por Pattern Rítmico
permalink: /pattern/
---
<meta charset="utf-8" />
<main class="main-content">
<div class="publication">
<div class="container">
<br />
<div class="tabs is-centered is-boxed is-medium mb-6">
{% include sidebar.html %}
</div>
<div class="columns is-centered">
<div class="column is-8">
<div
class="box has-background-white-ter has-text-centered shadow-sm"
style="border: 1px solid #cfe8fc"
>
<h2 class="title is-5 has-text-grey-dark mb-3">
<span class="icon has-text-info"
><i class="fa-solid fa-drum"></i
></span>
Busca por Pattern Rítmico
</h2>
<p class="subtitle is-7 has-text-grey mb-4">
Desenhe um ritmo (16 steps) abaixo ou clique nos patterns dos
instrumentos:
</p>
<div
id="pattern-search-box"
style="
display: flex;
gap: 6px;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 1rem;
"
>
{% for i in (0..15) %} {% assign mod = i | modulo: 4 %} {% if i >
0 and mod == 0 %}
<div style="width: 8px"></div>
{% endif %}
<div
class="search-step"
data-index="{{i}}"
title="Step {{ i | plus: 1 }}"
style="
width: 25px;
height: 45px;
background: #e8e8e8;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
transition: all 0.1s;
"
></div>
{% endfor %}
</div>
<button
id="clearFilterButton"
class="button is-small is-danger is-light is-rounded"
>
<span class="icon"><i class="fa-solid fa-trash"></i></span>
<span>Limpar Desenho</span>
</button>
</div>
</div>
</div>
<div id="project-list" class="columns is-multiline">
{% for projeto in site.data.all %} {% assign project_patterns_flat = ""
| split: "," %} {% for track in projeto.tracks %} {% if
track.instruments %} {% for instrument in track.instruments %} {% if
instrument.patterns %} {% for pattern in instrument.patterns %} {%
assign pattern_steps = pattern.steps %} {% if pattern_steps and
pattern_steps.size > 0 %} {% assign total_steps = pattern_steps.size %}
{% assign chunk_size = 4 %} {% assign num_chunks = total_steps |
divided_by: chunk_size %} {% assign remainder = total_steps | modulo:
chunk_size %} {% if remainder > 0 %}{% assign num_chunks = num_chunks |
plus: 1 %}{% endif %} {% for i in (0..num_chunks) %} {% assign
start_index = i | times: chunk_size %} {% assign current_chunk_array =
pattern_steps | slice: start_index, chunk_size %} {% if
current_chunk_array.size > 0 %} {% assign chunk_string = "" %} {% for
step in current_chunk_array %} {% if step == true or step == 'true' or
step == 1 %}{% assign chunk_string = chunk_string | append: '1' %}{%
else %}{% assign chunk_string = chunk_string | append: '0' %}{% endif %}
{% endfor %} {% unless project_patterns_flat contains chunk_string %} {%
assign project_patterns_flat = project_patterns_flat | push:
chunk_string %} {% endunless %} {% endif %} {% endfor %} {% endif %} {%
endfor %} {% endif %} {% endfor %} {% endif %} {% endfor %} {% if
project_patterns_flat.size > 0 %} {% assign project_patterns_string =
project_patterns_flat | join: ',' %}
<div
class="column is-12-mobile is-6-tablet is-4-desktop is-3-widescreen project-item"
data-patterns="{{ project_patterns_string }}"
>
<div
class="card project-card"
style="
height: 100%;
background-color: #f0f8ff;
border: 1px solid #cfe8fc;
border-radius: 12px;
display: flex;
flex-direction: column;
"
>
{% assign file_url = projeto.file | downcase | replace: ' ', '-' |
replace: 'ç', 'c' | replace: 'ã', 'a' | replace: 'á', 'a' | replace:
'â', 'a' | replace: 'é', 'e' | replace: 'ê', 'e' | replace: 'í', 'i'
| replace: 'ó', 'o' | replace: 'ô', 'o' | replace: 'õ', 'o' |
replace: 'ú', 'u' %} {% assign page_url = '../mmp_pages/' | append:
file_url | append: '.html' %}
<a
href="{{ page_url }}"
style="text-decoration: none; display: block"
>
<div class="card-content has-text-centered p-4 pb-0">
<div
style="
width: 40px;
height: 40px;
background-color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 0.5rem auto;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
"
>
<span class="icon" style="color: #3273dc"
><i class="fa-solid fa-music"></i
></span>
</div>
<p
class="title is-6 mb-3"
style="color: #205081; word-break: break-word"
>
{{ projeto.file }}
</p>
</div>
</a>
<div class="card-content px-4 pb-4 pt-2" style="flex: 1">
<div
class="patterns-visualizer p-3"
style="
background: #fff;
border-radius: 8px;
border: 1px dashed #cfe8fc;
text-align: left;
"
>
<p
class="is-size-7 has-text-grey mb-2 filter-status-label"
style="font-weight: 600; text-align: center"
>
Patterns por Instrumento:
</p>
<div style="display: flex; flex-direction: column; gap: 8px">
{% for track in projeto.tracks %} {% if track.instruments %}
{% for instrument in track.instruments %} {% if
instrument.patterns %} {% assign inst_patterns = "" | split:
"," %} {% for pattern in instrument.patterns %} {% assign
pattern_steps = pattern.steps %} {% if pattern_steps and
pattern_steps.size > 0 %} {% assign total_steps =
pattern_steps.size %} {% assign chunk_size = 4 %} {% assign
num_chunks = total_steps | divided_by: chunk_size %} {% assign
remainder = total_steps | modulo: chunk_size %} {% if
remainder > 0 %}{% assign num_chunks = num_chunks | plus: 1
%}{% endif %} {% for i in (0..num_chunks) %} {% assign
start_index = i | times: chunk_size %} {% assign
current_chunk_array = pattern_steps | slice: start_index,
chunk_size %} {% if current_chunk_array.size > 0 %} {% assign
chunk_string = "" %} {% for step in current_chunk_array %} {%
if step == true or step == 'true' or step == 1 %}{% assign
chunk_string = chunk_string | append: '1' %}{% else %}{%
assign chunk_string = chunk_string | append: '0' %}{% endif %}
{% endfor %} {% if chunk_string != "0000" %} {% unless
inst_patterns contains chunk_string %} {% assign inst_patterns
= inst_patterns | push: chunk_string %} {% endunless %} {%
endif %} {% endif %} {% endfor %} {% endif %} {% endfor %} {%
if inst_patterns.size > 0 %}
<div
class="instrument-row"
style="
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #f5f5f5;
padding-bottom: 4px;
"
>
{% assign instrument_slug = instrument.instrument_name |
replace: ' ', '+' %}
<a
href="{{ '/instruments/?instrument=' | append: instrument_slug | relative_url }}"
class="tag is-white is-light instrument-link"
style="
font-size: 0.6rem;
color: #555;
max-width: 85px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-decoration: none;
border: 1px solid transparent;
"
title="Ver projetos com {{ instrument.instrument_name }}"
>
{{ instrument.instrument_name }}
</a>
<div style="display: flex; gap: 3px">
{% for p_str in inst_patterns %}
<div
class="pattern-mini-grid js-pattern-trigger"
data-pattern-val="{{ p_str }}"
title="Filtrar por: {{ p_str }}"
>
{% assign bits = p_str | split: '' %} {% for bit in bits
%}
<div
style="width: 4px; height: 8px; background-color: {% if bit == '1' %}#4caf50{% else %}#eee{% endif %};"
></div>
{% endfor %}
</div>
{% endfor %}
</div>
</div>
{% endif %} {% endif %} {% endfor %} {% endif %} {% endfor %}
</div>
</div>
</div>
<footer
class="card-footer"
style="
border-top: 1px solid #cfe8fc;
background-color: #fff;
border-radius: 0 0 12px 12px;
overflow: hidden;
"
>
<a
href="#"
class="card-footer-item js-open-modal"
data-target-url="{{ page_url }}"
data-modal-title="Detalhes: {{ projeto.file }}"
data-full-btn-text="Ir para Página"
data-full-btn-link="{{ page_url }}"
style="
color: #5b7da3;
font-size: 0.8rem;
font-weight: 600;
border-right: 1px solid #eee;
"
>Ver</a
>
{% assign creation_url = '/mmpSearch/creation.html?project=' |
append: projeto.file %} {% assign embed_url = creation_url |
append: '&embed=true' %}
<a
href="#"
class="card-footer-item js-open-modal"
data-target-url="{{ embed_url }}"
data-modal-title="Editor: {{ projeto.file }}"
data-full-btn-text="Abrir Editor"
data-full-btn-link="{{ creation_url }}"
style="color: #3273dc; font-size: 0.8rem; font-weight: 600"
>Editar</a
>
</footer>
</div>
</div>
{% endif %} {% endfor %}
</div>
</div>
</div>
</main>
<style>
.search-step.is-active {
background-color: #4caf50 !important;
border-color: #388e3c !important;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
}
.pattern-mini-grid {
display: flex;
gap: 1px;
padding: 2px;
border: 1px solid #ddd;
border-radius: 2px;
cursor: pointer;
transition: all 0.2s ease;
background-color: #fafafa;
}
.pattern-mini-grid:hover {
transform: scale(1.2);
border-color: #3273dc;
z-index: 10;
}
.pattern-mini-grid.match-highlight {
border-color: #4caf50 !important;
background-color: #e8f5e9;
box-shadow: 0 0 0 1px #4caf50;
}
.instrument-link:hover {
background-color: #eef6fc !important;
color: #3273dc !important;
border-color: #3273dc !important;
}
</style>
<div id="preview-modal" class="modal">
<div class="modal-background"></div>
<div class="modal-card" style="width: 90%; max-width: 800px">
<header
class="modal-card-head"
style="background-color: #f0f8ff; border-bottom: 1px solid #cfe8fc"
>
<p
class="modal-card-title"
id="modal-title"
style="color: #205081; font-weight: bold"
>
Preview
</p>
<button class="delete" aria-label="close"></button>
</header>
<section
class="modal-card-body p-0"
style="height: 500px; background-color: #fff"
>
<iframe
id="preview-iframe"
src=""
style="width: 100%; height: 100%; border: none"
></iframe>
</section>
<footer
class="modal-card-foot"
style="
justify-content: flex-end;
background-color: #fff;
border-top: 1px solid #cfe8fc;
"
>
<button class="button" id="close-modal-btn">Fechar</button>
<a href="#" id="full-edit-btn" target="_blank" class="button is-info"
>Abrir</a
>
</footer>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
const projects = document.querySelectorAll(".project-item");
const clearFilterButton = document.querySelector("#clearFilterButton");
const searchSteps = document.querySelectorAll(".search-step");
let activeChunks = [];
function getChunksFromSearchBox() {
let fullPattern = "";
searchSteps.forEach(
(step) =>
(fullPattern += step.classList.contains("is-active") ? "1" : "0")
);
return fullPattern.match(/.{1,4}/g) || [];
}
function setSearchBoxFromChunks(chunks) {
let fullPattern = (chunks || []).join("").padEnd(16, "0");
const bits = fullPattern.split("");
searchSteps.forEach((step, index) => {
if (bits[index] === "1") step.classList.add("is-active");
else step.classList.remove("is-active");
});
}
// --- FUNÇÃO CRUCIAL: FILTRA PROJETOS E RESUME O CONTEÚDO INTERNO ---
function filterByPattern(chunks) {
const effectiveChunks = chunks.filter((c) => c !== "0000");
// RESET GLOBAL (Se não há filtro)
if (effectiveChunks.length === 0) {
projects.forEach((project) => {
project.style.display = "block";
// Mostra tudo dentro do card
project
.querySelectorAll(".instrument-row")
.forEach((row) => (row.style.display = "flex"));
project.querySelectorAll(".pattern-mini-grid").forEach((grid) => {
grid.style.display = "flex";
grid.classList.remove("match-highlight");
});
// Reseta label
const label = project.querySelector(".filter-status-label");
if (label) label.textContent = "Patterns por Instrumento:";
});
return;
}
// COM FILTRO ATIVO
projects.forEach((project) => {
const projectPatternsStr = project.getAttribute("data-patterns");
const projectPatterns = projectPatternsStr.split(",");
// 1. O projeto tem TODOS os patterns buscados?
const isMatch = effectiveChunks.every((chunk) =>
projectPatterns.includes(chunk)
);
if (!isMatch) {
project.style.display = "none";
} else {
project.style.display = "block";
// 2. Filtragem Interna: Mostrar apenas o que bateu
const rows = project.querySelectorAll(".instrument-row");
let matchCount = 0;
rows.forEach((row) => {
const grids = row.querySelectorAll(".pattern-mini-grid");
let rowHasVisible = false;
grids.forEach((grid) => {
const val = grid.dataset.patternVal;
// Se o pattern faz parte da busca, mostra. Se não, esconde.
if (effectiveChunks.includes(val)) {
grid.style.display = "flex";
grid.classList.add("match-highlight");
rowHasVisible = true;
} else {
grid.style.display = "none";
grid.classList.remove("match-highlight");
}
});
// Se a linha do instrumento não tem nenhum pattern visível, esconde a linha toda
row.style.display = rowHasVisible ? "flex" : "none";
if (rowHasVisible) matchCount++;
});
// Atualiza label para dar feedback
const label = project.querySelector(".filter-status-label");
if (label) label.textContent = "Patterns encontrados:";
}
});
}
function updateUrl(chunks) {
const newUrl = new URL(window.location.href);
const effectiveChunks = chunks.filter((c) => c !== "0000");
if (effectiveChunks.length > 0)
newUrl.searchParams.set("p", effectiveChunks.join(","));
else newUrl.searchParams.delete("p");
window.history.replaceState({}, "", newUrl);
}
function runFilter() {
const chunksFromBox = getChunksFromSearchBox();
activeChunks = chunksFromBox.filter((c) => c !== "0000");
filterByPattern(activeChunks);
updateUrl(activeChunks);
}
// --- TRIGGERS ---
document.querySelectorAll(".js-pattern-trigger").forEach((trigger) => {
trigger.addEventListener("click", function (e) {
e.preventDefault();
e.stopPropagation();
const pattern = this.dataset.patternVal;
let currentChunks = getChunksFromSearchBox().filter(
(c) => c !== "0000"
);
if (currentChunks.includes(pattern))
currentChunks = currentChunks.filter((c) => c !== pattern);
else {
if (currentChunks.length < 4) currentChunks.push(pattern);
}
activeChunks = currentChunks;
setSearchBoxFromChunks(activeChunks);
filterByPattern(activeChunks);
updateUrl(activeChunks);
});
});
searchSteps.forEach((step) => {
step.addEventListener("click", function () {
step.classList.toggle("is-active");
runFilter();
});
});
clearFilterButton.addEventListener("click", function () {
activeChunks = [];
setSearchBoxFromChunks([]);
filterByPattern([]);
updateUrl([]);
});
const urlParams = new URLSearchParams(window.location.search);
let patternFromUrl = urlParams.get("p");
if (patternFromUrl) {
activeChunks = patternFromUrl.split(",");
setSearchBoxFromChunks(activeChunks);
filterByPattern(activeChunks);
}
// Modal logic (igual)
const modal = document.getElementById("preview-modal");
const iframe = document.getElementById("preview-iframe");
const modalTitle = document.getElementById("modal-title");
const fullEditBtn = document.getElementById("full-edit-btn");
const closeButtons = document.querySelectorAll(
".modal-background, .modal-card-head .delete, #close-modal-btn"
);
function openModal(url, title, btnText, btnLink) {
if (!modal) return;
modalTitle.textContent = title;
iframe.src = url;
fullEditBtn.textContent = btnText;
fullEditBtn.href = btnLink;
modal.classList.add("is-active");
document.documentElement.classList.add("is-clipped");
}
function closeModal() {
modal.classList.remove("is-active");
document.documentElement.classList.remove("is-clipped");
iframe.src = "";
}
document.querySelectorAll(".js-open-modal").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.preventDefault();
openModal(
btn.dataset.targetUrl,
btn.dataset.modalTitle,
btn.dataset.fullBtnText,
btn.dataset.fullBtnLink
);
});
});
iframe.addEventListener("load", () => {
try {
const iframeDoc =
iframe.contentDocument || iframe.contentWindow.document;
const style = iframeDoc.createElement("style");
style.textContent = `.tabs, .navbar, .sidebar-wrapper, .main-header { display: none !important; } .publication { padding-top: 0 !important; } body { background-color: #fff !important; }`;
iframeDoc.head.appendChild(style);
} catch (e) {}
});
closeButtons.forEach((el) => el.addEventListener("click", closeModal));
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") closeModal();
});
});
</script>