108 lines
4.1 KiB
JavaScript
108 lines
4.1 KiB
JavaScript
|
|
/* 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) => `
|
|||
|
|
<div class="row">
|
|||
|
|
<div class="grid" data-row="${r}"></div>
|
|||
|
|
${labels ? `<div class="rowlabel">${label}</div>` : ""}
|
|||
|
|
</div>`).join("");
|
|||
|
|
this.shadowRoot.innerHTML = `
|
|||
|
|
<style>
|
|||
|
|
:host { display: inline-block; font: inherit; }
|
|||
|
|
.wrap { display: inline-flex; flex-direction: column; gap: 8px; }
|
|||
|
|
.rows { display: flex; flex-direction: column; gap: 3px; }
|
|||
|
|
.row { display: flex; align-items: center; gap: 8px; }
|
|||
|
|
.grid { display: grid; grid-template-columns: repeat(8, ${px}px); gap: 3px; }
|
|||
|
|
.cell {
|
|||
|
|
width: ${px}px; height: ${px}px; border-radius: 3px;
|
|||
|
|
background: var(--workshop-high, #10151a);
|
|||
|
|
transition: background .12s linear;
|
|||
|
|
}
|
|||
|
|
.rowlabel {
|
|||
|
|
color: var(--workshop-dim, #9aa3ad); font-size: 11px;
|
|||
|
|
font-variant-numeric: tabular-nums; white-space: nowrap;
|
|||
|
|
}
|
|||
|
|
.caption {
|
|||
|
|
color: var(--workshop-dim, #9aa3ad); font-size: 12px;
|
|||
|
|
letter-spacing: .04em;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
<div class="wrap">
|
|||
|
|
<div class="rows">${body}</div>
|
|||
|
|
${caption ? `<div class="caption">${caption}</div>` : ""}
|
|||
|
|
</div>`;
|
|||
|
|
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);
|