Register Tunnel and Contour themes, add locale keys

main
Warinyourself 3 months ago
parent b137b3438e
commit ae4208ff81

@ -11,6 +11,8 @@ import planet from '@/components/themes/planet'
import random from '@/components/themes/random'
import tunnel from '@/components/themes/tunnel'
import contour from '@/components/themes/contour'
import rings3d from '@/components/themes/rings3d'
import zappy from '@/components/themes/zappy'
const themeMap: Record<string, any> = {
flow,
@ -23,6 +25,8 @@ const themeMap: Record<string, any> = {
random,
tunnel,
contour,
rings3d,
zappy,
}
export default defineComponent({

@ -0,0 +1,150 @@
// Inspiration: https://www.shadertoy.com/view/WdB3Dw
#ifdef GL_ES
precision highp float;
#endif
uniform float uTime;
uniform vec2 uResolution;
// Torus tube cross-section radius — thicker values widen the ring walls
uniform float uTubeSize;
// Clipping sphere radius — controls how large the bubble appears
uniform float uBubbleSize;
// Ray march step fraction — lower = slower rays, denser glow/fog accumulation
uniform float uFogDensity;
// Spectrum cycling frequency — higher = more colour bands visible per ray
uniform float uSpectrumSpeed;
// Surface glow colour (normalised; internally scaled ×2.1 to allow overbright)
uniform vec3 uGlowColor;
// Ambient scatter/fog colour accumulated on every ray march step
uniform vec3 uFogColor;
#define PI 3.14159265359
void pR(inout vec2 p, float a) {
p = cos(a)*p + sin(a)*vec2(p.y, -p.x);
}
float smax(float a, float b, float r) {
vec2 u = max(vec2(r + a, r + b), vec2(0.0));
return min(-r, max(a, b)) + length(u);
}
// ─── Spectrum palette ────────────────────────────────────────────────────────
// Cosine colour palette — IQ https://www.shadertoy.com/view/ll2GD3
vec3 pal(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) {
return a + b*cos(6.28318*(c*t+d));
}
vec3 spectrum(float n) {
return pal(n, vec3(0.5,0.5,0.5), vec3(0.5,0.5,0.5),
vec3(1.0,1.0,1.0), vec3(0.0,0.33,0.67));
}
// ─── SDF via inverse stereographic projection ────────────────────────────────
vec4 inverseStereographic(vec3 p, out float k) {
k = 2.0 / (1.0 + dot(p, p));
return vec4(k*p, k - 1.0);
}
float fTorus(vec4 p4) {
float d1 = length(p4.xy) / length(p4.zw) - 1.0;
float d2 = length(p4.zw) / length(p4.xy) - 1.0;
float d = d1 < 0.0 ? -d1 : d2;
d /= PI;
return d;
}
float fixDistance(float d, float k) {
float sn = sign(d);
d = abs(d);
d = d / k * 1.82;
d += 1.0;
d = pow(d, 0.5);
d -= 1.0;
d *= 5.0 / 3.0;
d *= sn;
return d;
}
float gTime;
float map(vec3 p) {
float k;
vec4 p4 = inverseStereographic(p, k);
pR(p4.zy, gTime * -PI / 2.0);
pR(p4.xw, gTime * -PI / 2.0);
float d = fTorus(p4);
d = abs(d);
d -= uTubeSize;
d = fixDistance(d, k);
d = smax(d, length(p) - uBubbleSize, 0.2);
return d;
}
// ─── Rendering ───────────────────────────────────────────────────────────────
mat3 calcLookAtMatrix(vec3 ro, vec3 ta, vec3 up) {
vec3 ww = normalize(ta - ro);
vec3 uu = normalize(cross(ww, up));
vec3 vv = normalize(cross(uu, ww));
return mat3(uu, vv, ww);
}
void main() {
gTime = mod(uTime / 2.0, 1.0);
vec3 camPos = vec3(1.8, 5.5, -5.5) * 1.75;
vec3 camTar = vec3(0.0, 0.0, 0.0);
vec3 camUp = vec3(-1.0, 0.0, -1.5);
mat3 camMat = calcLookAtMatrix(camPos, camTar, camUp);
float focalLength = 5.0;
vec2 p = (-uResolution.xy + 2.0 * gl_FragCoord.xy) / uResolution.y;
vec3 rayDir = normalize(camMat * vec3(p, focalLength));
vec3 rayPos = camPos;
float rayLen = 0.0;
float dist = 0.0;
vec3 color = vec3(0.0);
const int ITER = 82;
const float MIN_STEP = 0.001;
const float MAX_DIST = 20.0;
for (int i = 0; i < ITER; i++) {
rayLen += max(MIN_STEP, abs(dist) * uFogDensity);
rayPos = camPos + rayDir * rayLen;
dist = map(rayPos);
// Surface glow: bright flare near the SDF surface, tinted by uGlowColor
vec3 c = vec3(max(0.0, 0.01 - abs(dist)) * 0.5);
c *= uGlowColor * 2.1; // ×2.1 restores the original overbright range
// Ambient fog: coloured scatter accumulated on every step
c += uFogColor * uFogDensity / 160.0;
c *= smoothstep(20.0, 7.0, length(rayPos));
float rl = smoothstep(MAX_DIST, 0.1, rayLen);
c *= rl;
c *= spectrum(rl * uSpectrumSpeed - 0.6);
color += c;
if (rayLen > MAX_DIST) break;
}
color = pow(color, vec3(1.0 / 1.8)) * 2.0;
color = pow(color, vec3(2.0)) * 3.0;
color = pow(color, vec3(1.0 / 2.2));
gl_FragColor = vec4(color, 1.0);
}

@ -0,0 +1,75 @@
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { useThemeStore } from '@/store/theme'
import { hexToRgb } from '@/utils/color'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
let render: GL
export default defineComponent({
name: 'Rings3dTheme',
setup() {
const themeStore = useThemeStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const animationSpeed = computed(() => themeStore.getThemeInput('animation-speed')?.value as number || 5)
const pxratio = computed(() => themeStore.getThemeInput('pxratio')?.value as number || 0.8)
const tubeSize = computed(() => themeStore.getThemeInput('tube-size')?.value as number || 0.2)
const bubbleSize = computed(() => themeStore.getThemeInput('bubble-size')?.value as number || 1.85)
const fogDensity = computed(() => themeStore.getThemeInput('fog-density')?.value as number || 0.8)
const spectrumSpeed = computed(() => themeStore.getThemeInput('spectrum-speed')?.value as number || 6.0)
const glowColor = computed(() => themeStore.getThemeInput('color-glow')?.value as string || '#AAFFCE')
const fogColor = computed(() => themeStore.getThemeInput('color-fog')?.value as string || '#9940B3')
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.tubeSize) {
gl.programInfo.uniforms.tubeSize = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uTubeSize')
gl.programInfo.uniforms.bubbleSize = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uBubbleSize')
gl.programInfo.uniforms.fogDensity = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uFogDensity')
gl.programInfo.uniforms.spectrumSpeed = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uSpectrumSpeed')
gl.programInfo.uniforms.glowColor = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uGlowColor')
gl.programInfo.uniforms.fogColor = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uFogColor')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
const gc = hexToRgb(glowColor.value)
const fc = hexToRgb(fogColor.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.tubeSize, tubeSize.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.bubbleSize, bubbleSize.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.fogDensity, fogDensity.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.spectrumSpeed, spectrumSpeed.value)
gl.ctx.uniform3f(gl.programInfo.uniforms.glowColor, gc[0] / 255, gc[1] / 255, gc[2] / 255)
gl.ctx.uniform3f(gl.programInfo.uniforms.fogColor, fc[0] / 255, fc[1] / 255, fc[2] / 255)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} />
</div>
)
}
})

@ -0,0 +1,5 @@
attribute vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

@ -0,0 +1,77 @@
// Inspiration: https://www.shadertoy.com/view/cfjGzV — Zippy Zaps
// Credits: -13 thanks to Nguyen2007 ⚡
#ifdef GL_ES
precision highp float;
#endif
uniform float uTime;
uniform vec2 uResolution;
// Overall zoom / scale of the electric pattern
uniform float uZoom;
// Shifts the base colour phase vector, cycling through hue combinations
uniform float uColorShift;
// tanh is GLSL ES 3.00+ only — not available in WebGL 1.0.
// Implemented via the identity tanh(x) = (e^2x - 1)/(e^2x + 1).
// Input clamped to ±15 to prevent exp() overflow on large inputs.
vec2 tanh(vec2 x) {
x = clamp(x, -15.0, 15.0);
vec2 e = exp(2.0 * x);
return (e - 1.0) / (e + 1.0);
}
void main() {
vec2 res = uResolution;
vec2 u = gl_FragCoord.xy;
// Centre and normalise to screen height; uZoom replaces the hardcoded 0.2
u = uZoom * (u + u - res) / res.y;
// Base colour phase vector — uColorShift scrolls through hue space
// z.wxzw swizzle = vec4(z.w, z.x, z.z, z.w) = vec4(0,1,3,0) used in mat2 rotation
vec4 z = vec4(1.0 + uColorShift, 2.0 + uColorShift, 3.0 + uColorShift, 0.0);
vec4 o = z;
float a = 0.5;
float t = uTime;
float i = 0.0;
// Original: for (float a=.5, t=iTime, i; ++i<19.; o+=...) v=..., u+=...
// Rewritten: float i counts 1..18 (pre-increment in condition);
// ++t and a+=.03 moved to explicit statements preserving order;
// comma-operator body split into sequential statements.
vec2 v = res;
for (int iter = 0; iter < 18; iter++) {
i += 1.0; // pre-increment: matches ++i in original condition
// ++t happens inside the v= expression (pre-increment before the rhs is evaluated)
t += 1.0;
a += 0.03;
v = cos(t - 7.0 * u * pow(a, i)) - 5.0 * u;
// mat2 from explicit components avoids mat2(vec4) constructor rejection on ANGLE/Mesa
// vec4(scalar) - vec4 ensures float-vec4 subtraction is unambiguous
vec4 r = cos(vec4(i + 0.02 * t) - z.wxzw * 11.0);
u *= mat2(r.x, r.y, r.z, r.w);
// If black/NaN artifacts appear, replace tanh with stanh (smoothstep-tanh approximation)
u += tanh(40.0 * dot(u, u) * cos(100.0 * u.yx + t)) / 200.0
+ 0.2 * a * u
+ cos(4.0 / exp(dot(o, o) / 100.0) + t) / 300.0;
// for-increment: accumulate glow using the freshly updated v and u
o += (1.0 + cos(z + t))
/ length((1.0 + i * dot(v, v))
* sin(1.5 * u / (0.5 - dot(u, u)) - 9.0 * u.yx + t));
}
// Tonemapping: multiply numerator/denominator by o to avoid float/vec4 division
// Equivalent to: 25.6 / (min(o,13.) + 164./o)
o = 25.6 * o / (min(o, 13.0) * o + 164.0) - dot(u, u) / 250.0;
gl_FragColor = vec4(o);
}

@ -0,0 +1,59 @@
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import GL from '@/utils/gl'
import { useThemeStore } from '@/store/theme'
import fragmentShader from './fragment.glsl'
import vertexShader from './vertex.glsl'
let render: GL
export default defineComponent({
name: 'ZappyTheme',
setup() {
const themeStore = useThemeStore()
const canvasRef = ref<HTMLCanvasElement | null>(null)
const animationSpeed = computed(() => themeStore.getThemeInput('animation-speed')?.value as number || 5)
const pxratio = computed(() => themeStore.getThemeInput('pxratio')?.value as number || 0.8)
const zoom = computed(() => themeStore.getThemeInput('zoom')?.value as number || 0.2)
const colorShift = computed(() => themeStore.getThemeInput('color-shift')?.value as number || 0.0)
onMounted(() => {
render = new GL(
canvasRef.value!,
vertexShader,
fragmentShader,
window.innerWidth,
window.innerHeight,
{
renderOptions: { externalTimeUse: true },
renderHook() {
const gl = this as unknown as GL
if (!gl.programInfo.uniforms.zoom) {
gl.programInfo.uniforms.zoom = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uZoom')
gl.programInfo.uniforms.colorShift = gl.ctx.getUniformLocation(gl.program as WebGLProgram, 'uColorShift')
}
gl.pxratio = pxratio.value
gl.time += animationSpeed.value / 500
gl.ctx.uniform1f(gl.programInfo.uniforms.time, gl.time)
gl.ctx.uniform1f(gl.programInfo.uniforms.zoom, zoom.value)
gl.ctx.uniform1f(gl.programInfo.uniforms.colorShift, colorShift.value)
}
}
)
render.running = true
})
onBeforeUnmount(() => {
render.running = false
})
return () => (
<div>
<canvas ref={canvasRef} />
</div>
)
}
})

@ -0,0 +1,5 @@
attribute vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

@ -33,7 +33,14 @@
"glow": "Leuchten",
"glow-width": "Leuchtbreite",
"max-line": "Max. Linie",
"color-bg": "Hintergrund"
"color-bg": "Hintergrund",
"tube-size": "Rohrdicke",
"bubble-size": "Blasengröße",
"fog-density": "Nebeldichte",
"spectrum-speed": "Spektralgeschwindigkeit",
"color-glow": "Leuchtfarbe",
"color-fog": "Nebelfarbe",
"color-shift": "Farbversatz"
},
"text": {
"password": "Passwort",

@ -33,7 +33,14 @@
"glow": "Glow",
"glow-width": "Glow Width",
"max-line": "Max Line",
"color-bg": "Background"
"color-bg": "Background",
"tube-size": "Tube Size",
"bubble-size": "Bubble Size",
"fog-density": "Fog Density",
"spectrum-speed": "Spectrum Speed",
"color-glow": "Glow Color",
"color-fog": "Fog Color",
"color-shift": "Color Shift"
},
"text": {
"password": "password",

@ -33,7 +33,14 @@
"glow": "Resplandor",
"glow-width": "Ancho del halo",
"max-line": "Línea máx.",
"color-bg": "Fondo"
"color-bg": "Fondo",
"tube-size": "Grosor del tubo",
"bubble-size": "Tamaño de burbuja",
"fog-density": "Densidad de niebla",
"spectrum-speed": "Velocidad del espectro",
"color-glow": "Color del brillo",
"color-fog": "Color de la niebla",
"color-shift": "Desplazamiento de color"
},
"text": {
"password": "contraseña",

@ -33,7 +33,14 @@
"glow": "Lueur",
"glow-width": "Largeur du halo",
"max-line": "Ligne max",
"color-bg": "Arrière-plan"
"color-bg": "Arrière-plan",
"tube-size": "Épaisseur du tube",
"bubble-size": "Taille de la bulle",
"fog-density": "Densité du brouillard",
"spectrum-speed": "Vitesse du spectre",
"color-glow": "Couleur du halo",
"color-fog": "Couleur du brouillard",
"color-shift": "Décalage couleur"
},
"text": {
"password": "mot de passe",

@ -33,7 +33,14 @@
"glow": "Свечение",
"glow-width": "Ширина свечения",
"max-line": "Макс. линия",
"color-bg": "Фон"
"color-bg": "Фон",
"tube-size": "Размер трубки",
"bubble-size": "Размер пузыря",
"fog-density": "Плотность тумана",
"spectrum-speed": "Скорость спектра",
"color-glow": "Цвет свечения",
"color-fog": "Цвет тумана",
"color-shift": "Сдвиг цвета"
},
"text": {
"password": "пароль",

@ -167,6 +167,38 @@ export const AppThemes: AppTheme[] = [
resetButton
]
},
{
name: 'Rings3D',
component: 'rings3d',
color: {
background: '#0a0015'
},
settings: [
pxratio(),
buildInputSlider({ value: 5, min: 0, max: 15, step: 0.1 }),
buildInputSlider({ name: 'tube-size', value: 0.2, min: 0.02, max: 0.6, step: 0.01 }),
buildInputSlider({ name: 'bubble-size', value: 1.85, min: 0.5, max: 3.5, step: 0.05 }),
buildInputSlider({ name: 'fog-density', value: 0.8, min: 0.2, max: 1.0, step: 0.01 }),
buildInputSlider({ name: 'spectrum-speed', value: 6.0, min: 1.0, max: 12.0, step: 0.5 }),
buildInputColor({ name: 'color-glow', value: '#AAFFCE' }),
buildInputColor({ name: 'color-fog', value: '#9940B3' }),
randomButton,
resetButton
]
},
{
name: 'Zappy',
component: 'zappy',
color: { background: '#000010' },
settings: [
pxratio(),
buildInputSlider({ value: 5, min: 0, max: 15, step: 0.1 }),
buildInputSlider({ name: 'zoom', value: 0.2, min: 0.05, max: 0.6, step: 0.01 }),
buildInputSlider({ name: 'color-shift', value: 0, min: 0, max: 6.28, step: 0.05 }),
randomButton,
resetButton
]
},
{
name: 'Contour',
component: 'contour',

Loading…
Cancel
Save