Initial commit

This commit is contained in:
2026-09-03 11:24:19 +02:00
commit 6ed3153f36
1897 changed files with 538007 additions and 0 deletions
+311
View File
@@ -0,0 +1,311 @@
/* ============================================================================
* SoftSPM — animowane tlo "flowing wave mesh" (Canvas 2D, bez bibliotek)
* ----------------------------------------------------------------------------
* Tryby:
* 'text' - wezly ukladaja sie w podany napis (drgaja z mala amplituda),
* 'wave' - pasmo falujacych linii (suma sinusow, ruch organiczny).
*
* Sterowanie z HTML przez atrybuty canvasu:
* data-mode="text|wave" data-text="SoftSPM"
*
* Wszystkie parametry w jednym obiekcie CONFIG ponizej.
* Zgodne z PHP 5.4 / starymi przegladarkami (czysty ES5, requestAnimationFrame).
* ========================================================================== */
(function () {
'use strict';
var CONFIG = {
mode: 'text', // 'text' | 'wave'
text: 'SoftSPM',
// --- siatka / probkowanie ---
grid: 7, // odleglosc miedzy wezlami [px] (mniej = gesciej)
alphaThreshold: 120, // min. alfa piksela litery, by postawic wezel
// --- tekst ---
fontWeight: '700',
fontFamily: '"Segoe UI", Roboto, Arial, sans-serif',
textHeightRatio: 0.17, // wysokosc fontu wzgledem wysokosci canvasu
textAnchor: { x: 0.07, y: 0.90 }, // kotwica napisu (ulamek szer./wys.) -> dol-lewo
// --- ruch ---
amplitude: 1.7, // amplituda drgania wezlow [px] (maly = czytelny napis)
speed: 0.0016, // mnoznik czasu
// --- kolor / poswiata ---
color: { r: 41, g: 171, b: 226 }, // turkus marki SoftSPM (rgb 41,171,226)
opacity: 0.85,
glow: 8, // shadowBlur (poswiata neonowa)
lineWidth: 1,
nodeRadius: 1.15,
drawNodes: true,
// --- reflektor (jasnosc wygasa przy krawedziach) ---
edgeFalloff: 0.55, // 0 = brak, 1 = pelne wygaszenie na krawedziach
spotlightCenter: null, // null = auto (srodek napisu / 0.5 dla fali)
// --- tryb 'wave' ---
waveCols: 90,
waveRows: 13,
bandCenter: 0.72, // srodek pasma (ulamek wysokosci)
bandWidth: 0.40, // szerokosc pasma (ulamek wysokosci)
connectorEvery: 3, // co ile kolumn pionowy lacznik (efekt siatki)
// skladowe fal: [amplituda(px), czestotliwosc, predkosc, faza]
waves: [
[22, 0.018, 0.9, 0.0],
[14, 0.035, -1.4, 1.7],
[9, 0.061, 2.1, 3.1],
[6, 0.090, -2.8, 0.6]
]
};
function MeshAnim(canvas, cfg) {
this.canvas = canvas;
this.cfg = cfg;
this.ctx = canvas.getContext('2d');
this.dpr = window.devicePixelRatio || 1;
this.w = 0;
this.h = 0;
this.nodes = [];
this.edges = [];
this.spotX = 0.5;
this.raf = 0;
this._resizeTimer = 0;
var self = this;
this._onResize = function () {
clearTimeout(self._resizeTimer);
self._resizeTimer = setTimeout(function () { self.resize(); }, 120);
};
window.addEventListener('resize', this._onResize);
}
MeshAnim.prototype.resize = function () {
var rect = this.canvas.getBoundingClientRect();
this.dpr = window.devicePixelRatio || 1;
this.w = Math.max(1, Math.round(rect.width));
this.h = Math.max(1, Math.round(rect.height));
this.canvas.width = Math.round(this.w * this.dpr);
this.canvas.height = Math.round(this.h * this.dpr);
this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
this.build();
};
MeshAnim.prototype.build = function () {
if (this.cfg.mode === 'wave') this.buildWave();
else this.buildText();
// srodek reflektora
if (this.cfg.spotlightCenter != null) {
this.spotX = this.cfg.spotlightCenter;
} else if (this.cfg.mode === 'text' && this.nodes.length) {
var minX = Infinity, maxX = -Infinity, i;
for (i = 0; i < this.nodes.length; i++) {
if (this.nodes[i].bx < minX) minX = this.nodes[i].bx;
if (this.nodes[i].bx > maxX) maxX = this.nodes[i].bx;
}
this.spotX = ((minX + maxX) / 2) / this.w;
} else {
this.spotX = 0.5;
}
};
// --- model TEKST: render na ukrytym canvasie -> probkowanie pikseli ---
MeshAnim.prototype.buildText = function () {
var cfg = this.cfg, w = this.w, h = this.h;
this.nodes = [];
this.edges = [];
if (!w || !h) return;
var off = document.createElement('canvas');
off.width = w; off.height = h;
var octx = off.getContext('2d');
var size = Math.round(h * cfg.textHeightRatio);
octx.font = cfg.fontWeight + ' ' + size + 'px ' + cfg.fontFamily;
octx.textBaseline = 'alphabetic';
octx.textAlign = 'left';
octx.fillStyle = '#fff';
octx.fillText(cfg.text, Math.round(w * cfg.textAnchor.x), Math.round(h * cfg.textAnchor.y));
var img = octx.getImageData(0, 0, w, h).data;
var step = cfg.grid;
var cols = Math.floor(w / step);
var rows = Math.floor(h / step);
var indexMap = {}; // "c_r" -> node index
var c, r, px, py, a, idx;
for (r = 0; r < rows; r++) {
for (c = 0; c < cols; c++) {
px = c * step + (step >> 1);
py = r * step + (step >> 1);
a = img[(py * w + px) * 4 + 3];
if (a >= cfg.alphaThreshold) {
indexMap[c + '_' + r] = this.nodes.length;
this.nodes.push({
bx: px, by: py,
ph: (c * 0.7 + r * 1.3), // faza drgania
fx: 0.9 + (c % 5) * 0.06, // lekko rozne czestotliwosci
fy: 1.1 + (r % 4) * 0.05
});
}
}
}
// krawedzie: prawo, dol, skos prawy-dol (siatka trojkatna)
var neigh = [[1, 0], [0, 1], [1, 1]];
for (r = 0; r < rows; r++) {
for (c = 0; c < cols; c++) {
idx = indexMap[c + '_' + r];
if (idx == null) continue;
for (var n = 0; n < neigh.length; n++) {
var j = indexMap[(c + neigh[n][0]) + '_' + (r + neigh[n][1])];
if (j != null) this.edges.push([idx, j]);
}
}
}
};
// --- model FALA: pasmo wezlow, ruch = suma sinusow ---
MeshAnim.prototype.buildWave = function () {
var cfg = this.cfg, w = this.w, h = this.h;
this.nodes = [];
this.edges = [];
if (!w || !h) return;
var cols = cfg.waveCols, rows = cfg.waveRows;
var band = cfg.bandWidth * h;
var top = cfg.bandCenter * h - band / 2;
var grid = [];
var c, r;
for (r = 0; r < rows; r++) {
grid[r] = [];
for (c = 0; c < cols; c++) {
var bx = (cols === 1 ? 0 : c / (cols - 1)) * w;
var by = top + (rows === 1 ? 0.5 : r / (rows - 1)) * band;
grid[r][c] = this.nodes.length;
this.nodes.push({ bx: bx, by: by, col: c, row: r });
}
}
for (r = 0; r < rows; r++) {
for (c = 0; c < cols; c++) {
if (c < cols - 1) this.edges.push([grid[r][c], grid[r][c + 1]]); // poziome
if (r < rows - 1 && c % cfg.connectorEvery === 0) {
this.edges.push([grid[r][c], grid[r + 1][c]]); // pionowe laczniki
}
}
}
};
MeshAnim.prototype.nodePos = function (nd, t) {
var cfg = this.cfg;
if (cfg.mode === 'wave') {
var y = nd.by, wv = cfg.waves, k;
for (k = 0; k < wv.length; k++) {
y += wv[k][0] * Math.sin(nd.bx * wv[k][1] + t * wv[k][2] + wv[k][3] + nd.row * 0.4);
}
return [nd.bx, y];
}
// tryb tekstu: drobne drganie (napis czytelny)
var a = cfg.amplitude;
return [
nd.bx + a * Math.sin(t * nd.fx + nd.ph),
nd.by + a * Math.cos(t * nd.fy + nd.ph * 1.3)
];
};
MeshAnim.prototype.frame = function (now) {
var self = this;
var cfg = this.cfg, ctx = this.ctx, w = this.w, h = this.h;
var t = now * cfg.speed;
ctx.clearRect(0, 0, w, h);
if (this.nodes.length) {
// policz pozycje raz na klatke
var pos = new Array(this.nodes.length), i;
for (i = 0; i < this.nodes.length; i++) pos[i] = this.nodePos(this.nodes[i], t);
var col = cfg.color;
var stroke = 'rgb(' + col.r + ',' + col.g + ',' + col.b + ')';
ctx.lineWidth = cfg.lineWidth;
ctx.lineCap = 'round';
ctx.shadowColor = stroke;
ctx.shadowBlur = cfg.glow;
ctx.strokeStyle = stroke;
// kubelki jasnosci (reflektor) -> 1 stroke na kubelek = wydajnie z glow
var BUCKETS = 6, b, paths = [];
for (b = 0; b < BUCKETS; b++) paths[b] = [];
var e, a, bxC, fac, bk;
for (i = 0; i < this.edges.length; i++) {
e = this.edges[i];
a = pos[e[0]]; var b2 = pos[e[1]];
bxC = ((a[0] + b2[0]) / 2) / w; // srodek krawedzi (ulamek szer.)
fac = 1 - cfg.edgeFalloff * Math.pow(Math.abs(bxC - this.spotX) * 2, 2);
if (fac <= 0.02) continue;
bk = Math.min(BUCKETS - 1, Math.max(0, Math.floor(fac * BUCKETS)));
paths[bk].push(a, b2);
}
for (b = 0; b < BUCKETS; b++) {
if (!paths[b].length) continue;
ctx.globalAlpha = cfg.opacity * ((b + 0.5) / BUCKETS);
ctx.beginPath();
for (i = 0; i < paths[b].length; i += 2) {
ctx.moveTo(paths[b][i][0], paths[b][i][1]);
ctx.lineTo(paths[b][i + 1][0], paths[b][i + 1][1]);
}
ctx.stroke();
}
// wezly jako drobne punkty (jeden fill)
if (cfg.drawNodes) {
ctx.globalAlpha = cfg.opacity;
ctx.fillStyle = stroke;
ctx.beginPath();
for (i = 0; i < pos.length; i++) {
ctx.moveTo(pos[i][0] + cfg.nodeRadius, pos[i][1]);
ctx.arc(pos[i][0], pos[i][1], cfg.nodeRadius, 0, Math.PI * 2);
}
ctx.fill();
}
ctx.globalAlpha = 1;
ctx.shadowBlur = 0;
}
this.raf = window.requestAnimationFrame(function (ts) { self.frame(ts); });
};
MeshAnim.prototype.start = function () {
var self = this;
this.resize();
this.raf = window.requestAnimationFrame(function (ts) { self.frame(ts); });
};
MeshAnim.prototype.destroy = function () {
window.cancelAnimationFrame(this.raf);
window.removeEventListener('resize', this._onResize);
};
function boot() {
var canvas = document.getElementById('spm-mesh');
if (!canvas) return;
var cfg = {};
for (var k in CONFIG) if (CONFIG.hasOwnProperty(k)) cfg[k] = CONFIG[k];
if (canvas.getAttribute('data-mode')) cfg.mode = canvas.getAttribute('data-mode');
if (canvas.getAttribute('data-text')) cfg.text = canvas.getAttribute('data-text');
var anim = new MeshAnim(canvas, cfg);
window.spmMesh = anim;
// przy wlasnej czcionce poczekaj na gotowosc fontow (czytelny napis)
if (cfg.mode === 'text' && document.fonts && document.fonts.ready) {
document.fonts.ready.then(function () { anim.start(); });
} else {
anim.start();
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
})();