From 4b378d1b906c66e8e3413213cd8839ce1cab76bc Mon Sep 17 00:00:00 2001 From: Rocky <72559939+hkgood@users.noreply.github.com> Date: Wed, 29 Jul 2026 22:27:36 +0800 Subject: [PATCH] docs(site): switch hero to Stripe-style wave gradient Replace the custom liquid aurora shader with the MIT wave-gradient mesh animation, vendored locally and tinted for OSG brand colors. --- CHANGELOG.md | 2 +- docs/assets/js/wave-gradient.LICENSE | 21 ++ docs/assets/js/wave-gradient.js | 341 ++++++++++++++++++++++++ docs/index.html | 370 ++++++--------------------- 4 files changed, 439 insertions(+), 295 deletions(-) create mode 100644 docs/assets/js/wave-gradient.LICENSE create mode 100644 docs/assets/js/wave-gradient.js diff --git a/CHANGELOG.md b/CHANGELOG.md index eae1ba7..31fb40c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- **Liquid aurora hero**: WebGL2 domain-warped FBM shader — slow green/teal/violet fluid, dithering, pointer pull, offscreen pause, and reduced-motion static frame (CSS gradient fallback). / **液态极光 Hero**:WebGL2 domain-warped FBM 着色器——缓慢绿/青/紫流体、抖动去色带、轻微指针牵引、离屏暂停、减少动态模式下静态帧(CSS 渐变回退)。 +- **Wave Gradient hero**: Stripe-style animated mesh gradient (vendored MIT `wave-gradient`) with OSG green/teal/violet palettes, reduced-motion freeze, and CSS fallback. / **Wave Gradient Hero**:Stripe 风格动态网格渐变(本地托管 MIT `wave-gradient`),配 OSG 绿/青/紫配色,支持减少动态模式冻结与 CSS 回退。 - **App Store download badges**: official badges on bilingual READMEs (hero + Get sections) and the website, with China-store `/cn/` links for Chinese and regionless App Store links for English. / **App Store 下载徽章**:中英文 README(顶部与获取区)与官网挂载官方徽章;中文链 `/cn/`,英文用无地区链接。 - **Website SEO & AI discovery**: richer meta/JSON-LD/FAQ, `llms.txt`, expanded sitemap, English landing, plus install / compare / Mac-dictation content pages. / **官网 SEO 与 AI 发现**:强化 meta/JSON-LD/FAQ、`llms.txt`、sitemap,并新增英文落地页与安装 / 对比 / Mac 听写专题页。 - **Context-aware polish safeguards**: polish can use a redacted cursor-neighborhood snapshot for natural continuation, validates protected terms and identifiers, retries once, and falls back to a conservative local cleanup when needed. / **上下文润色护栏**:润色可使用经截断脱敏的光标附近文字自然衔接,并校验受保护词与标识符;失败时重试一次,仍不合格则降级为本地保守清理。 diff --git a/docs/assets/js/wave-gradient.LICENSE b/docs/assets/js/wave-gradient.LICENSE new file mode 100644 index 0000000..3549ff0 --- /dev/null +++ b/docs/assets/js/wave-gradient.LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Mohamed ElSaadany + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/assets/js/wave-gradient.js b/docs/assets/js/wave-gradient.js new file mode 100644 index 0000000..ed289f5 --- /dev/null +++ b/docs/assets/js/wave-gradient.js @@ -0,0 +1,341 @@ +/*! wave-gradient (MIT) https://github.com/sa3dany/wave-gradient — Copyright (c) 2022 Mohamed ElSaadany */ +"use strict"; +var WaveGradientModule = (() => { + var __defProp = Object.defineProperty; + var __getOwnPropDesc = Object.getOwnPropertyDescriptor; + var __getOwnPropNames = Object.getOwnPropertyNames; + var __hasOwnProp = Object.prototype.hasOwnProperty; + var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); + }; + var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; + }; + var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + + // src/wave-gradient.js + var wave_gradient_exports = {}; + __export(wave_gradient_exports, { + WaveGradient: () => WaveGradient + }); + + // src/shaders.js + var vert = `#version 300 es +vec3 o(vec3 i,vec3 c,float r){return c*r+i*(1.-r);}vec3 o(vec3 n){return n-floor(n*(1./289.))*289.;}vec4 o(vec4 n){return n-floor(n*(1./289.))*289.;}vec4 e(vec4 n){return o((n*34.+1.)*n);}vec4 v(vec4 y){return 1.79284291400159-.85373472095314*y;}float t(vec3 l){const vec2 s=vec2(1./6.,1./3.);const vec4 u=vec4(0.,.5,1.,2.);vec3 a=floor(l+dot(l,s.yyy)),x=l-a+dot(a,s.xxx),d=step(x.yzx,x.xyz),f=1.-d,z=min(d.xyz,f.zxy),w=max(d.xyz,f.zxy),m=x-z+s.xxx,C=x-w+s.yyy,p=x-u.yyy;a=o(a);vec4 P=e(e(e(a.z+vec4(0.,z.z,w.z,1.))+a.y+vec4(0.,z.y,w.y,1.))+a.x+vec4(0.,z.x,w.x,1.));vec3 S=.142857142857*u.wyz-u.xzx;vec4 L=P-49.*floor(P*S.z*S.z),F=floor(L*S.z),R=floor(L-7.*F),n=F*S.x+S.yyyy,W=R*S.x+S.yyyy,b=1.-abs(n)-abs(W),G=vec4(n.xy,W.xy),q=vec4(n.zw,W.zw),h=floor(G)*2.+1.,g=floor(q)*2.+1.,O=-step(b,vec4(0.)),B=G.xzyw+h.xzyw*O.xxyy,A=q.xzyw+g.xzyw*O.zzww;vec3 E=vec3(B.xy,b.x),Z=vec3(B.zw,b.y),Y=vec3(A.xy,b.z),X=vec3(A.zw,b.w);vec4 V=v(vec4(dot(E,E),dot(Z,Z),dot(Y,Y),dot(X,X)));E*=V.x;Z*=V.y;Y*=V.z;X*=V.w;vec4 U=max(.6-vec4(dot(x,x),dot(m,m),dot(C,C),dot(p,p)),0.);U=U*U;return 42.*dot(U*U,vec4(dot(E,x),dot(Z,m),dot(Y,C),dot(X,p)));}uniform mediump vec2 u_Resolution;uniform float u_Amplitude,u_Realtime,u_Seed;uniform vec3 u_BaseColor;uniform int u_LayerCount;uniform struct WaveLayers{float noiseCeil;float noiseFloor;float noiseFlow;float noiseSeed;float noiseSpeed;vec2 noiseFreq;vec3 color;} u_WaveLayers[9];in vec3 a_Position;out vec3 v_Color;void main(){float T=u_Realtime*5e-6;vec2 Q=vec2(.00014,.00029),N=u_Resolution*a_Position.xy*Q;float M=u_Amplitude*(2./u_Resolution.y),K=t(vec3(N.x*3.+T*3.,N.y*4.,T*10.+u_Seed));K*=1.-pow(abs(a_Position.y),2.);K=max(0.,K);gl_Position=vec4(a_Position.x,a_Position.y+K*M,a_Position.z,1.);v_Color=u_BaseColor;for(int a=0;a { + return parseInt(c.length < 2 ? c + c : c, 16) / 255; + }) : null; + } + var ClipSpace = class { + static createPlaneGeometry(widthSegments, depthSegments) { + const gridX = Math.ceil(widthSegments); + const gridZ = Math.ceil(depthSegments); + const vertexCount = 3 * (gridX + 1) * (gridZ + 1); + const indexCount = 3 * 2 * gridX * gridZ; + const positions = new ArrayBuffer(4 * vertexCount); + const indices = new ArrayBuffer(4 * indexCount); + for (let z = gridZ, i = 0, view = new DataView(positions); z >= 0; z--) { + const v = z / gridZ; + const clipY = v * 2 - 1; + for (let x = gridX; x >= 0; x--, i += 3) { + const clipX = x / gridX * 2 - 1; + view.setFloat32((i + 0) * 4, clipX, true); + view.setFloat32((i + 1) * 4, clipY, true); + view.setFloat32((i + 2) * 4, v, true); + } + } + const verticesAcross = gridX + 1; + for (let z = 0, i = 0, view = new DataView(indices); z < gridZ; z++) { + for (let x = 0; x < gridX; x++, i += 6) { + view.setUint32((i + 0) * 4, (z + 0) * verticesAcross + x, true); + view.setUint32((i + 1) * 4, (z + 0) * verticesAcross + x + 1, true); + view.setUint32((i + 2) * 4, (z + 1) * verticesAcross + x, true); + view.setUint32((i + 3) * 4, (z + 0) * verticesAcross + x + 1, true); + view.setUint32((i + 4) * 4, (z + 1) * verticesAcross + x + 1, true); + view.setUint32((i + 5) * 4, (z + 1) * verticesAcross + x, true); + } + } + return { positions, indices, count: indexCount }; + } + static prefixName(name, prefix) { + return `${prefix}${name[0].toUpperCase()}${name.slice(1)}`; + } + constructor(config) { + this.gl = config.gl; + this.program = this.createProgram(config.shaders); + this._attributes = {}; + this.setupAttributes(config.attributes); + this._elementBuffer; + this.setElements(config.elements); + this._uniforms = {}; + this.setupUniforms(config.uniforms); + } + compileShader(type, source) { + const { gl } = this; + let shader = gl.createShader(type); + if (!shader) + throw new Error("can't create shader"); + gl.shaderSource(shader, source); + gl.compileShader(shader); + return shader; + } + debugProgram(program) { + const { gl } = this; + const [vs, fs] = gl.getAttachedShaders(program) ?? []; + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + throw new Error( + `can't link WebGL program. + ${gl.getProgramInfoLog(program)} + ${gl.getShaderInfoLog(vs)} + ${gl.getShaderInfoLog(fs)}` + ); + } + } + createProgram(shaders) { + const { gl } = this; + const [vs, fs] = [ + this.compileShader(gl.VERTEX_SHADER, shaders[0]), + this.compileShader(gl.FRAGMENT_SHADER, shaders[1]) + ]; + const program = gl.createProgram(); + if (!program) + throw new Error("can't create WebGL program"); + gl.attachShader(program, vs); + gl.attachShader(program, fs); + try { + gl.linkProgram(program); + this.debugProgram(program); + } catch (linkError) { + gl.deleteProgram(program); + throw linkError; + } finally { + gl.deleteShader(vs); + gl.deleteShader(fs); + } + gl.useProgram(program); + return program; + } + createBuffer() { + const { gl } = this; + const buffer = gl.createBuffer(); + if (!buffer) + throw new Error("can't create buffer"); + return buffer; + } + setupAttributes(attributes) { + const { gl, program } = this; + for (const [name, dataBuffer] of Object.entries(attributes)) { + const prefixedName = ClipSpace.prefixName(name, "a_"); + const buffer = this.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, dataBuffer, gl.STATIC_DRAW); + const location = gl.getAttribLocation(program, prefixedName); + gl.enableVertexAttribArray(location); + gl.vertexAttribPointer(location, 3, gl.FLOAT, false, 0, 0); + this._attributes[name] = { buffer, location }; + } + } + setAttribute(attributeName, dataBuffer) { + const { gl } = this; + gl.bufferData(gl.ARRAY_BUFFER, dataBuffer, gl.STATIC_DRAW); + } + setElements(elements) { + const { gl } = this; + if (!this._elementBuffer) { + const buffer = this.createBuffer(); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer); + this._elementBuffer = buffer; + } + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, elements, gl.STATIC_DRAW); + } + createUniformSetter(name, type, initialValue) { + const { gl, program } = this; + const uniformX = `uniform${type}`; + const location = gl.getUniformLocation(program, name); + const setter = (value) => { + Array.isArray(value) ? gl[uniformX](location, ...value) : gl[uniformX](location, value); + }; + if (initialValue) + setter(initialValue); + return setter; + } + setupUniforms(uniforms) { + for (const [name, uniform] of Object.entries(uniforms)) { + const prefixedName = ClipSpace.prefixName(name, "u_"); + switch (uniform.type) { + case void 0: + Array.isArray(uniform.value) && uniform.value.forEach( + (member, i) => { + const structName = name; + const prefixedStructName = prefixedName; + for (const [name2, uniform2] of Object.entries(member)) { + const key = `${structName}[${i}].${name2}`; + const prefixedKey = `${prefixedStructName}[${i}].${name2}`; + this._uniforms[key] = this.createUniformSetter( + prefixedKey, + uniform2.type, + uniform2.value + ); + } + } + ); + break; + default: + this._uniforms[name] = this.createUniformSetter( + prefixedName, + uniform.type, + uniform.value + ); + } + } + } + setUniform(uniformName, newValue) { + this._uniforms[uniformName](newValue); + } + delete() { + const { gl } = this; + gl.deleteProgram(this.program); + for (const [, attribute] of Object.entries(this._attributes)) { + this.gl.deleteBuffer(attribute.buffer); + } + } + }; + var WaveGradient = class { + constructor(canvas, options) { + const gl = canvas.getContext("webgl2", { + antialias: true, + depth: false, + powerPreference: "low-power" + }); + if (!gl) + throw new Error("can't get WebGL2 context"); + const { + amplitude = 320, + colors = ["#ef008f", "#6ec3f4", "#7038ff", "#ffba27"], + density = [0.06, 0.16], + fps = 24, + seed = 0, + speed = 1.25, + time = 0, + wireframe = false + } = options ?? {}; + const { clientWidth, clientHeight } = canvas; + canvas.width = clientWidth; + canvas.height = clientHeight; + gl.viewport(0, 0, clientWidth, clientHeight); + gl.enable(gl.CULL_FACE); + gl.disable(gl.DITHER); + gl.disable(gl.DEPTH_TEST); + const geometry = ClipSpace.createPlaneGeometry( + clientWidth * density[0], + clientHeight * density[1] + ); + const clipSpace = new ClipSpace({ + gl, + shaders: [vert, frag], + attributes: { position: geometry.positions }, + elements: geometry.indices, + uniforms: { + amplitude: { value: amplitude, type: "1f" }, + baseColor: { value: parseRGB(colors[0]), type: "3f" }, + realtime: { value: time, type: "1f" }, + resolution: { value: [clientWidth, clientHeight], type: "2f" }, + seed: { value: seed, type: "1f" }, + shadowPower: { value: 6, type: "1f" }, + layerCount: { value: colors.length - 1, type: "1i" }, + waveLayers: { + value: colors.slice(1).map((color, i, array) => { + const r = (i + 1) / array.length + 1; + return { + noiseCeil: { value: 0.63 + 0.07 * (i + 1), type: "1f" }, + noiseFloor: { value: 0.1, type: "1f" }, + noiseFlow: { value: 6.5 + 0.3 * (i + 1), type: "1f" }, + noiseSeed: { value: seed + 10 * (i + 1), type: "1f" }, + noiseSpeed: { value: 11 + 0.3 * (i + 1), type: "1f" }, + noiseFreq: { value: [2 + r, 3 + r], type: "2f" }, + color: { value: parseRGB(color), type: "3f" } + }; + }) + } + } + }); + this.gl = gl; + this.clipSpace = clipSpace; + this.density = density; + this.speed = speed; + this.frameInterval = 1e3 / fps; + this.lastFrameTime = 0; + this.shouldRender = true; + this.drawMode = wireframe ? this.gl.LINES : this.gl.TRIANGLES; + this.drawCount = geometry.count; + this.time = time; + requestAnimationFrame((now) => { + this.render(now); + }); + } + resize() { + const { gl, gl: { canvas }, clipSpace } = this; + const { width, clientWidth, height, clientHeight } = canvas; + const resized = width !== clientWidth || height !== clientHeight; + if (resized) { + canvas.width = clientWidth; + canvas.height = clientHeight; + gl.viewport(0, 0, clientWidth, clientHeight); + this.clipSpace.setUniform("resolution", [clientWidth, clientHeight]); + const geometry = ClipSpace.createPlaneGeometry( + clientWidth * this.density[0], + clientHeight * this.density[1] + ); + clipSpace.setAttribute("position", geometry.positions); + clipSpace.setElements(geometry.indices); + this.drawCount = geometry.count; + } + } + render(now) { + if (this.shouldRender) { + requestAnimationFrame((now2) => { + this.render(now2); + }); + } else { + return; + } + const delta = now - this.lastFrameTime; + if (delta < this.frameInterval) { + if (Math.random() > 0.75 === true) + this.resize(); + return; + } + this.lastFrameTime = now - delta % this.frameInterval; + this.time += Math.min(delta, this.frameInterval) * this.speed; + this.clipSpace.setUniform("realtime", this.time); + this.gl.drawElements( + this.drawMode, + this.drawCount, + this.gl.UNSIGNED_INT, + 0 + ); + } + destroy() { + this.clipSpace.delete(); + delete this.gl; + this.shouldRender = false; + } + }; + return __toCommonJS(wave_gradient_exports); +})(); + +window.WaveGradient = WaveGradientModule.WaveGradient; diff --git a/docs/index.html b/docs/index.html index d075f2a..00d6d1c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -370,9 +370,9 @@ position: absolute; inset: 0; background: - linear-gradient(180deg, transparent 52%, var(--hero-wash) 94%), - radial-gradient(ellipse 80% 55% at 50% -5%, transparent 35%, var(--hero-wash) 100%); - opacity: 0.9; + linear-gradient(180deg, transparent 58%, var(--hero-wash) 96%), + radial-gradient(ellipse 90% 60% at 50% -8%, transparent 40%, color-mix(in srgb, var(--hero-wash) 55%, transparent) 100%); + opacity: 0.72; pointer-events: none; } .hero .wrap { position: relative; z-index: 1; } @@ -854,6 +854,7 @@ .hero-aurora-canvas { transition: none; } } +