- build-pages.mjs: per-locale page/partial overrides (*.en.html), i18n token
resolution with ru fallback, <html lang> + assetPrefix per locale
- UI dictionaries src/i18n/{ru,en}.json; lang-switch partial
- headers/footer/partials converted to {{i18n:…}} tokens
- root-absolute static asset refs so /en/ subpath loads css/js/vendor
- EN content: homepage (index.en.html + partials) and cookie-policy.en.html
(cookie-policy body translated via apinex.bond /v1/tools/translator)
82 lines
2.8 KiB
HTML
82 lines
2.8 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head><meta charset="utf-8" /><title>bake paper texture</title></head>
|
|
<body style="margin:0">
|
|
<div id="host" style="width:512px;height:512px"></div>
|
|
<script type="module">
|
|
import { ShaderMount, paperTextureFragmentShader, getShaderNoiseTexture } from "/_lab/paper-bundle.js";
|
|
|
|
// Параметры из пресета Figma (по умолчанию)
|
|
const P = window.__PARAMS || {
|
|
u_colorBack: [1, 1, 1, 1],
|
|
u_colorFront: [0, 0, 0, 1],
|
|
u_contrast: 0.3,
|
|
u_roughness: 0.4,
|
|
u_fiber: 0.3,
|
|
u_fiberSize: 0.2,
|
|
u_crumples: 0.3,
|
|
u_crumpleSize: 0.35,
|
|
u_folds: 0.65,
|
|
u_foldCount: 5,
|
|
u_drops: 0.2,
|
|
u_fade: 0,
|
|
u_seed: 5.8,
|
|
u_scale: 0.6,
|
|
};
|
|
|
|
try {
|
|
const host = document.getElementById("host");
|
|
const noise = getShaderNoiseTexture();
|
|
await noise.decode();
|
|
|
|
const mount = new ShaderMount(
|
|
host,
|
|
paperTextureFragmentShader,
|
|
{ ...P, u_noiseTexture: noise },
|
|
// preserveDrawingBuffer обязателен: без него буфер очищается
|
|
// после композитинга и readPixels вернёт пустоту
|
|
{ antialias: false, preserveDrawingBuffer: true },
|
|
0,
|
|
0,
|
|
1,
|
|
);
|
|
|
|
await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));
|
|
|
|
const canvas = host.querySelector("canvas");
|
|
const gl = canvas.getContext("webgl2") || canvas.getContext("webgl");
|
|
const w = canvas.width, h = canvas.height;
|
|
const px = new Uint8Array(w * h * 4);
|
|
gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, px);
|
|
|
|
let min = 255, max = 0, sum = 0, n = 0;
|
|
for (let i = 0; i < px.length; i += 4) {
|
|
const v = px[i];
|
|
if (v < min) min = v;
|
|
if (v > max) max = v;
|
|
sum += v; n++;
|
|
}
|
|
const avg = sum / n;
|
|
let vs = 0;
|
|
for (let i = 0; i < px.length; i += 4) vs += (px[i] - avg) ** 2;
|
|
|
|
// readPixels читает снизу вверх — переворачиваем
|
|
const out = document.createElement("canvas");
|
|
out.width = w; out.height = h;
|
|
const ctx = out.getContext("2d");
|
|
const img = ctx.createImageData(w, h);
|
|
for (let y = 0; y < h; y++) {
|
|
img.data.set(px.subarray((h - 1 - y) * w * 4, (h - y) * w * 4), y * w * 4);
|
|
}
|
|
ctx.putImageData(img, 0, 0);
|
|
|
|
window.__STATS = { w, h, min, max, avg: +avg.toFixed(2), sd: +Math.sqrt(vs / n).toFixed(2) };
|
|
window.__URL = out.toDataURL("image/png");
|
|
window.__DONE = true;
|
|
} catch (e) {
|
|
window.__ERR = e.message + " | " + e.stack;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|