Skip to content

Spinning the BootSplash logo effect out into a standalone app

This page is a recipe for taking the WebGL logo effect that BootSplash.tsx runs at startup and lifting it into a brand-new project — for example, a one-off site, a "now playing" screen, or a splash screen for a different app you are prototyping. The goal is to give you a checklist of every file you would copy or re-implement, plus the gotchas to watch for, so the lift takes a few hours instead of a day.

What you actually need

The effect is split across three files plus a single texture asset. You can lift any of them independently, but for a fully working demo you want all three.

Role File in ButtonsCLI Purpose
WebGL bootstrap helpers src/plugins/effects/webglEffect.ts Compiles shaders, allocates a full-screen quad, manages uniforms, validates GLSL outside the runtime.
React component (live sliders, presets, dismiss) src/components/BootSplash.tsx Hosts the <canvas>, runs the requestAnimationFrame loop, owns the slider state, persists "Don't show again".
The fragment shader itself Inline GLSL string in BootSplash.tsx (HSYNC_LOGO_FRAGMENT_SHADER) The HSYNC sine-wave warp + the new hue / glitch / scanline / chromatic-aberration effects.
Logo asset src/assets/buttonscli.png The texture. Replace with any PNG/JPG up to ~2048×2048; the shader auto-centers it at 28% of viewport height.

If you only need the visual (no React, no sliders, no persistence), you can paste the GLSL into a single HTML file and you are done in under 200 lines. If you want the full preset library and the "don't show again" UX, copy BootSplash.tsx as-is into a new React project and delete the useConfigStore bits.

Minimal standalone HTML version

If you just want a hero animation, save the snippet below as logo.html and open it in a browser. It uses the same fragment shader as ButtonsCLI but with vanilla JS, no build step, no React.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Logo Effect</title>
    <style>
      html, body { margin: 0; height: 100%; background: #000; }
      canvas { display: block; width: 100vw; height: 100vh; }
    </style>
  </head>
  <body>
    <canvas id="c"></canvas>
    <script>
      // Paste HSYNC_LOGO_FRAGMENT_SHADER from src/components/BootSplash.tsx here.
      // It is the string that starts with `precision mediump float;` and
      // ends with the closing brace of main(). The body of main() expects
      // the uniforms listed below.
      const FRAG = `...`;

      const canvas = document.getElementById("c");
      const gl = canvas.getContext("webgl", { alpha: false, antialias: false });
      // ... compile + link, same pattern as webglEffect.ts.
    </script>
  </body>
</html>

If you would rather start from a runnable project, the cleanest incubation environment is a Vite + React + TypeScript scaffold (pnpm create vite@latest logo-app -- --template react-ts). Copy webglEffect.ts and BootSplash.tsx into src/, replace the @/assets/buttonscli.png import with a path to your own logo, and the dev server (pnpm dev) will render the animation on the default route.

Decision tree: which approach should you pick?

  • Marketing splash for a one-page site → plain HTML + the GLSL snippet. No build step, no React, easy to drop into any CMS.
  • Standalone desktop splash for a different Tauri / Electron app → Vite + React + TypeScript scaffold, copy BootSplash.tsx. You get the preset library and the slider UI for free.
  • Reusable npm package for other React apps → create a new package (@you/buttonscli-logo-effect) that exports a single <BootSplash logoUrl="..." onComplete={...} /> component. Pull the shader out of BootSplash.tsx into its own .glsl file and use vite-plugin-glsl (or rollup-plugin-glsl) to inline it.
  • You want a runtime where the shader is hot-editable → pair the React version with the existing src/plugins/effects/shaderLab pattern that already exists in ButtonsCLI for the terminal postprocess effects. The shader lab file watcher is the part to copy.

File-level checklist (full lift into a new project)

  1. Create the new project.
  2. Copy src/plugins/effects/webglEffect.ts → your project's src/effects/webglEffect.ts. The file is self-contained; it only depends on the DOM. There is no ButtonsCLI-specific code in it.
  3. Copy the GLSL string out of BootSplash.tsx into src/effects/bootSplash.glsl and load it via a Vite/Rollup glsl plugin, or just keep it as a TypeScript string constant. The inline-string approach is fine for a single shader.
  4. Copy src/components/BootSplash.tsx → src/BootSplash.tsx, strip the useConfigStore dependency, and point the asset import at your own logo. The component renders a full-screen canvas + a fixed-position slider panel + a dismiss dialog.
  5. Copy the asset src/assets/buttonscli.png (or your replacement) into your project's public/ folder and update the import path.
  6. If you want the in-app slider panel to be invisible (e.g. for a marketing splash), delete the <div> block in BootSplash.tsx that has the position: fixed; top: 12px; style. The animation still runs.
  7. If you want saveable presets, lift the BUILTIN_BOOT_PRESETS array too. Persist custom presets to localStorage (or any key-value store your new app already uses).

Things that will trip you up

  • Cross-origin assets. If you serve the logo from a CDN and the WebGL context is alpha: false, the image must be served with CORS headers. ButtonsCLI's logo is bundled with the app, so the effect sidesteps this. For a hosted logo, set gl.texImage2D(... img) after img.crossOrigin = "anonymous"; and host the asset with Access-Control-Allow-Origin: *.

  • DPR mismatch on retina. The boot loop multiplies the wave period by devicePixelRatio so a 4K display does not see a slower-looking animation than a 1080p display. If you copy the loop, keep the DPR scaling. The "what this does" comment in BootSplash.tsx is the place to start.

  • Frame timing. The boot loop calls cancelAnimationFrame in its cleanup but also clears the GL program. Forgetting the destroyFullscreenProgram call leaks GPU memory. The helper in webglEffect.ts does the right thing; just remember to call it on unmount.

  • Shader compile errors. If you tweak the GLSL and the screen goes black, open the browser console. WebGL compile errors surface in gl.getShaderInfoLog. The validateFullscreenFragmentShader helper in webglEffect.ts exists specifically so you can dry-run a shader before you wire it into the live loop — use it from a test or a debug button.

  • Color space. gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE); in BootSplash.tsx is intentional. Without it, Chrome will silently convert your PNG from sRGB to linear and the colors will look "off" on Windows. Leave the line in unless you are intentionally targeting linear color pipelines.

  • Stopping the loop. The onComplete callback in BootSplash.tsx is what unmounts the canvas. If your standalone app wants to keep the loop running forever (e.g. as a requestAnimationFrame background), just delete the OK button and the dismissal logic. The animation will keep going.

Where to extend the effect

A few directions the ButtonsCLI team has on the roadmap but is not shipping yet. If you fork the effect, any of these would be a natural next step:

  • Per-vertex displacement. Right now the warp is per-scanline (Y direction). A vertex-shader variant could displace individual logo pixels for a "molten" look. The current fragment shader already does this implicitly via the scanline trick, but a real vertex pipeline would be smoother on lower-end GPUs.
  • Beat / BPM sync. Add an optional u_bpm uniform and a beat envelope. The "shift range" already oscillates with a cosine, so multiplying it by a beat pulse is a one-liner.
  • Color stops. The logo is currently rendered as-is. A u_colorA → u_colorB mix based on the alpha mask would let the user pick a brand gradient. The existing terminalHsync.ts effect (in src/plugins/effects/) already does this for the terminal; the technique transfers.
  • Multi-layer logo. Render the logo twice, once shifted more than the other, with one of them inverted, for a "shadow ghosting" look. Cheap, dramatic, and very '90s.

What is intentionally not in this guide

  • Tauri-specific window controls. The animation is a pure browser/WebGL effect; once you have it running in a <canvas> it will work in any host — Tauri, Electron, a regular browser, a React Native WebView, etc.
  • The tab-bar / left-dock logos from TabBar.tsx and PresetBar.tsx. Those are static images with CSS transforms and do not use the WebGL pipeline. They are separate concerns; copy them as plain <img> tags.