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
86
assets/workshop/workshop-pass.js
Normal file
86
assets/workshop/workshop-pass.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/* <workshop-pass> — the words the fly flashes past, in flight order.
|
||||
*
|
||||
* Task 014 gave the fly inertia: it steers with a limited turn rate, so it
|
||||
* overshoots and *passes* many words instead of parking on them. The server
|
||||
* emits one `pass` event per fly-by (entering the pass radius, not one per
|
||||
* step inside it), at ~3–10 per second. This element turns that stream into
|
||||
* the visible flight: each passed word blinks in bright and settles dim, so
|
||||
* the eye reads a fast body moving through meaning — not a table of meals.
|
||||
*
|
||||
* Every passed word is shown, not only the unique ones (the author's
|
||||
* decision): the point is the motion, and the same word may be passed again
|
||||
* when the fly loops back. The stream is batched per animation frame and the
|
||||
* node count is capped, so 10 events/s costs one cheap append per frame, not
|
||||
* a DOM rebuild on every event.
|
||||
*
|
||||
* Honesty: the fly does not "aim" or "want". `pass` is our layer over the
|
||||
* brain's state; the field geometry decides what is near. Which word is
|
||||
* passed is measured (`sim`/`dist`); the motion is the author's frame.
|
||||
*/
|
||||
class WorkshopPass extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.count = parseInt(this.getAttribute("count") || "28", 10);
|
||||
this.nodes = [];
|
||||
this.queue = [];
|
||||
this._raf = null;
|
||||
this.renderEmpty();
|
||||
this._off = WorkshopStream.on("pass", (ev) => this.push(ev));
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this._off) this._off();
|
||||
if (this._raf != null) cancelAnimationFrame(this._raf);
|
||||
}
|
||||
|
||||
push(ev) {
|
||||
this.queue.push(ev);
|
||||
if (this._raf == null) {
|
||||
this._raf = requestAnimationFrame(() => this.flush());
|
||||
}
|
||||
}
|
||||
|
||||
flush() {
|
||||
this._raf = null;
|
||||
const batch = this.queue;
|
||||
this.queue = [];
|
||||
if (!batch.length) return;
|
||||
const placeholder = this.querySelector(".wp-empty");
|
||||
if (placeholder) placeholder.remove();
|
||||
for (const ev of batch) {
|
||||
const el = this._make(ev);
|
||||
this.appendChild(el);
|
||||
this.nodes.push(el);
|
||||
}
|
||||
// Cap the visible trail; the oldest words are already dim by now, so
|
||||
// dropping them is invisible.
|
||||
while (this.nodes.length > this.count) {
|
||||
this.nodes.shift().remove();
|
||||
}
|
||||
}
|
||||
|
||||
_make(ev) {
|
||||
const el = document.createElement("span");
|
||||
el.className = "wp-word";
|
||||
// Closeness at the fly-by brightens the resting glow a little: a word
|
||||
// passed very near lingers more than one clipped at the radius edge.
|
||||
if (typeof ev.sim === "number") {
|
||||
const closeness = Math.max(0, Math.min(1, (ev.sim + 1) / 2));
|
||||
el.style.setProperty("--wp-dim", (0.16 + 0.5 * closeness).toFixed(3));
|
||||
}
|
||||
el.textContent = ev.term;
|
||||
const sim = typeof ev.sim === "number" ? ev.sim.toFixed(3) : "—";
|
||||
const dist = typeof ev.dist === "number" ? ev.dist.toFixed(3) : "—";
|
||||
const hunger = typeof ev.hunger === "number" ? ev.hunger.toFixed(2) : "—";
|
||||
const entropy = typeof ev.entropy === "number" ? ev.entropy.toFixed(2) : "—";
|
||||
el.title = `lang=${ev.lang} sim=${sim} dist=${dist} ` +
|
||||
`hunger=${hunger} entropy=${entropy}`;
|
||||
return el;
|
||||
}
|
||||
|
||||
renderEmpty() {
|
||||
this.innerHTML =
|
||||
'<span class="wp-empty">полёт ещё не пошёл — слова мелькнут, когда муха полетит</span>';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("workshop-pass", WorkshopPass);
|
||||
Loading…
Add table
Add a link
Reference in a new issue