Files
vue-bits/public/r/FaultyTerminal.json
David Haz e621971723 jsrepo v3
2025-12-15 23:50:24 +02:00

1 line
12 KiB
JSON

{"name":"FaultyTerminal","title":"FaultyTerminal","description":"Terminal CRT scanline squares effect with flicker + noise.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { Color, Mesh, Program, Renderer, Triangle } from 'ogl';\nimport { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue';\n\ntype Vec2 = [number, number];\n\ninterface FaultyTerminalProps {\n scale?: number;\n gridMul?: Vec2;\n digitSize?: number;\n timeScale?: number;\n pause?: boolean;\n scanlineIntensity?: number;\n glitchAmount?: number;\n flickerAmount?: number;\n noiseAmp?: number;\n chromaticAberration?: number;\n dither?: number | boolean;\n curvature?: number;\n tint?: string;\n mouseReact?: boolean;\n mouseStrength?: number;\n dpr?: number;\n pageLoadAnimation?: boolean;\n brightness?: number;\n className?: string;\n style?: Record<string, string | number>;\n}\n\nconst vertexShader = `\nattribute vec2 position;\nattribute vec2 uv;\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision mediump float;\n\nvarying vec2 vUv;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uScale;\n\nuniform vec2 uGridMul;\nuniform float uDigitSize;\nuniform float uScanlineIntensity;\nuniform float uGlitchAmount;\nuniform float uFlickerAmount;\nuniform float uNoiseAmp;\nuniform float uChromaticAberration;\nuniform float uDither;\nuniform float uCurvature;\nuniform vec3 uTint;\nuniform vec2 uMouse;\nuniform float uMouseStrength;\nuniform float uUseMouse;\nuniform float uPageLoadProgress;\nuniform float uUsePageLoadAnimation;\nuniform float uBrightness;\n\nfloat time;\n\nfloat hash21(vec2 p){\n p = fract(p * 234.56);\n p += dot(p, p + 34.56);\n return fract(p.x * p.y);\n}\n\nfloat noise(vec2 p)\n{\n return sin(p.x * 10.0) * sin(p.y * (3.0 + sin(time * 0.090909))) + 0.2;\n}\n\nmat2 rotate(float angle)\n{\n float c = cos(angle);\n float s = sin(angle);\n return mat2(c, -s, s, c);\n}\n\nfloat fbm(vec2 p)\n{\n p *= 1.1;\n float f = 0.0;\n float amp = 0.5 * uNoiseAmp;\n\n mat2 modify0 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify0 * p * 2.0;\n amp *= 0.454545; // 1/2.2\n\n mat2 modify1 = rotate(time * 0.02);\n f += amp * noise(p);\n p = modify1 * p * 2.0;\n amp *= 0.454545;\n\n mat2 modify2 = rotate(time * 0.08);\n f += amp * noise(p);\n\n return f;\n}\n\nfloat pattern(vec2 p, out vec2 q, out vec2 r) {\n vec2 offset1 = vec2(1.0);\n vec2 offset0 = vec2(0.0);\n mat2 rot01 = rotate(0.1 * time);\n mat2 rot1 = rotate(0.1);\n\n q = vec2(fbm(p + offset1), fbm(rot01 * p + offset1));\n r = vec2(fbm(rot1 * q + offset0), fbm(q + offset0));\n return fbm(p + r);\n}\n\nfloat digit(vec2 p){\n vec2 grid = uGridMul * 15.0;\n vec2 s = floor(p * grid) / grid;\n p = p * grid;\n vec2 q, r;\n float intensity = pattern(s * 0.1, q, r) * 1.3 - 0.03;\n\n if(uUseMouse > 0.5){\n vec2 mouseWorld = uMouse * uScale;\n float distToMouse = distance(s, mouseWorld);\n float mouseInfluence = exp(-distToMouse * 8.0) * uMouseStrength * 10.0;\n intensity += mouseInfluence;\n\n float ripple = sin(distToMouse * 20.0 - iTime * 5.0) * 0.1 * mouseInfluence;\n intensity += ripple;\n }\n\n if(uUsePageLoadAnimation > 0.5){\n float cellRandom = fract(sin(dot(s, vec2(12.9898, 78.233))) * 43758.5453);\n float cellDelay = cellRandom * 0.8;\n float cellProgress = clamp((uPageLoadProgress - cellDelay) / 0.2, 0.0, 1.0);\n\n float fadeAlpha = smoothstep(0.0, 1.0, cellProgress);\n intensity *= fadeAlpha;\n }\n\n p = fract(p);\n p *= uDigitSize;\n\n float px5 = p.x * 5.0;\n float py5 = (1.0 - p.y) * 5.0;\n float x = fract(px5);\n float y = fract(py5);\n\n float i = floor(py5) - 2.0;\n float j = floor(px5) - 2.0;\n float n = i * i + j * j;\n float f = n * 0.0625;\n\n float isOn = step(0.1, intensity - f);\n float brightness = isOn * (0.2 + y * 0.8) * (0.75 + x * 0.25);\n\n return step(0.0, p.x) * step(p.x, 1.0) * step(0.0, p.y) * step(p.y, 1.0) * brightness;\n}\n\nfloat onOff(float a, float b, float c)\n{\n return step(c, sin(iTime + a * cos(iTime * b))) * uFlickerAmount;\n}\n\nfloat displace(vec2 look)\n{\n float y = look.y - mod(iTime * 0.25, 1.0);\n float window = 1.0 / (1.0 + 50.0 * y * y);\n return sin(look.y * 20.0 + iTime) * 0.0125 * onOff(4.0, 2.0, 0.8) * (1.0 + cos(iTime * 60.0)) * window;\n}\n\nvec3 getColor(vec2 p){\n\n float bar = step(mod(p.y + time * 20.0, 1.0), 0.2) * 0.4 + 1.0; // more efficient than ternary\n bar *= uScanlineIntensity;\n\n float displacement = displace(p);\n p.x += displacement;\n\n if (uGlitchAmount != 1.0) {\n float extra = displacement * (uGlitchAmount - 1.0);\n p.x += extra;\n }\n\n float middle = digit(p);\n\n const float off = 0.002;\n float sum = digit(p + vec2(-off, -off)) + digit(p + vec2(0.0, -off)) + digit(p + vec2(off, -off)) +\n digit(p + vec2(-off, 0.0)) + digit(p + vec2(0.0, 0.0)) + digit(p + vec2(off, 0.0)) +\n digit(p + vec2(-off, off)) + digit(p + vec2(0.0, off)) + digit(p + vec2(off, off));\n\n vec3 baseColor = vec3(0.9) * middle + sum * 0.1 * vec3(1.0) * bar;\n return baseColor;\n}\n\nvec2 barrel(vec2 uv){\n vec2 c = uv * 2.0 - 1.0;\n float r2 = dot(c, c);\n c *= 1.0 + uCurvature * r2;\n return c * 0.5 + 0.5;\n}\n\nvoid main() {\n time = iTime * 0.333333;\n vec2 uv = vUv;\n\n if(uCurvature != 0.0){\n uv = barrel(uv);\n }\n\n vec2 p = uv * uScale;\n vec3 col = getColor(p);\n\n if(uChromaticAberration != 0.0){\n vec2 ca = vec2(uChromaticAberration) / iResolution.xy;\n col.r = getColor(p + ca).r;\n col.b = getColor(p - ca).b;\n }\n\n col *= uTint;\n col *= uBrightness;\n\n if(uDither > 0.0){\n float rnd = hash21(gl_FragCoord.xy);\n col += (rnd - 0.5) * (uDither * 0.003922);\n }\n\n gl_FragColor = vec4(col, 1.0);\n}\n`;\n\nfunction hexToRgb(hex: string): [number, number, number] {\n let h = hex.replace('#', '').trim();\n if (h.length === 3)\n h = h\n .split('')\n .map(c => c + c)\n .join('');\n const num = parseInt(h, 16);\n return [((num >> 16) & 255) / 255, ((num >> 8) & 255) / 255, (num & 255) / 255];\n}\n\nconst props = withDefaults(defineProps<FaultyTerminalProps>(), {\n scale: 1,\n gridMul: () => [2, 1],\n digitSize: 1.5,\n timeScale: 0.3,\n pause: false,\n scanlineIntensity: 0.3,\n glitchAmount: 1,\n flickerAmount: 1,\n noiseAmp: 1,\n chromaticAberration: 0,\n dither: 0,\n curvature: 0.2,\n tint: '#ffffff',\n mouseReact: true,\n mouseStrength: 0.2,\n dpr: Math.min(window.devicePixelRatio || 1, 2),\n pageLoadAnimation: true,\n brightness: 1,\n className: '',\n style: () => ({})\n});\n\nconst containerRef = useTemplateRef('containerRef');\nconst programRef = ref<Program | null>(null);\nconst rendererRef = ref<Renderer | null>(null);\nconst mouseRef = ref({ x: 0.5, y: 0.5 });\nconst smoothMouseRef = ref({ x: 0.5, y: 0.5 });\nconst frozenTimeRef = ref(0);\nconst rafRef = ref<number>(0);\nconst loadAnimationStartRef = ref<number>(0);\nconst timeOffsetRef = ref<number>(Math.random() * 100);\n\nconst tintVec = computed(() => hexToRgb(props.tint));\n\nconst ditherValue = computed(() => (typeof props.dither === 'boolean' ? (props.dither ? 1 : 0) : props.dither));\n\nconst handleMouseMove = (e: MouseEvent) => {\n const ctn = containerRef.value;\n if (!ctn) return;\n const rect = ctn.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseRef.value = { x, y };\n};\n\nlet cleanup: (() => void) | null = null;\nconst setup = () => {\n const ctn = containerRef.value;\n if (!ctn) return;\n\n const renderer = new Renderer({ dpr: props.dpr });\n rendererRef.value = renderer;\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n const geometry = new Triangle(gl);\n\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)\n },\n uScale: { value: props.scale },\n\n uGridMul: { value: new Float32Array(props.gridMul) },\n uDigitSize: { value: props.digitSize },\n uScanlineIntensity: { value: props.scanlineIntensity },\n uGlitchAmount: { value: props.glitchAmount },\n uFlickerAmount: { value: props.flickerAmount },\n uNoiseAmp: { value: props.noiseAmp },\n uChromaticAberration: { value: props.chromaticAberration },\n uDither: { value: ditherValue },\n uCurvature: { value: props.curvature },\n uTint: { value: new Color(tintVec.value[0], tintVec.value[1], tintVec.value[2]) },\n uMouse: {\n value: new Float32Array([smoothMouseRef.value.x, smoothMouseRef.value.y])\n },\n uMouseStrength: { value: props.mouseStrength },\n uUseMouse: { value: props.mouseReact ? 1 : 0 },\n uPageLoadProgress: { value: props.pageLoadAnimation ? 0 : 1 },\n uUsePageLoadAnimation: { value: props.pageLoadAnimation ? 1 : 0 },\n uBrightness: { value: props.brightness }\n }\n });\n programRef.value = program;\n\n const mesh = new Mesh(gl, { geometry, program });\n\n function resize() {\n if (!ctn || !renderer) return;\n renderer.setSize(ctn.offsetWidth, ctn.offsetHeight);\n program.uniforms.iResolution.value = new Color(\n gl.canvas.width,\n gl.canvas.height,\n gl.canvas.width / gl.canvas.height\n );\n }\n\n const resizeObserver = new ResizeObserver(() => resize());\n resizeObserver.observe(ctn);\n resize();\n\n const update = (t: number) => {\n rafRef.value = requestAnimationFrame(update);\n\n if (props.pageLoadAnimation && loadAnimationStartRef.value === 0) {\n loadAnimationStartRef.value = t;\n }\n\n if (!props.pause) {\n const elapsed = (t * 0.001 + timeOffsetRef.value) * props.timeScale;\n program.uniforms.iTime.value = elapsed;\n frozenTimeRef.value = elapsed;\n } else {\n program.uniforms.iTime.value = frozenTimeRef.value;\n }\n\n if (props.pageLoadAnimation && loadAnimationStartRef.value > 0) {\n const animationDuration = 2000;\n const animationElapsed = t - loadAnimationStartRef.value;\n const progress = Math.min(animationElapsed / animationDuration, 1);\n program.uniforms.uPageLoadProgress.value = progress;\n }\n\n if (props.mouseReact) {\n const dampingFactor = 0.08;\n const smoothMouse = smoothMouseRef.value;\n const mouse = mouseRef.value;\n smoothMouse.x += (mouse.x - smoothMouse.x) * dampingFactor;\n smoothMouse.y += (mouse.y - smoothMouse.y) * dampingFactor;\n\n const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n mouseUniform[0] = smoothMouse.x;\n mouseUniform[1] = smoothMouse.y;\n }\n\n renderer.render({ scene: mesh });\n };\n rafRef.value = requestAnimationFrame(update);\n ctn.appendChild(gl.canvas);\n\n if (props.mouseReact) ctn.addEventListener('mousemove', handleMouseMove);\n\n cleanup = () => {\n cancelAnimationFrame(rafRef.value);\n resizeObserver.disconnect();\n if (props.mouseReact) ctn.removeEventListener('mousemove', handleMouseMove);\n if (gl.canvas.parentElement === ctn) ctn.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n loadAnimationStartRef.value = 0;\n timeOffsetRef.value = Math.random() * 100;\n };\n};\n\nonMounted(() => {\n const ctn = containerRef.value;\n if (ctn) {\n setup();\n }\n});\n\nonBeforeUnmount(() => {\n if (cleanup) {\n cleanup();\n cleanup = null;\n }\n});\n\nwatch(\n () => props,\n () => {\n if (cleanup) {\n cleanup();\n cleanup = null;\n }\n setup();\n },\n { deep: true }\n);\n</script>\n\n<template>\n <div\n ref=\"containerRef\"\n :class=\"['w-full h-full relative overflow-hidden', className]\"\n :style=\"style\"\n v-bind=\"$attrs\"\n />\n</template>\n","path":"FaultyTerminal/FaultyTerminal.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"ogl","version":"^1.0.11"}],"devDependencies":[],"categories":["Backgrounds"]}