/* workshop-fingerprint.js — 8x8 спектральный фингерпринт живой земли. * * 64 ячейки = 64 лог-полосы частот окна сейсмограммы, row-major: сверху-слева * низшая частота, снизу-справа высшая. Яркость — энергия полосы по лог-шкале. * Верхние строки светятся (микросейсмы, низкий гул Земли), нижние тёмные * (тишина на высоких частотах): это красный шум планеты, не настройка экрана. * * Темы читаются из CSS-переменных сайта (наследуются в shadow DOM), с фолбэком * на токены workshop. */ class WorkshopFingerprint extends HTMLElement { // Частотный охват строки — из band_edges окна 1024 сэмпла при 40 Гц. static ROWS = [ "0.04–0.35 Гц", "0.35–0.66 Гц", "0.66–0.98 Гц", "0.98–1.29 Гц", "1.29–1.91 Гц", "1.91–4.22 Гц", "4.22–9.18 Гц", "9.18–20 Гц", ]; static get observedAttributes() { return ["cell-size", "caption", "labels"]; } constructor() { super(); this.attachShadow({ mode: "open" }); this._cells = []; this._unsub = null; } connectedCallback() { this._render(); this._subscribe(); } disconnectedCallback() { if (this._unsub) this._unsub(); this._unsub = null; } attributeChangedCallback() { if (this.isConnected) this._render(); } get cellSize() { const v = parseInt(this.getAttribute("cell-size") || "26", 10); return Number.isFinite(v) && v > 0 ? v : 26; } _subscribe() { if (!this.isConnected || this._unsub) return; this._unsub = window.WorkshopStream.on("noise", (ev) => this._update(ev)); } _render() { const px = this.cellSize; const caption = this.getAttribute("caption") || ""; const labels = this.hasAttribute("labels"); const body = WorkshopFingerprint.ROWS.map((label, r) => `
${labels ? `
${label}
` : ""}
`).join(""); this.shadowRoot.innerHTML = `
${body}
${caption ? `
${caption}
` : ""}
`; this._cells = []; for (let r = 0; r < 8; r++) { const grid = this.shadowRoot.querySelector(`.grid[data-row="${r}"]`); for (let i = 0; i < 8; i++) { const c = document.createElement("div"); c.className = "cell"; grid.appendChild(c); this._cells.push(c); } } } _update(ev) { if (!ev.bands || ev.bands.length < 64) return; const LO = Math.log10(0.001), HI = Math.log10(0.3); for (let i = 0; i < 64; i++) { const x = ev.bands[i]; let v = x > 0 ? (Math.log10(x) - LO) / (HI - LO) : 0; v = Math.max(0, Math.min(1, v)); const r = Math.round(18 + 235 * v * v); const g = Math.round(30 + 170 * v); const b = Math.round(45 + 90 * (1 - v)); this._cells[i].style.background = `rgb(${r},${g},${b})`; } } } customElements.define("workshop-fingerprint", WorkshopFingerprint);