完整汉化:将所有用户可见文本翻译为简体中文
- 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:
@@ -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 实时渲染,每段动画都是数学上的完美循环。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Run it
|
## 运行
|
||||||
|
|
||||||
No build step, no dependencies.
|
无构建步骤,零依赖。
|
||||||
|
|
||||||
- **Live demo:** [lumenshaders.vercel.app](https://lumenshaders.vercel.app/)
|
- **在线演示:** [lumenshaders.vercel.app](https://lumenshaders.vercel.app/)
|
||||||
- **Local:** double-click `index.html`, or run `npx http-server . -p 8080`
|
- **本地:** 双击 `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.
|
- **随机化**(或按 `R`):新风格、调色板、形态、光照和种子,按艺术风格调校,确保结果始终出色。有时会发明全新的合成风格。
|
||||||
- **Style**: 9 art modes, each with its own renderer. "Keep style" locks the mode while randomizing.
|
- **风格**:9 种艺术模式,每种都有独立的渲染器。"随机化时保持风格"在随机化时锁定模式。
|
||||||
- **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.
|
- **生成风格**:"新风格"运行 12 基因风格合成器(6 种场类型 × 5 种域几何 × 4 种色彩映射 × 4 种着色模型 × 4 种表面叠加),产生基础集合中不存在的独立风格,如 FLUX(通量)、RIDGE(脊线)、WAVE(波)、RING(环)、CELL(细胞)和 FLOW(流)原型。"保存风格"将收藏保存在浏览器中,以一键标签形式显示。
|
||||||
- **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.
|
- **渐变套装**:套装按钮渲染当前设计的 4 至 12 个变体(相同风格和调色板,不同种子),并下载为 PNG 的 ZIP 文件,非常适合在网站上使用一致的视觉艺术。
|
||||||
- **Color**: 16 curated palettes plus a harmonic palette generator, 4 editable colors + background, hue / saturation / exposure.
|
- **色彩**:16 种精选调色板加和谐调色板生成器,4 个可编辑颜色 + 背景,色相/饱和度/曝光。
|
||||||
- **Form**: seed, zoom, fbm detail, domain warp, turbulence, anisotropic stretch.
|
- **形态**:种子、缩放、fbm 细节、域扭曲、湍流、各向异性拉伸。
|
||||||
- **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.
|
- **分享**:每个设计可序列化为紧凑代码(`LMN1.…`)。复制它,发送给任何人,粘贴到分享框即可重建完全相同的设计:风格、颜色、种子、所有参数。"复制链接"生成直接加载设计的 URL。
|
||||||
- `Space` pauses, `S` saves a PNG.
|
- `空格`暂停,`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.
|
- **PNG** 最高 3840×2160,离屏全质量渲染。
|
||||||
- **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).
|
- **视频**(WebM VP9/VP8)使用 **WebCodecs**(`VideoEncoder`)离线编码,由内置无依赖 WebM 封装器封装:无 `MediaRecorder`,无实时捕获,无标签页崩溃。可配置帧率(24/30/60 帧/秒)、分辨率(720p–1440p)和精确时长(1–8 个循环或 5–60 秒)。
|
||||||
- **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.
|
- **GIF** 逐帧确定性渲染,使用无依赖 GIF89a 编码器(中值切割调色板 + 有序抖动)在页面内编码。永久循环或播放一次。
|
||||||
|
|
||||||
## Files
|
## 文件
|
||||||
|
|
||||||
- `index.html` / `styles.css`: app shell and design system
|
- `index.html` / `styles.css`:应用外壳和设计系统
|
||||||
- `js/shaders.js`: the uber fragment shader with all 9 modes
|
- `js/shaders.js`:包含全部 9 种模式的超级片段着色器
|
||||||
- `js/engine.js`: WebGL2 engine and render loop
|
- `js/engine.js`:WebGL2 引擎和渲染循环
|
||||||
- `js/gifenc.js`: GIF encoder (quantizer + LZW)
|
- `js/gifenc.js`:GIF 编码器(量化器 + LZW)
|
||||||
- `js/webmmux.js`: WebM/Matroska muxer for WebCodecs output
|
- `js/webmmux.js`:WebCodecs 输出的 WebM/Matroska 封装器
|
||||||
- `js/exporter.js`: PNG / video / GIF pipelines
|
- `js/exporter.js`:PNG / 视频 / GIF 管线
|
||||||
- `js/palettes.js`, `js/ui.js`, `js/main.js`: palettes, control builders, state and randomizer
|
- `js/palettes.js`、`js/ui.js`、`js/main.js`:调色板、控件构建器、状态和随机化器
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Docs, LUMEN generative shader studio</title>
|
<title>文档 - LUMEN 生成式着色器工作室</title>
|
||||||
<meta name="description" content="How to use LUMEN: styles, synth styles, color, motion, share codes, gradient sets and exporting PNG, video or GIF." />
|
<meta name="description" content="LUMEN 使用指南:风格、合成风格、色彩、运动、分享代码、渐变套装及导出 PNG、视频或 GIF。" />
|
||||||
<link rel="canonical" href="https://lumenshaders.vercel.app/docs.html" />
|
<link rel="canonical" href="https://lumenshaders.vercel.app/docs.html" />
|
||||||
<meta property="og:url" content="https://lumenshaders.vercel.app/docs.html" />
|
<meta property="og:url" content="https://lumenshaders.vercel.app/docs.html" />
|
||||||
<meta property="og:image" content="https://lumenshaders.vercel.app/assets/og.jpg" />
|
<meta property="og:image" content="https://lumenshaders.vercel.app/assets/og.jpg" />
|
||||||
@@ -91,88 +91,88 @@
|
|||||||
<path d="M10 5.5 L14.5 10 L10 14.5 L5.5 10 Z" fill="currentColor"/>
|
<path d="M10 5.5 L14.5 10 L10 14.5 L5.5 10 Z" fill="currentColor"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="brand-name">LUMEN</span>
|
<span class="brand-name">LUMEN</span>
|
||||||
<span class="brand-sub">documentation</span>
|
<span class="brand-sub">文档</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="topbar-actions">
|
<div class="topbar-actions">
|
||||||
<a class="btn btn-primary" href="index.html">Open the studio</a>
|
<a class="btn btn-primary" href="index.html">打开工作室</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="docs-wrap">
|
<div class="docs-wrap">
|
||||||
<div class="docs-hero">
|
<div class="docs-hero">
|
||||||
<h1>How LUMEN works</h1>
|
<h1>LUMEN 工作原理</h1>
|
||||||
<p>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.</p>
|
<p>LUMEN 在你的浏览器中生成循环抽象着色器艺术:液态铬、丝绸、柔和渐变、半调场等。所有内容均通过 WebGL2 实时渲染,每段动画都是完美循环,你的所有创作都可以导出或以代码形式分享。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="docs-section">
|
<div class="docs-section">
|
||||||
<h2>Styles</h2>
|
<h2>风格</h2>
|
||||||
<p>Pick one of the 9 base styles in the <strong>Style</strong> section: Chrome, Silk, Bloom, Aura, Rays, Halftone, Glyphs, Reeded and Mosaic. Each one is its own renderer with its own character.</p>
|
<p>在<strong>风格</strong>区域选择 9 种基础风格之一:铬、丝绸、绽放、光环、光线、半调、字形、罗纹和马赛克。每种风格都有独立的渲染器和独特的视觉特征。</p>
|
||||||
<p><strong>Generated styles</strong> go far beyond the base set: <code>New style</code> 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 <strong>Randomize</strong> button also discovers new styles some of the time.</p>
|
<p><strong>生成风格</strong>远超基础集合:<code>新风格</code>运行一个 12 基因风格合成器,涵盖场类型、域几何、色彩映射、着色模型和表面叠加。每组基因都是一个独立风格,不存在于基础九种之中,以其场原型命名:FLUX(通量)、RIDGE(脊线)、WAVE(波)、RING(环)、CELL(细胞)或 FLOW(流)。<strong>随机化</strong>大按钮有时也会发现新风格。</p>
|
||||||
<p>Found something you love? <code>Save style</code> stores it in your browser. Saved styles appear as chips you can reload with one click, and they survive page reloads.</p>
|
<p>找到喜欢的了?<code>保存风格</code>将其存储在浏览器中。保存的风格以标签形式显示,一键即可重新加载,且页面刷新后依然保留。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="docs-section">
|
<div class="docs-section">
|
||||||
<h2>Color</h2>
|
<h2>色彩</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Presets:</strong> 16 curated palettes tuned for the renderers.</li>
|
<li><strong>预设:</strong>16 种为渲染器精心调校的调色板。</li>
|
||||||
<li><strong>Swatches A to D:</strong> the four palette colors every style draws from, plus <strong>BG</strong> for the background.</li>
|
<li><strong>色板 A 至 D:</strong>每种风格绘制所用的四种调色板颜色,加上<strong>背景</strong>色。</li>
|
||||||
<li><strong>Hue shift, saturation, exposure:</strong> grade the final image without touching the palette.</li>
|
<li><strong>色相偏移、饱和度、曝光:</strong>在不修改调色板的情况下调整最终图像。</li>
|
||||||
<li>The dice in the section header rolls a new harmonised palette that fits the current style.</li>
|
<li>分区标题中的骰子按钮会随机生成一个适合当前风格的和谐调色板。</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="docs-section">
|
<div class="docs-section">
|
||||||
<h2>Form, lighting, texture, motion</h2>
|
<h2>形态、光照、纹理、运动</h2>
|
||||||
<table class="docs-table">
|
<table class="docs-table">
|
||||||
<tr><td>Seed</td><td>Every design is deterministic. Same seed, same image. Type a number or roll a new one.</td></tr>
|
<tr><td>种子</td><td>每个设计都是确定性的。相同种子,相同图像。输入数字或随机生成。</td></tr>
|
||||||
<tr><td>Zoom / Detail</td><td>Size of the forms and the number of noise octaves.</td></tr>
|
<tr><td>缩放/细节</td><td>形态大小和噪声八度数。</td></tr>
|
||||||
<tr><td>Warp / Turbulence</td><td>How much the field folds into itself and how much it churns while moving.</td></tr>
|
<tr><td>扭曲/湍流</td><td>场向自身折叠的程度和运动时的搅动程度。</td></tr>
|
||||||
<tr><td>Stretch</td><td>Anisotropy. Positive stretches horizontally, negative vertically.</td></tr>
|
<tr><td>拉伸</td><td>各向异性。正值水平拉伸,负值垂直拉伸。</td></tr>
|
||||||
<tr><td>Lighting</td><td>Intensity, gloss tightness, light angle, iridescence and bloom glow.</td></tr>
|
<tr><td>光照</td><td>强度、光泽紧度、光照角度、虹彩和辉光。</td></tr>
|
||||||
<tr><td>Texture</td><td>Film grain, halftone and glyph density, ridge count, chromatic aberration, vignette, softness.</td></tr>
|
<tr><td>纹理</td><td>胶片颗粒、半调和字形密度、脊线数量、色差、暗角、柔和度。</td></tr>
|
||||||
<tr><td>Loop length</td><td>Duration of one seamless cycle, 2 to 12 seconds.</td></tr>
|
<tr><td>循环长度</td><td>一个无缝循环的持续时间,2 至 12 秒。</td></tr>
|
||||||
<tr><td>Travel</td><td>How far the field moves during one loop.</td></tr>
|
<tr><td>行程</td><td>场在一次循环中移动的距离。</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="docs-section">
|
<div class="docs-section">
|
||||||
<h2>Share codes</h2>
|
<h2>分享代码</h2>
|
||||||
<p>Every design serializes to a compact code starting with <code>LMN1.</code> It contains the style, all five colors, the seed and every parameter.</p>
|
<p>每个设计可序列化为以 <code>LMN1.</code> 开头的紧凑代码。它包含风格、全部五种颜色、种子和所有参数。</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Copy design code</strong> puts the code on your clipboard. Anyone can paste it into the Share box to get the exact same design.</li>
|
<li><strong>复制设计代码</strong>将代码复制到剪贴板。任何人都可以粘贴到分享框中获得完全相同的设计。</li>
|
||||||
<li><strong>Copy link</strong> creates a URL with the code in the hash. Opening it loads the design directly.</li>
|
<li><strong>复制链接</strong>创建一个包含代码的 URL。打开链接即可直接加载设计。</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="docs-section">
|
<div class="docs-section">
|
||||||
<h2>Gradient sets</h2>
|
<h2>渐变套装</h2>
|
||||||
<p>The <strong>Set</strong> 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.</p>
|
<p><strong>套装</strong>按钮生成当前设计的 4 至 12 个变体,它们共享风格、调色板和所有设置,但使用不同的种子。它们看起来像一个系列,非常适合网站使用:一个用于主视觉,其他用于卡片或分区背景。</p>
|
||||||
<p>Click a tile to apply that seed to the editor, or download the whole set as a ZIP of PNGs in one click.</p>
|
<p>点击缩略图将该种子应用到编辑器,或一键下载整套 PNG 的 ZIP 文件。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="docs-section">
|
<div class="docs-section">
|
||||||
<h2>Export</h2>
|
<h2>导出</h2>
|
||||||
<p>Each export button opens a dialog with a live preview and its settings, then downloads on confirm.</p>
|
<p>每个导出按钮打开一个带有实时预览和设置的对话框,确认后下载。</p>
|
||||||
<table class="docs-table">
|
<table class="docs-table">
|
||||||
<tr><td>PNG</td><td>Stills up to 3840 x 2160, rendered offscreen at full quality.</td></tr>
|
<tr><td>PNG</td><td>最高 3840×2160 静态图像,离屏全质量渲染。</td></tr>
|
||||||
<tr><td>WebM video</td><td>Encoded offline with WebCodecs, VP9 or VP8, muxed by a built-in WebM writer. 24, 30 or 60 fps, exact length in loops or seconds.</td></tr>
|
<tr><td>WebM 视频</td><td>使用 WebCodecs 离线编码,VP9 或 VP8,由内置 WebM 封装器封装。24、30 或 60 帧/秒,精确时长以循环数或秒数指定。</td></tr>
|
||||||
<tr><td>GIF</td><td>Frame-exact seamless loop, encoded in-page with a dependency-free GIF89a encoder. Optional dithering, loop forever or play once.</td></tr>
|
<tr><td>GIF</td><td>帧精确无缝循环,使用无依赖 GIF89a 编码器在页面内编码。可选抖动,永久循环或播放一次。</td></tr>
|
||||||
</table>
|
</table>
|
||||||
<p>Loops are seamless by construction: the noise field is sampled along a closed circle, so the last frame flows exactly into the first.</p>
|
<p>循环在构造上是无缝的:噪声场沿闭合圆采样,因此最后一帧精确过渡到第一帧。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="docs-section">
|
<div class="docs-section">
|
||||||
<h2>Shortcuts</h2>
|
<h2>快捷键</h2>
|
||||||
<table class="docs-table">
|
<table class="docs-table">
|
||||||
<tr><td><span class="kbd">R</span></td><td>Randomize everything</td></tr>
|
<tr><td><span class="kbd">R</span></td><td>随机化全部</td></tr>
|
||||||
<tr><td><span class="kbd">Space</span></td><td>Play or pause</td></tr>
|
<tr><td><span class="kbd">Space</span></td><td>播放或暂停</td></tr>
|
||||||
<tr><td><span class="kbd">S</span></td><td>Save a PNG with current settings</td></tr>
|
<tr><td><span class="kbd">S</span></td><td>以当前设置保存 PNG</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="docs-section">
|
<div class="docs-section">
|
||||||
<h2>Tech notes</h2>
|
<h2>技术说明</h2>
|
||||||
<p>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 <a href="https://github.com/Leonxlnx/lumenshaders" style="color: var(--text);">GitHub</a> under the MIT license.</p>
|
<p>LUMEN 是一个零依赖的静态页面:一个 WebGL2 超级着色器、一个手写 GIF 编码器、一个 WebM 封装器和一个仅存储的 ZIP 写入器。源代码在 <a href="https://github.com/Leonxlnx/lumenshaders" style="color: var(--text);">GitHub</a> 上,采用 MIT 许可证。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+26
-26
@@ -1,14 +1,14 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>LUMEN, the generative shader studio</title>
|
<title>LUMEN 生成式着色器工作室</title>
|
||||||
<meta name="description" content="Generate looping abstract shader art: liquid chrome, silk, halftone, aura and more. Randomize, fine-tune, share design codes and export PNG, video or GIF." />
|
<meta name="description" content="生成循环抽象着色器艺术:液态铬、丝绸、半调、光环等。随机化、微调、分享设计代码并导出 PNG、视频或 GIF。" />
|
||||||
<link rel="icon" type="image/png" sizes="64x64" href="assets/favicon.png" />
|
<link rel="icon" type="image/png" sizes="64x64" href="assets/favicon.png" />
|
||||||
<link rel="apple-touch-icon" href="assets/apple-touch-icon.png" />
|
<link rel="apple-touch-icon" href="assets/apple-touch-icon.png" />
|
||||||
<meta property="og:title" content="LUMEN, the generative shader studio" />
|
<meta property="og:title" content="LUMEN 生成式着色器工作室" />
|
||||||
<meta property="og:description" content="Looping abstract shader art in your browser. Randomize, fine-tune, share design codes and export PNG, video or GIF. No dependencies." />
|
<meta property="og:description" content="在浏览器中生成循环抽象着色器艺术。随机化、微调、分享设计代码并导出 PNG、视频或 GIF。零依赖。" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:url" content="https://lumenshaders.vercel.app/" />
|
<meta property="og:url" content="https://lumenshaders.vercel.app/" />
|
||||||
<meta property="og:image" content="https://lumenshaders.vercel.app/assets/og.jpg" />
|
<meta property="og:image" content="https://lumenshaders.vercel.app/assets/og.jpg" />
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
<meta property="og:image:type" content="image/jpeg" />
|
<meta property="og:image:type" content="image/jpeg" />
|
||||||
<meta property="og:image:width" content="1200" />
|
<meta property="og:image:width" content="1200" />
|
||||||
<meta property="og:image:height" content="630" />
|
<meta property="og:image:height" content="630" />
|
||||||
<meta property="og:image:alt" content="LUMEN generative shader studio with reeded glass ember art" />
|
<meta property="og:image:alt" content="LUMEN 生成式着色器工作室,展示罗纹玻璃余晖艺术" />
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta name="twitter:title" content="LUMEN, the generative shader studio" />
|
<meta name="twitter:title" content="LUMEN 生成式着色器工作室" />
|
||||||
<meta name="twitter:description" content="Looping abstract shader art in your browser. Randomize, fine-tune, share design codes and export PNG, video or GIF." />
|
<meta name="twitter:description" content="在浏览器中生成循环抽象着色器艺术。随机化、微调、分享设计代码并导出 PNG、视频或 GIF。" />
|
||||||
<meta name="twitter:image" content="https://lumenshaders.vercel.app/assets/og.jpg" />
|
<meta name="twitter:image" content="https://lumenshaders.vercel.app/assets/og.jpg" />
|
||||||
<link rel="canonical" href="https://lumenshaders.vercel.app/" />
|
<link rel="canonical" href="https://lumenshaders.vercel.app/" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<path d="M10 5.5 L14.5 10 L10 14.5 L5.5 10 Z" fill="currentColor"/>
|
<path d="M10 5.5 L14.5 10 L10 14.5 L5.5 10 Z" fill="currentColor"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="brand-name">LUMEN</span>
|
<span class="brand-name">LUMEN</span>
|
||||||
<span class="brand-sub">generative shader studio</span>
|
<span class="brand-sub">生成式着色器工作室</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="topbar-center">
|
<div class="topbar-center">
|
||||||
@@ -44,31 +44,31 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="topbar-actions">
|
<div class="topbar-actions">
|
||||||
<button class="btn btn-primary" id="btn-random" title="Randomize everything (R)">
|
<button class="btn btn-primary" id="btn-random" title="随机化全部 (R)">
|
||||||
<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="1.5" y="1.5" width="13" height="13" rx="3" fill="none" stroke="currentColor" stroke-width="1.5"/><circle cx="5.4" cy="5.4" r="1.25" fill="currentColor"/><circle cx="10.6" cy="10.6" r="1.25" fill="currentColor"/><circle cx="10.6" cy="5.4" r="1.25" fill="currentColor"/><circle cx="5.4" cy="10.6" r="1.25" fill="currentColor"/></svg>
|
<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="1.5" y="1.5" width="13" height="13" rx="3" fill="none" stroke="currentColor" stroke-width="1.5"/><circle cx="5.4" cy="5.4" r="1.25" fill="currentColor"/><circle cx="10.6" cy="10.6" r="1.25" fill="currentColor"/><circle cx="10.6" cy="5.4" r="1.25" fill="currentColor"/><circle cx="5.4" cy="10.6" r="1.25" fill="currentColor"/></svg>
|
||||||
Randomize
|
随机化
|
||||||
</button>
|
</button>
|
||||||
<div class="topbar-divider"></div>
|
<div class="topbar-divider"></div>
|
||||||
<button class="btn" id="btn-export-png" title="Save still image (S)">
|
<button class="btn" id="btn-export-png" title="保存静态图像 (S)">
|
||||||
<svg viewBox="0 0 16 16" aria-hidden="true"><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>
|
<svg viewBox="0 0 16 16" aria-hidden="true"><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>
|
||||||
Image
|
图像
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" id="btn-export-video" title="Record looping video">
|
<button class="btn" id="btn-export-video" title="录制循环视频">
|
||||||
<svg viewBox="0 0 16 16" aria-hidden="true"><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>
|
<svg viewBox="0 0 16 16" aria-hidden="true"><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>
|
||||||
Video
|
视频
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" id="btn-export-gif" title="Render seamless looping GIF">
|
<button class="btn" id="btn-export-gif" title="渲染无缝循环 GIF">
|
||||||
<svg viewBox="0 0 16 16" aria-hidden="true"><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>
|
<svg viewBox="0 0 16 16" aria-hidden="true"><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>
|
||||||
GIF
|
GIF
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" id="btn-set" title="Generate a consistent set of variations">
|
<button class="btn" id="btn-set" title="生成一组一致的变体">
|
||||||
<svg viewBox="0 0 16 16" aria-hidden="true"><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>
|
<svg viewBox="0 0 16 16" aria-hidden="true"><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>
|
||||||
Set
|
套装
|
||||||
</button>
|
</button>
|
||||||
<div class="topbar-divider"></div>
|
<div class="topbar-divider"></div>
|
||||||
<a class="btn btn-ghost" href="docs.html" title="Documentation">
|
<a class="btn btn-ghost" href="docs.html" title="文档">
|
||||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 2 H10 L13 5 V14 H3 Z" fill="none" stroke="currentColor" stroke-width="1.4"/><path d="M5.5 7.5 H10.5 M5.5 10 H10.5" stroke="currentColor" stroke-width="1.2"/></svg>
|
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 2 H10 L13 5 V14 H3 Z" fill="none" stroke="currentColor" stroke-width="1.4"/><path d="M5.5 7.5 H10.5 M5.5 10 H10.5" stroke="currentColor" stroke-width="1.2"/></svg>
|
||||||
Docs
|
文档
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -84,20 +84,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="stage-meta">
|
<div class="stage-meta">
|
||||||
<div class="meta-left">
|
<div class="meta-left">
|
||||||
<button class="icon-btn" id="btn-play" title="Play / pause (Space)">
|
<button class="icon-btn" id="btn-play" title="播放/暂停 (空格)">
|
||||||
<svg id="icon-pause" viewBox="0 0 14 14"><rect x="2.5" y="2" width="3" height="10" rx="1" fill="currentColor"/><rect x="8.5" y="2" width="3" height="10" rx="1" fill="currentColor"/></svg>
|
<svg id="icon-pause" viewBox="0 0 14 14"><rect x="2.5" y="2" width="3" height="10" rx="1" fill="currentColor"/><rect x="8.5" y="2" width="3" height="10" rx="1" fill="currentColor"/></svg>
|
||||||
<svg id="icon-play" viewBox="0 0 14 14" style="display:none"><path d="M3.5 2 L12 7 L3.5 12 Z" fill="currentColor"/></svg>
|
<svg id="icon-play" viewBox="0 0 14 14" style="display:none"><path d="M3.5 2 L12 7 L3.5 12 Z" fill="currentColor"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<span class="meta-item mono" id="meta-mode">...</span>
|
<span class="meta-item mono" id="meta-mode">...</span>
|
||||||
<span class="meta-sep"></span>
|
<span class="meta-sep"></span>
|
||||||
<span class="meta-item mono dim" id="meta-seed">seed 0000</span>
|
<span class="meta-item mono dim" id="meta-seed">种子 0000</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta-right">
|
<div class="meta-right">
|
||||||
<span class="meta-item mono dim" id="meta-loop">4.0s loop</span>
|
<span class="meta-item mono dim" id="meta-loop">4.0s 循环</span>
|
||||||
<span class="meta-sep"></span>
|
<span class="meta-sep"></span>
|
||||||
<span class="meta-item mono dim" id="meta-res">0×0</span>
|
<span class="meta-item mono dim" id="meta-res">0×0</span>
|
||||||
<span class="meta-sep"></span>
|
<span class="meta-sep"></span>
|
||||||
<span class="meta-item mono dim" id="meta-fps">60 fps</span>
|
<span class="meta-item mono dim" id="meta-fps">60 帧/秒</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -107,10 +107,10 @@
|
|||||||
|
|
||||||
<div class="overlay" id="overlay" hidden>
|
<div class="overlay" id="overlay" hidden>
|
||||||
<div class="overlay-card">
|
<div class="overlay-card">
|
||||||
<div class="overlay-title" id="overlay-title">Rendering</div>
|
<div class="overlay-title" id="overlay-title">渲染中</div>
|
||||||
<div class="overlay-detail mono" id="overlay-detail">...</div>
|
<div class="overlay-detail mono" id="overlay-detail">...</div>
|
||||||
<div class="progress"><div class="progress-fill" id="overlay-bar"></div></div>
|
<div class="progress"><div class="progress-fill" id="overlay-bar"></div></div>
|
||||||
<button class="btn overlay-cancel" id="overlay-cancel">Cancel</button>
|
<button class="btn overlay-cancel" id="overlay-cancel">取消</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+14
-14
@@ -9,7 +9,7 @@ var Exporter = (function () {
|
|||||||
function showOverlay(title) {
|
function showOverlay(title) {
|
||||||
cancelled = false;
|
cancelled = false;
|
||||||
$("overlay-title").textContent = title;
|
$("overlay-title").textContent = title;
|
||||||
$("overlay-detail").textContent = "preparing";
|
$("overlay-detail").textContent = "准备中";
|
||||||
$("overlay-bar").style.width = "0%";
|
$("overlay-bar").style.width = "0%";
|
||||||
$("overlay").hidden = false;
|
$("overlay").hidden = false;
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ var Exporter = (function () {
|
|||||||
busy = false;
|
busy = false;
|
||||||
if (blob) {
|
if (blob) {
|
||||||
download(blob, stamp(P, "png"));
|
download(blob, stamp(P, "png"));
|
||||||
FX.celebrate("Saved " + w + "\u00d7" + h + " PNG");
|
FX.celebrate("已保存 " + w + "\u00d7" + h + " PNG");
|
||||||
}
|
}
|
||||||
}, "image/png");
|
}, "image/png");
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ var Exporter = (function () {
|
|||||||
async function exportVideo(P, aspect) {
|
async function exportVideo(P, aspect) {
|
||||||
if (busy) return;
|
if (busy) return;
|
||||||
if (typeof VideoEncoder === "undefined" || typeof VideoFrame === "undefined") {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
busy = true;
|
busy = true;
|
||||||
@@ -104,7 +104,7 @@ var Exporter = (function () {
|
|||||||
var totalSec = videoDurationSec(P);
|
var totalSec = videoDurationSec(P);
|
||||||
var nFrames = Math.max(2, Math.round(totalSec * fps));
|
var nFrames = Math.max(2, Math.round(totalSec * fps));
|
||||||
|
|
||||||
showOverlay("Rendering video");
|
showOverlay("渲染视频中");
|
||||||
Engine.suspend();
|
Engine.suspend();
|
||||||
Engine.setPlaying(false);
|
Engine.setPlaying(false);
|
||||||
Engine.setSize(w, h);
|
Engine.setSize(w, h);
|
||||||
@@ -112,7 +112,7 @@ var Exporter = (function () {
|
|||||||
var picked = await pickEncoderConfig(w, h, fps);
|
var picked = await pickEncoderConfig(w, h, fps);
|
||||||
if (!picked) {
|
if (!picked) {
|
||||||
restore();
|
restore();
|
||||||
UI.toast("No supported video codec (VP9/VP8) found");
|
UI.toast("未找到支持的视频编码器 (VP9/VP8)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ var Exporter = (function () {
|
|||||||
vf.close();
|
vf.close();
|
||||||
|
|
||||||
setProgress(0.9 * (f + 1) / nFrames,
|
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 */
|
/* backpressure: never let the encoder queue grow unbounded */
|
||||||
while (encoder.encodeQueueSize > 2) await wait(2);
|
while (encoder.encodeQueueSize > 2) await wait(2);
|
||||||
@@ -160,7 +160,7 @@ var Exporter = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!cancelled && !encError) {
|
if (!cancelled && !encError) {
|
||||||
setProgress(0.93, "finalizing encode");
|
setProgress(0.93, "完成编码");
|
||||||
await encoder.flush();
|
await encoder.flush();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -177,11 +177,11 @@ var Exporter = (function () {
|
|||||||
if (cancelled) { hideOverlay(); busy = false; return; }
|
if (cancelled) { hideOverlay(); busy = false; return; }
|
||||||
if (encError || !encFrames.length) {
|
if (encError || !encFrames.length) {
|
||||||
hideOverlay(); busy = false;
|
hideOverlay(); busy = false;
|
||||||
UI.toast("Video encode failed" + (encError && encError.message ? ": " + encError.message : ""));
|
UI.toast("视频编码失败" + (encError && encError.message ? ":" + encError.message : ""));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setProgress(0.97, "writing webm container");
|
setProgress(0.97, "写入 WebM 容器");
|
||||||
await wait(0);
|
await wait(0);
|
||||||
var webm = WebMMux.mux({
|
var webm = WebMMux.mux({
|
||||||
codecId: picked.codecId,
|
codecId: picked.codecId,
|
||||||
@@ -193,7 +193,7 @@ var Exporter = (function () {
|
|||||||
hideOverlay();
|
hideOverlay();
|
||||||
busy = false;
|
busy = false;
|
||||||
download(new Blob([webm], { type: "video/webm" }), stamp(P, "webm"));
|
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() {
|
function restore() {
|
||||||
Engine.setSize(prev[0], prev[1]);
|
Engine.setSize(prev[0], prev[1]);
|
||||||
@@ -218,7 +218,7 @@ var Exporter = (function () {
|
|||||||
var fps = parseInt(P.gifFps, 10);
|
var fps = parseInt(P.gifFps, 10);
|
||||||
var nFrames = Math.max(2, Math.round(P.loop * fps));
|
var nFrames = Math.max(2, Math.round(P.loop * fps));
|
||||||
|
|
||||||
showOverlay("Rendering GIF");
|
showOverlay("渲染 GIF 中");
|
||||||
Engine.setSize(w, h);
|
Engine.setSize(w, h);
|
||||||
|
|
||||||
var frames = [];
|
var frames = [];
|
||||||
@@ -226,7 +226,7 @@ var Exporter = (function () {
|
|||||||
if (cancelled) break;
|
if (cancelled) break;
|
||||||
Engine.renderAt(f / nFrames);
|
Engine.renderAt(f / nFrames);
|
||||||
frames.push(Engine.readPixels());
|
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);
|
if (f % 4 === 3) await wait(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +238,7 @@ var Exporter = (function () {
|
|||||||
var data = await GIFEnc.encode({
|
var data = await GIFEnc.encode({
|
||||||
frames: frames, width: w, height: h, fps: fps,
|
frames: frames, width: w, height: h, fps: fps,
|
||||||
dither: P.gifDither, loop: P.gifLoop,
|
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; }
|
isCancelled: function () { return cancelled; }
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -246,7 +246,7 @@ var Exporter = (function () {
|
|||||||
busy = false;
|
busy = false;
|
||||||
if (data && !cancelled) {
|
if (data && !cancelled) {
|
||||||
download(new Blob([data], { type: "image/gif" }), stamp(P, "gif"));
|
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
@@ -3,23 +3,23 @@
|
|||||||
/* ---------------- modes ---------------- */
|
/* ---------------- modes ---------------- */
|
||||||
|
|
||||||
var 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>' },
|
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>' },
|
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>' },
|
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>' },
|
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>' },
|
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>' },
|
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>' },
|
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>' },
|
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>' }
|
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;
|
if (list.length > 24) list.length = 24;
|
||||||
persistSavedStyles(list);
|
persistSavedStyles(list);
|
||||||
renderSavedStyles();
|
renderSavedStyles();
|
||||||
UI.toast("Style saved: " + name);
|
UI.toast("\u98ce\u683c\u5df2\u4fdd\u5b58\uff1a" + name);
|
||||||
}
|
}
|
||||||
var renderSavedStyles = function () {};
|
var renderSavedStyles = function () {};
|
||||||
|
|
||||||
@@ -360,9 +360,9 @@ function copyText(text, okMsg) {
|
|||||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
navigator.clipboard.writeText(text).then(
|
navigator.clipboard.writeText(text).then(
|
||||||
function () { UI.toast(okMsg); },
|
function () { UI.toast(okMsg); },
|
||||||
function () { UI.toast("Could not access clipboard"); });
|
function () { UI.toast("\u65e0\u6cd5\u8bbf\u95ee\u526a\u8d34\u677f"); });
|
||||||
} else {
|
} 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");
|
var rail = document.getElementById("rail");
|
||||||
|
|
||||||
/* STYLE */
|
/* STYLE */
|
||||||
var sStyle = UI.section(rail, "Style", function () {
|
var sStyle = UI.section(rail, "\u98ce\u683c", function () {
|
||||||
P.synthOn = false;
|
P.synthOn = false;
|
||||||
P.mode = Math.floor(rnd() * MODES.length);
|
P.mode = Math.floor(rnd() * MODES.length);
|
||||||
refreshAll();
|
refreshAll();
|
||||||
@@ -399,23 +399,23 @@ function buildRail() {
|
|||||||
/* genome: brand-new generated styles */
|
/* genome: brand-new generated styles */
|
||||||
var synthRow = UI.el("div", "share-row synth-row", sStyle);
|
var synthRow = UI.el("div", "share-row synth-row", sStyle);
|
||||||
var btnSynth = UI.el("button", "mini-btn", synthRow);
|
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 () {
|
btnSynth.addEventListener("click", function () {
|
||||||
if (!Engine.hasGenome()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
generateGenomeStyle();
|
generateGenomeStyle();
|
||||||
newSeed();
|
newSeed();
|
||||||
refreshAll();
|
refreshAll();
|
||||||
UI.toast("New style discovered: " + genomeName());
|
UI.toast("\u53d1\u73b0\u65b0\u98ce\u683c\uff1a" + genomeName());
|
||||||
});
|
});
|
||||||
var btnSave = UI.el("button", "mini-btn", synthRow);
|
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);
|
btnSave.addEventListener("click", saveCurrentStyle);
|
||||||
|
|
||||||
var synthCtl = UI.el("div", "synth-ctl", sStyle);
|
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(); } }));
|
get: get("blend"), set: function (v) { P.blend = v; updateMeta(); } }));
|
||||||
reg(function () { synthCtl.style.display = P.synthOn ? "" : "none"; });
|
reg(function () { synthCtl.style.display = P.synthOn ? "" : "none"; });
|
||||||
|
|
||||||
@@ -437,16 +437,16 @@ function buildRail() {
|
|||||||
renderSavedStyles();
|
renderSavedStyles();
|
||||||
});
|
});
|
||||||
chip.addEventListener("click", function () {
|
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();
|
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 */
|
/* 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) {
|
setPresetActive = UI.presetChips(sColor, PALETTES, function (i) {
|
||||||
applyPalette(PALETTES[i], i);
|
applyPalette(PALETTES[i], i);
|
||||||
refreshAll();
|
refreshAll();
|
||||||
@@ -457,69 +457,69 @@ function buildRail() {
|
|||||||
{ gap: true }, { key: "bg", label: "BG" }],
|
{ gap: true }, { key: "bg", label: "BG" }],
|
||||||
function (k) { return P[k]; },
|
function (k) { return P[k]; },
|
||||||
function (k, v) { P[k] = v; activePreset = -1; setPresetActive(-1); }));
|
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: "\u8272\u76f8\u504f\u79fb", 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: "\u9971\u548c\u5ea6", 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: "\u66dd\u5149", min: 0.5, max: 1.6, step: 0.01, fmt: fmt2, get: get("exposure"), set: set("exposure") }));
|
||||||
|
|
||||||
/* FORM */
|
/* 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.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: "\u7f29\u653e", 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: "\u7ec6\u8282", 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: "\u626d\u66f2", 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: "\u6e4d\u6d41", 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: "\u62c9\u4f38", min: -1, max: 1, step: 0.01, fmt: fmt2, get: get("stretch"), set: set("stretch") }));
|
||||||
|
|
||||||
/* LIGHTING */
|
/* LIGHTING */
|
||||||
var sLight = UI.section(rail, "Lighting", function () { randomizeKeys(LIGHT_KEYS); refreshAll(); });
|
var sLight = UI.section(rail, "\u5149\u7167", 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: "\u5f3a\u5ea6", 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: "\u5149\u6cfd", 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: "\u89d2\u5ea6", 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: "\u8679\u5f69", 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: "\u8f89\u5149", 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") }));
|
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 */
|
/* TEXTURE */
|
||||||
var sTex = UI.section(rail, "Texture", function () { randomizeKeys(TEXTURE_KEYS); refreshAll(); });
|
var sTex = UI.section(rail, "\u7eb9\u7406", 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: "\u9897\u7c92", 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: "\u5bc6\u5ea6", 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: "\u810a\u7ebf", 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: "\u8272\u5dee", 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: "\u6697\u89d2", 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") }));
|
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 */
|
/* MOTION */
|
||||||
var sMotion = UI.section(rail, "Motion", function () { randomizeKeys(MOTION_KEYS); refreshAll(); });
|
var sMotion = UI.section(rail, "\u8fd0\u52a8", 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: "\u5faa\u73af\u957f\u5ea6", 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") }));
|
reg(UI.slider(sMotion, { label: "\u884c\u7a0b", min: 0, max: 1.5, step: 0.01, fmt: fmt2, get: get("travel"), set: set("travel") }));
|
||||||
|
|
||||||
/* SHARE */
|
/* 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 shareRow = UI.el("div", "share-row", sShare);
|
||||||
var btnCopyCode = UI.el("button", "mini-btn", shareRow);
|
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 () {
|
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);
|
var btnCopyLink = UI.el("button", "mini-btn", shareRow);
|
||||||
btnCopyLink.textContent = "Copy link";
|
btnCopyLink.textContent = "\u590d\u5236\u94fe\u63a5";
|
||||||
btnCopyLink.addEventListener("click", function () {
|
btnCopyLink.addEventListener("click", function () {
|
||||||
var url = location.origin + location.pathname + "#" + encodeDesign();
|
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);
|
var pasteInput = UI.el("input", "num-input mono share-input", sShare);
|
||||||
pasteInput.type = "text";
|
pasteInput.type = "text";
|
||||||
pasteInput.placeholder = "Paste a design code\u2026";
|
pasteInput.placeholder = "\u7c98\u8d34\u8bbe\u8ba1\u4ee3\u7801\u2026";
|
||||||
pasteInput.spellcheck = false;
|
pasteInput.spellcheck = false;
|
||||||
function tryApplyPaste() {
|
function tryApplyPaste() {
|
||||||
var v = pasteInput.value.trim();
|
var v = pasteInput.value.trim();
|
||||||
if (!v) return;
|
if (!v) return;
|
||||||
if (decodeDesign(v)) {
|
if (decodeDesign(v)) {
|
||||||
UI.toast("Design loaded");
|
UI.toast("\u8bbe\u8ba1\u5df2\u52a0\u8f7d");
|
||||||
pasteInput.value = "";
|
pasteInput.value = "";
|
||||||
pasteInput.blur();
|
pasteInput.blur();
|
||||||
} else {
|
} else {
|
||||||
UI.toast("Invalid design code");
|
UI.toast("\u65e0\u6548\u7684\u8bbe\u8ba1\u4ee3\u7801");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pasteInput.addEventListener("paste", function () { setTimeout(tryApplyPaste, 0); });
|
pasteInput.addEventListener("paste", function () { setTimeout(tryApplyPaste, 0); });
|
||||||
@@ -529,18 +529,18 @@ function buildRail() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/* EXPORT: buttons open a dialog with preview and settings */
|
/* 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);
|
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>',
|
'<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"); });
|
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>',
|
'<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"); });
|
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>',
|
'<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"); });
|
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>',
|
'<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(); });
|
function () { Modals.openSetGenerator(); });
|
||||||
}
|
}
|
||||||
@@ -549,8 +549,8 @@ function buildRail() {
|
|||||||
|
|
||||||
function updateMeta() {
|
function updateMeta() {
|
||||||
document.getElementById("meta-mode").textContent = styleName();
|
document.getElementById("meta-mode").textContent = styleName();
|
||||||
document.getElementById("meta-seed").textContent = "seed " + String(Math.round(P.seed)).padStart(4, "0");
|
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 loop";
|
document.getElementById("meta-loop").textContent = P.loop.toFixed(1) + "s \u5faa\u73af";
|
||||||
if (Engine.isReady()) {
|
if (Engine.isReady()) {
|
||||||
var s = Engine.size();
|
var s = Engine.size();
|
||||||
document.getElementById("meta-res").textContent = s[0] + "\u00d7" + s[1];
|
document.getElementById("meta-res").textContent = s[0] + "\u00d7" + s[1];
|
||||||
@@ -654,7 +654,7 @@ function showStageError(msg) {
|
|||||||
var el = document.getElementById("stage-error");
|
var el = document.getElementById("stage-error");
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
el.innerHTML =
|
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>";
|
'<span style="color:#5e5e68;font-size:11px">' + String(msg).split("\n")[0] + "</span>";
|
||||||
el.hidden = false;
|
el.hidden = false;
|
||||||
}
|
}
|
||||||
@@ -673,8 +673,8 @@ function onContextLost() {
|
|||||||
detachStageResize();
|
detachStageResize();
|
||||||
Engine.suspend();
|
Engine.suspend();
|
||||||
document.body.classList.remove("ready");
|
document.body.classList.remove("ready");
|
||||||
showStageError("WebGL context lost. Reload the page.");
|
showStageError("WebGL \u4e0a\u4e0b\u6587\u4e22\u5931\u3002\u8bf7\u5237\u65b0\u9875\u9762\u3002");
|
||||||
UI.toast("WebGL context lost. Reload the page.");
|
UI.toast("WebGL \u4e0a\u4e0b\u6587\u4e22\u5931\u3002\u8bf7\u5237\u65b0\u9875\u9762\u3002");
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
@@ -707,7 +707,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
|
|
||||||
var hash = decodeURIComponent(location.hash.slice(1) || "");
|
var hash = decodeURIComponent(location.hash.slice(1) || "");
|
||||||
if (hash.indexOf("LMN1.") === 0 && decodeDesign(hash)) {
|
if (hash.indexOf("LMN1.") === 0 && decodeDesign(hash)) {
|
||||||
UI.toast("Shared design loaded");
|
UI.toast("\u5df2\u52a0\u8f7d\u5206\u4eab\u7684\u8bbe\u8ba1");
|
||||||
}
|
}
|
||||||
|
|
||||||
fitCanvas(false);
|
fitCanvas(false);
|
||||||
|
|||||||
+26
-26
@@ -73,8 +73,8 @@ var Modals = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openExport(kind) {
|
function openExport(kind) {
|
||||||
var titles = { png: "Export image", video: "Export video", gif: "Export GIF" };
|
var titles = { png: "导出图像", video: "导出视频", gif: "导出 GIF" };
|
||||||
var body = open(titles[kind], MODES[P.mode].full + " \u00b7 seed " + Math.round(P.seed));
|
var body = open(titles[kind], MODES[P.mode].full + " \u00b7 种子 " + Math.round(P.seed));
|
||||||
|
|
||||||
/* live preview */
|
/* live preview */
|
||||||
var prevWrap = el("div", "modal-preview", body);
|
var prevWrap = el("div", "modal-preview", body);
|
||||||
@@ -101,40 +101,40 @@ var Modals = (function () {
|
|||||||
var sec = (String(P.vidLen).charAt(0) === "s")
|
var sec = (String(P.vidLen).charAt(0) === "s")
|
||||||
? parseInt(String(P.vidLen).slice(1), 10)
|
? parseInt(String(P.vidLen).slice(1), 10)
|
||||||
: P.loop * 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 " +
|
return Math.round(vh * ar) + " \u00d7 " + vh + " \u00b7 " + fps + " 帧/秒 \u00b7 " +
|
||||||
sec.toFixed(1) + "s \u00b7 " + Math.round(sec * fps) + " frames";
|
sec.toFixed(1) + "s \u00b7 " + Math.round(sec * fps) + " 帧";
|
||||||
}
|
}
|
||||||
var gw = parseInt(P.gifW, 10);
|
var gw = parseInt(P.gifW, 10);
|
||||||
return gw + " \u00d7 " + Math.round(gw / ar) + " \u00b7 " + P.gifFps + " fps \u00b7 " +
|
return gw + " \u00d7 " + Math.round(gw / ar) + " \u00b7 " + P.gifFps + " 帧/秒 \u00b7 " +
|
||||||
Math.round(P.loop * P.gifFps) + " frames \u00b7 " + P.loop.toFixed(1) + "s loop";
|
Math.round(P.loop * P.gifFps) + " 帧 \u00b7 " + P.loop.toFixed(1) + "s 循环";
|
||||||
}
|
}
|
||||||
function refreshMeta() { pvMeta.textContent = metaText(); }
|
function refreshMeta() { pvMeta.textContent = metaText(); }
|
||||||
refreshMeta();
|
refreshMeta();
|
||||||
|
|
||||||
if (kind === "png") {
|
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(); });
|
P.imgRes, function (v) { P.imgRes = v; refreshMeta(); });
|
||||||
} else if (kind === "video") {
|
} 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(); });
|
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(); });
|
P.vidFps, function (v) { P.vidFps = v; refreshMeta(); });
|
||||||
selectField(form, "Length", [
|
selectField(form, "时长", [
|
||||||
["l1", "1 loop"], ["l2", "2 loops"], ["l3", "3 loops"], ["l4", "4 loops"], ["l6", "6 loops"], ["l8", "8 loops"],
|
["l1", "1 个循环"], ["l2", "2 个循环"], ["l3", "3 个循环"], ["l4", "4 个循环"], ["l6", "6 个循环"], ["l8", "8 个循环"],
|
||||||
["s5", "5 seconds"], ["s10", "10 seconds"], ["s15", "15 seconds"], ["s30", "30 seconds"], ["s60", "60 seconds"]
|
["s5", "5 秒"], ["s10", "10 秒"], ["s15", "15 秒"], ["s30", "30 秒"], ["s60", "60 秒"]
|
||||||
], P.vidLen, function (v) { P.vidLen = v; refreshMeta(); });
|
], P.vidLen, function (v) { P.vidLen = v; refreshMeta(); });
|
||||||
} else {
|
} 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(); });
|
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(); });
|
String(P.gifFps), function (v) { P.gifFps = parseInt(v, 10); refreshMeta(); });
|
||||||
toggleField(form, "Dithering", P.gifDither, function (v) { P.gifDither = v; });
|
toggleField(form, "抖动", P.gifDither, function (v) { P.gifDither = v; });
|
||||||
toggleField(form, "Loop forever", P.gifLoop, function (v) { P.gifLoop = v; });
|
toggleField(form, "永久循环", P.gifLoop, function (v) { P.gifLoop = v; });
|
||||||
}
|
}
|
||||||
|
|
||||||
var actions = el("div", "modal-actions", body);
|
var actions = el("div", "modal-actions", body);
|
||||||
var dl = el("button", "btn btn-primary modal-dl", actions);
|
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");
|
(kind === "png" ? "PNG" : kind === "video" ? "WebM" : "GIF");
|
||||||
dl.addEventListener("click", function () {
|
dl.addEventListener("click", function () {
|
||||||
close();
|
close();
|
||||||
@@ -154,21 +154,21 @@ var Modals = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function openSetGenerator() {
|
async function openSetGenerator() {
|
||||||
var body = open("Gradient set", "consistent variations of the current design");
|
var body = open("渐变套装", "当前设计的一致变体");
|
||||||
var info = el("div", "modal-note", body);
|
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 form = el("div", "modal-form", body);
|
||||||
var state = { count: 6, res: "1080" };
|
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(); });
|
"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; });
|
"1080", function (v) { state.res = v; });
|
||||||
|
|
||||||
var grid = el("div", "set-grid", body);
|
var grid = el("div", "set-grid", body);
|
||||||
var actions = el("div", "modal-actions", body);
|
var actions = el("div", "modal-actions", body);
|
||||||
var dl = el("button", "btn btn-primary modal-dl", actions);
|
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 = [];
|
var seeds = [];
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@ var Modals = (function () {
|
|||||||
tile.addEventListener("click", function () {
|
tile.addEventListener("click", function () {
|
||||||
P.seed = sd;
|
P.seed = sd;
|
||||||
refreshAll();
|
refreshAll();
|
||||||
UI.toast("Applied seed " + sd);
|
UI.toast("已应用种子 " + sd);
|
||||||
close();
|
close();
|
||||||
});
|
});
|
||||||
})(seeds[i]);
|
})(seeds[i]);
|
||||||
@@ -212,7 +212,7 @@ var Modals = (function () {
|
|||||||
|
|
||||||
dl.addEventListener("click", async function () {
|
dl.addEventListener("click", async function () {
|
||||||
dl.disabled = true;
|
dl.disabled = true;
|
||||||
dl.textContent = "Rendering\u2026";
|
dl.textContent = "渲染中\u2026";
|
||||||
var ar = ASPECTS[P.aspect];
|
var ar = ASPECTS[P.aspect];
|
||||||
var h = parseInt(state.res, 10);
|
var h = parseInt(state.res, 10);
|
||||||
var w = 2 * Math.round(h * ar / 2);
|
var w = 2 * Math.round(h * ar / 2);
|
||||||
@@ -235,7 +235,7 @@ var Modals = (function () {
|
|||||||
data: new Uint8Array(await blob.arrayBuffer())
|
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); });
|
await new Promise(function (r) { setTimeout(r, 0); });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,7 +251,7 @@ var Modals = (function () {
|
|||||||
a.click();
|
a.click();
|
||||||
setTimeout(function () { URL.revokeObjectURL(a.href); }, 4000);
|
setTimeout(function () { URL.revokeObjectURL(a.href); }, 4000);
|
||||||
close();
|
close();
|
||||||
FX.celebrate("Set saved: " + entries.length + " PNGs (" + w + "\u00d7" + h + ")");
|
FX.celebrate("套装已保存:" + entries.length + " 张 PNG (" + w + "\u00d7" + h + ")");
|
||||||
});
|
});
|
||||||
|
|
||||||
await build();
|
await build();
|
||||||
|
|||||||
+17
-17
@@ -2,37 +2,37 @@
|
|||||||
tone: 'dark' | 'light' decides which modes prefer them. */
|
tone: 'dark' | 'light' decides which modes prefer them. */
|
||||||
|
|
||||||
var PALETTES = [
|
var PALETTES = [
|
||||||
{ name: "Inferno Chrome", tone: "dark",
|
{ name: "炼狱铬", tone: "dark",
|
||||||
bg: "#050507", colors: ["#e0220a", "#ff5a1f", "#1f8cff", "#bfe7ff"] },
|
bg: "#050507", colors: ["#e0220a", "#ff5a1f", "#1f8cff", "#bfe7ff"] },
|
||||||
{ name: "Neon Silk", tone: "dark",
|
{ name: "霓虹丝绸", tone: "dark",
|
||||||
bg: "#040406", colors: ["#19e3e3", "#ff2d78", "#ff7a1a", "#7a2dff"] },
|
bg: "#040406", colors: ["#19e3e3", "#ff2d78", "#ff7a1a", "#7a2dff"] },
|
||||||
{ name: "Ultraviolet", tone: "dark",
|
{ name: "紫外线", tone: "dark",
|
||||||
bg: "#06040c", colors: ["#2440ff", "#8a2bff", "#e22bd0", "#ff5470"] },
|
bg: "#06040c", colors: ["#2440ff", "#8a2bff", "#e22bd0", "#ff5470"] },
|
||||||
{ name: "Ember", tone: "dark",
|
{ name: "余晖", tone: "dark",
|
||||||
bg: "#070403", colors: ["#ff6a00", "#ffb347", "#a81c00", "#3d0c02"] },
|
bg: "#070403", colors: ["#ff6a00", "#ffb347", "#a81c00", "#3d0c02"] },
|
||||||
{ name: "Deep Signal", tone: "dark",
|
{ name: "深信号", tone: "dark",
|
||||||
bg: "#030608", colors: ["#0e3a5c", "#2e7fb8", "#9fd4e8", "#16222e"] },
|
bg: "#030608", colors: ["#0e3a5c", "#2e7fb8", "#9fd4e8", "#16222e"] },
|
||||||
{ name: "Red Telemetry", tone: "dark",
|
{ name: "红色遥测", tone: "dark",
|
||||||
bg: "#0a0202", colors: ["#ff2414", "#c81204", "#ff7a5c", "#5c0a02"] },
|
bg: "#0a0202", colors: ["#ff2414", "#c81204", "#ff7a5c", "#5c0a02"] },
|
||||||
{ name: "Ghost Mono", tone: "dark",
|
{ name: "幽灵单色", tone: "dark",
|
||||||
bg: "#030304", colors: ["#f2f2f4", "#9a9aa6", "#3c3c46", "#c8c8d2"] },
|
bg: "#030304", colors: ["#f2f2f4", "#9a9aa6", "#3c3c46", "#c8c8d2"] },
|
||||||
{ name: "Acid Garden", tone: "dark",
|
{ name: "酸性花园", tone: "dark",
|
||||||
bg: "#04070a", colors: ["#b8ff2e", "#1fd9a4", "#0a7a5c", "#eaffd0"] },
|
bg: "#04070a", colors: ["#b8ff2e", "#1fd9a4", "#0a7a5c", "#eaffd0"] },
|
||||||
{ name: "Blush", tone: "light",
|
{ name: "绯红", tone: "light",
|
||||||
bg: "#fbf6f2", colors: ["#d4607a", "#f0b890", "#fde8d8", "#b8434f"] },
|
bg: "#fbf6f2", colors: ["#d4607a", "#f0b890", "#fde8d8", "#b8434f"] },
|
||||||
{ name: "Prism Pastel", tone: "light",
|
{ name: "棱镜粉彩", tone: "light",
|
||||||
bg: "#f4f1fa", colors: ["#ffb340", "#2b3bd4", "#ff4f9a", "#9a8cff"] },
|
bg: "#f4f1fa", colors: ["#ffb340", "#2b3bd4", "#ff4f9a", "#9a8cff"] },
|
||||||
{ name: "Sky Aura", tone: "light",
|
{ name: "天空光环", tone: "light",
|
||||||
bg: "#e8edfb", colors: ["#2451e8", "#6fa3f5", "#f0a8c8", "#fdfdff"] },
|
bg: "#e8edfb", colors: ["#2451e8", "#6fa3f5", "#f0a8c8", "#fdfdff"] },
|
||||||
{ name: "Halo", tone: "light",
|
{ name: "光晕", tone: "light",
|
||||||
bg: "#f4f6ff", colors: ["#4a30e0", "#7a8cff", "#e89ab8", "#c2d4ff"] },
|
bg: "#f4f6ff", colors: ["#4a30e0", "#7a8cff", "#e89ab8", "#c2d4ff"] },
|
||||||
{ name: "Solar Flare", tone: "light",
|
{ name: "太阳耀斑", tone: "light",
|
||||||
bg: "#fefcf8", colors: ["#e8401c", "#ff8a2a", "#ffc04a", "#a82408"] },
|
bg: "#fefcf8", colors: ["#e8401c", "#ff8a2a", "#ffc04a", "#a82408"] },
|
||||||
{ name: "Glacier", tone: "light",
|
{ name: "冰川", tone: "light",
|
||||||
bg: "#f8fafc", colors: ["#1a56d6", "#4a9af0", "#a8d4ff", "#0a2a6e"] },
|
bg: "#f8fafc", colors: ["#1a56d6", "#4a9af0", "#a8d4ff", "#0a2a6e"] },
|
||||||
{ name: "Tangerine Glass", tone: "dark",
|
{ name: "橘色玻璃", tone: "dark",
|
||||||
bg: "#1c1e22", colors: ["#ff7a14", "#e8e4dc", "#3a3e46", "#ffb066"] },
|
bg: "#1c1e22", colors: ["#ff7a14", "#e8e4dc", "#3a3e46", "#ffb066"] },
|
||||||
{ name: "Velvet Dusk", tone: "dark",
|
{ name: "丝绒黄昏", tone: "dark",
|
||||||
bg: "#08050c", colors: ["#ff3d2e", "#ff8c5a", "#5a1eb8", "#1a0a3c"] }
|
bg: "#08050c", colors: ["#ff3d2e", "#ff8c5a", "#5a1eb8", "#1a0a3c"] }
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ function generateRandomPalette(rand, tone) {
|
|||||||
var bg = dark
|
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.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);
|
: 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) {
|
function hslToHex(h, s, l) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ var UI = (function () {
|
|||||||
if (onDice) {
|
if (onDice) {
|
||||||
var d = el("button", "section-dice", head);
|
var d = el("button", "section-dice", head);
|
||||||
d.innerHTML = DICE_SVG;
|
d.innerHTML = DICE_SVG;
|
||||||
d.title = "Randomize " + title.toLowerCase();
|
d.title = "随机化" + title;
|
||||||
d.addEventListener("click", onDice);
|
d.addEventListener("click", onDice);
|
||||||
}
|
}
|
||||||
return sec;
|
return sec;
|
||||||
@@ -168,7 +168,7 @@ var UI = (function () {
|
|||||||
opts.set(Math.max(0, Math.min(9999, parseInt(input.value, 10) || 0)));
|
opts.set(Math.max(0, Math.min(9999, parseInt(input.value, 10) || 0)));
|
||||||
});
|
});
|
||||||
var btn = el("button", "mini-btn", row);
|
var btn = el("button", "mini-btn", row);
|
||||||
btn.innerHTML = DICE_SVG + "New seed";
|
btn.innerHTML = DICE_SVG + "新种子";
|
||||||
btn.addEventListener("click", opts.onDice);
|
btn.addEventListener("click", opts.onDice);
|
||||||
function refresh() { input.value = Math.round(opts.get()); }
|
function refresh() { input.value = Math.round(opts.get()); }
|
||||||
refresh();
|
refresh();
|
||||||
|
|||||||
Reference in New Issue
Block a user