Add portal web simulator, SPI bridges, and Pico firmware updates.

Bring the five-panel hex portal online with a browser 3D/schematic preview, Pi SPI backends, and renamed multi-panel Pico UDP firmware.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-30 14:54:51 +12:00
parent d5cab2efdf
commit 5094c7bcee
78 changed files with 62251 additions and 832 deletions

View File

@@ -0,0 +1,90 @@
import { paintIdentify, paintRgb } from "../panel-paint.js";
const PANEL_STYLES = `
:host {
display: block;
background: #000;
border: 1px solid var(--border, #2a3044);
border-radius: 4px;
image-rendering: pixelated;
}
canvas {
display: block;
width: 100%;
height: auto;
}
.label {
text-align: center;
font-size: 0.75rem;
color: var(--muted, #8b93a8);
margin: 0.35rem 0;
font-family: system-ui, sans-serif;
}
`;
export class PortalPanel extends HTMLElement {
static observedAttributes = ["index", "position", "width", "height"];
constructor() {
super();
this._index = 0;
this._pixelWidth = 45;
this._pixelHeight = 9;
}
connectedCallback() {
if (this.shadowRoot) return;
const root = this.attachShadow({ mode: "open" });
root.innerHTML = `
<style>${PANEL_STYLES}</style>
<canvas part="canvas"></canvas>
<div class="label" part="label"></div>
`;
this._canvas = /** @type {HTMLCanvasElement} */ (root.querySelector("canvas"));
this._ctx = /** @type {CanvasRenderingContext2D} */ (this._canvas.getContext("2d"));
this._label = root.querySelector(".label");
this._syncFromAttributes();
}
attributeChangedCallback(name) {
if (!this.shadowRoot) return;
this._syncFromAttributes();
}
_syncFromAttributes() {
this._index = Number(this.getAttribute("index") ?? 0);
this._pixelWidth = Number(this.getAttribute("width") ?? 45);
this._pixelHeight = Number(this.getAttribute("height") ?? 9);
const position = this.getAttribute("position") ?? "";
if (this._label) {
this._label.textContent = `${this._index} · ${position}`;
}
if (this._canvas) {
this._canvas.width = this._pixelWidth;
this._canvas.height = this._pixelHeight;
}
}
/** @returns {HTMLCanvasElement} */
get canvas() {
if (!this._canvas) this.connectedCallback();
return this._canvas;
}
/**
* @param {number[] | Uint8Array} rgb
* @param {number} width
* @param {number} height
*/
setFrame(rgb, width, height) {
if (!this._ctx) this.connectedCallback();
paintRgb(this._ctx, this._canvas, rgb, width, height);
}
showIdentify() {
if (!this._ctx) this.connectedCallback();
paintIdentify(this._ctx, this._canvas, this._index, this._pixelWidth, this._pixelHeight);
}
}
customElements.define("portal-panel", PortalPanel);