完整汉化:将所有用户可见文本翻译为简体中文

- index.html: 页面标题、meta 标签、按钮文字/提示、状态栏
- docs.html: 完整文档页面 9 个章节
- js/main.js: 模式名称、分区标题、滑块标签、toast 消息、按钮文字
- js/ui.js: 新种子按钮、随机化提示
- js/modals.js: 导出对话框标题/选项/标签、套装生成器文本
- js/exporter.js: 渲染进度文本、完成/错误 toast 消息
- js/palettes.js: 16 种调色板名称
- README.md: 项目说明全文
This commit is contained in:
2026-06-16 19:58:21 +08:00
parent 3c54400025
commit d3471018b4
8 changed files with 232 additions and 232 deletions
+14 -14
View File
@@ -9,7 +9,7 @@ var Exporter = (function () {
function showOverlay(title) {
cancelled = false;
$("overlay-title").textContent = title;
$("overlay-detail").textContent = "preparing";
$("overlay-detail").textContent = "准备中";
$("overlay-bar").style.width = "0%";
$("overlay").hidden = false;
}
@@ -49,7 +49,7 @@ var Exporter = (function () {
busy = false;
if (blob) {
download(blob, stamp(P, "png"));
FX.celebrate("Saved " + w + "\u00d7" + h + " PNG");
FX.celebrate("已保存 " + w + "\u00d7" + h + " PNG");
}
}, "image/png");
}
@@ -91,7 +91,7 @@ var Exporter = (function () {
async function exportVideo(P, aspect) {
if (busy) return;
if (typeof VideoEncoder === "undefined" || typeof VideoFrame === "undefined") {
UI.toast("This browser has no WebCodecs support, use a current Chrome, Edge or Firefox");
UI.toast("此浏览器不支持 WebCodecs,请使用最新版 ChromeEdge Firefox");
return;
}
busy = true;
@@ -104,7 +104,7 @@ var Exporter = (function () {
var totalSec = videoDurationSec(P);
var nFrames = Math.max(2, Math.round(totalSec * fps));
showOverlay("Rendering video");
showOverlay("渲染视频中");
Engine.suspend();
Engine.setPlaying(false);
Engine.setSize(w, h);
@@ -112,7 +112,7 @@ var Exporter = (function () {
var picked = await pickEncoderConfig(w, h, fps);
if (!picked) {
restore();
UI.toast("No supported video codec (VP9/VP8) found");
UI.toast("未找到支持的视频编码器 (VP9/VP8)");
return;
}
@@ -152,7 +152,7 @@ var Exporter = (function () {
vf.close();
setProgress(0.9 * (f + 1) / nFrames,
"frame " + (f + 1) + "/" + nFrames + " \u00b7 " + w + "\u00d7" + h + " @ " + fps + "fps");
" " + (f + 1) + "/" + nFrames + " \u00b7 " + w + "\u00d7" + h + " @ " + fps + "帧/秒");
/* backpressure: never let the encoder queue grow unbounded */
while (encoder.encodeQueueSize > 2) await wait(2);
@@ -160,7 +160,7 @@ var Exporter = (function () {
}
if (!cancelled && !encError) {
setProgress(0.93, "finalizing encode");
setProgress(0.93, "完成编码");
await encoder.flush();
}
} catch (e) {
@@ -177,11 +177,11 @@ var Exporter = (function () {
if (cancelled) { hideOverlay(); busy = false; return; }
if (encError || !encFrames.length) {
hideOverlay(); busy = false;
UI.toast("Video encode failed" + (encError && encError.message ? ": " + encError.message : ""));
UI.toast("视频编码失败" + (encError && encError.message ? "" + encError.message : ""));
return;
}
setProgress(0.97, "writing webm container");
setProgress(0.97, "写入 WebM 容器");
await wait(0);
var webm = WebMMux.mux({
codecId: picked.codecId,
@@ -193,7 +193,7 @@ var Exporter = (function () {
hideOverlay();
busy = false;
download(new Blob([webm], { type: "video/webm" }), stamp(P, "webm"));
FX.celebrate("Saved " + totalSec.toFixed(1) + "s video \u00b7 " + w + "\u00d7" + h + " @ " + fps + "fps");
FX.celebrate("已保存 " + totalSec.toFixed(1) + "s 视频 \u00b7 " + w + "\u00d7" + h + " @ " + fps + "帧/秒");
function restore() {
Engine.setSize(prev[0], prev[1]);
@@ -218,7 +218,7 @@ var Exporter = (function () {
var fps = parseInt(P.gifFps, 10);
var nFrames = Math.max(2, Math.round(P.loop * fps));
showOverlay("Rendering GIF");
showOverlay("渲染 GIF");
Engine.setSize(w, h);
var frames = [];
@@ -226,7 +226,7 @@ var Exporter = (function () {
if (cancelled) break;
Engine.renderAt(f / nFrames);
frames.push(Engine.readPixels());
setProgress(0.4 * (f + 1) / nFrames, "capturing " + (f + 1) + "/" + nFrames);
setProgress(0.4 * (f + 1) / nFrames, "捕获 " + (f + 1) + "/" + nFrames);
if (f % 4 === 3) await wait(0);
}
@@ -238,7 +238,7 @@ var Exporter = (function () {
var data = await GIFEnc.encode({
frames: frames, width: w, height: h, fps: fps,
dither: P.gifDither, loop: P.gifLoop,
onProgress: function (frac, detail) { setProgress(0.4 + 0.6 * frac, "encoding \u00b7 " + detail); },
onProgress: function (frac, detail) { setProgress(0.4 + 0.6 * frac, "编码中 \u00b7 " + detail); },
isCancelled: function () { return cancelled; }
});
@@ -246,7 +246,7 @@ var Exporter = (function () {
busy = false;
if (data && !cancelled) {
download(new Blob([data], { type: "image/gif" }), stamp(P, "gif"));
FX.celebrate("Saved " + nFrames + "-frame looping GIF (" + w + "\u00d7" + h + ")");
FX.celebrate("已保存 " + nFrames + " 帧循环 GIF (" + w + "\u00d7" + h + ")");
}
}
+66 -66
View File
@@ -3,23 +3,23 @@
/* ---------------- modes ---------------- */
var MODES = [
{ id: 0, key: "chrome", name: "Chrome", full: "Liquid Chrome",
{ id: 0, key: "chrome", name: "\u94ec", full: "\u6db2\u6001\u94ec",
icon: '<svg viewBox="0 0 26 18"><path d="M1 12 C5 4 9 15 13 9 C17 3 21 13 25 7" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M1 15 C5 9 10 17 14 12 C18 8 22 15 25 11" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.45"/></svg>' },
{ id: 1, key: "silk", name: "Silk", full: "Silk Ribbons",
{ id: 1, key: "silk", name: "\u4e1d\u7ef8", full: "\u4e1d\u5e26",
icon: '<svg viewBox="0 0 26 18"><path d="M1 13 C8 11 12 3 25 4" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M1 15.5 C8 13.5 12 5.5 25 6.5" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.65"/><path d="M1 18 C8 16 12 8 25 9" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.35"/></svg>' },
{ id: 2, key: "bloom", name: "Bloom", full: "Soft Bloom",
{ id: 2, key: "bloom", name: "\u7efd\u653e", full: "\u67d4\u5149\u7efd\u653e",
icon: '<svg viewBox="0 0 26 18"><circle cx="9" cy="8" r="5.5" fill="currentColor" opacity="0.35"/><circle cx="17" cy="11" r="4" fill="currentColor" opacity="0.6"/></svg>' },
{ id: 3, key: "aura", name: "Aura", full: "Aura Rings",
{ id: 3, key: "aura", name: "\u5149\u73af", full: "\u5149\u73af",
icon: '<svg viewBox="0 0 26 18"><circle cx="13" cy="9" r="7" fill="none" stroke="currentColor" stroke-width="1.4" opacity="0.35"/><circle cx="13" cy="9" r="4.2" fill="none" stroke="currentColor" stroke-width="1.4" opacity="0.7"/><circle cx="13" cy="9" r="1.6" fill="currentColor"/></svg>' },
{ id: 4, key: "rays", name: "Rays", full: "Light Rays",
{ id: 4, key: "rays", name: "\u5149\u7ebf", full: "\u5149\u7ebf",
icon: '<svg viewBox="0 0 26 18"><path d="M13 1 L7 17 M13 1 L13 17 M13 1 L19 17 M13 1 L2 13 M13 1 L24 13" fill="none" stroke="currentColor" stroke-width="1.3" opacity="0.8"/></svg>' },
{ id: 5, key: "halftone", name: "Halftone", full: "Halftone",
{ id: 5, key: "halftone", name: "\u534a\u8c03", full: "\u534a\u8c03",
icon: '<svg viewBox="0 0 26 18"><circle cx="4" cy="5" r="2.4" fill="currentColor"/><circle cx="11" cy="5" r="1.8" fill="currentColor"/><circle cx="18" cy="5" r="1.2" fill="currentColor"/><circle cx="24" cy="5" r="0.7" fill="currentColor"/><circle cx="4" cy="12" r="1.6" fill="currentColor"/><circle cx="11" cy="12" r="2.2" fill="currentColor"/><circle cx="18" cy="12" r="1.5" fill="currentColor"/><circle cx="24" cy="12" r="0.9" fill="currentColor"/></svg>' },
{ id: 6, key: "glyphs", name: "Glyphs", full: "Data Glyphs",
{ id: 6, key: "glyphs", name: "\u5b57\u5f62", full: "\u6570\u636e\u5b57\u5f62",
icon: '<svg viewBox="0 0 26 18"><g fill="currentColor"><rect x="2" y="2" width="2" height="3"/><rect x="7" y="2" width="2" height="3" opacity="0.5"/><rect x="12" y="2" width="2" height="3"/><rect x="17" y="2" width="2" height="3" opacity="0.3"/><rect x="22" y="2" width="2" height="3" opacity="0.7"/><rect x="2" y="8" width="2" height="3" opacity="0.4"/><rect x="7" y="8" width="2" height="3"/><rect x="12" y="8" width="2" height="3" opacity="0.6"/><rect x="17" y="8" width="2" height="3"/><rect x="22" y="8" width="2" height="3" opacity="0.4"/><rect x="2" y="14" width="2" height="3" opacity="0.7"/><rect x="7" y="14" width="2" height="3" opacity="0.3"/><rect x="12" y="14" width="2" height="3" opacity="0.8"/><rect x="17" y="14" width="2" height="3" opacity="0.5"/><rect x="22" y="14" width="2" height="3"/></g></svg>' },
{ id: 7, key: "reeded", name: "Reeded", full: "Reeded Glass",
{ id: 7, key: "reeded", name: "\u7f57\u7eb9", full: "\u7f57\u7eb9\u73bb\u7483",
icon: '<svg viewBox="0 0 26 18"><g stroke="currentColor" stroke-width="1.8" fill="none"><path d="M3 1 V17" opacity="0.9"/><path d="M8 1 V17" opacity="0.5"/><path d="M13 1 V17" opacity="0.9"/><path d="M18 1 V17" opacity="0.5"/><path d="M23 1 V17" opacity="0.9"/></g></svg>' },
{ id: 8, key: "mosaic", name: "Mosaic", full: "Pixel Bloom",
{ id: 8, key: "mosaic", name: "\u9a6c\u8d5b\u514b", full: "\u50cf\u7d20\u7efd\u653e",
icon: '<svg viewBox="0 0 26 18"><g fill="currentColor"><rect x="2" y="2" width="6" height="6" opacity="0.9"/><rect x="9" y="2" width="6" height="6" opacity="0.4"/><rect x="16" y="2" width="6" height="6" opacity="0.7"/><rect x="2" y="9" width="6" height="6" opacity="0.3"/><rect x="9" y="9" width="6" height="6" opacity="0.8"/><rect x="16" y="9" width="6" height="6" opacity="0.5"/></g></svg>' }
];
@@ -275,7 +275,7 @@ function saveCurrentStyle() {
if (list.length > 24) list.length = 24;
persistSavedStyles(list);
renderSavedStyles();
UI.toast("Style saved: " + name);
UI.toast("\u98ce\u683c\u5df2\u4fdd\u5b58\uff1a" + name);
}
var renderSavedStyles = function () {};
@@ -360,9 +360,9 @@ function copyText(text, okMsg) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(
function () { UI.toast(okMsg); },
function () { UI.toast("Could not access clipboard"); });
function () { UI.toast("\u65e0\u6cd5\u8bbf\u95ee\u526a\u8d34\u677f"); });
} else {
UI.toast("Clipboard not available in this browser");
UI.toast("\u6b64\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u526a\u8d34\u677f");
}
}
@@ -388,7 +388,7 @@ function buildRail() {
var rail = document.getElementById("rail");
/* STYLE */
var sStyle = UI.section(rail, "Style", function () {
var sStyle = UI.section(rail, "\u98ce\u683c", function () {
P.synthOn = false;
P.mode = Math.floor(rnd() * MODES.length);
refreshAll();
@@ -399,23 +399,23 @@ function buildRail() {
/* genome: brand-new generated styles */
var synthRow = UI.el("div", "share-row synth-row", sStyle);
var btnSynth = UI.el("button", "mini-btn", synthRow);
btnSynth.innerHTML = '<svg viewBox="0 0 16 16"><path d="M8 1.5 L9.8 6.2 L14.5 8 L9.8 9.8 L8 14.5 L6.2 9.8 L1.5 8 L6.2 6.2 Z" fill="none" stroke="currentColor" stroke-width="1.3"/></svg>New style';
btnSynth.innerHTML = '<svg viewBox="0 0 16 16"><path d="M8 1.5 L9.8 6.2 L14.5 8 L9.8 9.8 L8 14.5 L6.2 9.8 L1.5 8 L6.2 6.2 Z" fill="none" stroke="currentColor" stroke-width="1.3"/></svg>\u65b0\u98ce\u683c';
btnSynth.addEventListener("click", function () {
if (!Engine.hasGenome()) {
UI.toast("Style synthesizer is still warming up, try again in a moment");
UI.toast("\u98ce\u683c\u5408\u6210\u5668\u6b63\u5728\u9884\u70ed\uff0c\u8bf7\u7a0d\u540e\u518d\u8bd5");
return;
}
generateGenomeStyle();
newSeed();
refreshAll();
UI.toast("New style discovered: " + genomeName());
UI.toast("\u53d1\u73b0\u65b0\u98ce\u683c\uff1a" + genomeName());
});
var btnSave = UI.el("button", "mini-btn", synthRow);
btnSave.innerHTML = '<svg viewBox="0 0 16 16"><path d="M3 2 H11 L14 5 V14 H3 Z" fill="none" stroke="currentColor" stroke-width="1.5"/><rect x="5.5" y="9" width="5" height="4" fill="currentColor"/></svg>Save style';
btnSave.innerHTML = '<svg viewBox="0 0 16 16"><path d="M3 2 H11 L14 5 V14 H3 Z" fill="none" stroke="currentColor" stroke-width="1.5"/><rect x="5.5" y="9" width="5" height="4" fill="currentColor"/></svg>\u4fdd\u5b58\u98ce\u683c';
btnSave.addEventListener("click", saveCurrentStyle);
var synthCtl = UI.el("div", "synth-ctl", sStyle);
reg(UI.slider(synthCtl, { label: "Synth blend", min: 0, max: 1, step: 0.01, fmt: fmt2,
reg(UI.slider(synthCtl, { label: "\u5408\u6210\u6df7\u5408", min: 0, max: 1, step: 0.01, fmt: fmt2,
get: get("blend"), set: function (v) { P.blend = v; updateMeta(); } }));
reg(function () { synthCtl.style.display = P.synthOn ? "" : "none"; });
@@ -437,16 +437,16 @@ function buildRail() {
renderSavedStyles();
});
chip.addEventListener("click", function () {
if (decodeDesign(st.code)) UI.toast("Loaded " + st.name);
if (decodeDesign(st.code)) UI.toast("\u5df2\u52a0\u8f7d " + st.name);
});
});
};
renderSavedStyles();
reg(UI.lockRow(sStyle, { label: "Keep style when randomizing", get: get("lockStyle"), set: set("lockStyle") }));
reg(UI.lockRow(sStyle, { label: "\u968f\u673a\u5316\u65f6\u4fdd\u6301\u98ce\u683c", get: get("lockStyle"), set: set("lockStyle") }));
/* COLOR */
var sColor = UI.section(rail, "Color", function () { randomizePalette(); refreshAll(); });
var sColor = UI.section(rail, "\u8272\u5f69", function () { randomizePalette(); refreshAll(); });
setPresetActive = UI.presetChips(sColor, PALETTES, function (i) {
applyPalette(PALETTES[i], i);
refreshAll();
@@ -457,69 +457,69 @@ function buildRail() {
{ gap: true }, { key: "bg", label: "BG" }],
function (k) { return P[k]; },
function (k, v) { P[k] = v; activePreset = -1; setPresetActive(-1); }));
reg(UI.slider(sColor, { label: "Hue shift", min: -180, max: 180, step: 1, fmt: fmtDeg, get: get("hue"), set: set("hue") }));
reg(UI.slider(sColor, { label: "Saturation", min: 0, max: 2, step: 0.01, fmt: fmt2, get: get("sat"), set: set("sat") }));
reg(UI.slider(sColor, { label: "Exposure", min: 0.5, max: 1.6, step: 0.01, fmt: fmt2, get: get("exposure"), set: set("exposure") }));
reg(UI.slider(sColor, { label: "\u8272\u76f8\u504f\u79fb", min: -180, max: 180, step: 1, fmt: fmtDeg, get: get("hue"), set: set("hue") }));
reg(UI.slider(sColor, { label: "\u9971\u548c\u5ea6", min: 0, max: 2, step: 0.01, fmt: fmt2, get: get("sat"), set: set("sat") }));
reg(UI.slider(sColor, { label: "\u66dd\u5149", min: 0.5, max: 1.6, step: 0.01, fmt: fmt2, get: get("exposure"), set: set("exposure") }));
/* FORM */
var sForm = UI.section(rail, "Form", function () { randomizeKeys(FORM_KEYS); newSeed(); refreshAll(); });
var sForm = UI.section(rail, "\u5f62\u6001", function () { randomizeKeys(FORM_KEYS); newSeed(); refreshAll(); });
reg(UI.seedRow(sForm, { get: get("seed"), set: function (v) { P.seed = v; updateMeta(); refreshAll(); }, onDice: function () { newSeed(); refreshAll(); } }));
reg(UI.slider(sForm, { label: "Zoom", min: 0.5, max: 3, step: 0.01, fmt: fmt2, get: get("scale"), set: set("scale") }));
reg(UI.slider(sForm, { label: "Detail", min: 1, max: 8, step: 0.1, fmt: fmt1, get: get("complex"), set: set("complex") }));
reg(UI.slider(sForm, { label: "Warp", min: 0, max: 2.5, step: 0.01, fmt: fmt2, get: get("warp"), set: set("warp") }));
reg(UI.slider(sForm, { label: "Turbulence", min: 0, max: 2, step: 0.01, fmt: fmt2, get: get("flow"), set: set("flow") }));
reg(UI.slider(sForm, { label: "Stretch", min: -1, max: 1, step: 0.01, fmt: fmt2, get: get("stretch"), set: set("stretch") }));
reg(UI.slider(sForm, { label: "\u7f29\u653e", min: 0.5, max: 3, step: 0.01, fmt: fmt2, get: get("scale"), set: set("scale") }));
reg(UI.slider(sForm, { label: "\u7ec6\u8282", min: 1, max: 8, step: 0.1, fmt: fmt1, get: get("complex"), set: set("complex") }));
reg(UI.slider(sForm, { label: "\u626d\u66f2", min: 0, max: 2.5, step: 0.01, fmt: fmt2, get: get("warp"), set: set("warp") }));
reg(UI.slider(sForm, { label: "\u6e4d\u6d41", min: 0, max: 2, step: 0.01, fmt: fmt2, get: get("flow"), set: set("flow") }));
reg(UI.slider(sForm, { label: "\u62c9\u4f38", min: -1, max: 1, step: 0.01, fmt: fmt2, get: get("stretch"), set: set("stretch") }));
/* LIGHTING */
var sLight = UI.section(rail, "Lighting", function () { randomizeKeys(LIGHT_KEYS); refreshAll(); });
reg(UI.slider(sLight, { label: "Intensity", min: 0, max: 2.2, step: 0.01, fmt: fmt2, get: get("light"), set: set("light") }));
reg(UI.slider(sLight, { label: "Gloss", min: 4, max: 120, step: 1, fmt: fmtInt, get: get("gloss"), set: set("gloss") }));
reg(UI.slider(sLight, { label: "Angle", min: 0, max: 360, step: 1, fmt: fmtDeg, get: get("lightAngle"), set: set("lightAngle") }));
reg(UI.slider(sLight, { label: "Iridescence", min: 0, max: 1, step: 0.01, fmt: fmt2, get: get("irid"), set: set("irid") }));
reg(UI.slider(sLight, { label: "Glow", min: 0, max: 1, step: 0.01, fmt: fmt2, get: get("glow"), set: set("glow") }));
reg(UI.slider(sLight, { label: "Contrast", min: 0.6, max: 1.6, step: 0.01, fmt: fmt2, get: get("contrast"), set: set("contrast") }));
var sLight = UI.section(rail, "\u5149\u7167", function () { randomizeKeys(LIGHT_KEYS); refreshAll(); });
reg(UI.slider(sLight, { label: "\u5f3a\u5ea6", min: 0, max: 2.2, step: 0.01, fmt: fmt2, get: get("light"), set: set("light") }));
reg(UI.slider(sLight, { label: "\u5149\u6cfd", min: 4, max: 120, step: 1, fmt: fmtInt, get: get("gloss"), set: set("gloss") }));
reg(UI.slider(sLight, { label: "\u89d2\u5ea6", min: 0, max: 360, step: 1, fmt: fmtDeg, get: get("lightAngle"), set: set("lightAngle") }));
reg(UI.slider(sLight, { label: "\u8679\u5f69", min: 0, max: 1, step: 0.01, fmt: fmt2, get: get("irid"), set: set("irid") }));
reg(UI.slider(sLight, { label: "\u8f89\u5149", min: 0, max: 1, step: 0.01, fmt: fmt2, get: get("glow"), set: set("glow") }));
reg(UI.slider(sLight, { label: "\u5bf9\u6bd4\u5ea6", min: 0.6, max: 1.6, step: 0.01, fmt: fmt2, get: get("contrast"), set: set("contrast") }));
/* TEXTURE */
var sTex = UI.section(rail, "Texture", function () { randomizeKeys(TEXTURE_KEYS); refreshAll(); });
reg(UI.slider(sTex, { label: "Grain", min: 0, max: 0.4, step: 0.005, fmt: fmt2, get: get("grain"), set: set("grain") }));
reg(UI.slider(sTex, { label: "Density", min: 14, max: 180, step: 1, fmt: fmtInt, get: get("cell"), set: set("cell") }));
reg(UI.slider(sTex, { label: "Ridges", min: 8, max: 160, step: 1, fmt: fmtInt, get: get("lines"), set: set("lines") }));
reg(UI.slider(sTex, { label: "Aberration", min: 0, max: 1, step: 0.01, fmt: fmt2, get: get("ca"), set: set("ca") }));
reg(UI.slider(sTex, { label: "Vignette", min: 0, max: 1, step: 0.01, fmt: fmt2, get: get("vig"), set: set("vig") }));
reg(UI.slider(sTex, { label: "Softness", min: 0.3, max: 1.6, step: 0.01, fmt: fmt2, get: get("soft"), set: set("soft") }));
var sTex = UI.section(rail, "\u7eb9\u7406", function () { randomizeKeys(TEXTURE_KEYS); refreshAll(); });
reg(UI.slider(sTex, { label: "\u9897\u7c92", min: 0, max: 0.4, step: 0.005, fmt: fmt2, get: get("grain"), set: set("grain") }));
reg(UI.slider(sTex, { label: "\u5bc6\u5ea6", min: 14, max: 180, step: 1, fmt: fmtInt, get: get("cell"), set: set("cell") }));
reg(UI.slider(sTex, { label: "\u810a\u7ebf", min: 8, max: 160, step: 1, fmt: fmtInt, get: get("lines"), set: set("lines") }));
reg(UI.slider(sTex, { label: "\u8272\u5dee", min: 0, max: 1, step: 0.01, fmt: fmt2, get: get("ca"), set: set("ca") }));
reg(UI.slider(sTex, { label: "\u6697\u89d2", min: 0, max: 1, step: 0.01, fmt: fmt2, get: get("vig"), set: set("vig") }));
reg(UI.slider(sTex, { label: "\u67d4\u548c\u5ea6", min: 0.3, max: 1.6, step: 0.01, fmt: fmt2, get: get("soft"), set: set("soft") }));
/* MOTION */
var sMotion = UI.section(rail, "Motion", function () { randomizeKeys(MOTION_KEYS); refreshAll(); });
reg(UI.slider(sMotion, { label: "Loop length", min: 2, max: 12, step: 0.5, fmt: fmtSec, get: get("loop"), set: set("loop") }));
reg(UI.slider(sMotion, { label: "Travel", min: 0, max: 1.5, step: 0.01, fmt: fmt2, get: get("travel"), set: set("travel") }));
var sMotion = UI.section(rail, "\u8fd0\u52a8", function () { randomizeKeys(MOTION_KEYS); refreshAll(); });
reg(UI.slider(sMotion, { label: "\u5faa\u73af\u957f\u5ea6", min: 2, max: 12, step: 0.5, fmt: fmtSec, get: get("loop"), set: set("loop") }));
reg(UI.slider(sMotion, { label: "\u884c\u7a0b", min: 0, max: 1.5, step: 0.01, fmt: fmt2, get: get("travel"), set: set("travel") }));
/* SHARE */
var sShare = UI.section(rail, "Share", null);
var sShare = UI.section(rail, "\u5206\u4eab", null);
var shareRow = UI.el("div", "share-row", sShare);
var btnCopyCode = UI.el("button", "mini-btn", shareRow);
btnCopyCode.textContent = "Copy design code";
btnCopyCode.textContent = "\u590d\u5236\u8bbe\u8ba1\u4ee3\u7801";
btnCopyCode.addEventListener("click", function () {
copyText(encodeDesign(), "Design code copied, paste it anywhere");
copyText(encodeDesign(), "\u8bbe\u8ba1\u4ee3\u7801\u5df2\u590d\u5236\uff0c\u53ef\u7c98\u8d34\u5230\u4efb\u4f55\u5730\u65b9");
});
var btnCopyLink = UI.el("button", "mini-btn", shareRow);
btnCopyLink.textContent = "Copy link";
btnCopyLink.textContent = "\u590d\u5236\u94fe\u63a5";
btnCopyLink.addEventListener("click", function () {
var url = location.origin + location.pathname + "#" + encodeDesign();
copyText(url, "Share link copied");
copyText(url, "\u5206\u4eab\u94fe\u63a5\u5df2\u590d\u5236");
});
var pasteInput = UI.el("input", "num-input mono share-input", sShare);
pasteInput.type = "text";
pasteInput.placeholder = "Paste a design code\u2026";
pasteInput.placeholder = "\u7c98\u8d34\u8bbe\u8ba1\u4ee3\u7801\u2026";
pasteInput.spellcheck = false;
function tryApplyPaste() {
var v = pasteInput.value.trim();
if (!v) return;
if (decodeDesign(v)) {
UI.toast("Design loaded");
UI.toast("\u8bbe\u8ba1\u5df2\u52a0\u8f7d");
pasteInput.value = "";
pasteInput.blur();
} else {
UI.toast("Invalid design code");
UI.toast("\u65e0\u6548\u7684\u8bbe\u8ba1\u4ee3\u7801");
}
}
pasteInput.addEventListener("paste", function () { setTimeout(tryApplyPaste, 0); });
@@ -529,18 +529,18 @@ function buildRail() {
});
/* EXPORT: buttons open a dialog with preview and settings */
var sExp = UI.section(rail, "Export", null);
var sExp = UI.section(rail, "\u5bfc\u51fa", null);
var grid = UI.el("div", "export-grid", sExp);
UI.exportButton(grid, "Image", "PNG",
UI.exportButton(grid, "\u56fe\u50cf", "PNG",
'<svg viewBox="0 0 16 16"><rect x="1.5" y="1.5" width="13" height="13" rx="2" fill="none" stroke="currentColor" stroke-width="1.5"/><circle cx="5.5" cy="5.5" r="1.5" fill="currentColor"/><path d="M2 12 L6 8 L9 11 L11.5 8.5 L14 11" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>',
function () { Modals.openExport("png"); });
UI.exportButton(grid, "Video", "WEBM",
UI.exportButton(grid, "\u89c6\u9891", "WEBM",
'<svg viewBox="0 0 16 16"><rect x="1.5" y="3.5" width="9" height="9" rx="2" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M10.5 7 L14.5 4.5 V11.5 L10.5 9" fill="currentColor"/></svg>',
function () { Modals.openExport("video"); });
UI.exportButton(grid, "Looping GIF", "GIF",
UI.exportButton(grid, "\u5faa\u73af GIF", "GIF",
'<svg viewBox="0 0 16 16"><path d="M13.5 8 a5.5 5.5 0 1 1 -1.6 -3.9" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M13.8 1.6 V4.4 H11" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>',
function () { Modals.openExport("gif"); });
UI.exportButton(grid, "Gradient set", "ZIP",
UI.exportButton(grid, "\u6e10\u53d8\u5957\u88c5", "ZIP",
'<svg viewBox="0 0 16 16"><rect x="1.5" y="1.5" width="5.5" height="5.5" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.4"/><rect x="9" y="1.5" width="5.5" height="5.5" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.4"/><rect x="1.5" y="9" width="5.5" height="5.5" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.4"/><rect x="9" y="9" width="5.5" height="5.5" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.4"/></svg>',
function () { Modals.openSetGenerator(); });
}
@@ -549,8 +549,8 @@ function buildRail() {
function updateMeta() {
document.getElementById("meta-mode").textContent = styleName();
document.getElementById("meta-seed").textContent = "seed " + String(Math.round(P.seed)).padStart(4, "0");
document.getElementById("meta-loop").textContent = P.loop.toFixed(1) + "s loop";
document.getElementById("meta-seed").textContent = "\u79cd\u5b50 " + String(Math.round(P.seed)).padStart(4, "0");
document.getElementById("meta-loop").textContent = P.loop.toFixed(1) + "s \u5faa\u73af";
if (Engine.isReady()) {
var s = Engine.size();
document.getElementById("meta-res").textContent = s[0] + "\u00d7" + s[1];
@@ -654,7 +654,7 @@ function showStageError(msg) {
var el = document.getElementById("stage-error");
if (!el) return;
el.innerHTML =
"WebGL2 is required. Please use a recent Chrome, Edge or Firefox.<br>" +
"\u9700\u8981 WebGL2\u3002\u8bf7\u4f7f\u7528\u6700\u65b0\u7248 Chrome\u3001Edge \u6216 Firefox\u3002<br>" +
'<span style="color:#5e5e68;font-size:11px">' + String(msg).split("\n")[0] + "</span>";
el.hidden = false;
}
@@ -673,8 +673,8 @@ function onContextLost() {
detachStageResize();
Engine.suspend();
document.body.classList.remove("ready");
showStageError("WebGL context lost. Reload the page.");
UI.toast("WebGL context lost. Reload the page.");
showStageError("WebGL \u4e0a\u4e0b\u6587\u4e22\u5931\u3002\u8bf7\u5237\u65b0\u9875\u9762\u3002");
UI.toast("WebGL \u4e0a\u4e0b\u6587\u4e22\u5931\u3002\u8bf7\u5237\u65b0\u9875\u9762\u3002");
}
document.addEventListener("DOMContentLoaded", function () {
@@ -707,7 +707,7 @@ document.addEventListener("DOMContentLoaded", function () {
var hash = decodeURIComponent(location.hash.slice(1) || "");
if (hash.indexOf("LMN1.") === 0 && decodeDesign(hash)) {
UI.toast("Shared design loaded");
UI.toast("\u5df2\u52a0\u8f7d\u5206\u4eab\u7684\u8bbe\u8ba1");
}
fitCanvas(false);
+26 -26
View File
@@ -73,8 +73,8 @@ var Modals = (function () {
}
function openExport(kind) {
var titles = { png: "Export image", video: "Export video", gif: "Export GIF" };
var body = open(titles[kind], MODES[P.mode].full + " \u00b7 seed " + Math.round(P.seed));
var titles = { png: "导出图像", video: "导出视频", gif: "导出 GIF" };
var body = open(titles[kind], MODES[P.mode].full + " \u00b7 种子 " + Math.round(P.seed));
/* live preview */
var prevWrap = el("div", "modal-preview", body);
@@ -101,40 +101,40 @@ var Modals = (function () {
var sec = (String(P.vidLen).charAt(0) === "s")
? parseInt(String(P.vidLen).slice(1), 10)
: P.loop * parseInt(String(P.vidLen).slice(1), 10);
return Math.round(vh * ar) + " \u00d7 " + vh + " \u00b7 " + fps + " fps \u00b7 " +
sec.toFixed(1) + "s \u00b7 " + Math.round(sec * fps) + " frames";
return Math.round(vh * ar) + " \u00d7 " + vh + " \u00b7 " + fps + " 帧/秒 \u00b7 " +
sec.toFixed(1) + "s \u00b7 " + Math.round(sec * fps) + " ";
}
var gw = parseInt(P.gifW, 10);
return gw + " \u00d7 " + Math.round(gw / ar) + " \u00b7 " + P.gifFps + " fps \u00b7 " +
Math.round(P.loop * P.gifFps) + " frames \u00b7 " + P.loop.toFixed(1) + "s loop";
return gw + " \u00d7 " + Math.round(gw / ar) + " \u00b7 " + P.gifFps + " 帧/秒 \u00b7 " +
Math.round(P.loop * P.gifFps) + " \u00b7 " + P.loop.toFixed(1) + "s 循环";
}
function refreshMeta() { pvMeta.textContent = metaText(); }
refreshMeta();
if (kind === "png") {
selectField(form, "Resolution", [["1080", "1920 \u00d7 1080"], ["1440", "2560 \u00d7 1440"], ["2160", "3840 \u00d7 2160"]],
selectField(form, "分辨率", [["1080", "1920 \u00d7 1080"], ["1440", "2560 \u00d7 1440"], ["2160", "3840 \u00d7 2160"]],
P.imgRes, function (v) { P.imgRes = v; refreshMeta(); });
} else if (kind === "video") {
selectField(form, "Resolution", [["720", "720p"], ["1080", "1080p"], ["1440", "1440p"]],
selectField(form, "分辨率", [["720", "720p"], ["1080", "1080p"], ["1440", "1440p"]],
P.vidRes, function (v) { P.vidRes = v; refreshMeta(); });
selectField(form, "Frame rate", [["24", "24 fps"], ["30", "30 fps"], ["60", "60 fps"]],
selectField(form, "帧率", [["24", "24 帧/秒"], ["30", "30 帧/秒"], ["60", "60 帧/秒"]],
P.vidFps, function (v) { P.vidFps = v; refreshMeta(); });
selectField(form, "Length", [
["l1", "1 loop"], ["l2", "2 loops"], ["l3", "3 loops"], ["l4", "4 loops"], ["l6", "6 loops"], ["l8", "8 loops"],
["s5", "5 seconds"], ["s10", "10 seconds"], ["s15", "15 seconds"], ["s30", "30 seconds"], ["s60", "60 seconds"]
selectField(form, "时长", [
["l1", "1 个循环"], ["l2", "2 个循环"], ["l3", "3 个循环"], ["l4", "4 个循环"], ["l6", "6 个循环"], ["l8", "8 个循环"],
["s5", "5 "], ["s10", "10 "], ["s15", "15 "], ["s30", "30 "], ["s60", "60 "]
], P.vidLen, function (v) { P.vidLen = v; refreshMeta(); });
} else {
selectField(form, "Width", [["360", "360 px"], ["480", "480 px"], ["640", "640 px"], ["800", "800 px"]],
selectField(form, "宽度", [["360", "360 px"], ["480", "480 px"], ["640", "640 px"], ["800", "800 px"]],
P.gifW, function (v) { P.gifW = v; refreshMeta(); });
selectField(form, "Frame rate", [["15", "15 fps"], ["20", "20 fps"], ["25", "25 fps"], ["30", "30 fps"]],
selectField(form, "帧率", [["15", "15 帧/秒"], ["20", "20 帧/秒"], ["25", "25 帧/秒"], ["30", "30 帧/秒"]],
String(P.gifFps), function (v) { P.gifFps = parseInt(v, 10); refreshMeta(); });
toggleField(form, "Dithering", P.gifDither, function (v) { P.gifDither = v; });
toggleField(form, "Loop forever", P.gifLoop, function (v) { P.gifLoop = v; });
toggleField(form, "抖动", P.gifDither, function (v) { P.gifDither = v; });
toggleField(form, "永久循环", P.gifLoop, function (v) { P.gifLoop = v; });
}
var actions = el("div", "modal-actions", body);
var dl = el("button", "btn btn-primary modal-dl", actions);
dl.innerHTML = '<svg viewBox="0 0 16 16"><path d="M8 2 V10 M4.5 7 L8 10.5 L11.5 7" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M3 13.5 H13" stroke="currentColor" stroke-width="1.6"/></svg>Download ' +
dl.innerHTML = '<svg viewBox="0 0 16 16"><path d="M8 2 V10 M4.5 7 L8 10.5 L11.5 7" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M3 13.5 H13" stroke="currentColor" stroke-width="1.6"/></svg>下载 ' +
(kind === "png" ? "PNG" : kind === "video" ? "WebM" : "GIF");
dl.addEventListener("click", function () {
close();
@@ -154,21 +154,21 @@ var Modals = (function () {
}
async function openSetGenerator() {
var body = open("Gradient set", "consistent variations of the current design");
var body = open("渐变套装", "当前设计的一致变体");
var info = el("div", "modal-note", body);
info.textContent = "Same style, palette and settings with different seeds. Use a set for hero, cards and section backgrounds that visually belong together.";
info.textContent = "相同风格、调色板和设置,不同种子。适用于主视觉、卡片和分区背景等需要视觉一致性的场景。";
var form = el("div", "modal-form", body);
var state = { count: 6, res: "1080" };
selectField(form, "Variations", [["4", "4"], ["6", "6"], ["8", "8"], ["12", "12"]],
selectField(form, "变体数量", [["4", "4"], ["6", "6"], ["8", "8"], ["12", "12"]],
"6", function (v) { state.count = parseInt(v, 10); build(); });
selectField(form, "PNG size", [["720", "1280 \u00d7 720"], ["1080", "1920 \u00d7 1080"], ["2160", "3840 \u00d7 2160"]],
selectField(form, "PNG 尺寸", [["720", "1280 \u00d7 720"], ["1080", "1920 \u00d7 1080"], ["2160", "3840 \u00d7 2160"]],
"1080", function (v) { state.res = v; });
var grid = el("div", "set-grid", body);
var actions = el("div", "modal-actions", body);
var dl = el("button", "btn btn-primary modal-dl", actions);
dl.innerHTML = '<svg viewBox="0 0 16 16"><path d="M8 2 V10 M4.5 7 L8 10.5 L11.5 7" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M3 13.5 H13" stroke="currentColor" stroke-width="1.6"/></svg>Download set as ZIP';
dl.innerHTML = '<svg viewBox="0 0 16 16"><path d="M8 2 V10 M4.5 7 L8 10.5 L11.5 7" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M3 13.5 H13" stroke="currentColor" stroke-width="1.6"/></svg>下载套装 ZIP';
var seeds = [];
@@ -197,7 +197,7 @@ var Modals = (function () {
tile.addEventListener("click", function () {
P.seed = sd;
refreshAll();
UI.toast("Applied seed " + sd);
UI.toast("已应用种子 " + sd);
close();
});
})(seeds[i]);
@@ -212,7 +212,7 @@ var Modals = (function () {
dl.addEventListener("click", async function () {
dl.disabled = true;
dl.textContent = "Rendering\u2026";
dl.textContent = "渲染中\u2026";
var ar = ASPECTS[P.aspect];
var h = parseInt(state.res, 10);
var w = 2 * Math.round(h * ar / 2);
@@ -235,7 +235,7 @@ var Modals = (function () {
data: new Uint8Array(await blob.arrayBuffer())
});
}
dl.textContent = "Rendering " + (i + 1) + "/" + seeds.length + "\u2026";
dl.textContent = "渲染中 " + (i + 1) + "/" + seeds.length + "\u2026";
await new Promise(function (r) { setTimeout(r, 0); });
}
@@ -251,7 +251,7 @@ var Modals = (function () {
a.click();
setTimeout(function () { URL.revokeObjectURL(a.href); }, 4000);
close();
FX.celebrate("Set saved: " + entries.length + " PNGs (" + w + "\u00d7" + h + ")");
FX.celebrate("套装已保存:" + entries.length + " PNG (" + w + "\u00d7" + h + ")");
});
await build();
+17 -17
View File
@@ -2,37 +2,37 @@
tone: 'dark' | 'light' decides which modes prefer them. */
var PALETTES = [
{ name: "Inferno Chrome", tone: "dark",
{ name: "炼狱铬", tone: "dark",
bg: "#050507", colors: ["#e0220a", "#ff5a1f", "#1f8cff", "#bfe7ff"] },
{ name: "Neon Silk", tone: "dark",
{ name: "霓虹丝绸", tone: "dark",
bg: "#040406", colors: ["#19e3e3", "#ff2d78", "#ff7a1a", "#7a2dff"] },
{ name: "Ultraviolet", tone: "dark",
{ name: "紫外线", tone: "dark",
bg: "#06040c", colors: ["#2440ff", "#8a2bff", "#e22bd0", "#ff5470"] },
{ name: "Ember", tone: "dark",
{ name: "余晖", tone: "dark",
bg: "#070403", colors: ["#ff6a00", "#ffb347", "#a81c00", "#3d0c02"] },
{ name: "Deep Signal", tone: "dark",
{ name: "深信号", tone: "dark",
bg: "#030608", colors: ["#0e3a5c", "#2e7fb8", "#9fd4e8", "#16222e"] },
{ name: "Red Telemetry", tone: "dark",
{ name: "红色遥测", tone: "dark",
bg: "#0a0202", colors: ["#ff2414", "#c81204", "#ff7a5c", "#5c0a02"] },
{ name: "Ghost Mono", tone: "dark",
{ name: "幽灵单色", tone: "dark",
bg: "#030304", colors: ["#f2f2f4", "#9a9aa6", "#3c3c46", "#c8c8d2"] },
{ name: "Acid Garden", tone: "dark",
{ name: "酸性花园", tone: "dark",
bg: "#04070a", colors: ["#b8ff2e", "#1fd9a4", "#0a7a5c", "#eaffd0"] },
{ name: "Blush", tone: "light",
{ name: "绯红", tone: "light",
bg: "#fbf6f2", colors: ["#d4607a", "#f0b890", "#fde8d8", "#b8434f"] },
{ name: "Prism Pastel", tone: "light",
{ name: "棱镜粉彩", tone: "light",
bg: "#f4f1fa", colors: ["#ffb340", "#2b3bd4", "#ff4f9a", "#9a8cff"] },
{ name: "Sky Aura", tone: "light",
{ name: "天空光环", tone: "light",
bg: "#e8edfb", colors: ["#2451e8", "#6fa3f5", "#f0a8c8", "#fdfdff"] },
{ name: "Halo", tone: "light",
{ name: "光晕", tone: "light",
bg: "#f4f6ff", colors: ["#4a30e0", "#7a8cff", "#e89ab8", "#c2d4ff"] },
{ name: "Solar Flare", tone: "light",
{ name: "太阳耀斑", tone: "light",
bg: "#fefcf8", colors: ["#e8401c", "#ff8a2a", "#ffc04a", "#a82408"] },
{ name: "Glacier", tone: "light",
{ name: "冰川", tone: "light",
bg: "#f8fafc", colors: ["#1a56d6", "#4a9af0", "#a8d4ff", "#0a2a6e"] },
{ name: "Tangerine Glass", tone: "dark",
{ name: "橘色玻璃", tone: "dark",
bg: "#1c1e22", colors: ["#ff7a14", "#e8e4dc", "#3a3e46", "#ffb066"] },
{ name: "Velvet Dusk", tone: "dark",
{ name: "丝绒黄昏", tone: "dark",
bg: "#08050c", colors: ["#ff3d2e", "#ff8c5a", "#5a1eb8", "#1a0a3c"] }
];
@@ -59,7 +59,7 @@ function generateRandomPalette(rand, tone) {
var bg = dark
? hslToHex(h + (rand() - 0.5) * 60, 0.3 + rand() * 0.4, 0.015 + rand() * 0.035)
: hslToHex(h + (rand() - 0.5) * 60, 0.25 + rand() * 0.35, 0.93 + rand() * 0.05);
return { name: "Generated", tone: tone, bg: bg, colors: colors };
return { name: "随机生成", tone: tone, bg: bg, colors: colors };
}
function hslToHex(h, s, l) {
+2 -2
View File
@@ -19,7 +19,7 @@ var UI = (function () {
if (onDice) {
var d = el("button", "section-dice", head);
d.innerHTML = DICE_SVG;
d.title = "Randomize " + title.toLowerCase();
d.title = "随机化" + title;
d.addEventListener("click", onDice);
}
return sec;
@@ -168,7 +168,7 @@ var UI = (function () {
opts.set(Math.max(0, Math.min(9999, parseInt(input.value, 10) || 0)));
});
var btn = el("button", "mini-btn", row);
btn.innerHTML = DICE_SVG + "New seed";
btn.innerHTML = DICE_SVG + "新种子";
btn.addEventListener("click", opts.onDice);
function refresh() { input.value = Math.round(opts.get()); }
refresh();