eureka-web/assets/workshop/workshop-fingerprint.js
eureka cc3d6c5ca0 workshop: страница в общей оболочке сайта + компоненты
Мастерская переехала на eureka2: страница /workshop — часть сайта (lib/workshop.js,
общая шапка/навигация/подвал), оформление в tokens сайта (assets/workshop/
workshop.css), компоненты читают данные пода по SSE /workshop/sse.
Компоненты перенесены на сторону сайта, геометрия мозга — assets/workshop/.
build.js собирает страницу (active: workshop).
2026-10-04 01:08:06 +03:00

107 lines
4.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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);