workshop: страница в общей оболочке сайта + компоненты
Мастерская переехала на eureka2: страница /workshop — часть сайта (lib/workshop.js, общая шапка/навигация/подвал), оформление в tokens сайта (assets/workshop/ workshop.css), компоненты читают данные пода по SSE /workshop/sse. Компоненты перенесены на сторону сайта, геометрия мозга — assets/workshop/. build.js собирает страницу (active: workshop).
This commit is contained in:
parent
e2fe11f723
commit
cc3d6c5ca0
15 changed files with 1627 additions and 2 deletions
107
assets/workshop/workshop-fingerprint.js
Normal file
107
assets/workshop/workshop-fingerprint.js
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/* 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);
|
||||
Loading…
Add table
Add a link
Reference in a new issue