/* 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) => `