diff --git a/README.md b/README.md index 1c31ab6..70ea616 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,54 @@ -# LUMEN, the generative shader studio +# LUMEN 生成式着色器工作室 -**[Try it live →](https://lumenshaders.vercel.app/)** +**[在线体验 →](https://lumenshaders.vercel.app/)** -A self-contained web tool that generates looping abstract shader art: liquid chrome, silk ribbons, soft gradient blooms, aura rings, light rays, halftone fields, data glyphs, reeded glass and pixel mosaics. Everything is rendered in real time with WebGL2 and every animation is a mathematically perfect loop. +一个自包含的 Web 工具,用于生成循环抽象着色器艺术:液态铬、丝带、柔和渐变绽放、光环、光线、半调场、数据字形、罗纹玻璃和像素马赛克。所有内容均通过 WebGL2 实时渲染,每段动画都是数学上的完美循环。 -![Liquid Chrome](docs/preview-chrome.png) +![液态铬](docs/preview-chrome.png) -![Silk Ribbons](docs/preview-silk.png) +![丝带](docs/preview-silk.png) -![Data Glyphs](docs/preview-glyphs.png) +![数据字形](docs/preview-glyphs.png) -## Run it +## 运行 -No build step, no dependencies. +无构建步骤,零依赖。 -- **Live demo:** [lumenshaders.vercel.app](https://lumenshaders.vercel.app/) -- **Local:** double-click `index.html`, or run `npx http-server . -p 8080` +- **在线演示:** [lumenshaders.vercel.app](https://lumenshaders.vercel.app/) +- **本地:** 双击 `index.html`,或运行 `npx http-server . -p 8080` -Requires a browser with WebGL2 (Chrome, Edge, Firefox). +需要支持 WebGL2 的浏览器(Chrome、Edge、Firefox)。 -## Controls +## 控制 -- **Randomize** (or press `R`): new style, palette, form, lighting and seed, tuned per art style so results stay good. Sometimes it invents a brand-new synth style. -- **Style**: 9 art modes, each with its own renderer. "Keep style" locks the mode while randomizing. -- **Generated styles**: "New style" runs a 12-gene style synthesizer (6 field types x 5 domain geometries x 4 color mappings x 4 shading models x 4 overlays) that produces standalone styles which do not exist in the base set, like FLUX, RIDGE, WAVE, RING, CELL and FLOW archetypes. "Save style" keeps favorites in your browser as one-click chips. -- **Gradient sets**: the Set button renders 4 to 12 variations of the current design (same style and palette, different seeds) and downloads them as a ZIP of PNGs, ideal for using consistent art across one website. -- **Color**: 16 curated palettes plus a harmonic palette generator, 4 editable colors + background, hue / saturation / exposure. -- **Form**: seed, zoom, fbm detail, domain warp, turbulence, anisotropic stretch. -- **Lighting**: intensity, gloss, light angle, iridescence, glow, contrast. -- **Texture**: film grain, halftone/glyph density, ridge count, chromatic aberration, vignette, softness. -- **Motion**: loop length and travel distance. Loops are seamless by construction (the noise field is sampled along a closed circle). -- **Share**: every design serializes to a compact code (`LMN1.…`). Copy it, send it to anyone, and pasting it into the Share box recreates the exact design: style, colors, seed, all parameters. "Copy link" produces a URL that loads the design directly. -- `Space` pauses, `S` saves a PNG. +- **随机化**(或按 `R`):新风格、调色板、形态、光照和种子,按艺术风格调校,确保结果始终出色。有时会发明全新的合成风格。 +- **风格**:9 种艺术模式,每种都有独立的渲染器。"随机化时保持风格"在随机化时锁定模式。 +- **生成风格**:"新风格"运行 12 基因风格合成器(6 种场类型 × 5 种域几何 × 4 种色彩映射 × 4 种着色模型 × 4 种表面叠加),产生基础集合中不存在的独立风格,如 FLUX(通量)、RIDGE(脊线)、WAVE(波)、RING(环)、CELL(细胞)和 FLOW(流)原型。"保存风格"将收藏保存在浏览器中,以一键标签形式显示。 +- **渐变套装**:套装按钮渲染当前设计的 4 至 12 个变体(相同风格和调色板,不同种子),并下载为 PNG 的 ZIP 文件,非常适合在网站上使用一致的视觉艺术。 +- **色彩**:16 种精选调色板加和谐调色板生成器,4 个可编辑颜色 + 背景,色相/饱和度/曝光。 +- **形态**:种子、缩放、fbm 细节、域扭曲、湍流、各向异性拉伸。 +- **光照**:强度、光泽、光照角度、虹彩、辉光、对比度。 +- **纹理**:胶片颗粒、半调/字形密度、脊线数量、色差、暗角、柔和度。 +- **运动**:循环长度和行程距离。循环在构造上是无缝的(噪声场沿闭合圆采样)。 +- **分享**:每个设计可序列化为紧凑代码(`LMN1.…`)。复制它,发送给任何人,粘贴到分享框即可重建完全相同的设计:风格、颜色、种子、所有参数。"复制链接"生成直接加载设计的 URL。 +- `空格`暂停,`S` 保存 PNG。 -Full guide: [docs page](https://lumenshaders.vercel.app/docs.html). +完整指南:[文档页面](https://lumenshaders.vercel.app/docs.html)。 -## Export +## 导出 -Every export button opens a dialog with a live preview and its settings before downloading. +每个导出按钮打开一个带有实时预览和设置的对话框,确认后下载。 -- **PNG** up to 3840×2160, rendered offscreen at full quality. -- **Video** (WebM VP9/VP8) encoded offline with **WebCodecs** (`VideoEncoder`) and muxed by a built-in dependency-free WebM writer: no `MediaRecorder`, no realtime capture, no tab crashes. Configurable fps (24/30/60), resolution (720p–1440p) and exact length (1–8 loops or 5–60 seconds). -- **GIF** rendered deterministically frame by frame, encoded in-page with a dependency-free GIF89a encoder (median-cut palette + ordered dithering). Loop forever or play once. +- **PNG** 最高 3840×2160,离屏全质量渲染。 +- **视频**(WebM VP9/VP8)使用 **WebCodecs**(`VideoEncoder`)离线编码,由内置无依赖 WebM 封装器封装:无 `MediaRecorder`,无实时捕获,无标签页崩溃。可配置帧率(24/30/60 帧/秒)、分辨率(720p–1440p)和精确时长(1–8 个循环或 5–60 秒)。 +- **GIF** 逐帧确定性渲染,使用无依赖 GIF89a 编码器(中值切割调色板 + 有序抖动)在页面内编码。永久循环或播放一次。 -## Files +## 文件 -- `index.html` / `styles.css`: app shell and design system -- `js/shaders.js`: the uber fragment shader with all 9 modes -- `js/engine.js`: WebGL2 engine and render loop -- `js/gifenc.js`: GIF encoder (quantizer + LZW) -- `js/webmmux.js`: WebM/Matroska muxer for WebCodecs output -- `js/exporter.js`: PNG / video / GIF pipelines -- `js/palettes.js`, `js/ui.js`, `js/main.js`: palettes, control builders, state and randomizer +- `index.html` / `styles.css`:应用外壳和设计系统 +- `js/shaders.js`:包含全部 9 种模式的超级片段着色器 +- `js/engine.js`:WebGL2 引擎和渲染循环 +- `js/gifenc.js`:GIF 编码器(量化器 + LZW) +- `js/webmmux.js`:WebCodecs 输出的 WebM/Matroska 封装器 +- `js/exporter.js`:PNG / 视频 / GIF 管线 +- `js/palettes.js`、`js/ui.js`、`js/main.js`:调色板、控件构建器、状态和随机化器 diff --git a/docs.html b/docs.html index 15daf23..d949598 100644 --- a/docs.html +++ b/docs.html @@ -1,10 +1,10 @@ - + -Docs, LUMEN generative shader studio - +文档 - LUMEN 生成式着色器工作室 + @@ -91,88 +91,88 @@ LUMEN - documentation + 文档
- Open the studio + 打开工作室
-

How LUMEN works

-

LUMEN generates looping abstract shader art in your browser: liquid chrome, silk, soft gradients, halftone fields and more. Everything renders in real time with WebGL2, every animation is a perfect loop, and everything you make can be exported or shared as a code.

+

LUMEN 工作原理

+

LUMEN 在你的浏览器中生成循环抽象着色器艺术:液态铬、丝绸、柔和渐变、半调场等。所有内容均通过 WebGL2 实时渲染,每段动画都是完美循环,你的所有创作都可以导出或以代码形式分享。

-

Styles

-

Pick one of the 9 base styles in the Style section: Chrome, Silk, Bloom, Aura, Rays, Halftone, Glyphs, Reeded and Mosaic. Each one is its own renderer with its own character.

-

Generated styles go far beyond the base set: New style runs a style synthesizer with 12 genes covering field type, domain geometry, color mapping, shading model and surface overlay. Every gene set is a standalone style that does not exist in the base nine, named by its field archetype: FLUX, RIDGE, WAVE, RING, CELL or FLOW. The big Randomize button also discovers new styles some of the time.

-

Found something you love? Save style stores it in your browser. Saved styles appear as chips you can reload with one click, and they survive page reloads.

+

风格

+

风格区域选择 9 种基础风格之一:铬、丝绸、绽放、光环、光线、半调、字形、罗纹和马赛克。每种风格都有独立的渲染器和独特的视觉特征。

+

生成风格远超基础集合:新风格运行一个 12 基因风格合成器,涵盖场类型、域几何、色彩映射、着色模型和表面叠加。每组基因都是一个独立风格,不存在于基础九种之中,以其场原型命名:FLUX(通量)、RIDGE(脊线)、WAVE(波)、RING(环)、CELL(细胞)或 FLOW(流)。随机化大按钮有时也会发现新风格。

+

找到喜欢的了?保存风格将其存储在浏览器中。保存的风格以标签形式显示,一键即可重新加载,且页面刷新后依然保留。

-

Color

+

色彩

    -
  • Presets: 16 curated palettes tuned for the renderers.
  • -
  • Swatches A to D: the four palette colors every style draws from, plus BG for the background.
  • -
  • Hue shift, saturation, exposure: grade the final image without touching the palette.
  • -
  • The dice in the section header rolls a new harmonised palette that fits the current style.
  • +
  • 预设:16 种为渲染器精心调校的调色板。
  • +
  • 色板 A 至 D:每种风格绘制所用的四种调色板颜色,加上背景色。
  • +
  • 色相偏移、饱和度、曝光:在不修改调色板的情况下调整最终图像。
  • +
  • 分区标题中的骰子按钮会随机生成一个适合当前风格的和谐调色板。
-

Form, lighting, texture, motion

+

形态、光照、纹理、运动

- - - - - - - - + + + + + + + +
SeedEvery design is deterministic. Same seed, same image. Type a number or roll a new one.
Zoom / DetailSize of the forms and the number of noise octaves.
Warp / TurbulenceHow much the field folds into itself and how much it churns while moving.
StretchAnisotropy. Positive stretches horizontally, negative vertically.
LightingIntensity, gloss tightness, light angle, iridescence and bloom glow.
TextureFilm grain, halftone and glyph density, ridge count, chromatic aberration, vignette, softness.
Loop lengthDuration of one seamless cycle, 2 to 12 seconds.
TravelHow far the field moves during one loop.
种子每个设计都是确定性的。相同种子,相同图像。输入数字或随机生成。
缩放/细节形态大小和噪声八度数。
扭曲/湍流场向自身折叠的程度和运动时的搅动程度。
拉伸各向异性。正值水平拉伸,负值垂直拉伸。
光照强度、光泽紧度、光照角度、虹彩和辉光。
纹理胶片颗粒、半调和字形密度、脊线数量、色差、暗角、柔和度。
循环长度一个无缝循环的持续时间,2 至 12 秒。
行程场在一次循环中移动的距离。
-

Share codes

-

Every design serializes to a compact code starting with LMN1. It contains the style, all five colors, the seed and every parameter.

+

分享代码

+

每个设计可序列化为以 LMN1. 开头的紧凑代码。它包含风格、全部五种颜色、种子和所有参数。

    -
  • Copy design code puts the code on your clipboard. Anyone can paste it into the Share box to get the exact same design.
  • -
  • Copy link creates a URL with the code in the hash. Opening it loads the design directly.
  • +
  • 复制设计代码将代码复制到剪贴板。任何人都可以粘贴到分享框中获得完全相同的设计。
  • +
  • 复制链接创建一个包含代码的 URL。打开链接即可直接加载设计。
-

Gradient sets

-

The Set button generates 4 to 12 variations of your current design that share the style, palette and all settings but use different seeds. They look like one family, which makes them perfect for a website: one for the hero, others for cards or section backgrounds.

-

Click a tile to apply that seed to the editor, or download the whole set as a ZIP of PNGs in one click.

+

渐变套装

+

套装按钮生成当前设计的 4 至 12 个变体,它们共享风格、调色板和所有设置,但使用不同的种子。它们看起来像一个系列,非常适合网站使用:一个用于主视觉,其他用于卡片或分区背景。

+

点击缩略图将该种子应用到编辑器,或一键下载整套 PNG 的 ZIP 文件。

-

Export

-

Each export button opens a dialog with a live preview and its settings, then downloads on confirm.

+

导出

+

每个导出按钮打开一个带有实时预览和设置的对话框,确认后下载。

- - - + + +
PNGStills up to 3840 x 2160, rendered offscreen at full quality.
WebM videoEncoded offline with WebCodecs, VP9 or VP8, muxed by a built-in WebM writer. 24, 30 or 60 fps, exact length in loops or seconds.
GIFFrame-exact seamless loop, encoded in-page with a dependency-free GIF89a encoder. Optional dithering, loop forever or play once.
PNG最高 3840×2160 静态图像,离屏全质量渲染。
WebM 视频使用 WebCodecs 离线编码,VP9 或 VP8,由内置 WebM 封装器封装。24、30 或 60 帧/秒,精确时长以循环数或秒数指定。
GIF帧精确无缝循环,使用无依赖 GIF89a 编码器在页面内编码。可选抖动,永久循环或播放一次。
-

Loops are seamless by construction: the noise field is sampled along a closed circle, so the last frame flows exactly into the first.

+

循环在构造上是无缝的:噪声场沿闭合圆采样,因此最后一帧精确过渡到第一帧。

-

Shortcuts

+

快捷键

- - - + + +
RRandomize everything
SpacePlay or pause
SSave a PNG with current settings
R随机化全部
Space播放或暂停
S以当前设置保存 PNG
-

Tech notes

-

LUMEN is a static page with zero dependencies: one WebGL2 uber-shader, a hand-written GIF encoder, a WebM muxer and a store-only ZIP writer. The source is on GitHub under the MIT license.

+

技术说明

+

LUMEN 是一个零依赖的静态页面:一个 WebGL2 超级着色器、一个手写 GIF 编码器、一个 WebM 封装器和一个仅存储的 ZIP 写入器。源代码在 GitHub 上,采用 MIT 许可证。

diff --git a/index.html b/index.html index 28d10e0..0b63718 100644 --- a/index.html +++ b/index.html @@ -1,14 +1,14 @@ - + -LUMEN, the generative shader studio - +LUMEN 生成式着色器工作室 + - - + + @@ -16,10 +16,10 @@ - + - - + + @@ -36,7 +36,7 @@ LUMEN - generative shader studio + 生成式着色器工作室
@@ -44,31 +44,31 @@
-
- - - -
- + - Docs + 文档
@@ -84,20 +84,20 @@
- ... - seed 0000 + 种子 0000
- 4.0s loop + 4.0s 循环 0×0 - 60 fps + 60 帧/秒
@@ -107,10 +107,10 @@ diff --git a/js/exporter.js b/js/exporter.js index 08e40f2..1b08dff 100644 --- a/js/exporter.js +++ b/js/exporter.js @@ -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,请使用最新版 Chrome、Edge 或 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 + ")"); } } diff --git a/js/main.js b/js/main.js index f850fa8..517a672 100644 --- a/js/main.js +++ b/js/main.js @@ -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: '' }, - { id: 1, key: "silk", name: "Silk", full: "Silk Ribbons", + { id: 1, key: "silk", name: "\u4e1d\u7ef8", full: "\u4e1d\u5e26", icon: '' }, - { id: 2, key: "bloom", name: "Bloom", full: "Soft Bloom", + { id: 2, key: "bloom", name: "\u7efd\u653e", full: "\u67d4\u5149\u7efd\u653e", icon: '' }, - { id: 3, key: "aura", name: "Aura", full: "Aura Rings", + { id: 3, key: "aura", name: "\u5149\u73af", full: "\u5149\u73af", icon: '' }, - { id: 4, key: "rays", name: "Rays", full: "Light Rays", + { id: 4, key: "rays", name: "\u5149\u7ebf", full: "\u5149\u7ebf", icon: '' }, - { id: 5, key: "halftone", name: "Halftone", full: "Halftone", + { id: 5, key: "halftone", name: "\u534a\u8c03", full: "\u534a\u8c03", icon: '' }, - { id: 6, key: "glyphs", name: "Glyphs", full: "Data Glyphs", + { id: 6, key: "glyphs", name: "\u5b57\u5f62", full: "\u6570\u636e\u5b57\u5f62", icon: '' }, - { id: 7, key: "reeded", name: "Reeded", full: "Reeded Glass", + { id: 7, key: "reeded", name: "\u7f57\u7eb9", full: "\u7f57\u7eb9\u73bb\u7483", icon: '' }, - { id: 8, key: "mosaic", name: "Mosaic", full: "Pixel Bloom", + { id: 8, key: "mosaic", name: "\u9a6c\u8d5b\u514b", full: "\u50cf\u7d20\u7efd\u653e", icon: '' } ]; @@ -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 = 'New style'; + btnSynth.innerHTML = '\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 = 'Save style'; + btnSave.innerHTML = '\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", '', function () { Modals.openExport("png"); }); - UI.exportButton(grid, "Video", "WEBM", + UI.exportButton(grid, "\u89c6\u9891", "WEBM", '', function () { Modals.openExport("video"); }); - UI.exportButton(grid, "Looping GIF", "GIF", + UI.exportButton(grid, "\u5faa\u73af GIF", "GIF", '', function () { Modals.openExport("gif"); }); - UI.exportButton(grid, "Gradient set", "ZIP", + UI.exportButton(grid, "\u6e10\u53d8\u5957\u88c5", "ZIP", '', 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.
" + + "\u9700\u8981 WebGL2\u3002\u8bf7\u4f7f\u7528\u6700\u65b0\u7248 Chrome\u3001Edge \u6216 Firefox\u3002
" + '' + String(msg).split("\n")[0] + ""; 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); diff --git a/js/modals.js b/js/modals.js index 3d10aa8..bb92d02 100644 --- a/js/modals.js +++ b/js/modals.js @@ -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 = 'Download ' + + dl.innerHTML = '下载 ' + (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 = 'Download set as ZIP'; + dl.innerHTML = '下载套装 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(); diff --git a/js/palettes.js b/js/palettes.js index e65c8f9..866d293 100644 --- a/js/palettes.js +++ b/js/palettes.js @@ -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) { diff --git a/js/ui.js b/js/ui.js index c5591a9..eaf958e 100644 --- a/js/ui.js +++ b/js/ui.js @@ -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();