├── .changeset └── config.json ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .gitpod.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.sh ├── dist ├── pocket-shader.d.ts ├── pocket-shader.js └── pocket-shader.min.js ├── jsr.json ├── package.json ├── pnpm-lock.yaml ├── src └── pocket-shader.ts ├── tsconfig.json ├── tsup.config.ts └── www ├── .gitignore ├── .prettierrc.mjs ├── .vscode ├── extensions.json └── launch.json ├── README.md ├── assets ├── favicon.png ├── pocket-shader-transparent.png ├── pocket-shader-transparent.svg ├── pocket-shader-transparent.webp ├── pocket-shader.svg └── pocket-shader.webp ├── astro.config.mjs ├── package.json ├── patches └── astro@4.10.0.patch ├── pnpm-lock.yaml ├── public ├── android-chrome-192x192.png ├── android-chrome-384x384.png ├── apple-touch-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── favicon.svg ├── fonts │ ├── Caveat-VariableFont_wght.ttf │ ├── Fredoka-VariableFont_wdth-wght.ttf │ └── MonaspaceNeon_wght-wdth-slnt.ttf ├── mstile-150x150.png ├── pocket-shader.png ├── safari-pinned-tab.svg └── site.webmanifest ├── src ├── components │ ├── Code.astro │ ├── Copy.svelte │ ├── Example.svelte │ ├── Github.astro │ ├── GridShader.svelte │ ├── Hero.svelte │ ├── Nav.svelte │ ├── Nav │ │ └── Mobile │ │ │ ├── Burger.svelte │ │ │ ├── Mobile.svelte │ │ │ └── PageFill.svelte │ ├── Range.svelte │ ├── Retro.svelte │ ├── ShaderSelector.svelte │ └── sections │ │ ├── Dpr.svelte │ │ ├── MouseSmoothing.svelte │ │ ├── Playback.svelte │ │ ├── Speed.svelte │ │ └── Uniforms.svelte ├── env.d.ts ├── layouts │ └── Layout.astro ├── pages │ ├── index.astro │ └── test.astro ├── shaders │ ├── bruh.frag │ ├── clouds.frag │ ├── default.vert │ ├── dyingUniverse.glsl │ ├── fractalPyramid.frag │ ├── glass.frag │ ├── grid copy 2.frag │ ├── grid.frag │ ├── gridColored.frag │ ├── gridGreyscale.frag │ ├── kirby.frag │ └── retro.frag ├── styles │ ├── code.css │ ├── elements.css │ ├── font.css │ ├── utilities.css │ └── variables.css └── utils │ ├── animations.ts │ ├── clickOutside.ts │ ├── dedent.ts │ ├── gridColor.ts │ ├── nanoid.ts │ └── teleportIntoView.ts ├── svelte.config.js └── tsconfig.json /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.6.3/schema.json", 3 | "changelog": [ 4 | "@svitejs/changesets-changelog-github-compact", 5 | { "repo": "braebo/pocket-shader" } 6 | ], 7 | "commit": false, 8 | "linked": [], 9 | "access": "public", 10 | "ignore": [], 11 | "baseBranch": "main", 12 | "updateInternalDependencies": "patch" 13 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | if: github.repository == 'TODO' 11 | name: Release 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write # to create release (changesets/action) 15 | pull-requests: write # to create pull request (changesets/action) 16 | id-token: write # required for JSR 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | with: 21 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 22 | fetch-depth: 0 23 | 24 | - name: Setup Node 20 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: 20 28 | 29 | - name: Setup PNPM 30 | uses: pnpm/action-setup@v3.0.0 31 | with: 32 | version: 8.15.7 33 | 34 | - name: Install 35 | run: pnpm install 36 | 37 | - name: Build 38 | run: pnpm build 39 | 40 | - name: Release or Create Release Pull Request or Do Nothing 41 | id: changesets 42 | uses: changesets/action@v1 43 | with: 44 | version: pnpm sync-version 45 | publish: pnpm changeset publish 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 49 | - name: Publish to jsr 50 | if: steps.changesets.outputs.published == 'true' 51 | run: npx -y jsr publish --allow-dirty 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | _ -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | # Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart 6 | 7 | tasks: 8 | - init: pnpm install 9 | command: pnpm run dev 10 | 11 | 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # pocket-shader 2 | 3 | ## 0.3.1 4 | 5 | ### Patch Changes 6 | 7 | - fix: import.meta types ([`9306ab80628d49356346c994e6b34b7cb6848b35`](https://github.com/braebo/pocket-shader/commit/9306ab80628d49356346c994e6b34b7cb6848b35)) 8 | 9 | ## 0.3.0 10 | 11 | ### Minor Changes 12 | 13 | - fix: npm types ([`a8febfc9a19655442e926e4c62bfb36948772211`](https://github.com/braebo/pocket-shader/commit/a8febfc9a19655442e926e4c62bfb36948772211)) 14 | 15 | ## 0.2.0 16 | 17 | ### Minor Changes 18 | 19 | - chore: npm release ([`db0c710f578e681bd40e0fe2d7d466d6746a3d78`](https://github.com/braebo/pocket-shader/commit/db0c710f578e681bd40e0fe2d7d466d6746a3d78)) 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Braden Wiggins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | ### _This project is still early-alpha, so the API is unstable. I'd love to hear your feedback / ideas!_ 10 | 11 |
12 | 13 | - 📦 Simple API 14 | - 🐭 Lightweight - _2.63 kB_ 15 | - ✅ Typesafe 16 | - 👌 Zero Dependencies 17 | - 🌐 ESM 18 | 19 |
20 | 21 | ## Installation 22 | 23 | ```bash 24 | npx jsr add @braebo/pocket-shader 25 | ``` 26 | 27 | ## Documentation 28 | 29 | [https://pocket-shader.braebo.dev/](https://pocket-shader.braebo.dev/) 30 | 31 | ## RoadMap 32 | 33 | - [x] Fragment / Vertex Shaders 34 | - [x] Responsive / Automatic Resizing 35 | - [x] (Reactive) Custom Uniforms 36 | - [x] Render Loop 37 | - [x] Playback Controls 38 | - [x] Mouse Input 39 | - [x] Mouse Smoothing 40 | - [x] Render Hook 41 | - [ ] Texture Support 42 | - [ ] WebGPU 43 | - [ ] Auto Pause/Resume _(Intersection Observer)_ 44 | 45 | ## Contributing 46 | 47 | Just remember to include a changeset with your pr: 48 | 49 | ```bash 50 | pnpm changeset 51 | ``` 52 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # #* https://github.com/microsoft/TypeScript/issues/14619#issuecomment-1971477006 4 | # pnpm tsc --project ./tsconfig.json --declaration --emitDeclarationOnly 5 | # pnpm tsc --project ./tsconfig.json --removeComments 6 | # pnpm esbuild ./src/pocket-shader.ts --format=esm --minify --outfile=./dist/pocket-shader.min.js 7 | 8 | # pnpm tsup 9 | -------------------------------------------------------------------------------- /dist/pocket-shader.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple webgl shader renderer. 3 | * @module 4 | */ 5 | interface PocketShaderOptions = Record> { 6 | /** 7 | * The canvas element to render the shader on. 8 | * @default A new canvas element. 9 | */ 10 | canvas?: HTMLCanvasElement; 11 | /** 12 | * The vertex shader source code. 13 | */ 14 | vertex?: string; 15 | /** 16 | * The fragment shader source code. 17 | */ 18 | fragment?: string; 19 | /** 20 | * When true, the render loop will start automatically. Otherwise, you will need to call the 21 | * {@link PocketShader.render|`render`} method manually. 22 | * @default true 23 | */ 24 | autoStart?: boolean; 25 | /** 26 | * A record of uniform values to pass to the shader. 27 | * @default {} 28 | */ 29 | uniforms?: T; 30 | /** 31 | * The maximum resolution multiplier, determined by the device pixel ratio by default. 32 | * @default window.devicePixelRatio || 1 33 | */ 34 | maxPixelRatio?: number; 35 | /** 36 | * A time multiplier. 37 | * @default 1 38 | */ 39 | speed?: number; 40 | /** 41 | * The mouse smoothing factor. 42 | * @default 0.1 43 | */ 44 | mouseSmoothing?: number; 45 | /** 46 | * The initial mouse position, normalized to the range `[0, 1]`. 47 | * @default [0, 0] 48 | */ 49 | mousePosition?: { 50 | x: number; 51 | y: number; 52 | }; 53 | /** 54 | * Where to listen for mouse events. By default, the renderer listens for mouse events on the 55 | * canvas element. You can also listen for mouse events on the container element, or the window 56 | * with this option. 57 | * @default 'canvas' 58 | */ 59 | mouseTarget?: 'canvas' | 'container' | 'window'; 60 | /** 61 | * When true, the instance will automatically initialize itself. Otherwise, you will need to 62 | * call the {@link PocketShader.init|`init`} method manually. This is useful for server-side 63 | * rendering, or when you need to perform additional setup before initializing the instance. 64 | * @default true 65 | */ 66 | autoInit?: boolean; 67 | /** 68 | * When `true`, touching the canvas on mobile devices will call preventDefault when capturing 69 | * touch event {@link PocketShader.mouse|mouse} positions for the 70 | * {@link PocketShader.builtinUniforms|u_mouse} uniform. 71 | * @default false 72 | */ 73 | preventScroll?: boolean; 74 | } 75 | interface Uniform { 76 | type: 'int' | 'float' | 'vec2' | 'vec3' | 'vec4'; 77 | value: number | number[]; 78 | } 79 | /** 80 | * A simple canvas-based shader renderer. 81 | * 82 | * @param container The element to append the canvas to. Can be an HTMLElement or a string selector. 83 | */ 84 | declare class PocketShader = Record> { 85 | /** 86 | * The container for the canvas element is used to determine the size of the canvas. 87 | */ 88 | container: HTMLElement | null; 89 | /** 90 | * The canvas element used to render the shader. 91 | */ 92 | canvas: HTMLCanvasElement; 93 | /** 94 | * The WebGL2 rendering context. 95 | */ 96 | ctx: WebGL2RenderingContext | null; 97 | /** 98 | * The vertex shader used by this instance. 99 | * @default 100 | * ```glsl 101 | * #version 300 es 102 | * in vec4 a_position; 103 | * out vec2 vUv; 104 | * void main() { 105 | * vUv = a_position.xy * 0.5 + 0.5; 106 | * gl_Position = a_position; 107 | * } 108 | * ``` 109 | */ 110 | vertex: string; 111 | /** 112 | * The fragment shader used by this instance. 113 | * @default 114 | * ```glsl 115 | * #version 300 es 116 | * precision mediump float; 117 | * 118 | * in vec2 vUv; 119 | * out vec4 color; 120 | * 121 | * uniform float u_time; 122 | * uniform float u_mouse; 123 | * uniform vec2 u_resolution; 124 | * 125 | * void main() { 126 | * color = vec4(vUv, 0.5 + 0.5 * sin(u_time), 1.0); 127 | * } 128 | *``` 129 | */ 130 | fragment: string; 131 | /** 132 | * The maximum resolution multiplier. By default, this value is set to `2` to avoid nuking 133 | * high-dpi phones with expensive shaders _(iPhone is `3` for example, so 3x more expensive)_. 134 | * @default 2 135 | */ 136 | maxPixelRatio: number; 137 | /** 138 | * A time multiplier. 139 | * @default 1 140 | */ 141 | speed: number; 142 | /** 143 | * The current state of the renderer. 144 | * @default 'stopped' 145 | */ 146 | state: 'running' | 'paused' | 'stopped' | 'disposed'; 147 | /** 148 | * The options used to create this instance. 149 | */ 150 | opts: PocketShaderOptions>; 151 | /** 152 | * The WebGL program used to render the shader. 153 | */ 154 | program: WebGLProgram | false; 155 | /** 156 | * The current mouse position normalized, to the range `[0, 1]`. 157 | * @default { x: 0.5, y: 0.5 } 158 | */ 159 | mouse: { 160 | x: number; 161 | y: number; 162 | }; 163 | /** 164 | * The current _smoothed_ mouse position, normalized to the range `[0, 1]`. 165 | * @default { x: 0.5, y: 0.5 } 166 | */ 167 | mouseSmoothed: { 168 | x: number; 169 | y: number; 170 | }; 171 | /** 172 | * The mouse smoothing factor. 173 | * @default 0.1 174 | */ 175 | mouseSmoothing: number; 176 | /** 177 | * These uniforms are always available to the shader, and are updated automatically whenever 178 | * the shader state is `running`. 179 | * @default { u_time: 0, u_resolution: [0, 0], u_mouse: [0.5, 0.5] } 180 | */ 181 | builtinUniforms: { 182 | u_time: number; 183 | u_resolution: [number, number]; 184 | u_mouse: [number, number]; 185 | }; 186 | private _positionBuffer; 187 | private _builtinUniformLocations; 188 | private _uniformLocations; 189 | private _uniforms; 190 | private _time; 191 | private _resizeObserver; 192 | private _canvasRectCache; 193 | private _listeners; 194 | private _arg1; 195 | private _arg2; 196 | constructor(options?: PocketShaderOptions); 197 | constructor( 198 | /** 199 | * The container element (or query selector) to use for the canvas. The canvas will 200 | * automatically adjust its size to fill the container, and re-render whenever the 201 | * window is resized. 202 | * @default document.body 203 | */ 204 | container: HTMLElement | string, 205 | /** 206 | * @see {@link PocketShaderOptions} 207 | */ 208 | options?: PocketShaderOptions); 209 | /** 210 | * A record of uniform values to pass to the shader. 211 | */ 212 | get uniforms(): T; 213 | set uniforms(value: T); 214 | /** 215 | * The current time in seconds. This value is updated automatically after calling 216 | * {@link start|`start()`}, and is reset to `0` when {@link stop|`stop()`} is called. You can 217 | * also set this value yourself if you prefer to control the time uniform manually. 218 | */ 219 | get time(): number; 220 | set time(value: number); 221 | /** 222 | * The current canvas width and height. 223 | */ 224 | get resolution(): { 225 | width: number; 226 | height: number; 227 | }; 228 | /** 229 | * Initializes the instance, creating the canvas element and WebGL context, and compiling the 230 | * shaders. This method is called automatically when the instance is created, unless the 231 | * {@link autoInit|`autoInit`} option is set to `false`. 232 | * 233 | * All references to {@link Window} and {@link Document} are contained within this method 234 | * instead of the constructor. 235 | */ 236 | init(): this; 237 | /** 238 | * Starts a render loop, updating the time uniform and re-rendering the shader each frame. 239 | * If the {@link state} is already `running`, this method does nothing. 240 | * @throws If the {@link state} is already `disposed`. 241 | */ 242 | start(): this; 243 | /** 244 | * Pauses the render loop. 245 | */ 246 | pause(): this; 247 | /** 248 | * Stops the render loop and resets the time uniform to `0`. 249 | */ 250 | stop(): this; 251 | /** 252 | * Restarts the render loop. If the WebGL context has already been disposed of, this will 253 | * call {@link reload} to create a new one. 254 | */ 255 | restart(): this; 256 | /** 257 | * Fully dipsoses the current WebGL context and creates a new one. 258 | */ 259 | reload(): this; 260 | /** 261 | * Resizes the canvas to fill the container. 262 | */ 263 | private _resize; 264 | /** 265 | * A throttled, debounced {@link _resize}. Resizes the canvas to fill the container. 266 | */ 267 | resize: () => void; 268 | /** 269 | * Performs a single render to the canvas. This method is called automatically when the 270 | * instance is created, unless the {@link autoStart|`autoStart`} option is set to `false`. 271 | * 272 | * This method is also called automatically when the window is resized, or when the 273 | * {@link uniforms} are updated. If the {@link state} is `running`, this method will be called 274 | * recursively to update the time uniform and re-render the shader each frame. 275 | * 276 | * If the {@link state} is `disposed`, this method does nothing. 277 | */ 278 | render(): this; 279 | /** 280 | * This method runs `onmousemove` and does the following: 281 | * 1. Calculates the mouse position relative to the canvas. 282 | * 2. Normalizes it to the range `[0, 1]`. 283 | * 3. Applies the {@link mouseSmoothing|`mouseSmoothing`} factor. 284 | * 4. Updates the `u_mouse` uniform. 285 | */ 286 | setMousePosition: (e: MouseEvent | Touch) => void; 287 | /** 288 | * Just forwards the first touch event to {@link setMousePosition} on `touchmove`. 289 | * @todo - Can we just use`onpointermove` and get rid of this? 290 | */ 291 | setTouchPosition: (e: TouchEvent) => void; 292 | /** 293 | * Listen for events emitted by the renderer. 294 | * @param event The event to listen for. Currently, only `'render'` is supported, which runs _before_ each render. 295 | * @param listener The callback to run when the event is emitted. 296 | */ 297 | on: (event: 'render', listener: (data: { 298 | time: number; 299 | delta: number; 300 | }) => void) => this; 301 | /** 302 | * Dispatches the `render` event to all listeners. 303 | */ 304 | private _emit; 305 | /** 306 | * Compiles the shaders and creates the WebGL program. 307 | */ 308 | compile(): void; 309 | /** 310 | * Returns the target element to listen for mouse events on based on the 311 | * {@link PocketShaderOptions.mouseTarget|mouseTarget} option. 312 | */ 313 | getMouseTarget: () => HTMLElement | Window; 314 | private _setupCanvas; 315 | /** 316 | * Updates the cached bounding rect of the canvas element. 317 | */ 318 | private _updateRectCache; 319 | /** 320 | * Creates a WebGL program from the provided vertex and fragment shaders. 321 | */ 322 | private _createProgram; 323 | /** 324 | * Creates a WebGL shader from the provided source code. 325 | */ 326 | private _createShader; 327 | /** 328 | * Updates a uniform value on the shader program. 329 | */ 330 | private _setUniform; 331 | /** 332 | * Parses all of the uniforms in the fragment shader string and verifies that they exist in the 333 | * current {@link uniforms} object, throwing an error 334 | */ 335 | private _validateUniforms; 336 | /** 337 | * Cleans up all event listeners and WebGL resources. 338 | */ 339 | private _cleanupListeners; 340 | /** 341 | * Disposes of all resources and event listeners, the WebGL context, and removes the canvas 342 | * element from the DOM. 343 | */ 344 | dispose(): void; 345 | } 346 | 347 | export { PocketShader, type PocketShaderOptions }; 348 | -------------------------------------------------------------------------------- /dist/pocket-shader.min.js: -------------------------------------------------------------------------------- 1 | var a=class{container;canvas;ctx=null;vertex;fragment;maxPixelRatio;speed;state="stopped";opts={};program=!1;mouse={x:.5,y:.5};mouseSmoothed={x:.5,y:.5};mouseSmoothing=.1;builtinUniforms={u_time:0,u_resolution:[0,0],u_mouse:[.5,.5]};_positionBuffer=null;_builtinUniformLocations=new Map;_uniformLocations=new Map;_uniforms;_time=0;_resizeObserver;_canvasRectCache;_listeners=new Map;_arg1;_arg2;constructor(t,e){this._arg1=t,this._arg2=e;let s=!0;if(typeof t=="object"&&"autoInit"in t&&t.autoInit===!1&&(s=!1),typeof e=="object"&&"autoInit"in e&&e.autoInit===!1&&(s=!1),s){if(typeof globalThis.window>"u"){import.meta?.env?.DEV&&console.warn("PocketShader is not running in a browser environment. Aborting automatic initialization.");return}globalThis.document?.readyState==="loading"?document.addEventListener("DOMContentLoaded",this.init):this.init()}}get uniforms(){return this._uniforms}set uniforms(t){this._uniforms=t,this.state.match(/paused|stopped/)&&this.render()}get time(){return this._time}set time(t){this._time=t,this.builtinUniforms.u_time=t}get resolution(){return{width:this.canvas?.width,height:this.canvas?.height}}init(){let t,e;this._arg1 instanceof Element?(t=this._arg1,e=this._arg2):typeof this._arg1=="string"?(t=document.querySelector(this._arg1),e=this._arg2):(t=document.body,e=this._arg1),this.opts=e??{},this.opts.mouseTarget=e?.mouseTarget??"canvas",this.speed=e?.speed??1,this.mouseSmoothing=e?.mouseSmoothing??.1,this.maxPixelRatio=e?.maxPixelRatio??(globalThis.window?.devicePixelRatio||1),e?.canvas?(this.canvas=e.canvas,t=this.canvas.parentElement):this.canvas=document.createElement("canvas"),this.container=t??document.body,this._canvasRectCache=this.canvas.getBoundingClientRect(),this.vertex=e?.vertex??`#version 300 es 2 | layout(location = 0) in vec4 a_position;out vec2 vUv;void main() {vUv = a_position.xy * 0.5 + 0.5;gl_Position = a_position;}`,this.fragment=e?.fragment??`#version 300 es 3 | precision mediump float;uniform float u_time;in vec2 vUv;out vec4 color;void main() {color = vec4(vUv, 0.5 + 0.5 * sin(u_time), 1.0);}`,this.vertex.startsWith("#version")||(this.vertex=`#version 300 es 4 | `+this.vertex);let s=this.fragment.split(` 5 | `),i=[s[0].startsWith("#version")?s.shift():"#version 300 es"];return this.fragment.includes("precision")||i.push(`precision mediump float; 6 | `),this.fragment=i.concat(s).join(` 7 | `),this._uniforms=e?.uniforms??{},this._setupCanvas(),this._resizeObserver=new ResizeObserver(this.resize),this._resizeObserver.observe(this.canvas),this._resizeObserver.observe(this.container),this.compile(),e?.mousePosition&&(this.mouse=e.mousePosition),e?.autoStart===!0?this.start():this._resize(),(this.container.clientWidth===0||this.container.clientHeight===0)&&console.error("PocketShader container has a width or height of 0px. The canvas will not be visible until the container has a non-zero size size.",{container:this.container,canvas:this.canvas,this:this}),this}start(){switch(this.state){case"stopped":case"paused":this.state="running",this._resize(),this.render();break;case"running":break;case"disposed":throw new Error("Cannot start a disposed PocketShader. Call restart() instead.")}return this}pause(){return this.state="paused",this}stop(){return this.state="stopped",this.time=0,this}restart(){switch(this.state){case"running":break;case"stopped":case"paused":this.start();break;case"disposed":this.reload()}return this}reload(){let t=this.state==="running";return t&&this.pause(),this.dispose(),this.state="stopped",this.compile(),this._resize(),t&&this.start(),this}_resize=()=>{let t=this.container instanceof HTMLBodyElement?window.innerWidth:this.container.clientWidth,e=this.container instanceof HTMLBodyElement?window.innerHeight:this.container.clientHeight;return(this.canvas.width!==t||this.canvas.height!==e||this.maxPixelRatio!==this.opts.maxPixelRatio)&&(this.canvas.width=t*this.maxPixelRatio,this.canvas.height=e*this.maxPixelRatio,this.canvas.style.width=t+"px",this.canvas.style.height=e+"px",this.builtinUniforms.u_resolution[0]=t*this.maxPixelRatio,this.builtinUniforms.u_resolution[1]=e*this.maxPixelRatio),this._canvasRectCache=this.canvas.getBoundingClientRect(),this.state.match(/paused|stopped/)&&this.render(),this};resize=h(this._resize);render(){let t=0,e=s=>{if(this.state==="disposed")return;if(!this.ctx)throw new Error("WebGL context lost.");s*=.001;let i=Math.min(s-t,.1);this._listeners.size&&this._emit(this.time,i),this.state==="running"&&(this.time+=i*this.speed),t=s,this.mouseSmoothed.x+=(this.mouse.x-this.mouseSmoothed.x)*(1-this.mouseSmoothing),this.mouseSmoothed.y+=(this.mouse.y-this.mouseSmoothed.y)*(1-this.mouseSmoothing),this.builtinUniforms.u_mouse[0]=this.mouseSmoothed.x,this.builtinUniforms.u_mouse[1]=this.mouseSmoothed.y,this.ctx.viewport(0,0,this.ctx.canvas.width,this.ctx.canvas.height),this.ctx.useProgram(this.program);let n=this._builtinUniformLocations.get("a_position"),o=this._builtinUniformLocations.get("u_resolution"),c=this._builtinUniformLocations.get("u_mouse"),u=this._builtinUniformLocations.get("u_time");this.ctx.enableVertexAttribArray(n),this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER,this._positionBuffer),this.ctx.vertexAttribPointer(n,2,this.ctx.FLOAT,!1,0,0),this.ctx.uniform2f(o,this.builtinUniforms.u_resolution[0],this.builtinUniforms.u_resolution[1]),this.ctx.uniform2f(c,this.builtinUniforms.u_mouse[0],this.builtinUniforms.u_mouse[1]),this.ctx.uniform1f(u,this.builtinUniforms.u_time);for(let[m,d]of this._uniformLocations)this._setUniform(this.ctx,d,this.uniforms[m]);this.ctx.drawArrays(this.ctx.TRIANGLES,0,6),this.state==="running"&&requestAnimationFrame(e)};return requestAnimationFrame(e),this}setMousePosition=t=>{this.mouse.x=(t.clientX-this._canvasRectCache.left)/this._canvasRectCache.width,this.mouse.y=(this._canvasRectCache.height-(t.clientY-this._canvasRectCache.top)-1)/this._canvasRectCache.height};setTouchPosition=t=>{this.opts.preventScroll===!0&&t.preventDefault(),this.setMousePosition(t.touches[0])};on=(t,e)=>(this._listeners.set(t,e),this);_emit=(t,e)=>{for(let[s,i]of this._listeners)switch(s){case"render":i({time:t,delta:e});break}};compile(){if(this.ctx=this.canvas.getContext("webgl2"),!this.ctx)throw new Error("WebGL2 context not found.");let t=this._createShader(this.ctx,this.ctx.VERTEX_SHADER,this.vertex),e=this._createShader(this.ctx,this.ctx.FRAGMENT_SHADER,this.fragment);this.program=this._createProgram(this.ctx,t,e),this._builtinUniformLocations.set("a_position",this.ctx.getUniformLocation(this.program,"a_position")),this._builtinUniformLocations.set("u_resolution",this.ctx.getUniformLocation(this.program,"u_resolution")),this._builtinUniformLocations.set("u_mouse",this.ctx.getUniformLocation(this.program,"u_mouse")),this._builtinUniformLocations.set("u_time",this.ctx.getUniformLocation(this.program,"u_time")),this.opts.fragment&&this._validateUniforms(this.opts.fragment);for(let s in this.uniforms)this._uniformLocations.set(s,this.ctx.getUniformLocation(this.program,s));this._positionBuffer=this.ctx.createBuffer(),this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER,this._positionBuffer),this.ctx.bufferData(this.ctx.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]),this.ctx.STATIC_DRAW)}getMouseTarget=()=>{switch(this.opts.mouseTarget){case"container":return this.container??window;case"window":return window;case"canvas":default:return this.canvas}};_setupCanvas(){if(!this.container)throw new Error("Container not found.");if(this.canvas.style.setProperty("contain","strict"),this.container instanceof HTMLBodyElement){let t=window.innerWidth,e=window.innerHeight;this.canvas.style.setProperty("width",`${t}px`),this.canvas.style.setProperty("height",`${e}px`),this.canvas.style.setProperty("position","fixed"),this.canvas.style.setProperty("inset","0"),this.canvas.style.setProperty("zIndex","0")}else this.canvas.style.setProperty("width",`${this.container.clientWidth}px`),this.canvas.style.setProperty("height",`${this.container.clientHeight}px`),this.canvas.style.setProperty("position","absolute"),this.canvas.style.setProperty("inset","0"),this.canvas.style.setProperty("zIndex","1"),this.container.style.position===""&&(this.container.style.position="relative");this.getMouseTarget().addEventListener("mousemove",this.setMousePosition),this.getMouseTarget().addEventListener("touchmove",this.setTouchPosition,{passive:!1}),Array.from(this.container.children).includes(this.canvas)||this.container.appendChild(this.canvas),window.addEventListener("resize",this.resize),window.addEventListener("scroll",this._updateRectCache)}_updateRectCache=h(()=>{this._canvasRectCache=this.canvas.getBoundingClientRect()});_createProgram(t,e,s){let i=t.createProgram();return t.attachShader(i,e),t.attachShader(i,s),t.linkProgram(i),t.getProgramParameter(i,t.LINK_STATUS)?i:(console.error(t.getProgramInfoLog(i)),t.deleteProgram(i),!1)}_createShader(t,e,s){let i=t.createShader(e);return t.shaderSource(i,s),t.compileShader(i),t.getShaderParameter(i,t.COMPILE_STATUS)?i:(console.error("Failed to compile shader:"),console.error({type:e,source:s,this:this}),console.error(t.getShaderInfoLog(i)),t.deleteShader(i),!1)}_setUniform(t,e,s){if(e===null)return;let{type:i,value:n}=s;switch(i){case"float":t.uniform1f(e,n);break;case"vec2":t.uniform2fv(e,n);break;case"vec3":t.uniform3fv(e,n);break;case"vec4":t.uniform4fv(e,n);break;case"int":t.uniform1i(e,n);break;default:throw new Error(`Unsupported uniform type: ${i}`)}}_validateUniforms(t){let e=/uniform\s+(\w+)\s+(\w+)\s*;/g,s;for(;(s=e.exec(t))!==null;){let[,,i]=s;if(![...this._builtinUniformLocations.keys(),...Object.keys(this.uniforms)].some(n=>n===i))throw new Error(`Uniform "${i}" is referenced in the shader, but no value was provided in PocketShaderOptions.uniforms.`)}return this}_cleanupListeners(){this.canvas.removeEventListener("mousemove",this.setMousePosition),this.canvas.removeEventListener("touchmove",this.setTouchPosition),window.removeEventListener("resize",this.resize),window.removeEventListener("scroll",this._updateRectCache),this._resizeObserver.disconnect(),this._listeners.clear()}dispose(){this.state="disposed",this._cleanupListeners(),this._builtinUniformLocations.clear(),this._uniformLocations.clear(),this.canvas?.remove(),this.ctx?.getExtension("WEBGL_lose_context")?.loseContext(),this.ctx=null}};function h(r,t=50,e=75){let s,i=0;return function(...n){let o=performance.now();clearTimeout(s),o-i>=t&&(i=o,r(...n)),s=setTimeout(()=>{r(...n)},e)}}export{a as PocketShader}; 8 | -------------------------------------------------------------------------------- /jsr.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://jsr.io/schema/config-file.v1.json", 3 | "name": "@braebo/pocket-shader", 4 | "version": "0.3.1", 5 | "exports": "./src/pocket-shader.ts", 6 | "publish": { 7 | "include": [ 8 | "./src/pocket-shader.ts", 9 | "README.md", 10 | "LICENSE" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pocket-shader", 3 | "version": "0.3.1", 4 | "description": "A typescript package that makes it easy to render WebGL shaders in the browser.", 5 | "homepage": "https://pocket-shader.braebo.dev", 6 | "keywords": [ 7 | "webgl", 8 | "shaders", 9 | "typescript", 10 | "javascript", 11 | "animation", 12 | "glsl", 13 | "jsr" 14 | ], 15 | "license": "MIT", 16 | "author": "braebo", 17 | "type": "module", 18 | "files": [ 19 | "dist", 20 | "README.md", 21 | "LICENSE" 22 | ], 23 | "types": "./dist/pocket-shader.d.ts", 24 | "exports": { 25 | "./package.json": "./package.json", 26 | ".": { 27 | "types": "./dist/pocket-shader.d.ts", 28 | "default": "./dist/pocket-shader.js" 29 | } 30 | }, 31 | "scripts": { 32 | "dev": "cd www && pnpm dev", 33 | "build": "tsup", 34 | "build:site": "cd www && pnpm build", 35 | "release": "pnpm changeset", 36 | "size": "cat dist/pocket-shader.min.js | pnpx brotli-size-cli", 37 | "sync-version": "changeset version && pnpx sync-version jsr.json", 38 | "lint": "publint" 39 | }, 40 | "devDependencies": { 41 | "@changesets/cli": "^2.27.5", 42 | "@svitejs/changesets-changelog-github-compact": "^1.1.0", 43 | "@types/bun": "^1.1.3", 44 | "@types/node": "^20.14.2", 45 | "changeset": "^0.2.6", 46 | "publint": "^0.2.8", 47 | "tsup": "^8.1.0", 48 | "typescript": "^5.4.5" 49 | } 50 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["ESNext", "DOM"], 4 | "types": ["node"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | 8 | "strict": true, 9 | "allowJs": true, 10 | "checkJs": true, 11 | "skipLibCheck": true, 12 | "noFallthroughCasesInSwitch": true, 13 | 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noPropertyAccessFromIndexSignature": true, 17 | 18 | // JSR 19 | "noImplicitReturns": true, 20 | 21 | // Decorators 22 | "experimentalDecorators": true, 23 | "emitDecoratorMetadata": true, 24 | 25 | "outDir": "dist" 26 | }, 27 | "include": ["src/pocket-shader.ts"] 28 | } 29 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig([ 4 | { 5 | entry: { 6 | 'pocket-shader.min': 'src/pocket-shader.ts', 7 | }, 8 | minify: !0, 9 | name: 'minified', 10 | format: ['esm'], 11 | clean: true, 12 | dts: !!0, 13 | }, 14 | { 15 | entry: ['src/pocket-shader.ts'], 16 | name: 'standard', 17 | format: ['esm'], 18 | clean: true, 19 | dts: true, 20 | }, 21 | ]) 22 | -------------------------------------------------------------------------------- /www/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | # jetbrains setting folder 24 | .idea/ 25 | -------------------------------------------------------------------------------- /www/.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | "plugins": ['prettier-plugin-astro'], 3 | "overrides": [ 4 | { 5 | "files": "*.astro", 6 | "options": { 7 | "parser": "astro" 8 | } 9 | } 10 | ], 11 | "trailingComma": "all", 12 | "requirePragma": false, 13 | "arrowParens": "avoid", 14 | "bracketSpacing": true, 15 | "singleQuote": true, 16 | "printWidth": 100, 17 | "useTabs": true, 18 | "tabWidth": 4, 19 | "semi": false 20 | } -------------------------------------------------------------------------------- /www/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /www/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /www/README.md: -------------------------------------------------------------------------------- 1 | # pocket-shader 2 | 3 | WIP 4 | -------------------------------------------------------------------------------- /www/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/assets/favicon.png -------------------------------------------------------------------------------- /www/assets/pocket-shader-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/assets/pocket-shader-transparent.png -------------------------------------------------------------------------------- /www/assets/pocket-shader-transparent.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/assets/pocket-shader-transparent.webp -------------------------------------------------------------------------------- /www/assets/pocket-shader.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/assets/pocket-shader.webp -------------------------------------------------------------------------------- /www/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config' 2 | import svelte from '@astrojs/svelte' 3 | 4 | export default defineConfig({ 5 | integrations: [svelte()], 6 | }) 7 | -------------------------------------------------------------------------------- /www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pocket-shader-www", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro check && astro build", 9 | "preview": "astro preview", 10 | "astro": "astro" 11 | }, 12 | "dependencies": { 13 | "@astrojs/check": "0.7.0", 14 | "@astrojs/svelte": "5.5.0", 15 | "astro": "4.10.0", 16 | "svelte": "4.2.18", 17 | "typescript": "5.4.5" 18 | }, 19 | "devDependencies": { 20 | "@shikijs/transformers": "1.6.2", 21 | "@vitest/ui": "1.6.0", 22 | "picocolors": "1.0.1", 23 | "pocket-shader": "link:..", 24 | "prettier": "3.3.1", 25 | "prettier-plugin-astro": "0.14.0", 26 | "sass": "1.77.4", 27 | "shiki": "1.6.2", 28 | "vite": "^5.2.12", 29 | "vitest": "1.6.0" 30 | }, 31 | "pnpm": { 32 | "patchedDependencies": { 33 | "astro@4.10.0": "patches/astro@4.10.0.patch" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /www/patches/astro@4.10.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/components/Code.astro b/components/Code.astro 2 | index f0cb26326516686554a6136da4ab84b6dc70184f..309cb6e65f2668b93799fa3a54afb2511cdaaeb0 100644 3 | --- a/components/Code.astro 4 | +++ b/components/Code.astro 5 | @@ -6,6 +6,7 @@ import type { 6 | SpecialLanguage, 7 | ThemeRegistration, 8 | ThemeRegistrationRaw, 9 | + ShikiTransformer, 10 | } from 'shiki'; 11 | import { bundledLanguages } from 'shiki/langs'; 12 | import { getCachedHighlighter } from '../dist/core/shiki.js'; 13 | @@ -50,6 +51,12 @@ interface Props extends Omit, 'lang'> { 14 | * @default false 15 | */ 16 | inline?: boolean; 17 | + /** 18 | + * Shiki transformers to customize the generated HTML by manipulating the hast tree. 19 | + * Supports all transformers listed here: https://shiki.style/packages/transformers#transformers 20 | + * Instructions for custom transformers: https://shiki.style/guide/transformers 21 | + */ 22 | + transformers?: ShikiTransformer[]; 23 | } 24 | 25 | const { 26 | @@ -59,6 +66,7 @@ const { 27 | themes = {}, 28 | wrap = false, 29 | inline = false, 30 | + transformers = [], 31 | ...rest 32 | } = Astro.props; 33 | 34 | @@ -85,6 +93,7 @@ const highlighter = await getCachedHighlighter({ 35 | theme, 36 | themes, 37 | wrap, 38 | + transformers, 39 | }); 40 | 41 | const html = await highlighter.highlight(code, typeof lang === 'string' ? lang : lang.name, { 42 | -------------------------------------------------------------------------------- /www/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /www/public/android-chrome-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/public/android-chrome-384x384.png -------------------------------------------------------------------------------- /www/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/public/apple-touch-icon.png -------------------------------------------------------------------------------- /www/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #ffffff 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /www/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/public/favicon-16x16.png -------------------------------------------------------------------------------- /www/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/public/favicon-32x32.png -------------------------------------------------------------------------------- /www/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/public/favicon.ico -------------------------------------------------------------------------------- /www/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /www/public/fonts/Caveat-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/public/fonts/Caveat-VariableFont_wght.ttf -------------------------------------------------------------------------------- /www/public/fonts/Fredoka-VariableFont_wdth-wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/public/fonts/Fredoka-VariableFont_wdth-wght.ttf -------------------------------------------------------------------------------- /www/public/fonts/MonaspaceNeon_wght-wdth-slnt.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/public/fonts/MonaspaceNeon_wght-wdth-slnt.ttf -------------------------------------------------------------------------------- /www/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/public/mstile-150x150.png -------------------------------------------------------------------------------- /www/public/pocket-shader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/public/pocket-shader.png -------------------------------------------------------------------------------- /www/public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 30 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /www/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-384x384.png", 12 | "sizes": "384x384", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /www/src/components/Code.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { createCssVariablesTheme, type BuiltinLanguage } from 'shiki' 3 | import { Code as AstroCode } from 'astro:components' 4 | import { dedent } from '../utils/dedent.ts' 5 | import { nanoid } from '../utils/nanoid' 6 | import Copy from './Copy.svelte' 7 | import { 8 | transformerNotationWordHighlight, 9 | transformerNotationHighlight, 10 | transformerNotationFocus, 11 | transformerNotationDiff, 12 | } from '@shikijs/transformers' 13 | 14 | interface Props { 15 | id: string 16 | code: string 17 | codes?: { lang: string, code: string, trim?: boolean }[] 18 | lang?: string 19 | fontSize?: string 20 | lineHeight?: string 21 | title?: string 22 | width?: string 23 | } 24 | 25 | const { 26 | id, 27 | code, 28 | codes = [], 29 | lang = 'ts', 30 | fontSize = 'var(--font-sm)', 31 | lineHeight = 'var(--ling-height-sm)', 32 | title = '', 33 | width = '', 34 | } = Astro.props 35 | 36 | const theme = createCssVariablesTheme({ 37 | name: 'pocket-shader', 38 | fontStyle: true 39 | }) 40 | 41 | const copyId = 'copyId-' + nanoid(8) 42 | --- 43 | 44 |
45 | {title && } 46 | 47 |
48 |
49 | 50 |
51 |
52 | 53 |
54 | 55 | {codes.length ? ( 56 | codes.map(({lang, code, trim = false}) => ( 57 | 68 | )) 69 | ) : ( 70 | 82 | ) 83 | } 84 |
85 |
86 |
87 |
88 | 89 | 171 | -------------------------------------------------------------------------------- /www/src/components/Copy.svelte: -------------------------------------------------------------------------------- 1 | 50 | 51 | 97 | 98 | 230 | -------------------------------------------------------------------------------- /www/src/components/Example.svelte: -------------------------------------------------------------------------------- 1 | 81 | 82 | 94 | -------------------------------------------------------------------------------- /www/src/components/Github.astro: -------------------------------------------------------------------------------- 1 | 2 | 13 | 16 | 17 | 18 | 19 | 44 | -------------------------------------------------------------------------------- /www/src/components/GridShader.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 86 | 87 | { 89 | if (ps) { 90 | ps.uniforms.u_parallax.value = window.scrollY / ps.resolution.height 91 | } 92 | }} 93 | on:pointerdown={yeet} 94 | on:keydown={setYeet} 95 | /> 96 | -------------------------------------------------------------------------------- /www/src/components/Hero.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 |

Pocket Shader

11 | 12 |

A pocket-sized javascript library for easy WebGL shader rendering.

13 | 14 |
15 |
(hovering = true)} 18 | on:pointerout={() => (hovering = false)} 19 | > 20 | 21 |
npx
22 |
jsr add
23 |
24 | @braebo 25 | / 26 | pocket-shader 27 |
28 |
29 |
30 | 31 | 32 | 48 | 49 |
50 |
51 | 52 | 237 | -------------------------------------------------------------------------------- /www/src/components/Nav.svelte: -------------------------------------------------------------------------------- 1 | 70 | 71 | (mobile = globalThis.window.innerWidth < 900)} /> 72 | 73 | {#if mobile && sections} 74 | 75 | {/if} 76 | 77 | 91 | 92 | 225 | -------------------------------------------------------------------------------- /www/src/components/Nav/Mobile/Burger.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 |
32 | {#if mounted} 33 | {#key showMenu} 34 | 41 | 50 | 61 | 70 | 71 | {/key} 72 | {/if} 73 |
74 | 75 | 216 | -------------------------------------------------------------------------------- /www/src/components/Nav/Mobile/Mobile.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
(showMenu = false)} 13 | > 14 | 15 |
16 | 17 | 18 | 19 | {#if showMenu} 20 | 21 | {/if} 22 | 23 | 28 | -------------------------------------------------------------------------------- /www/src/components/Nav/Mobile/PageFill.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 | 37 | -------------------------------------------------------------------------------- /www/src/components/Range.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | 31 | 32 | 90 | -------------------------------------------------------------------------------- /www/src/components/Retro.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Retro

23 | 24 |
25 |
26 | 27 | 33 | -------------------------------------------------------------------------------- /www/src/components/ShaderSelector.svelte: -------------------------------------------------------------------------------- 1 | 59 | 60 |
61 | 62 | 67 | 68 | 82 | -------------------------------------------------------------------------------- /www/src/components/sections/Dpr.svelte: -------------------------------------------------------------------------------- 1 | 33 | 34 | 35 | 36 | 37 | 38 |

{dpr.toFixed(1)}

39 | 40 | 41 | -------------------------------------------------------------------------------- /www/src/components/sections/MouseSmoothing.svelte: -------------------------------------------------------------------------------- 1 | 54 | 55 | 56 | 57 | 58 | 59 |

{smoothing.toFixed(2)}

60 | 61 | 62 | -------------------------------------------------------------------------------- /www/src/components/sections/Playback.svelte: -------------------------------------------------------------------------------- 1 | 56 | 57 | 58 | 59 | 60 | 61 |
62 |
63 | 71 | 79 | 86 |
87 | 88 |
89 |
90 |
91 | 92 | ps 93 | . 94 | state 95 | 96 |
97 |
{state}
98 |
99 | 100 |
101 |
102 | 103 | ps 104 | . 105 | time 106 | 107 |
108 |
{t}
109 |
110 |
111 |
112 | 113 | 212 | -------------------------------------------------------------------------------- /www/src/components/sections/Speed.svelte: -------------------------------------------------------------------------------- 1 | 33 | 34 | 35 | 36 | 37 | 38 |

{speed.toFixed(2)}

39 | 40 | 41 | -------------------------------------------------------------------------------- /www/src/components/sections/Uniforms.svelte: -------------------------------------------------------------------------------- 1 | 58 | 59 | 60 | 61 | 62 | 63 |
64 |
65 | octave 66 |
{octave}
67 |
68 |
69 | 70 |
71 |
72 | 73 |
74 |
75 | zoom 76 |
{poly_zoom.toFixed(2)}
77 |
78 |
79 | 80 |
81 |
82 | 83 |
84 |
85 | sphere 86 |
{inner_sphere.toFixed(2)}
87 |
88 |
89 | 90 |
91 |
92 | 93 |
94 |
95 | refraction 96 |
{refr_index.toFixed(2)}
97 |
98 |
99 | 100 |
101 |
102 | 103 |
104 |
105 | U 106 |
{poly_U.toFixed(2)}
107 |
108 |
109 | 110 |
111 |
112 | 113 |
114 |
115 | V 116 |
{poly_V.toFixed(2)}
117 |
118 |
119 | 120 |
121 |
122 | 123 |
124 |
125 | W 126 |
{poly_W.toFixed(2)}
127 |
128 |
129 | 130 |
131 |
132 | 133 | 192 | -------------------------------------------------------------------------------- /www/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare const __DEV__: boolean; 4 | -------------------------------------------------------------------------------- /www/src/layouts/Layout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Github from '../components/Github.astro' 3 | import '../styles/font.css' 4 | import '../styles/variables.css' 5 | import '../styles/utilities.css' 6 | import '../styles/code.css' 7 | import '../styles/elements.css' // should be last 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Pocket Shader 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 59 | 60 | 61 | 88 | 89 | -------------------------------------------------------------------------------- /www/src/pages/test.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import ShaderSelector from "../components/ShaderSelector.svelte"; 3 | import Layout from "../layouts/Layout.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /www/src/shaders/bruh.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | // https://www.shadertoy.com/view/tlVGDt 3 | 4 | // precision highp float; 5 | precision mediump float; 6 | 7 | uniform vec2 u_resolution; 8 | uniform float u_time; 9 | 10 | float gTime = 0.; 11 | const float REPEAT = 5.0; 12 | 13 | // 回転行列 14 | mat2 rot(float a) { 15 | float c = cos(a), s = sin(a); 16 | return mat2(c, s, -s, c); 17 | } 18 | 19 | float sdBox(vec3 p, vec3 b) { 20 | vec3 q = abs(p) - b; 21 | return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0); 22 | } 23 | 24 | float box(vec3 pos, float scale) { 25 | pos *= scale; 26 | float base = sdBox(pos, vec3(.4, .4, .1)) / 1.5; 27 | pos.xy *= 5.; 28 | pos.y -= 3.5; 29 | pos.xy *= rot(.75); 30 | float result = -base; 31 | return result; 32 | } 33 | 34 | float box_set(vec3 pos, float u_time) { 35 | vec3 pos_origin = pos; 36 | pos = pos_origin; 37 | pos.y += sin(gTime * 0.4) * 2.5; 38 | pos.xy *= rot(.8); 39 | float box1 = box(pos, 2. - abs(sin(gTime * 0.4)) * 1.5); 40 | pos = pos_origin; 41 | pos.y -= sin(gTime * 0.4) * 2.5; 42 | pos.xy *= rot(.8); 43 | float box2 = box(pos, 2. - abs(sin(gTime * 0.4)) * 1.5); 44 | pos = pos_origin; 45 | pos.x += sin(gTime * 0.4) * 2.5; 46 | pos.xy *= rot(.8); 47 | float box3 = box(pos, 2. - abs(sin(gTime * 0.4)) * 1.5); 48 | pos = pos_origin; 49 | pos.x -= sin(gTime * 0.4) * 2.5; 50 | pos.xy *= rot(.8); 51 | float box4 = box(pos, 2. - abs(sin(gTime * 0.4)) * 1.5); 52 | pos = pos_origin; 53 | pos.xy *= rot(.8); 54 | float box5 = box(pos, .5) * 6.; 55 | pos = pos_origin; 56 | float box6 = box(pos, .5) * 6.; 57 | float result = max(max(max(max(max(box1, box2), box3), box4), box5), box6); 58 | return result; 59 | } 60 | 61 | float map(vec3 pos, float u_time) { 62 | vec3 pos_origin = pos; 63 | float box_set1 = box_set(pos, u_time); 64 | 65 | return box_set1; 66 | } 67 | 68 | in vec2 vUv; 69 | out vec4 fragColor; 70 | 71 | void main() { 72 | // vec2 fragCoord = gl_FragCoord.xy; 73 | vec2 fragCoord = vUv * u_resolution; 74 | vec2 p = (fragCoord.xy * 2. - u_resolution.xy) / min(u_resolution.x, u_resolution.y); 75 | vec3 ro = vec3(0., -0.2, u_time * 4.); 76 | vec3 ray = normalize(vec3(p, 1.5)); 77 | ray.xy = ray.xy * rot(sin(u_time * .03) * 5.); 78 | ray.yz = ray.yz * rot(sin(u_time * .05) * .2); 79 | float t = 0.1; 80 | vec3 col = vec3(0.); 81 | float ac = 0.0; 82 | 83 | // for(int i = 0; i < 99; i++) { 84 | for(int i = 0; i < 99; i++) { 85 | vec3 pos = ro + ray * t; 86 | pos = mod(pos - 2., 4.) - 2.; 87 | gTime = u_time - float(i) * 0.01; 88 | 89 | float d = map(pos, u_time); 90 | 91 | d = max(abs(d), 0.01); 92 | ac += exp(-d * 23.); 93 | 94 | t += d * 0.55; 95 | } 96 | 97 | col = vec3(ac * 0.02); 98 | 99 | col += vec3(0., 0.2 * abs(sin(u_time)), 0.5 + sin(u_time) * 0.2); 100 | 101 | fragColor = vec4(col, 1.0 - t * (0.02 + 0.02 * sin(u_time))); 102 | } 103 | -------------------------------------------------------------------------------- /www/src/shaders/clouds.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 u_resolution; 4 | uniform vec2 u_mouse; 5 | uniform float u_time; 6 | 7 | in vec2 vUv; 8 | out vec4 fragColor; 9 | 10 | float parameter1 = 0.; 11 | 12 | // Protean clouds by nimitz (twitter: @stormoid) 13 | // https://www.shadertoy.com/view/3l23Rh 14 | // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License 15 | // Contact the author for other licensing options 16 | 17 | /* 18 | Technical details: 19 | 20 | The main volume noise is generated from a deformed periodic grid, which can produce 21 | a large range of noise-like patterns at very cheap evalutation cost. Allowing for multiple 22 | fetches of volume gradient computation for improved lighting. 23 | 24 | To further accelerate marching, since the volume is smooth, more than half the density 25 | information isn't used to rendering or shading but only as an underlying volume distance to 26 | determine dynamic step size, by carefully selecting an equation (polynomial for speed) to 27 | step as a function of overall density (not necessarialy rendered) the visual results can be 28 | the same as a naive implementation with ~40% increase in rendering performance. 29 | 30 | Since the dynamic marching step size is even less uniform due to steps not being rendered at all 31 | the fog is evaluated as the difference of the fog integral at each rendered step. 32 | */ 33 | 34 | mat2 rot(in float a) { 35 | float c = cos(a), s = sin(a); 36 | return mat2(c, s, -s, c); 37 | } 38 | const mat3 m3 = mat3(0.33338, 0.56034, -0.71817, -0.87887, 0.32651, -0.15323, 0.15162, 0.69596, 0.61339) * 1.93; 39 | float mag2(vec2 p) { 40 | return dot(p, p); 41 | } 42 | float linstep(in float mn, in float mx, in float x) { 43 | return clamp((x - mn) / (mx - mn), 0., 1.); 44 | } 45 | 46 | vec2 disp(float t) { 47 | return vec2(sin(t * 0.22) * 1., cos(t * 0.175) * 1.) * 2.; 48 | } 49 | 50 | vec2 map(vec3 p) { 51 | vec3 p2 = p; 52 | p2.xy -= disp(p.z).xy; 53 | p.xy *= rot(sin(p.z + u_time) * (0.1 + parameter1 * 0.05) + u_time * 0.09); 54 | float cl = mag2(p2.xy); 55 | float d = 0.; 56 | p *= .61; 57 | float z = 1.; 58 | float trk = 1.; 59 | float dspAmp = 0.1 + parameter1 * 0.2; 60 | for(int i = 0; i < 5; i++) { 61 | p += sin(p.zxy * 0.75 * trk + u_time * trk * .8) * dspAmp; 62 | d -= abs(dot(cos(p), sin(p.yzx)) * z); 63 | z *= 0.57; 64 | trk *= 1.4; 65 | p = p * m3; 66 | } 67 | // d = abs(d + parameter1 * 3.) + parameter1 * .3 - 2.5 + u_mouse.y; 68 | d = abs(d + parameter1 * 3.) + parameter1 * .3 - 2.5 + pow(sin(u_time * 0.5), 5.0) * 2.; 69 | return vec2(d + cl * .2 + 0.25, cl); 70 | } 71 | 72 | vec4 render(in vec3 ro, in vec3 rd, float u_time) { 73 | vec4 rez = vec4(0); 74 | const float ldst = 8.; 75 | vec3 lpos = vec3(disp(u_time + ldst) * 0.5, u_time + ldst); 76 | float t = 1.5; 77 | float fogT = 0.; 78 | for(int i = 0; i < 130; i++) { 79 | if(rez.a > 0.99) 80 | break; 81 | 82 | vec3 pos = ro + t * rd; 83 | vec2 mpv = map(pos); 84 | float den = clamp(mpv.x - 0.3, 0., 1.) * 1.12; 85 | float dn = clamp((mpv.x + 2.), 0., 3.); 86 | 87 | vec4 col = vec4(0); 88 | if(mpv.x > 0.6) { 89 | 90 | col = vec4(sin(vec3(5., 0.4, 0.2) + mpv.y * 0.1 + sin(pos.z * 0.4) * 0.5 + 1.8) * 0.5 + 0.5, 0.08); 91 | col *= den * den * den; 92 | col.rgb *= linstep(4., -2.5, mpv.x) * 2.3; 93 | float dif = clamp((den - map(pos + .8).x) / 9., 0.001, 1.); 94 | dif += clamp((den - map(pos + .35).x) / 2.5, 0.001, 1.); 95 | col.xyz *= den * (vec3(0.005, .045, .075) + 1.5 * vec3(0.033, 0.07, 0.03) * dif); 96 | } 97 | 98 | float fogC = exp(t * 0.2 - 2.2); 99 | col.rgba += vec4(0.06, 0.11, 0.11, 0.1) * clamp(fogC - fogT, 0., 1.); 100 | fogT = fogC; 101 | rez = rez + col * (1. - rez.a); 102 | t += clamp(0.5 - dn * dn * .05, 0.09, 0.3); 103 | } 104 | return clamp(rez, 0.0, 1.0); 105 | } 106 | 107 | float getsat(vec3 c) { 108 | float mi = min(min(c.x, c.y), c.z); 109 | float ma = max(max(c.x, c.y), c.z); 110 | return (ma - mi) / (ma + 1e-7); 111 | } 112 | 113 | //from my "Will it blend" shader (https://www.shadertoy.com/view/lsdGzN) 114 | vec3 iLerp(in vec3 a, in vec3 b, in float x) { 115 | vec3 ic = mix(a, b, x) + vec3(1e-6, 0., 0.); 116 | float sd = abs(getsat(ic) - mix(getsat(a), getsat(b), x)); 117 | vec3 dir = normalize(vec3(2. * ic.x - ic.y - ic.z, 2. * ic.y - ic.x - ic.z, 2. * ic.z - ic.y - ic.x)); 118 | float lgt = dot(vec3(1.0), ic); 119 | float ff = dot(dir, normalize(ic)); 120 | ic += 1.5 * dir * sd * ff * lgt; 121 | return clamp(ic, 0., 1.); 122 | } 123 | 124 | void main() { 125 | vec2 q = vUv; 126 | vec2 center = (vUv - 0.5) * vec2(u_resolution.x / u_resolution.y, 1.0); 127 | 128 | float time = u_time * 2.0; 129 | vec3 ro = vec3(0, 0, time); 130 | 131 | // ro += vec3(sin(time) * 0.5, sin(time * 1.0) * 0.0, 0); 132 | ro += vec3(sin(time * 0.5) * 0.5, sin(time * 0.5) * 0.0, 0); 133 | 134 | float displacement = 0.85; 135 | ro.xy += disp(ro.z) * displacement; 136 | float target_distance = 3.5; 137 | 138 | vec3 target = normalize(ro - vec3(disp(time + target_distance) * displacement, time + target_distance)); 139 | 140 | // Center and scale mouse offset 141 | vec2 mouseOffset = (u_mouse - 0.5) * vec2(-3.0, 3.0); // Invert x-axis and scale 142 | ro.xy += mouseOffset; 143 | 144 | vec3 rightdir = normalize(cross(target, vec3(0, 1, 0))); 145 | vec3 updir = normalize(cross(rightdir, target)); 146 | rightdir = normalize(cross(updir, target)); 147 | vec3 rd = normalize((center.x * rightdir + center.y * updir) - target); 148 | 149 | // float parameter1 = smoothstep(-0.4, 0.4, sin(time * 0.3)); 150 | float parameter1 = smoothstep(-0.4, 0.4, sin(time * 0.25)); 151 | vec4 scn = render(ro, rd, time); 152 | 153 | vec3 col = scn.rgb; 154 | col = iLerp(col.bgr, col.rgb, clamp(1.0 - parameter1, 0.05, 1.0)); 155 | 156 | col = pow(col, vec3(0.55, 0.65, 0.6)) * vec3(1.0, 0.97, 0.9); 157 | 158 | col *= pow(16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y), 0.12) * 0.7 + 0.3; // Vignette effect 159 | 160 | fragColor = vec4(col, 1.0); 161 | } 162 | -------------------------------------------------------------------------------- /www/src/shaders/default.vert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braebo/pocket-shader/39b598481629bb8e080bb8a1764debc3764bb7cd/www/src/shaders/default.vert -------------------------------------------------------------------------------- /www/src/shaders/dyingUniverse.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform vec2 u_resolution; 5 | uniform float u_time; 6 | uniform vec4 u_mouse; 7 | 8 | in vec2 vUv; 9 | out vec4 fragColor; 10 | 11 | // "Dying Universe" by Martijn Steinrucken aka BigWings - 2015 12 | // License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. 13 | // Email:countfrolic@gmail.com Twitter:@The_ArtOfCode 14 | 15 | // Song: 16 | // Cubicolor - Fictionalise (Lindstrøm & Prins Thomas Remix) 17 | // https://soundcloud.com/cubicolor/fictionalise-lindstrom-prins-thomas-remix 18 | 19 | vec4 COOLCOLOR = vec4(1.f, .5f, 0.f, 0.f); 20 | vec4 HOTCOLOR = vec4(0.f, 0.1f, 1.f, 1.f); 21 | 22 | vec4 MIDCOLOR = vec4(0.5f, 0.3f, 0.f, 1.f); 23 | float STARSIZE = 0.03f; 24 | #define NUM_STARS 100 25 | #define NUM_BOUNCES 6 26 | #define FLOOR_REFLECT 27 | 28 | #define saturate(x) clamp(x,0.,1.) 29 | float DistSqr(vec3 a, vec3 b) { 30 | vec3 D = a - b; 31 | return dot(D, D); 32 | } 33 | float dist2(vec2 P0, vec2 P1) { 34 | vec2 D = P1 - P0; 35 | return dot(D, D); 36 | } 37 | 38 | const vec3 up = vec3(0.f, 1.f, 0.f); 39 | const float pi = 3.141592653589793238f; 40 | const float twopi = 6.283185307179586f; 41 | float time; 42 | 43 | struct ray { 44 | vec3 o; 45 | vec3 d; 46 | }; 47 | ray e; // the eye ray 48 | 49 | struct camera { 50 | vec3 p; // the position of the camera 51 | vec3 forward; // the camera forward vector 52 | vec3 left; // the camera left vector 53 | vec3 up; // the camera up vector 54 | 55 | vec3 center; // the center of the screen, in world coords 56 | vec3 i; // where the current ray intersects the screen, in world coords 57 | ray ray; // the current ray: from cam pos, through current uv projected on screen 58 | vec3 lookAt; // the lookat point 59 | float zoom; // the zoom factor 60 | }; 61 | camera cam; 62 | 63 | void CameraSetup(vec2 uv, vec3 position, vec3 lookAt, float zoom) { 64 | 65 | cam.p = position; 66 | cam.lookAt = lookAt; 67 | cam.forward = normalize(cam.lookAt - cam.p); 68 | cam.left = cross(up, cam.forward); 69 | cam.up = cross(cam.forward, cam.left); 70 | cam.zoom = zoom; 71 | 72 | cam.center = cam.p + cam.forward * cam.zoom; 73 | cam.i = cam.center + cam.left * uv.x + cam.up * uv.y; 74 | 75 | cam.ray.o = cam.p; // ray origin = camera position 76 | cam.ray.d = normalize(cam.i - cam.p); // ray direction is the vector from the cam pos through the point on the imaginary screen 77 | } 78 | 79 | vec4 Noise401(vec4 x) { 80 | return fract(sin(x) * 5346.1764f); 81 | } 82 | vec4 Noise4(vec4 x) { 83 | return fract(sin(x) * 5346.1764f) * 2.f - 1.f; 84 | } 85 | float Noise101(float x) { 86 | return fract(sin(x) * 5346.1764f); 87 | } 88 | 89 | float hash(float n) { 90 | return fract(sin(n) * 753.5453123f); 91 | } 92 | float noise(in vec3 x) { 93 | vec3 p = floor(x); 94 | vec3 f = fract(x); 95 | f = f * f * (3.0f - 2.0f * f); 96 | 97 | float n = p.x + p.y * 157.0f + 113.0f * p.z; 98 | return mix(mix(mix(hash(n + 0.0f), hash(n + 1.0f), f.x), mix(hash(n + 157.0f), hash(n + 158.0f), f.x), f.y), mix(mix(hash(n + 113.0f), hash(n + 114.0f), f.x), mix(hash(n + 270.0f), hash(n + 271.0f), f.x), f.y), f.z); 99 | } 100 | const mat3 m = mat3(0.00f, 0.80f, 0.60f, -0.80f, 0.36f, -0.48f, -0.60f, -0.48f, 0.64f); 101 | 102 | float PeriodicPulse(float x, float p) { 103 | // pulses from 0 to 1 with a period of 2 pi 104 | // increasing p makes the pulse sharper 105 | return pow((cos(x + sin(x)) + 1.f) / 2.f, p); 106 | } 107 | 108 | float SlantedCosine(float x) { 109 | // its a cosine.. but skewed so that it rises slowly and drops quickly 110 | // if anyone has a better function for this i'd love to hear about it 111 | x -= 3.55f; // shift the phase so its in line with a cosine 112 | return cos(x - cos(x) * 0.5f); 113 | } 114 | 115 | vec3 ClosestPoint(ray r, vec3 p) { 116 | // returns the closest point on ray r to point p 117 | return r.o + max(0.f, dot(p - r.o, r.d)) * r.d; 118 | } 119 | 120 | float BounceFast(float t) { 121 | // Precomputed bounced interpolation 122 | // 2 bounces, decay of 0.3 123 | 124 | t *= 2.695445115f; // comment out if you don't need t normalized 125 | 126 | float a1 = 1.f - t * t; 127 | t -= 1.5477225575f; // 1 + sqrt(0.3) 128 | float a2 = 0.3f - t * t; 129 | t -= 0.8477225575f; // sqrt(0.3) + sqrt(0.09) 130 | float a3 = 0.09f - t * t; 131 | 132 | return max(max(a1, a2), max(a3, 0.f)); 133 | } 134 | 135 | #define NUM_ARCS NUM_BOUNCES+1 136 | float Bounce(float t, float decay) { 137 | // Returns a bounced interpolation 138 | // t = u_time 139 | // start of bounce is 0 140 | // end of bounce depends on number of bounces and decay param 141 | // decay = how much lower each successive bounce is 142 | // 0 = there is no bounce at all 143 | // 0.5 = each successive bounce is half as high as the previous one 144 | // 1 = there is no energy loss, it would bounce forever 145 | 146 | float height = 1.f; 147 | float halfWidth = 1.f; 148 | float previousHalf = 1.f; 149 | 150 | float y = 1.f - t * t; 151 | 152 | height = 1.f; 153 | for(int i = 1; i < NUM_BOUNCES; i++) { 154 | height *= decay; 155 | previousHalf = halfWidth; 156 | halfWidth = sqrt(height); 157 | t -= previousHalf + halfWidth; 158 | y = max(y, height - t * t); 159 | } 160 | 161 | return saturate(y); 162 | } 163 | 164 | float BounceNorm(float t, float decay) { 165 | // Returns a bounced interpolation 166 | // Like Bounce but this one is u_time-normalized 167 | // t = 0 is start of bounce 168 | // t = 1 is end of bounce 169 | 170 | float height = 1.f; 171 | 172 | float heights[NUM_ARCS]; 173 | heights[0] = 1.f; 174 | float halfDurations[NUM_ARCS]; 175 | halfDurations[0] = 1.f; 176 | float halfDuration = 0.5f; 177 | for(int i = 1; i < NUM_ARCS; i++) { // calculate the heights and durations of each bounc 178 | height *= decay; 179 | heights[i] = height; 180 | halfDurations[i] = sqrt(height); 181 | halfDuration += halfDurations[i]; 182 | } 183 | t *= halfDuration * 2.f; // normalize u_time 184 | 185 | float y = 1.f - t * t; 186 | 187 | for(int i = 1; i < NUM_ARCS; i++) { 188 | t -= halfDurations[i - 1] + halfDurations[i]; 189 | y = max(y, heights[i] - t * t); 190 | } 191 | 192 | return saturate(y); 193 | } 194 | 195 | vec3 IntersectPlane(ray r, vec4 plane) { 196 | // returns the intersection point between a ray and a plane 197 | vec3 n = plane.xyz; 198 | vec3 p0 = plane.xyz * plane.w; 199 | float t = dot(p0 - r.o, n) / dot(r.d, n); 200 | return r.o + max(0.f, t) * r.d; // not quite sure what to return if there is no intersection 201 | // right now it just returns the ray origin 202 | } 203 | vec3 IntersectPlane(ray r) { 204 | // no plane param gives ground-plane intersection 205 | return IntersectPlane(r, vec4(0.f, 1.f, 0.f, 0.f)); 206 | } 207 | 208 | float Circle(vec2 pos, vec2 uv, float radius) { 209 | return smoothstep(radius, radius * 0.9f, length(uv - pos)); 210 | } 211 | 212 | // ------------------------------------------------------------- 213 | 214 | vec4 Star(ray r, float seed) { 215 | vec4 noise = Noise4(vec4(seed, seed + 1.f, seed + 2.f, seed + 3.f)); 216 | 217 | float t = fract(u_time * 0.1f + seed) * 2.f; 218 | 219 | float fade = smoothstep(2.f, 0.5f, t); // fade out; 220 | vec4 col = mix(COOLCOLOR, HOTCOLOR, fade); // vary color with size 221 | float size = STARSIZE + seed * 0.03f; // random variation in size 222 | size *= fade; 223 | 224 | float b = BounceNorm(t, 0.4f + seed * 0.1f) * 7.f; 225 | b += size; 226 | 227 | vec3 sparkPos = vec3(noise.x * 10.f, b, noise.y * 10.f); 228 | vec3 closestPoint = ClosestPoint(r, sparkPos); 229 | 230 | float dist = DistSqr(closestPoint, sparkPos) / (size * size); 231 | float brightness = 1.f / dist; 232 | col *= brightness; 233 | 234 | return col; 235 | } 236 | 237 | vec3 stars[100]; 238 | 239 | vec4 Star2(ray r, int i) { 240 | vec3 sparkPos = stars[10]; 241 | vec3 closestPoint = ClosestPoint(r, sparkPos); 242 | 243 | float dist = DistSqr(closestPoint, sparkPos) / (0.01f); 244 | float brightness = 1.f / dist; 245 | vec4 col = vec4(brightness); 246 | 247 | return col; 248 | } 249 | 250 | vec4 Stars(ray r) { 251 | vec4 col = vec4(0.f); 252 | 253 | float s = 0.f; 254 | for(int i = 0; i < NUM_STARS; i++) { 255 | s++; 256 | col += Star(r, Noise101(s)); 257 | } 258 | 259 | return col; 260 | } 261 | 262 | float Greasy(vec3 I) { 263 | vec3 q = 8.0f * I; 264 | float f; 265 | f = 0.5000f * noise(q); 266 | q = m * q * 2.01f; 267 | f += 0.2500f * noise(q); 268 | q = m * q * 2.02f; 269 | f += 0.1250f * noise(q); 270 | q = m * q * 2.03f; 271 | f += 0.0625f * noise(q); 272 | q = m * q * 2.01f; 273 | 274 | return f; 275 | 276 | } 277 | 278 | vec4 CalcStarPos(int i) { 279 | // returns the position in xyz and the fade value in w 280 | 281 | float n = Noise101(float(i)); 282 | 283 | vec4 noise = Noise4(vec4(n, n + 1.f, n + 2.f, n + 3.f)); 284 | 285 | float t = fract(u_time * 0.1f + n) * 2.f; 286 | 287 | float fade = smoothstep(2.f, 0.5f, t); // fade out; 288 | 289 | float size = STARSIZE + n * 0.03f; // random variation in size 290 | size *= fade; 291 | 292 | float b = BounceNorm(t, 0.4f + n * 0.1f) * 7.f; 293 | b += size; 294 | 295 | vec3 sparkPos = vec3(noise.x * 10.f, b, noise.y * 10.f); 296 | 297 | return vec4(sparkPos.xyz, fade); 298 | } 299 | 300 | vec4 Ground(ray r) { 301 | 302 | vec4 ground = vec4(0.f); 303 | 304 | if(r.d.y > 0.f) 305 | return ground; 306 | 307 | vec3 I = IntersectPlane(r); // eye-ray ground intersection point 308 | 309 | vec3 R = reflect(r.d, up); 310 | ray ref = ray(I, R); 311 | 312 | for(int i = 0; i < NUM_STARS; i++) { 313 | vec4 star = CalcStarPos(i); 314 | 315 | vec3 L = star.xyz - I; 316 | float dist = length(L); 317 | L /= dist; 318 | 319 | float lambert = saturate(dot(L, up)); 320 | float light = lambert / pow(dist, 1.f); 321 | 322 | vec4 col = mix(COOLCOLOR, MIDCOLOR, star.w); // vary color with size 323 | vec4 diffuseLight = vec4(light) * 0.1f * col; 324 | 325 | ground += diffuseLight * (sin(u_time) * 0.5f + 0.6f); 326 | 327 | #ifdef FLOOR_REFLECT 328 | float spec = pow(saturate(dot(R, L)), 400.f); 329 | float fresnel = 1.f - saturate(dot(L, up)); 330 | fresnel = pow(fresnel, 10.f); 331 | 332 | vec4 specLight = col * spec / (dist); 333 | specLight *= star.w; 334 | ground += specLight * 0.5f * fresnel; 335 | #endif 336 | 337 | } 338 | 339 | return ground; 340 | } 341 | 342 | void main() { 343 | vec2 uv = (gl_FragCoord.xy / u_resolution.xy) - 0.5f; 344 | // vec2 uv = vUv; 345 | uv.y *= u_resolution.y / u_resolution.x; 346 | 347 | time = u_time * 0.2f; 348 | fragColor = vec4(uv, 0.5f + 0.5f * sin(time), 1.0f); 349 | 350 | time *= 2.f; 351 | 352 | float t = time * pi * 0.1f; 353 | COOLCOLOR = vec4(sin(t), cos(t * 0.23f), cos(t * 0.3453f), 1.f) * 0.5f + 0.5f; 354 | HOTCOLOR = vec4(sin(t * 2.f), cos(t * 2.f * 0.33f), cos(t * 0.3453f), 1.f) * 0.5f + 0.5f; 355 | 356 | vec4 white = vec4(1.f); 357 | float whiteFade = sin(time * 2.f) * 0.5f + 0.5f; 358 | HOTCOLOR = mix(HOTCOLOR, white, whiteFade); 359 | 360 | MIDCOLOR = (HOTCOLOR + COOLCOLOR) * 0.5f; 361 | 362 | float s = sin(t); 363 | float c = cos(t); 364 | mat3 rot = mat3(c, 0.f, s, 0.f, 1.f, 0.f, s, 0.f, -c); 365 | 366 | float camHeight = mix(3.5f, 0.1f, PeriodicPulse(time * 0.1f, 2.f)); 367 | vec3 pos = vec3(0.f, camHeight, -10.f) * rot * (1.f + sin(time) * 0.3f); 368 | 369 | CameraSetup(uv, pos, vec3(0.f), 0.5f); 370 | 371 | fragColor = Ground(cam.ray); 372 | fragColor += Stars(cam.ray); 373 | } 374 | -------------------------------------------------------------------------------- /www/src/shaders/fractalPyramid.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision mediump float; 3 | 4 | uniform vec2 u_resolution; 5 | uniform float u_time; 6 | 7 | in vec2 vUv; 8 | out vec4 fragColor; 9 | 10 | vec3 palette(float d) { 11 | return mix(vec3(0.2f, 0.7f, 0.9f), vec3(1.f, 0.f, 1.f), d); 12 | } 13 | 14 | vec2 rotate(vec2 p, float a) { 15 | float c = cos(a); 16 | float s = sin(a); 17 | return p * mat2(c, s, -s, c); 18 | } 19 | 20 | float map(vec3 p) { 21 | for(int i = 0; i < 8; ++i) { 22 | float t = u_time * 0.1f; 23 | p.xz = rotate(p.xz, t); 24 | p.xy = rotate(p.xy, t * 1.89f); 25 | p.xz = abs(p.xz); 26 | p.xz -= .5f; 27 | } 28 | return dot(sign(p), p) / 4.f; 29 | } 30 | 31 | vec4 rm(vec3 ro, vec3 rd) { 32 | float t = 0.f; 33 | vec3 col = vec3(0.f); 34 | float d; 35 | for(float i = 0.f; i < 150.f; i++) { 36 | vec3 p = ro + rd * t; 37 | d = map(p) * .5f; 38 | if(d < 0.05f) { 39 | break; 40 | } 41 | if(d > 100.f) { 42 | break; 43 | } 44 | col += palette(length(p) * .1f) / (500.f * (d)); 45 | t += d; 46 | } 47 | return vec4(col, 1.f / (d * 100.f)); 48 | } 49 | 50 | 51 | // in vec2 vUv; 52 | 53 | void main() { 54 | // vec2 uv = (vUv - (u_resolution.xy / 2.)) / u_resolution.x; 55 | vec2 uv = vUv * 2.f - 1.f; 56 | vec3 ro = vec3(0.f, 0.f, -50.f); 57 | ro.xz = rotate(ro.xz, u_time); 58 | vec3 cf = normalize(-ro); 59 | vec3 cs = normalize(cross(cf, vec3(0.f, 1.f, 0.f))); 60 | vec3 cu = normalize(cross(cf, cs)); 61 | 62 | vec3 uuv = ro + cf * 3.f + uv.x * cs + uv.y * cu; 63 | 64 | vec3 rd = normalize(uuv - ro); 65 | 66 | vec4 col = rm(ro, rd); 67 | 68 | fragColor = col; 69 | } -------------------------------------------------------------------------------- /www/src/shaders/glass.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision mediump float; 3 | 4 | out vec4 fragColor; 5 | in vec2 vUv; 6 | 7 | uniform vec2 u_resolution; 8 | uniform vec2 u_mouse; 9 | uniform float u_time; 10 | 11 | uniform float poly_U; 12 | uniform float poly_V; 13 | uniform float poly_W; 14 | uniform int octave; 15 | uniform float inner_sphere; 16 | uniform float refr_index; 17 | uniform float poly_zoom; 18 | 19 | //! ------------------------------------------------------ 20 | 21 | // CC0: Let's self reflect - https://www.shadertoy.com/view/XfyXRV 22 | // Function to generate the solid found here: https://www.shadertoy.com/view/MsKGzw 23 | 24 | // Tinker with these parameters to create different solids 25 | // ------------------------------------------------------- 26 | const float rotation_speed = 0.25f; 27 | 28 | #define MAX_BOUNCES2 6 29 | // ------------------------------------------------------- 30 | 31 | #define TIME u_time 32 | #define RESOLUTION u_resolution 33 | #define PI 3.141592654 34 | #define TAU (2.0*PI) 35 | 36 | // License: WTFPL, author: sam hocevar, found: https://stackoverflow.com/a/17897228/418488 37 | const vec4 hsv2rgb_K = vec4(1.0f, 2.0f / 3.0f, 1.0f / 3.0f, 3.0f); 38 | vec3 hsv2rgb(vec3 c) { 39 | vec3 p = abs(fract(c.xxx + hsv2rgb_K.xyz) * 6.0f - hsv2rgb_K.www); 40 | return c.z * mix(hsv2rgb_K.xxx, clamp(p - hsv2rgb_K.xxx, 0.0f, 1.0f), c.y); 41 | } 42 | // License: WTFPL, author: sam hocevar, found: https://stackoverflow.com/a/17897228/418488 43 | // Macro version of above to enable compile-u_time constants 44 | #define HSV2RGB(c) (c.z * mix(hsv2rgb_K.xxx, clamp(abs(fract(c.xxx + hsv2rgb_K.xyz) * 6.0 - hsv2rgb_K.www) - hsv2rgb_K.xxx, 0.0, 1.0), c.y)) 45 | 46 | #define TOLERANCE2 0.0005 47 | //#define MAX_RAY_LENGTH2 10.0 48 | #define MAX_RAY_MARCHES2 50 49 | #define NORM_OFF2 0.005 50 | #define BACKSTEP2 51 | 52 | #define TOLERANCE3 0.0005 53 | #define MAX_RAY_LENGTH3 10.0 54 | #define MAX_RAY_MARCHES3 90 55 | #define NORM_OFF3 0.005 56 | 57 | const vec3 rayOrigin = vec3(0.0f, 1.f, -5.f); 58 | const vec3 sunDir = normalize(-rayOrigin); 59 | 60 | const vec3 sunCol = HSV2RGB(vec3(0.06f, 0.90f, 1E-2f)) * 1.f; 61 | // const vec3 sunCol = HSV2RGB(vec3(0.506f, 0.90f, 1E-2f)) * 1.f; 62 | const vec3 bottomBoxCol = HSV2RGB(vec3(0.66f, 0.80f, 0.5f)) * 1.f; 63 | const vec3 topBoxCol = HSV2RGB(vec3(0.60f, 0.90f, 1.f)) * 1.f; 64 | const vec3 glowCol0 = HSV2RGB(vec3(0.05f, 0.7f, 1E-3f)) * 1.f; 65 | const vec3 glowCol1 = HSV2RGB(vec3(0.95f, 0.7f, 1E-3f)) * 1.f; 66 | const vec3 beerCol = -HSV2RGB(vec3(0.15f + 0.5f, 0.7f, 2.f)); 67 | // const vec3 beerCol = -HSV2RGB(vec3(0.715f + 0.5f, 0.7f, 2.f)); 68 | // float rrefr_index = 1.f / refr_index; 69 | 70 | // // License: Unknown, author: knighty, found: https://www.shadertoy.com/view/MsKGzw 71 | // float poly_cospin = cos(PI / float(octave)); 72 | // float poly_scospin = sqrt(0.75f - poly_cospin * poly_cospin); 73 | // vec3 poly_nc = vec3(-0.5f, -poly_cospin, poly_scospin); 74 | // vec3 poly_pab = vec3(0.f, 0.f, 1.f); 75 | // vec3 poly_pbc_ = vec3(poly_scospin, 0.f, 0.5f); 76 | // vec3 poly_pca_ = vec3(0.f, poly_scospin, poly_cospin); 77 | // vec3 poly_p = normalize((poly_U * poly_pab + poly_V * poly_pbc_ + poly_W * poly_pca_)); 78 | // vec3 poly_pbc = normalize(poly_pbc_); 79 | // vec3 poly_pca = normalize(poly_pca_); 80 | float rrefr_index; 81 | float poly_cospin; 82 | float poly_scospin; 83 | vec3 poly_nc; 84 | vec3 poly_pab; 85 | vec3 poly_pbc_; 86 | vec3 poly_pca_; 87 | vec3 poly_p; 88 | vec3 poly_pbc; 89 | vec3 poly_pca; 90 | 91 | void initializeGlobals() { 92 | rrefr_index = 1.0f / refr_index; 93 | poly_cospin = cos(PI / float(octave)); 94 | poly_scospin = sqrt(0.75f - poly_cospin * poly_cospin); 95 | poly_nc = vec3(-0.5f, -poly_cospin, poly_scospin); 96 | poly_pab = vec3(0.0f, 0.0f, 1.0f); 97 | poly_pbc_ = vec3(poly_scospin, 0.0f, 0.5f); 98 | poly_pca_ = vec3(0.0f, poly_scospin, poly_cospin); 99 | poly_p = normalize((poly_U * poly_pab + poly_V * poly_pbc_ + poly_W * poly_pca_)); 100 | poly_pbc = normalize(poly_pbc_); 101 | poly_pca = normalize(poly_pca_); 102 | } 103 | 104 | mat3 g_rot; 105 | vec2 g_gd; 106 | 107 | // License: MIT, author: Inigo Quilez, found: https://iquilezles.org/articles/noacos/ 108 | mat3 rot(vec3 d, vec3 z) { 109 | vec3 v = cross(z, d); 110 | float c = dot(z, d); 111 | float k = 1.0f / (1.0f + c); 112 | 113 | return mat3(v.x * v.x * k + c, v.y * v.x * k - v.z, v.z * v.x * k + v.y, v.x * v.y * k + v.z, v.y * v.y * k + c, v.z * v.y * k - v.x, v.x * v.z * k - v.y, v.y * v.z * k + v.x, v.z * v.z * k + c); 114 | } 115 | 116 | // License: Unknown, author: Matt Taylor (https://github.com/64), found: https://64.github.io/tonemapping/ 117 | vec3 aces_approx(vec3 v) { 118 | v = max(v, 0.0f); 119 | v *= 0.6f; 120 | float a = 2.51f; 121 | float b = 0.03f; 122 | float c = 2.43f; 123 | float d = 0.59f; 124 | float e = 0.14f; 125 | return clamp((v * (a * v + b)) / (v * (c * v + d) + e), 0.0f, 1.0f); 126 | } 127 | 128 | float sphere(vec3 p, float r) { 129 | return length(p) - r; 130 | } 131 | 132 | // License: MIT, author: Inigo Quilez, found: https://iquilezles.org/articles/distfunctions/ 133 | float box(vec2 p, vec2 b) { 134 | vec2 d = abs(p) - b; 135 | return length(max(d, 0.0f)) + min(max(d.x, d.y), 0.0f); 136 | } 137 | 138 | // License: Unknown, author: knighty, found: https://www.shadertoy.com/view/MsKGzw 139 | void poly_fold(inout vec3 pos) { 140 | vec3 p = pos; 141 | 142 | for(int i = 0; i < octave; ++i) { 143 | p.xy = abs(p.xy); 144 | p -= 2.f * min(0.f, dot(p, poly_nc)) * poly_nc; 145 | } 146 | 147 | pos = p; 148 | } 149 | 150 | float poly_plane(vec3 pos) { 151 | float d0 = dot(pos, poly_pab); 152 | float d1 = dot(pos, poly_pbc); 153 | float d2 = dot(pos, poly_pca); 154 | float d = d0; 155 | d = max(d, d1); 156 | d = max(d, d2); 157 | return d; 158 | } 159 | 160 | float poly_corner(vec3 pos) { 161 | float d = length(pos) - .0125f; 162 | return d; 163 | } 164 | 165 | float dot2(vec3 p) { 166 | return dot(p, p); 167 | } 168 | 169 | float poly_edge(vec3 pos) { 170 | float dla = dot2(pos - min(0.f, pos.x) * vec3(1.f, 0.f, 0.f)); 171 | float dlb = dot2(pos - min(0.f, pos.y) * vec3(0.f, 1.f, 0.f)); 172 | float dlc = dot2(pos - min(0.f, dot(pos, poly_nc)) * poly_nc); 173 | return sqrt(min(min(dla, dlb), dlc)) - 2E-3f; 174 | } 175 | 176 | vec3 shape(vec3 pos) { 177 | pos *= g_rot; 178 | pos /= poly_zoom; 179 | poly_fold(pos); 180 | pos -= poly_p; 181 | 182 | return vec3(poly_plane(pos), poly_edge(pos), poly_corner(pos)) * poly_zoom; 183 | } 184 | 185 | vec3 render0(vec3 ro, vec3 rd) { 186 | vec3 col = vec3(0.0f); 187 | 188 | float srd = sign(rd.y); 189 | float tp = -(ro.y - 6.f) / abs(rd.y); 190 | 191 | if(srd < 0.f) { 192 | col += bottomBoxCol * exp(-0.5f * (length((ro + tp * rd).xz))); 193 | } 194 | 195 | if(srd > 0.0f) { 196 | vec3 pos = ro + tp * rd; 197 | vec2 pp = pos.xz; 198 | float db = box(pp, vec2(5.0f, 9.0f)) - 3.0f; 199 | 200 | col += topBoxCol * rd.y * rd.y * smoothstep(0.25f, 0.0f, db); 201 | col += 0.2f * topBoxCol * exp(-0.5f * max(db, 0.0f)); 202 | col += 0.05f * sqrt(topBoxCol) * max(-db, 0.0f); 203 | } 204 | 205 | col += sunCol / (1.001f - dot(sunDir, rd)); 206 | return col; 207 | } 208 | 209 | float df2(vec3 p) { 210 | vec3 ds = shape(p); 211 | float d2 = ds.y - 5E-3f; 212 | float d0 = min(-ds.x, d2); 213 | float d1 = sphere(p, inner_sphere); 214 | g_gd = min(g_gd, vec2(d2, d1)); 215 | float d = (min(d0, d1)); 216 | return d; 217 | } 218 | 219 | float rayMarch2(vec3 ro, vec3 rd, float tinit) { 220 | float t = tinit; 221 | #if defined(BACKSTEP2) 222 | vec2 dti = vec2(1e10f, 0.0f); 223 | #endif 224 | int i; 225 | for(i = 0; i < MAX_RAY_MARCHES2; ++i) { 226 | float d = df2(ro + rd * t); 227 | #if defined(BACKSTEP2) 228 | if(d < dti.x) { 229 | dti = vec2(d, t); 230 | } 231 | #endif 232 | // Bouncing in a closed shell, will never miss 233 | if(d < TOLERANCE2/* || t > MAX_RAY_LENGTH3 */) { 234 | break; 235 | } 236 | t += d; 237 | } 238 | #if defined(BACKSTEP2) 239 | if(i == MAX_RAY_MARCHES2) { 240 | t = dti.y; 241 | }; 242 | #endif 243 | return t; 244 | } 245 | 246 | vec3 normal2(vec3 pos) { 247 | vec2 eps = vec2(NORM_OFF2, 0.0f); 248 | vec3 nor; 249 | nor.x = df2(pos + eps.xyy) - df2(pos - eps.xyy); 250 | nor.y = df2(pos + eps.yxy) - df2(pos - eps.yxy); 251 | nor.z = df2(pos + eps.yyx) - df2(pos - eps.yyx); 252 | return normalize(nor); 253 | } 254 | 255 | vec3 render2(vec3 ro, vec3 rd, float db) { 256 | vec3 agg = vec3(0.0f); 257 | float ragg = 1.f; 258 | float tagg = 0.f; 259 | 260 | for(int bounce = 0; bounce < MAX_BOUNCES2; ++bounce) { 261 | if(ragg < 0.1f) 262 | break; 263 | g_gd = vec2(1E3f); 264 | float t2 = rayMarch2(ro, rd, min(db + 0.05f, 0.3f)); 265 | vec2 gd2 = g_gd; 266 | tagg += t2; 267 | 268 | vec3 p2 = ro + rd * t2; 269 | vec3 n2 = normal2(p2); 270 | vec3 r2 = reflect(rd, n2); 271 | vec3 rr2 = refract(rd, n2, rrefr_index); 272 | float fre2 = 1.f + dot(n2, rd); 273 | 274 | vec3 beer = ragg * exp(0.2f * beerCol * tagg); 275 | agg += glowCol1 * beer * ((1.f + tagg * tagg * 4E-2f) * 6.f / max(gd2.x, 5E-4f + tagg * tagg * 2E-4f / ragg)); 276 | vec3 ocol = 0.2f * beer * render0(p2, rr2); 277 | if(gd2.y <= TOLERANCE2) { 278 | ragg *= 1.f - 0.9f * fre2; 279 | } else { 280 | agg += ocol; 281 | ragg *= 0.8f; 282 | } 283 | 284 | ro = p2; 285 | rd = r2; 286 | db = gd2.x; 287 | } 288 | 289 | return agg; 290 | } 291 | 292 | float df3(vec3 p) { 293 | vec3 ds = shape(p); 294 | g_gd = min(g_gd, ds.yz); 295 | const float sw = 0.02f; 296 | float d1 = min(ds.y, ds.z) - sw; 297 | float d0 = ds.x; 298 | d0 = min(d0, ds.y); 299 | d0 = min(d0, ds.z); 300 | return d0; 301 | } 302 | 303 | float rayMarch3(vec3 ro, vec3 rd, float tinit, out int iter) { 304 | float t = tinit; 305 | int i; 306 | for(i = 0; i < MAX_RAY_MARCHES3; ++i) { 307 | float d = df3(ro + rd * t); 308 | if(d < TOLERANCE3 || t > MAX_RAY_LENGTH3) { 309 | break; 310 | } 311 | t += d; 312 | } 313 | iter = i; 314 | return t; 315 | } 316 | 317 | vec3 normal3(vec3 pos) { 318 | vec2 eps = vec2(NORM_OFF3, 0.0f); 319 | vec3 nor; 320 | nor.x = df3(pos + eps.xyy) - df3(pos - eps.xyy); 321 | nor.y = df3(pos + eps.yxy) - df3(pos - eps.yxy); 322 | nor.z = df3(pos + eps.yyx) - df3(pos - eps.yyx); 323 | return normalize(nor); 324 | } 325 | 326 | vec3 render3(vec3 ro, vec3 rd) { 327 | int iter; 328 | 329 | vec3 skyCol = render0(ro, rd); 330 | vec3 col = skyCol; 331 | 332 | g_gd = vec2(1E3f); 333 | float t1 = rayMarch3(ro, rd, 0.1f, iter); 334 | vec2 gd1 = g_gd; 335 | vec3 p1 = ro + t1 * rd; 336 | vec3 n1 = normal3(p1); 337 | vec3 r1 = reflect(rd, n1); 338 | vec3 rr1 = refract(rd, n1, refr_index); 339 | float fre1 = 1.f + dot(rd, n1); 340 | fre1 *= fre1; 341 | 342 | float ifo = mix(0.5f, 1.f, smoothstep(1.0f, 0.9f, float(iter) / float(MAX_RAY_MARCHES3))); 343 | 344 | if(t1 < MAX_RAY_LENGTH3) { 345 | col = render0(p1, r1) * (0.5f + 0.5f * fre1) * ifo; 346 | vec3 icol = render2(p1, rr1, gd1.x); 347 | if(gd1.x > TOLERANCE3 && gd1.y > TOLERANCE3 && rr1 != vec3(0.f)) { 348 | col += icol * (1.f - 0.75f * fre1) * ifo; 349 | } 350 | } 351 | 352 | col += (glowCol0 + 1.f * fre1 * (glowCol0)) / max(gd1.x, 3E-4f); 353 | return col; 354 | 355 | } 356 | 357 | vec3 effect(vec2 p, vec2 pp) { 358 | const float fov = 2.0f; 359 | 360 | const vec3 up = vec3(0.f, 1.f, 0.f); 361 | const vec3 la = vec3(0.0f); 362 | 363 | const vec3 ww = normalize(normalize(la - rayOrigin)); 364 | const vec3 uu = normalize(cross(up, ww)); 365 | const vec3 vv = cross(ww, uu); 366 | 367 | vec3 rd = normalize(-p.x * uu + p.y * vv + fov * ww); 368 | 369 | vec3 col = vec3(0.0f); 370 | col = render3(rayOrigin, rd); 371 | 372 | col -= 2E-2f * vec3(2.f, 3.f, 1.f) * (length(p) + 0.25f); 373 | col = aces_approx(col); 374 | col = sqrt(col); 375 | return col; 376 | } 377 | 378 | void main() { 379 | vec2 q = vUv; 380 | vec2 p = -1.0f + 2.0f * q; 381 | vec2 pp = p; 382 | p.x *= RESOLUTION.x / RESOLUTION.y; 383 | 384 | initializeGlobals(); 385 | 386 | float a = TIME * rotation_speed; 387 | vec3 r0 = vec3(1.0f, sin(vec2(sqrt(0.5f), 1.0f) * a)); 388 | vec3 r1 = vec3(cos(vec2(sqrt(0.5f), 1.0f) * 0.913f * a), 1.0f); 389 | mat3 rot = rot(normalize(r0), normalize(r1)); 390 | g_rot = rot; 391 | 392 | vec3 col = effect(p, pp); 393 | 394 | fragColor = vec4(col, 1.0f); 395 | } 396 | -------------------------------------------------------------------------------- /www/src/shaders/grid copy 2.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision mediump float; 3 | 4 | // Original shadertoy by srtuss, 2013 5 | uniform vec2 u_resolution; 6 | uniform float u_time; 7 | uniform vec2 u_mouse; 8 | 9 | in vec2 vUv; 10 | out vec4 fragColor; 11 | 12 | const float size = 9.; 13 | 14 | // rotate position around axis 15 | vec2 rotate(vec2 p, float a) { 16 | return vec2(p.x * cos(a) - p.y * sin(a), p.x * sin(a) + p.y * cos(a)); 17 | } 18 | 19 | // 1D random numbers 20 | float rand(float n) { 21 | return fract(sin(n) * 43758.5453123); 22 | } 23 | 24 | // 2D random numbers 25 | vec2 rand2(in vec2 p) { 26 | return fract(vec2(sin(p.x * 591.32 + p.y * 154.077), cos(p.x * 391.32 + p.y * 49.077))); 27 | } 28 | 29 | // 1D noise 30 | float noise1(float p) { 31 | float fl = floor(p); 32 | float fc = fract(p); 33 | return mix(rand(fl), rand(fl + 1.0), fc); 34 | } 35 | 36 | // voronoi distance noise, based on iq's articles 37 | float voronoi(in vec2 x) { 38 | vec2 p = floor(x); 39 | vec2 f = fract(x); 40 | 41 | vec2 res = vec2(8.0); 42 | for(int j = -1; j <= 1; j++) { 43 | for(int i = -1; i <= 1; i++) { 44 | vec2 b = vec2(i, j); 45 | vec2 r = vec2(b) - f + rand2(p + b); 46 | 47 | // chebyshev distance, one of many ways to do this 48 | float d = max(abs(r.x), abs(r.y)); 49 | 50 | if(d < res.x) { 51 | res.y = res.x; 52 | res.x = d; 53 | } else if(d < res.y) { 54 | res.y = d; 55 | } 56 | } 57 | } 58 | return res.y - res.x; 59 | } 60 | 61 | void main() { 62 | float time = u_time * 0.1; 63 | float flicker = noise1(u_time * 2.0) * 0.8 + 0.4; 64 | 65 | float aspect = u_resolution.x / u_resolution.y; 66 | float scale = min(1.0, aspect / 1.0); 67 | float gridSize = size * 1.0 * scale; 68 | 69 | 70 | vec2 mouse = u_mouse; 71 | 72 | float zoom = -10.0; 73 | 74 | // uv = gl_FragCoord.xy / u_resolution.xy; 75 | vec2 uv = vec2(vUv.x * aspect, vUv.y); 76 | 77 | uv *= zoom; 78 | 79 | // Center the coordinates around the middle of the screen. 80 | // uv -= 0.5; 81 | 82 | // float zoomSpeed = 0.5; 83 | 84 | // Scale the coordinates for zoom / zoom the camera in and out, but stay centered. 85 | // vec2 zoomLevel = vec2(sin(u_time * zoomSpeed) * (0.5 + 1.5)); // This will vary between 1 and 2 over time 86 | 87 | // uv *= zoomLevel - 13.; 88 | 89 | // Translate the coordinates back. 90 | // uv += 0.5; 91 | 92 | // // a bit of camera movement 93 | // uv *= 0.6 + sin(time * 0.1) * 0.4; 94 | // uv = rotate(uv, sin(time * 0.3) * 1.0); 95 | // uv += time * 0.4; 96 | 97 | // // vignette that follows the mouse 98 | float blinkSpeed = 6.5; 99 | float blinkFrequency = 0.5; 100 | float blinkAmplitude = .5; 101 | float blinkOffset = 1.8; 102 | float blink = blinkOffset + (sin(time * blinkSpeed) * blinkAmplitude); 103 | 104 | float brightness = 0.15 * exp( 105 | -9.5 * length(vUv - u_mouse) 106 | ) * 1.2; 107 | 108 | // float spreadSpeed = 75.0; 109 | 110 | // brightness *= exp( 111 | // -01.5 * 112 | // pow( 113 | // length(vUv - mouse), 114 | // 1.1 + sin(time * spreadSpeed) 115 | // ) 116 | // ) * 1.9; 117 | 118 | // Apply the blink effect 119 | brightness *= blink; 120 | 121 | //? add some noise octaves 122 | // float a = 0.26, f = 2.0; 123 | float a = 0.25; 124 | float f = 2.0; 125 | 126 | for(int i = 0; i < 2; i++) // 4 octaves also look nice, its getting a bit slow though 127 | { 128 | float v1 = voronoi(uv * f + 5.0); 129 | float v2 = 0.0; 130 | 131 | //? make the moving electrons-effect for higher octaves 132 | if(i > 0) { 133 | //? of course everything based on voronoi 134 | // v2 = voronoi(uv * f * 0.5 + 50.0 + time); 135 | v2 = voronoi(uv * f * 0.2 + 50.0 + time); 136 | 137 | float va = 0.0, vb = 0.0; 138 | va = 1.0 - smoothstep(0.0, 0.1, v1); 139 | vb = 1.0 - smoothstep(0.0, 0.08, v2); 140 | brightness += a * pow(va * (0.5 + vb), 2.0); 141 | } 142 | 143 | //? make sharp edges 144 | v1 = 1.0 - smoothstep(0.0, 0.3, v1); 145 | 146 | //? noise is used as intensity map 147 | v2 = a * (noise1(v1 * 5.5 + 0.1)); 148 | 149 | //? octave 0's intensity changes a bit 150 | if(i == 0) 151 | brightness += v2 * flicker; 152 | else 153 | brightness += v2; 154 | 155 | f *= 3.0; 156 | a *= 0.7; 157 | } 158 | 159 | // vec3 themeA = vec3(0.57, 0.23, 1.); 160 | // vec3 themeB = vec3(0.2, 0.77, 0.96); 161 | // vec3 themeC = vec3(1., 0.23137254901960785, 0.5254901960784314); 162 | 163 | //? use texture channel0 for color? why not. 164 | //- We don't have textures in PocketShader 165 | //- vec3 cexp = texture(iChannel0, uv * 0.001).xyz * 3.0 + texture(iChannel0, uv * 0.01).xyz; 166 | 167 | 168 | // vec3 color1 = vec3(0.1, 0.1, 0.1); // RGB color based on uv coordinates 169 | // vec3 color2 = vec3(0.1, 0.075, 0.0); // RGB color based on inverted uv coordinates 170 | 171 | // vec3 color1 = themeA; 172 | // vec3 color2 = themeA; 173 | 174 | // vec3 color1 = vec3(0.1); 175 | // vec3 color2 = vec3(0.1); 176 | 177 | vec3 color1 = vec3(0.1); 178 | vec3 color2 = vec3(0.1); 179 | 180 | 181 | // vec3 cexp = color1 * 3.0 + color2; 182 | vec3 cexp = color1 * 50.0 + color2; 183 | // blend between two colors based on uv 184 | // vec3 cexp = mix(color1, color2, uv.y / size) * 20.1; 185 | 186 | // cexp *= 1.4; 187 | cexp *= 1.9; 188 | 189 | //? old blueish color set 190 | // cexp = vec3(6.0, 4.0, 2.0); 191 | 192 | // vec3 col = vec3( 193 | // pow(brightness, cexp.x), 194 | // pow(brightness, cexp.y), 195 | // pow(brightness, cexp.z) 196 | // ) * 2.0; 197 | 198 | // vec3 col = vec3(pow(v, cexp.x), pow(v, cexp.y), pow(v, cexp.z)) * 2.0; 199 | 200 | vec3 col = vec3(pow(brightness, cexp.x), pow(brightness, cexp.y), pow(brightness, cexp.z)) * 2.0; 201 | 202 | // fragColor = vec4(col, 0.925); 203 | 204 | // fragColor = vec4(col, 1.0); 205 | fragColor = vec4(col, (col.r + col.g + col.b) / 3.0); 206 | } 207 | 208 | 209 | 210 | // #version 300 es 211 | // precision mediump float; 212 | 213 | // // Original shadertoy by srtuss, 2013 214 | // uniform vec2 u_resolution; 215 | // uniform float u_time; 216 | // uniform vec2 u_mouse; 217 | 218 | // in vec2 vUv; 219 | // out vec4 fragColor; 220 | 221 | // const float size = 9.; 222 | 223 | // // rotate position around axis 224 | // vec2 rotate(vec2 p, float a) { 225 | // return vec2(p.x * cos(a) - p.y * sin(a), p.x * sin(a) + p.y * cos(a)); 226 | // } 227 | 228 | // // 1D random numbers 229 | // float rand(float n) { 230 | // return fract(sin(n) * 43758.5453123); 231 | // } 232 | 233 | // // 2D random numbers 234 | // vec2 rand2(in vec2 p) { 235 | // return fract(vec2(sin(p.x * 591.32 + p.y * 154.077), cos(p.x * 391.32 + p.y * 49.077))); 236 | // } 237 | 238 | // // 1D noise 239 | // float noise1(float p) { 240 | // float fl = floor(p); 241 | // float fc = fract(p); 242 | // return mix(rand(fl), rand(fl + 1.0), fc); 243 | // } 244 | 245 | // // voronoi distance noise, based on iq's articles 246 | // float voronoi(in vec2 x) { 247 | // vec2 p = floor(x); 248 | // vec2 f = fract(x); 249 | 250 | // vec2 res = vec2(8.0); 251 | // for(int j = -1; j <= 1; j++) { 252 | // for(int i = -1; i <= 1; i++) { 253 | // vec2 b = vec2(i, j); 254 | // vec2 r = vec2(b) - f + rand2(p + b); 255 | 256 | // // chebyshev distance, one of many ways to do this 257 | // float d = max(abs(r.x), abs(r.y)); 258 | 259 | // if(d < res.x) { 260 | // res.y = res.x; 261 | // res.x = d; 262 | // } else if(d < res.y) { 263 | // res.y = d; 264 | // } 265 | // } 266 | // } 267 | // return res.y - res.x; 268 | // } 269 | 270 | // void main() { 271 | // float flicker = noise1(u_time * 2.0) * 0.8 + 0.4; 272 | 273 | // float aspect = u_resolution.x / u_resolution.y; 274 | // float scale = min(1.0, aspect / 1.0); 275 | // float gridSize = size * 1.0 * scale; 276 | // // vec2 uv = vUv * gridSize; 277 | // vec2 uv = vUv; 278 | // // vec2 mouse = (u_mouse / u_resolution) * gridSize; 279 | // // vec2 mouse = (u_mouse) * gridSize; 280 | // vec2 mouse = u_mouse; 281 | 282 | // // vec2 p = uv + vec2(0.5); 283 | 284 | // // float brightness = 0.15; 285 | // // float brightness = 0.3; 286 | // // float brightness = 0.5; 287 | 288 | // // uv = gl_FragCoord.xy / u_resolution.xy; 289 | // uv = vec2(uv.x * aspect, uv.y); 290 | 291 | // // Center the coordinates around the middle of the screen. 292 | // uv -= 0.5; 293 | 294 | // // Scale the coordinates for zoom. 295 | // vec2 zoomLevel = vec2(sin(u_time * 0.1) * (0.5 + 1.5)); // This will vary between 1 and 2 over time 296 | // uv *= zoomLevel - 13.; 297 | 298 | // // Translate the coordinates back. 299 | // uv += 0.5; 300 | 301 | // // brightness *= exp(-1.5 * length(uv - mouse)) * 1.2; 302 | 303 | // // vec2 uv = vUv / u_resolution.xy; 304 | // // uv *= size; 305 | // // vec2 mouse = vUv - u_mouse; 306 | 307 | // // // vignette that follows the mouse 308 | // float brightness = 0.15 * exp(-1.5 * length(vUv - u_mouse)) * 1.2; 309 | 310 | // // zoom the camera in and out, but stay centered 311 | 312 | 313 | // float time = u_time * 0.1; 314 | 315 | // // float brightness = 0.5; 316 | // // brightness *= exp(-1.5 * length(uv - mouse)) * 1.2; 317 | // // brightness *= exp(-0.5 * pow(length(uv - mouse), 0.175 + (1.1 + sin(time * 75.)))) * 1.2; 318 | // // brightness *= exp(-01.5 * pow(length(vUv - mouse), 0.175 + (1.1 + sin(time * 75.)))) * 1.2; 319 | // brightness *= exp( 320 | // -01.5 * 321 | // pow( 322 | // length(vUv - mouse), 323 | // 0.1175 + 324 | // ( 325 | // 1.1 + sin(time * 75.) 326 | // ) 327 | // ) 328 | // ) * 1.9; 329 | 330 | // //? add some noise octaves 331 | // // float a = 0.26, f = 2.0; 332 | // float a = 0.25; 333 | // float f = 2.0; 334 | 335 | // for(int i = 0; i < 2; i++) // 4 octaves also look nice, its getting a bit slow though 336 | // { 337 | // float v1 = voronoi(uv * f + 5.0); 338 | // float v2 = 0.0; 339 | 340 | // //? make the moving electrons-effect for higher octaves 341 | // if(i > 0) { 342 | // //? of course everything based on voronoi 343 | // // v2 = voronoi(uv * f * 0.5 + 50.0 + time); 344 | // v2 = voronoi(uv * f * 0.2 + 50.0 + time); 345 | 346 | // float va = 0.0, vb = 0.0; 347 | // va = 1.0 - smoothstep(0.0, 0.1, v1); 348 | // vb = 1.0 - smoothstep(0.0, 0.08, v2); 349 | // brightness += a * pow(va * (0.5 + vb), 2.0); 350 | // } 351 | 352 | // //? make sharp edges 353 | // v1 = 1.0 - smoothstep(0.0, 0.3, v1); 354 | 355 | // //? noise is used as intensity map 356 | // v2 = a * (noise1(v1 * 5.5 + 0.1)); 357 | 358 | // //? octave 0's intensity changes a bit 359 | // if(i == 0) 360 | // brightness += v2 * flicker; 361 | // else 362 | // brightness += v2; 363 | 364 | // f *= 3.0; 365 | // a *= 0.7; 366 | // } 367 | 368 | // vec3 themeA = vec3(0.57, 0.23, 1.); 369 | // vec3 themeB = vec3(0.2, 0.77, 0.96); 370 | // vec3 themeC = vec3(1., 0.23137254901960785, 0.5254901960784314); 371 | 372 | // //? use texture channel0 for color? why not. 373 | // //- We don't have textures in PocketShader 374 | // //- vec3 cexp = texture(iChannel0, uv * 0.001).xyz * 3.0 + texture(iChannel0, uv * 0.01).xyz; 375 | // // vec3 color1 = vec3(0.1, 0.1, 0.1); // RGB color based on uv coordinates 376 | // // vec3 color2 = vec3(0.1, 0.075, 0.0); // RGB color based on inverted uv coordinates 377 | // vec3 color1 = themeA; 378 | // vec3 color2 = themeA; 379 | // // vec3 color2 = themeB; 380 | 381 | // // vec3 cexp = color1 * 3.0 + color2; 382 | // // vec3 cexp = color1 * 50.0 + color2; 383 | // // blend between two colors based on uv 384 | // vec3 cexp = mix(color1, color2, uv.y / size) * 20.1; 385 | 386 | // // cexp *= 1.4; 387 | // cexp *= 1.9; 388 | 389 | // //? old blueish color set 390 | // // cexp = vec3(6.0, 4.0, 2.0); 391 | 392 | // // vec3 col = vec3( 393 | // // pow(brightness, cexp.x), 394 | // // pow(brightness, cexp.y), 395 | // // pow(brightness, cexp.z) 396 | // // ) * 2.0; 397 | 398 | // // vec3 col = vec3(pow(v, cexp.x), pow(v, cexp.y), pow(v, cexp.z)) * 2.0; 399 | 400 | // vec3 col = vec3(pow(brightness, cexp.x), pow(brightness, cexp.y), pow(brightness, cexp.z)) * 2.0; 401 | 402 | // fragColor = vec4(col, 0.925); 403 | 404 | // // fragColor = vec4(col, 1.0); 405 | // // fragColor = vec4(col, (col.r + col.g + col.b) / 3.0); 406 | // } 407 | 408 | -------------------------------------------------------------------------------- /www/src/shaders/grid.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | //? Original shadertoy by srtuss - https://www.shadertoy.com/view/4sl3Dr 3 | 4 | precision mediump float; 5 | 6 | uniform vec2 u_resolution; 7 | uniform float u_time; 8 | uniform vec2 u_mouse; 9 | uniform float u_parallax; 10 | uniform vec3 u_color; 11 | uniform float u_yeet; 12 | 13 | in vec2 vUv; 14 | out vec4 fragColor; 15 | 16 | // 1D random numbers 17 | float rand(float n) { 18 | return fract(sin(n) * 43758.5453123); 19 | } 20 | 21 | // 2D random numbers 22 | vec2 rand2(in vec2 p) { 23 | return fract(vec2(sin(p.x * 591.32 + p.y * 154.077), cos(p.x * 391.32 + p.y * 49.077))); 24 | } 25 | 26 | // 1D noise 27 | float noise1(float p) { 28 | float fl = floor(p); 29 | float fc = fract(p); 30 | return mix(rand(fl), rand(fl + 1.0), fc); 31 | } 32 | 33 | // voronoi distance noise, based on iq's articles 34 | float voronoi(in vec2 x) { 35 | vec2 p = floor(x); 36 | vec2 f = fract(x); 37 | 38 | vec2 res = vec2(8.0); 39 | for(int j = -1; j <= 1; j++) { 40 | for(int i = -1; i <= 1; i++) { 41 | vec2 b = vec2(i, j); 42 | vec2 r = vec2(b) - f + rand2(p + b); 43 | 44 | // chebyshev distance, one of many ways to do this 45 | float d = max(abs(r.x), abs(r.y)); 46 | 47 | if(d < res.x) { 48 | res.y = res.x; 49 | res.x = d; 50 | } else if(d < res.y) { 51 | res.y = d; 52 | } 53 | } 54 | } 55 | return res.y - res.x; 56 | } 57 | 58 | void main() { 59 | float time = u_time * 0.1; 60 | float flicker = noise1(u_time * 2.0) * 0.8 + 0.4; 61 | 62 | float aspect = u_resolution.x / u_resolution.y; 63 | float scale = min(1.0, aspect / 1.0); 64 | 65 | vec2 mouse = u_mouse; 66 | 67 | // uv = gl_FragCoord.xy / u_resolution.xy; 68 | vec2 uv = vec2(vUv.x * aspect, vUv.y - (u_parallax * aspect)); 69 | 70 | //? Zoom out. 71 | uv *= -10.0; 72 | 73 | float brightness = 74 | 0.15 * u_yeet * 75 | //? Vignette that follows the mouse. 76 | exp( 77 | -9.5 * length(vUv - u_mouse) 78 | ) * 1.2; 79 | 80 | float blinkSpeed = 6.5; 81 | 82 | //? Subtle, slow blink effect. 83 | float blink = 1.8 + sin(time * blinkSpeed) * 0.5; 84 | 85 | //? Raise the blink floor. 86 | // blink = clamp(blink, 2., 5.); 87 | 88 | brightness *= blink; 89 | 90 | //? Add some noise octaves. 91 | float a = 0.25; 92 | float f = 2.0; 93 | 94 | for(int i = 0; i < 2; i++) // 4 octaves also look nice, its getting a bit slow though 95 | { 96 | float v1 = voronoi(uv * f + 5.0); 97 | float v2 = 0.0; 98 | 99 | //? make the moving electrons-effect for higher octaves 100 | if(i > 0) { 101 | //? of course everything based on voronoi 102 | // v2 = voronoi(uv * f * 0.5 + 50.0 + time); 103 | v2 = voronoi(uv * f * 0.2 + 50.0 + time); 104 | // v2 = voronoi(uv * f * 0.4 + (50.0 * sin(time * .01)) + time); 105 | 106 | float va = 0.0, vb = 0.0; 107 | va = 1.0 - smoothstep(0.0, 0.1, v1); 108 | // vb = 1.0 - smoothstep(0.0, 0.08, v2); 109 | vb = 1.0 - smoothstep(0.0, 0.08, v2); 110 | brightness += a * pow(va * (0.5 + vb), 2.0); 111 | } 112 | 113 | //? make sharp edges 114 | v1 = 1.0 - smoothstep(0.0, 0.3, v1); 115 | 116 | //? noise is used as intensity map 117 | v2 = a * (noise1(v1 * 5.5 + 0.1)); 118 | 119 | //? octave 0's intensity changes a bit 120 | if(i == 0) 121 | brightness += v2 * flicker; 122 | else 123 | brightness += v2; 124 | 125 | f *= 3.0; 126 | a *= 0.7; 127 | } 128 | 129 | vec3 color = (u_color * 14.0) * pow(brightness, 14.1); 130 | 131 | color = clamp(color, 0., 1.); 132 | brightness = clamp(brightness, 0., 1.); 133 | 134 | fragColor = vec4(color, (color.r + color.g + color.b) / 3.0); 135 | } 136 | -------------------------------------------------------------------------------- /www/src/shaders/gridColored.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision mediump float; 3 | 4 | // Original shadertoy by srtuss, 2013 5 | uniform vec2 u_resolution; 6 | uniform float u_time; 7 | uniform vec2 u_mouse; 8 | uniform float u_parallax; 9 | 10 | in vec2 vUv; 11 | out vec4 fragColor; 12 | 13 | const float size = 9.; 14 | 15 | // rotate position around axis 16 | vec2 rotate(vec2 p, float a) { 17 | return vec2(p.x * cos(a) - p.y * sin(a), p.x * sin(a) + p.y * cos(a)); 18 | } 19 | 20 | // 1D random numbers 21 | float rand(float n) { 22 | return fract(sin(n) * 43758.5453123); 23 | } 24 | 25 | // 2D random numbers 26 | vec2 rand2(in vec2 p) { 27 | return fract(vec2(sin(p.x * 591.32 + p.y * 154.077), cos(p.x * 391.32 + p.y * 49.077))); 28 | } 29 | 30 | // 1D noise 31 | float noise1(float p) { 32 | float fl = floor(p); 33 | float fc = fract(p); 34 | return mix(rand(fl), rand(fl + 1.0), fc); 35 | } 36 | 37 | // voronoi distance noise, based on iq's articles 38 | float voronoi(in vec2 x) { 39 | vec2 p = floor(x); 40 | vec2 f = fract(x); 41 | 42 | vec2 res = vec2(8.0); 43 | for(int j = -1; j <= 1; j++) { 44 | for(int i = -1; i <= 1; i++) { 45 | vec2 b = vec2(i, j); 46 | vec2 r = vec2(b) - f + rand2(p + b); 47 | 48 | // chebyshev distance, one of many ways to do this 49 | float d = max(abs(r.x), abs(r.y)); 50 | 51 | if(d < res.x) { 52 | res.y = res.x; 53 | res.x = d; 54 | } else if(d < res.y) { 55 | res.y = d; 56 | } 57 | } 58 | } 59 | return res.y - res.x; 60 | } 61 | 62 | void main() { 63 | float time = u_time * 0.1; 64 | float flicker = noise1(u_time * 2.0) * 0.8 + 0.4; 65 | 66 | float aspect = u_resolution.x / u_resolution.y; 67 | float scale = min(1.0, aspect / 1.0); 68 | 69 | vec2 mouse = u_mouse; 70 | 71 | // uv = gl_FragCoord.xy / u_resolution.xy; 72 | vec2 uv = vec2(vUv.x * aspect, vUv.y); 73 | 74 | //? Zoom out. 75 | uv *= -10.0; 76 | 77 | float brightness = 78 | 0.15 * 79 | //? Vignette that follows the mouse. 80 | exp( 81 | -9.5 * length(vUv - u_mouse) 82 | ) * 1.2; 83 | 84 | //? Subtle, slow blink effect. 85 | float blink = 1.8 + sin(time * 6.5) * 0.5; 86 | brightness *= blink; 87 | 88 | //? Add some noise octaves. 89 | float a = 0.25; 90 | float f = 2.0; 91 | 92 | for(int i = 0; i < 2; i++) // 4 octaves also look nice, its getting a bit slow though 93 | { 94 | float v1 = voronoi(uv * f + 5.0); 95 | float v2 = 0.0; 96 | 97 | //? make the moving electrons-effect for higher octaves 98 | if(i > 0) { 99 | //? of course everything based on voronoi 100 | // v2 = voronoi(uv * f * 0.5 + 50.0 + time); 101 | v2 = voronoi(uv * f * 0.2 + 50.0 + time); 102 | // v2 = voronoi(uv * f * 0.4 + (50.0 * sin(time * .01)) + time); 103 | 104 | float va = 0.0, vb = 0.0; 105 | va = 1.0 - smoothstep(0.0, 0.1, v1); 106 | // vb = 1.0 - smoothstep(0.0, 0.08, v2); 107 | vb = 1.0 - smoothstep(0.0, 0.08, v2); 108 | brightness += a * pow(va * (0.5 + vb), 2.0); 109 | } 110 | 111 | //? make sharp edges 112 | v1 = 1.0 - smoothstep(0.0, 0.3, v1); 113 | 114 | //? noise is used as intensity map 115 | v2 = a * (noise1(v1 * 5.5 + 0.1)); 116 | 117 | //? octave 0's intensity changes a bit 118 | if(i == 0) 119 | brightness += v2 * flicker; 120 | else 121 | brightness += v2; 122 | 123 | f *= 3.0; 124 | a *= 0.7; 125 | } 126 | 127 | vec3 themeA = vec3(0.57, 0.23, 1.); 128 | vec3 themeB = vec3(0.2, 0.77, 0.96); 129 | vec3 themeC = vec3(1., 0.23137254901960785, 0.5254901960784314); 130 | 131 | vec3 color1 = themeC; 132 | vec3 color2 = themeA; 133 | 134 | vec3 color = themeA * 14.; 135 | 136 | color *= pow(brightness, 14.1); 137 | 138 | fragColor = vec4(color, (color.r + color.g + color.b) / 3.0); 139 | } 140 | -------------------------------------------------------------------------------- /www/src/shaders/gridGreyscale.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision mediump float; 3 | 4 | // Original shadertoy by srtuss, 2013 5 | uniform vec2 u_resolution; 6 | uniform float u_time; 7 | uniform vec2 u_mouse; 8 | 9 | in vec2 vUv; 10 | out vec4 fragColor; 11 | 12 | const float size = 9.; 13 | 14 | // rotate position around axis 15 | vec2 rotate(vec2 p, float a) { 16 | return vec2(p.x * cos(a) - p.y * sin(a), p.x * sin(a) + p.y * cos(a)); 17 | } 18 | 19 | // 1D random numbers 20 | float rand(float n) { 21 | return fract(sin(n) * 43758.5453123); 22 | } 23 | 24 | // 2D random numbers 25 | vec2 rand2(in vec2 p) { 26 | return fract(vec2(sin(p.x * 591.32 + p.y * 154.077), cos(p.x * 391.32 + p.y * 49.077))); 27 | } 28 | 29 | // 1D noise 30 | float noise1(float p) { 31 | float fl = floor(p); 32 | float fc = fract(p); 33 | return mix(rand(fl), rand(fl + 1.0), fc); 34 | } 35 | 36 | // voronoi distance noise, based on iq's articles 37 | float voronoi(in vec2 x) { 38 | vec2 p = floor(x); 39 | vec2 f = fract(x); 40 | 41 | vec2 res = vec2(8.0); 42 | for(int j = -1; j <= 1; j++) { 43 | for(int i = -1; i <= 1; i++) { 44 | vec2 b = vec2(i, j); 45 | vec2 r = vec2(b) - f + rand2(p + b); 46 | 47 | // chebyshev distance, one of many ways to do this 48 | float d = max(abs(r.x), abs(r.y)); 49 | 50 | if(d < res.x) { 51 | res.y = res.x; 52 | res.x = d; 53 | } else if(d < res.y) { 54 | res.y = d; 55 | } 56 | } 57 | } 58 | return res.y - res.x; 59 | } 60 | 61 | void main() { 62 | float time = u_time * 0.1; 63 | float flicker = noise1(u_time * 2.0) * 0.8 + 0.4; 64 | 65 | float aspect = u_resolution.x / u_resolution.y; 66 | float scale = min(1.0, aspect / 1.0); 67 | float gridSize = size * 1.0 * scale; 68 | 69 | 70 | vec2 mouse = u_mouse; 71 | 72 | float zoom = -10.0; 73 | 74 | // uv = gl_FragCoord.xy / u_resolution.xy; 75 | vec2 uv = vec2(vUv.x * aspect, vUv.y); 76 | 77 | uv *= zoom; 78 | 79 | // Center the coordinates around the middle of the screen. 80 | // uv -= 0.5; 81 | 82 | // float zoomSpeed = 0.5; 83 | 84 | // Scale the coordinates for zoom / zoom the camera in and out, but stay centered. 85 | // vec2 zoomLevel = vec2(sin(u_time * zoomSpeed) * (0.5 + 1.5)); // This will vary between 1 and 2 over time 86 | 87 | // uv *= zoomLevel - 13.; 88 | 89 | // Translate the coordinates back. 90 | // uv += 0.5; 91 | 92 | // // a bit of camera movement 93 | // uv *= 0.6 + sin(time * 0.1) * 0.4; 94 | // uv = rotate(uv, sin(time * 0.3) * 1.0); 95 | // uv += time * 0.4; 96 | 97 | // // vignette that follows the mouse 98 | float blinkSpeed = 6.5; 99 | float blinkFrequency = 0.5; 100 | float blinkAmplitude = .5; 101 | float blinkOffset = 1.8; 102 | float blink = blinkOffset + (sin(time * blinkSpeed) * blinkAmplitude); 103 | 104 | float brightness = 0.15 * exp( 105 | -9.5 * length(vUv - u_mouse) 106 | ) * 1.2; 107 | 108 | // float spreadSpeed = 75.0; 109 | 110 | // brightness *= exp( 111 | // -01.5 * 112 | // pow( 113 | // length(vUv - mouse), 114 | // 1.1 + sin(time * spreadSpeed) 115 | // ) 116 | // ) * 1.9; 117 | 118 | // Apply the blink effect 119 | brightness *= blink; 120 | 121 | //? add some noise octaves 122 | // float a = 0.26, f = 2.0; 123 | float a = 0.25; 124 | float f = 2.0; 125 | 126 | for(int i = 0; i < 2; i++) // 4 octaves also look nice, its getting a bit slow though 127 | { 128 | float v1 = voronoi(uv * f + 5.0); 129 | float v2 = 0.0; 130 | 131 | //? make the moving electrons-effect for higher octaves 132 | if(i > 0) { 133 | //? of course everything based on voronoi 134 | // v2 = voronoi(uv * f * 0.5 + 50.0 + time); 135 | v2 = voronoi(uv * f * 0.2 + 50.0 + time); 136 | 137 | float va = 0.0, vb = 0.0; 138 | va = 1.0 - smoothstep(0.0, 0.1, v1); 139 | vb = 1.0 - smoothstep(0.0, 0.08, v2); 140 | brightness += a * pow(va * (0.5 + vb), 2.0); 141 | } 142 | 143 | //? make sharp edges 144 | v1 = 1.0 - smoothstep(0.0, 0.3, v1); 145 | 146 | //? noise is used as intensity map 147 | v2 = a * (noise1(v1 * 5.5 + 0.1)); 148 | 149 | //? octave 0's intensity changes a bit 150 | if(i == 0) 151 | brightness += v2 * flicker; 152 | else 153 | brightness += v2; 154 | 155 | f *= 3.0; 156 | a *= 0.7; 157 | } 158 | 159 | vec3 color1 = vec3(0.1); 160 | vec3 color2 = vec3(0.1); 161 | 162 | vec3 grid = color1 * 50.0 + color2; 163 | grid *= 1.9; 164 | 165 | vec3 color = vec3(pow(brightness, grid.x), pow(brightness, grid.y), pow(brightness, grid.z)) * 2.0; 166 | 167 | fragColor = vec4(color, (color.r + color.g + color.b) / 3.0); 168 | } 169 | 170 | 171 | 172 | // #version 300 es 173 | // precision mediump float; 174 | 175 | // // Original shadertoy by srtuss, 2013 176 | // uniform vec2 u_resolution; 177 | // uniform float u_time; 178 | // uniform vec2 u_mouse; 179 | 180 | // in vec2 vUv; 181 | // out vec4 fragColor; 182 | 183 | // const float size = 9.; 184 | 185 | // // rotate position around axis 186 | // vec2 rotate(vec2 p, float a) { 187 | // return vec2(p.x * cos(a) - p.y * sin(a), p.x * sin(a) + p.y * cos(a)); 188 | // } 189 | 190 | // // 1D random numbers 191 | // float rand(float n) { 192 | // return fract(sin(n) * 43758.5453123); 193 | // } 194 | 195 | // // 2D random numbers 196 | // vec2 rand2(in vec2 p) { 197 | // return fract(vec2(sin(p.x * 591.32 + p.y * 154.077), cos(p.x * 391.32 + p.y * 49.077))); 198 | // } 199 | 200 | // // 1D noise 201 | // float noise1(float p) { 202 | // float fl = floor(p); 203 | // float fc = fract(p); 204 | // return mix(rand(fl), rand(fl + 1.0), fc); 205 | // } 206 | 207 | // // voronoi distance noise, based on iq's articles 208 | // float voronoi(in vec2 x) { 209 | // vec2 p = floor(x); 210 | // vec2 f = fract(x); 211 | 212 | // vec2 res = vec2(8.0); 213 | // for(int j = -1; j <= 1; j++) { 214 | // for(int i = -1; i <= 1; i++) { 215 | // vec2 b = vec2(i, j); 216 | // vec2 r = vec2(b) - f + rand2(p + b); 217 | 218 | // // chebyshev distance, one of many ways to do this 219 | // float d = max(abs(r.x), abs(r.y)); 220 | 221 | // if(d < res.x) { 222 | // res.y = res.x; 223 | // res.x = d; 224 | // } else if(d < res.y) { 225 | // res.y = d; 226 | // } 227 | // } 228 | // } 229 | // return res.y - res.x; 230 | // } 231 | 232 | // void main() { 233 | // float flicker = noise1(u_time * 2.0) * 0.8 + 0.4; 234 | 235 | // float aspect = u_resolution.x / u_resolution.y; 236 | // float scale = min(1.0, aspect / 1.0); 237 | // float gridSize = size * 1.0 * scale; 238 | // // vec2 uv = vUv * gridSize; 239 | // vec2 uv = vUv; 240 | // // vec2 mouse = (u_mouse / u_resolution) * gridSize; 241 | // // vec2 mouse = (u_mouse) * gridSize; 242 | // vec2 mouse = u_mouse; 243 | 244 | // // vec2 p = uv + vec2(0.5); 245 | 246 | // // float brightness = 0.15; 247 | // // float brightness = 0.3; 248 | // // float brightness = 0.5; 249 | 250 | // // uv = gl_FragCoord.xy / u_resolution.xy; 251 | // uv = vec2(uv.x * aspect, uv.y); 252 | 253 | // // Center the coordinates around the middle of the screen. 254 | // uv -= 0.5; 255 | 256 | // // Scale the coordinates for zoom. 257 | // vec2 zoomLevel = vec2(sin(u_time * 0.1) * (0.5 + 1.5)); // This will vary between 1 and 2 over time 258 | // uv *= zoomLevel - 13.; 259 | 260 | // // Translate the coordinates back. 261 | // uv += 0.5; 262 | 263 | // // brightness *= exp(-1.5 * length(uv - mouse)) * 1.2; 264 | 265 | // // vec2 uv = vUv / u_resolution.xy; 266 | // // uv *= size; 267 | // // vec2 mouse = vUv - u_mouse; 268 | 269 | // // // vignette that follows the mouse 270 | // float brightness = 0.15 * exp(-1.5 * length(vUv - u_mouse)) * 1.2; 271 | 272 | // // zoom the camera in and out, but stay centered 273 | 274 | 275 | // float time = u_time * 0.1; 276 | 277 | // // float brightness = 0.5; 278 | // // brightness *= exp(-1.5 * length(uv - mouse)) * 1.2; 279 | // // brightness *= exp(-0.5 * pow(length(uv - mouse), 0.175 + (1.1 + sin(time * 75.)))) * 1.2; 280 | // // brightness *= exp(-01.5 * pow(length(vUv - mouse), 0.175 + (1.1 + sin(time * 75.)))) * 1.2; 281 | // brightness *= exp( 282 | // -01.5 * 283 | // pow( 284 | // length(vUv - mouse), 285 | // 0.1175 + 286 | // ( 287 | // 1.1 + sin(time * 75.) 288 | // ) 289 | // ) 290 | // ) * 1.9; 291 | 292 | // //? add some noise octaves 293 | // // float a = 0.26, f = 2.0; 294 | // float a = 0.25; 295 | // float f = 2.0; 296 | 297 | // for(int i = 0; i < 2; i++) // 4 octaves also look nice, its getting a bit slow though 298 | // { 299 | // float v1 = voronoi(uv * f + 5.0); 300 | // float v2 = 0.0; 301 | 302 | // //? make the moving electrons-effect for higher octaves 303 | // if(i > 0) { 304 | // //? of course everything based on voronoi 305 | // // v2 = voronoi(uv * f * 0.5 + 50.0 + time); 306 | // v2 = voronoi(uv * f * 0.2 + 50.0 + time); 307 | 308 | // float va = 0.0, vb = 0.0; 309 | // va = 1.0 - smoothstep(0.0, 0.1, v1); 310 | // vb = 1.0 - smoothstep(0.0, 0.08, v2); 311 | // brightness += a * pow(va * (0.5 + vb), 2.0); 312 | // } 313 | 314 | // //? make sharp edges 315 | // v1 = 1.0 - smoothstep(0.0, 0.3, v1); 316 | 317 | // //? noise is used as intensity map 318 | // v2 = a * (noise1(v1 * 5.5 + 0.1)); 319 | 320 | // //? octave 0's intensity changes a bit 321 | // if(i == 0) 322 | // brightness += v2 * flicker; 323 | // else 324 | // brightness += v2; 325 | 326 | // f *= 3.0; 327 | // a *= 0.7; 328 | // } 329 | 330 | // vec3 themeA = vec3(0.57, 0.23, 1.); 331 | // vec3 themeB = vec3(0.2, 0.77, 0.96); 332 | // vec3 themeC = vec3(1., 0.23137254901960785, 0.5254901960784314); 333 | 334 | // //? use texture channel0 for color? why not. 335 | // //- We don't have textures in PocketShader 336 | // //- vec3 cexp = texture(iChannel0, uv * 0.001).xyz * 3.0 + texture(iChannel0, uv * 0.01).xyz; 337 | // // vec3 color1 = vec3(0.1, 0.1, 0.1); // RGB color based on uv coordinates 338 | // // vec3 color2 = vec3(0.1, 0.075, 0.0); // RGB color based on inverted uv coordinates 339 | // vec3 color1 = themeA; 340 | // vec3 color2 = themeA; 341 | // // vec3 color2 = themeB; 342 | 343 | // // vec3 cexp = color1 * 3.0 + color2; 344 | // // vec3 cexp = color1 * 50.0 + color2; 345 | // // blend between two colors based on uv 346 | // vec3 cexp = mix(color1, color2, uv.y / size) * 20.1; 347 | 348 | // // cexp *= 1.4; 349 | // cexp *= 1.9; 350 | 351 | // //? old blueish color set 352 | // // cexp = vec3(6.0, 4.0, 2.0); 353 | 354 | // // vec3 col = vec3( 355 | // // pow(brightness, cexp.x), 356 | // // pow(brightness, cexp.y), 357 | // // pow(brightness, cexp.z) 358 | // // ) * 2.0; 359 | 360 | // // vec3 col = vec3(pow(v, cexp.x), pow(v, cexp.y), pow(v, cexp.z)) * 2.0; 361 | 362 | // vec3 col = vec3(pow(brightness, cexp.x), pow(brightness, cexp.y), pow(brightness, cexp.z)) * 2.0; 363 | 364 | // fragColor = vec4(col, 0.925); 365 | 366 | // // fragColor = vec4(col, 1.0); 367 | // // fragColor = vec4(col, (col.r + col.g + col.b) / 3.0); 368 | // } 369 | 370 | -------------------------------------------------------------------------------- /www/src/shaders/kirby.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform float u_jump; 5 | uniform float u_time; 6 | uniform vec2 u_resolution; 7 | 8 | in vec2 vUv; 9 | out vec4 fragColor; 10 | 11 | const float jumpAmount = 0.25; 12 | 13 | // polynomial smooth min (from IQ) 14 | float smin(float a, float b, float k) { 15 | float h = clamp((u_jump * jumpAmount) + 0.5 + 0.5 * (b - a) / k, 0.0, 1.0); 16 | return mix(b, a, h) - k * h * (1.0 - h); 17 | } 18 | 19 | float smax(float a, float b, float k) { 20 | return -smin(-a, -b, k); 21 | } 22 | 23 | mat2 rotmat(float a) { 24 | return mat2(cos(a), sin(a), -sin(a), cos(a)); 25 | } 26 | 27 | float shoesDist(vec3 p) { 28 | vec3 op = p; 29 | float d = 1e4; 30 | 31 | p.y -= 1.25 + u_jump * jumpAmount; 32 | 33 | // right shoe 34 | op = p; 35 | p -= vec3(-.5, -.6, -.9); 36 | p.yz = rotmat(-.7) * p.yz; 37 | p.xz = rotmat(0.1) * p.xz; 38 | d = min(d, -smin(p.y, -(length(p * vec3(1.6, 1, 1)) - .64), .2)); 39 | p = op; 40 | 41 | // left shoe 42 | op = p; 43 | p -= vec3(.55, -.8, 0.4); 44 | p.x = -p.x; 45 | p.yz = rotmat(1.4) * p.yz; 46 | d = min(d, -smin(p.y, -(length(p * vec3(1.6, 1, 1)) - .73), .2)); 47 | p = op; 48 | return d; 49 | } 50 | 51 | float sceneDist(vec3 p) { 52 | float jump = u_jump * jumpAmount; 53 | vec3 op = p; 54 | float d = shoesDist(p); 55 | 56 | 57 | d = min(d, p.y); 58 | p.y -= 1.5 + jump * 0.5; 59 | 60 | // torso 61 | d = min(d, length(p) - 1.); 62 | 63 | // left arm 64 | op = p; 65 | p -= vec3(.66, .7, 0); 66 | p.xz = rotmat(-0.1) * p.xz; 67 | d = smin(d, (length(p * vec3(1.8, 1, 1)) - .58), .07); 68 | p = op; 69 | 70 | // right arm 71 | op = p; 72 | p -= vec3(-.75, 0.2 + jump * 0.5, 0); 73 | d = smin(d, (length(p * vec3(1, 1.5, 1)) - .54), .03); 74 | p = op; 75 | 76 | // mouth 77 | p.y -= .11 + -jump * 0.5; 78 | float md = smax(p.z + .84, smax(smax(p.x - .2, p.y - .075, .2), dot(p, vec3(.7071, -.7071, 0)) - .1, .08), .04); 79 | p.x = -p.x; 80 | md = smax(md, smax(p.z + .84, smax(smax(p.x - .2, p.y - .075, .2), dot(p, vec3(.7071, -.7071, 0)) - .1, .08), .01), .13); 81 | d = smax(d, -md, .012); 82 | 83 | // tongue 84 | p = op; 85 | d = smin(d, length((p - vec3(0, .03, -.75)) * vec3(1, 1, 1)) - .16, .01); 86 | 87 | return min(d, 10.); 88 | } 89 | 90 | vec3 sceneNorm(vec3 p) { 91 | vec3 e = vec3(1e-3, 0, 0); 92 | float d = sceneDist(p); 93 | return normalize(vec3(sceneDist(p + e.xyy) - sceneDist(p - e.xyy), sceneDist(p + e.yxy) - sceneDist(p - e.yxy), sceneDist(p + e.yyx) - sceneDist(p - e.yyx))); 94 | } 95 | 96 | // from simon green and others 97 | float ambientOcclusion(vec3 p, vec3 n) { 98 | const int steps = 4; 99 | const float delta = 0.15; 100 | 101 | float a = 0.0; 102 | float weight = 4.; 103 | for(int i = 1; i <= steps; i++) { 104 | float d = (float(i) / float(steps)) * delta; 105 | a += weight * (d - sceneDist(p + n * d)); 106 | weight *= 0.5; 107 | } 108 | return clamp(1.0 - a, 0.0, 1.0); 109 | } 110 | 111 | // a re-shaped cosine, to make the peaks more pointy 112 | float cos2(float x) { 113 | return cos(x - sin(x) / 3.); 114 | } 115 | 116 | float starShape(vec2 p) { 117 | float a = atan(p.y, p.x) + u_time / 3.; 118 | float l = pow(length(p), .8); 119 | float star = 1. - smoothstep(0., (3. - cos2(a * 5. * 2.)) * .02, l - .5 + cos2(a * 5.) * .1); 120 | return star; 121 | } 122 | 123 | void main() { 124 | float jump = u_jump * jumpAmount; 125 | vec2 uv = vUv; 126 | 127 | float an = cos(u_time) * .1; 128 | 129 | vec2 ot = uv * 2. - 1.; 130 | ot.y *= u_resolution.y / u_resolution.x; 131 | vec3 ro = vec3(0., 1.4, 4.); 132 | vec3 rd = normalize(vec3(ot.xy, -1.3)); 133 | 134 | rd.xz = mat2(cos(an), sin(an), sin(an), -cos(an)) * rd.xz; 135 | ro.xz = mat2(cos(an), sin(an), sin(an), -cos(an)) * ro.xz; 136 | 137 | float s = 20.; 138 | 139 | // primary ray 140 | float t = 0., d = 0.; 141 | for(int i = 0; i < 80; ++i) { 142 | d = sceneDist(ro + rd * t); 143 | if(d < 1e-4) 144 | break; 145 | if(t > 10.) 146 | break; 147 | t += d * .9; 148 | } 149 | 150 | t = min(t, 10.0); 151 | 152 | //// shadow ray 153 | vec3 rp = ro + rd * t; 154 | vec3 n = sceneNorm(rp); 155 | float st = 5e-3; 156 | // vec3 ld = normalize(vec3(2, 4, -4)); 157 | // for(int i = 0; i < 20; ++i) { 158 | // d = sceneDist(rp + ld * st); 159 | // if(d < 1e-5) 160 | // break; 161 | // if(st > 5.) 162 | // break; 163 | // st += d * 2.; 164 | // } 165 | 166 | // Set color if an intersection is found 167 | if (t < 10.0) { 168 | vec3 rp = ro + rd * t; 169 | vec3 n = sceneNorm(rp); 170 | vec3 ao = vec3(ambientOcclusion(rp, n)); 171 | // Previous code: 172 | // fragColor.rgb = mix(vec3(0.15, 0, 0), vec3(1), ao) * shad * diff * 1.1; 173 | fragColor = vec4(mix(vec3(0.15, 0.0, 0.0), vec3(1.0), ao) * 1.1, 1.0); // Annotated new color setting 174 | } else { 175 | // Background color if no intersection 176 | // Previous code: 177 | // fragColor.rgb += emit; 178 | fragColor = vec4(0.0, 0.0, 0.0, 1.0); // Annotated new background color setting 179 | } 180 | 181 | // ambient occlusion and shadowing 182 | vec3 ao = vec3(ambientOcclusion(rp, n)); 183 | float shad = mix(.85, 1., step(5., st)); 184 | 185 | ao *= mix(.3, 1., .5 + .5 * n.y); 186 | 187 | // soft floor shadow 188 | if(rp.y < 1e-3) 189 | ao *= mix(mix(vec3(1, .5, .7), vec3(1), .4) * .6, vec3(1), smoothstep(0., 1.6, length(rp.xz))); 190 | 191 | vec3 diff = vec3(1); 192 | vec3 emit = vec3(0); 193 | 194 | // skin 195 | diff *= vec3(1.15, .3, .41) * 1.4; 196 | diff += .4 * mix(1., 0., smoothstep(0., 1., length(rp.xy - vec2(0., 1.9 + jump * 0.5)))); 197 | diff += .5 * mix(1., 0., smoothstep(0., .5, length(rp.xy - vec2(.7, 2.5 + jump * 0.5)))); 198 | diff += .36 * mix(1., 0., smoothstep(0., .5, length(rp.xy - vec2(-1.1, 1.8 + jump * 0.5)))); 199 | 200 | if(rp.y < 1e-3) 201 | diff = vec3(.6, 1, .6); 202 | 203 | // mouth 204 | diff *= mix(vec3(1, .3, .2), vec3(1), smoothstep(.97, .99, length(rp - vec3(0, 1.5 + jump * 0.5, 0)))); 205 | 206 | // shoes 207 | diff = mix(vec3(1., .05, .1), diff, smoothstep(0., 0.01, shoesDist(rp))); 208 | diff += .2 * mix(1., 0., smoothstep(0., .2, length(rp.xy - vec2(-0.5, 1.4 + jump * 0.5)))); 209 | diff += .12 * mix(1., 0., smoothstep(0., .25, length(rp.xy - vec2(0.57, .3 + jump * 0.5)))); 210 | 211 | // bounce light from the floor 212 | diff += vec3(.25, 1., .25) * smoothstep(-.3, 1.7, -rp.y + 1.) * max(0., -n.y) * .7; 213 | 214 | vec3 orp = rp; 215 | rp.y -= 1.6 + (jump * 0.19); 216 | rp.x = abs(rp.x); 217 | 218 | // blushes 219 | diff *= mix(vec3(1, .5, .5), vec3(1), smoothstep(.1, .15, length((rp.xy - vec2(.4, .2)) * vec2(1, 1.65)))); 220 | 221 | rp.xy -= vec2(.16, .45); 222 | rp.xy *= .9; 223 | orp = rp; 224 | rp.y = pow(abs(rp.y), 1.4) * sign(rp.y); 225 | 226 | // eye outline 227 | diff *= smoothstep(.058, .067, length((rp.xy) * vec2(.9, .52))); 228 | 229 | rp = orp; 230 | rp.y += .08; 231 | rp.y -= pow(abs(rp.x), 2.) * 16.; 232 | 233 | // eye reflections 234 | emit += vec3(.1, .5, 1.) * (1. - smoothstep(.03, .036, length((rp.xy) * vec2(.7, .3)))) * max(0., -rp.y) * 10.; 235 | 236 | rp = orp; 237 | rp.y -= .12; 238 | 239 | // eye highlights 240 | emit += vec3(1) * (1. - smoothstep(.03, .04, length((rp.xy) * vec2(1., .48)))); 241 | 242 | // fresnel 243 | diff += pow(clamp(1. - dot(-rd, n), 0., .9), 4.) * .5; 244 | 245 | // background and floor fade 246 | vec3 backg = vec3(1.15, .3, .41) * .9; 247 | ot.x += .6 + u_time / 50.; 248 | ot.y += cos(floor(ot.x * 2.) * 3.) * .1 + .2; 249 | ot.x = mod(ot.x, .5) - .25; 250 | backg = mix(backg, vec3(1., 1., .5), .1 * starShape((ot - vec2(0., .6)) * 8.) * smoothstep(9., 10., t)); 251 | diff = mix(diff, backg, smoothstep(.9, 10., t)); 252 | 253 | fragColor.rgb = mix(vec3(.15, 0, 0), vec3(1), ao) * shad * diff * 1.1; 254 | fragColor.rgb += emit; 255 | 256 | fragColor.rgb = pow(fragColor.rgb, vec3(1. / 2.4)); 257 | } -------------------------------------------------------------------------------- /www/src/shaders/retro.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform vec2 u_resolution; 5 | uniform float u_time; 6 | uniform float u_timeDelta; 7 | // uniform sampler2D iChannel0; 8 | uniform float32Array u_audioData; 9 | 10 | in vec2 vUv; 11 | out vec4 fragColor; 12 | 13 | //#define AA 2 14 | //#define VAPORWAVE 15 | //#define stereo 1. // -1. for cross-eyed (defaults to parallel view) 16 | #define speed -10. 17 | #define wave_thing 18 | //#define city 19 | 20 | //you can add any sound texture in iChannel0 to turn it into a cool audio visualizer 21 | // (it looks better with lower speeds though) 22 | //you should commment out or remove the following line to enable it (it's disabled mainly for performance reasons): 23 | // #define disable_sound_texture_sampling 24 | 25 | #ifndef disable_sound_texture_sampling 26 | #undef speed 27 | // lower value of speed when using as audio visualizer 28 | #define speed -5. 29 | #endif 30 | 31 | //self-explainatory 32 | #define audio_vibration_amplitude .9125 33 | 34 | float jTime; 35 | 36 | #ifdef disable_sound_texture_sampling 37 | #define textureMirror(a, b) vec4(0) 38 | #else 39 | vec4 textureMirror(sampler2D tex, vec2 c) { 40 | vec2 cf = fract(c); 41 | return texture(tex, mix(cf, 1.f - cf, mod(floor(c), 2.f))); 42 | } 43 | #endif 44 | 45 | float amp(vec2 p) { 46 | return smoothstep(1.f, 8.f, abs(p.x)); 47 | } 48 | 49 | float pow512(float a) { 50 | a *= a;//^2 51 | a *= a;//^4 52 | a *= a;//^8 53 | a *= a;//^16 54 | a *= a;//^32 55 | a *= a;//^64 56 | a *= a;//^128 57 | a *= a;//^256 58 | return a * a; 59 | } 60 | float pow1d5(float a) { 61 | return a * sqrt(a); 62 | } 63 | float hash21(vec2 co) { 64 | return fract(sin(dot(co.xy, vec2(1.9898f, 7.233f))) * 45758.5433f); 65 | } 66 | float hash(vec2 uv) { 67 | float a = amp(uv); 68 | 69 | // Get the audio spectrum data 70 | vec2 audioSpectrum = textureMirror(iChannel0, vec2(uv.x, 0.0f)).xy; 71 | 72 | // Map the audio spectrum to terrain height 73 | float bassHeight = audioSpectrum.x * 0.1f; 74 | float trebleHeight = audioSpectrum.y * 2.0f; 75 | 76 | float w = mix(bassHeight, trebleHeight, uv.x); 77 | 78 | return (a > 0.0f ? a * pow1d5(hash21(uv)) * w : 0.0f) - (textureMirror(iChannel0, vec2((uv.x * 29.0f + uv.y) * 0.03125f, 2.0f)).x) * audio_vibration_amplitude; 79 | } 80 | 81 | float edgeMin(float dx, vec2 da, vec2 db, vec2 uv) { 82 | uv.x += 5.f; 83 | vec3 c = fract((round(vec3(uv, uv.x + uv.y))) * (vec3(0, 1, 2) + 0.61803398875f)); 84 | float a1 = textureMirror(iChannel0, vec2(c.y, 0.f)).x > .6f ? .15f : 1.f; 85 | float a2 = textureMirror(iChannel0, vec2(c.x, 0.f)).x > .6f ? .15f : 1.f; 86 | float a3 = textureMirror(iChannel0, vec2(c.z, 0.f)).x > .6f ? .15f : 1.f; 87 | 88 | return min(min((1.f - dx) * db.y * a3, da.x * a2), da.y * a1); 89 | } 90 | 91 | vec2 trinoise(vec2 uv) { 92 | const float sq = sqrt(3.f / 2.f); 93 | uv.x *= sq; 94 | uv.y -= .5f * uv.x; 95 | vec2 d = fract(uv); 96 | uv -= d; 97 | 98 | bool c = dot(d, vec2(1)) > 1.f; 99 | 100 | vec2 dd = 1.f - d; 101 | vec2 da = c ? dd : d, db = c ? d : dd; 102 | 103 | float nn = hash(uv + float(c)); 104 | float n2 = hash(uv + vec2(1, 0)); 105 | float n3 = hash(uv + vec2(0, 1)); 106 | 107 | float nmid = mix(n2, n3, d.y); 108 | float ns = mix(nn, c ? n2 : n3, da.y); 109 | float dx = da.x / db.y; 110 | return vec2(mix(ns, nmid, dx), edgeMin(dx, da, db, uv + d)); 111 | } 112 | 113 | vec2 map(vec3 p) { 114 | vec2 n = trinoise(p.xz); 115 | return vec2(p.y - 2.f * n.x, n.y); 116 | } 117 | 118 | vec3 grad(vec3 p) { 119 | const vec2 e = vec2(.005f, 0); 120 | float a = map(p).x; 121 | return vec3(map(p + e.xyy).x - a, map(p + e.yxy).x - a, map(p + e.yyx).x - a) / e.x; 122 | } 123 | 124 | vec2 intersect(vec3 ro, vec3 rd) { 125 | float d = 0.f, h = 0.f; 126 | for(int i = 0; i < 500; i++) { //look nice with 50 iterations 127 | vec3 p = ro + d * rd; 128 | vec2 s = map(p); 129 | h = s.x; 130 | d += h * .5f; 131 | if(abs(h) < .003f * d) 132 | return vec2(d, s.y); 133 | if(d > 150.f || p.y > 2.f) 134 | break; 135 | } 136 | 137 | return vec2(-1); 138 | } 139 | 140 | void addsun(vec3 rd, vec3 ld, inout vec3 col) { 141 | 142 | float sun = smoothstep(.21f, .2f, distance(rd, ld)); 143 | 144 | if(sun > 0.f) { 145 | float yd = (rd.y - ld.y); 146 | 147 | float a = sin(3.1f * exp(-(yd) * 14.f)); 148 | 149 | sun *= smoothstep(-.8f, 0.f, a); 150 | 151 | col = mix(col, vec3(1.f, .8f, .4f) * .75f, sun); 152 | } 153 | } 154 | 155 | float starnoise(vec3 rd) { 156 | float c = 0.8f; 157 | vec3 p = normalize(rd) * 300.f; 158 | for(float i = 0.f; i < 4.f; i++) { 159 | vec3 q = fract(p) - .5f; 160 | vec3 id = floor(p); 161 | float c2 = smoothstep(.5f, 0.f, length(q)); 162 | c2 *= step(hash21(id.xz / id.y), .06f - i * i * 0.005f); 163 | c += c2; 164 | p = p * .6f + .5f * p * mat3(3.f / 5.f, 0, 4.f / 5.f, 0, 1, 0, -4.f / 5.f, 0, 3.f / 5.f); 165 | } 166 | c *= c; 167 | float g = dot(sin(rd * 10.512f), cos(rd.yzx * 10.512f)); 168 | c *= smoothstep(-3.14f, -.9f, g) * .5f + .5f * smoothstep(-.3f, 1.f, g); 169 | return c * c; 170 | } 171 | 172 | vec3 gsky(vec3 rd, vec3 ld, bool mask) { 173 | float haze = exp2(-5.f * (abs(rd.y) - .2f * dot(rd, ld))); 174 | 175 | //float st = mask?pow512(texture(iChannel0,(rd.xy+vec2(300.1,100)*rd.z)*10.).r)*(1.-min(haze,1.)):0.; 176 | //float st = mask?pow512(hash21((rd.xy+vec2(300.1,100)*rd.z)*10.))*(1.-min(haze,1.)):0.; 177 | float st = mask ? (starnoise(rd)) * (1.f - min(haze, 1.f)) : 0.f; 178 | vec3 back = vec3(.4f, .1f, .7f) * (1.f - .5f * textureMirror(iChannel0, vec2(.5f + .05f * rd.x / rd.y, 0.f)).x * exp2(-.1f * abs(length(rd.xz) / rd.y)) * max(sign(rd.y), 0.f)); 179 | #ifdef city 180 | float x = round(rd.x * 30.f); 181 | float h = hash21(vec2(x - 166.f)); 182 | bool building = (h * h * .125f * exp2(-x * x * x * x * .0025f) > rd.y); 183 | if(mask && building) 184 | back *= 0.f, haze = .8f, mask = mask && !building; 185 | #endif 186 | // vec3 col=clamp(mix(back,vec3(.7,.1,.4),haze)+st,0.,1.); 187 | // if(mask)addsun(rd,ld,col); 188 | vec3 col = st * vec3(0.f, 0.1f, 0.1f); 189 | return col; 190 | } 191 | 192 | void main() { 193 | fragColor = vec4(0); 194 | #ifdef AA 195 | for(float x = 0.f; x < 1.f; x += 1.f / float(AA)) { 196 | for(float y = 0.f; y < 1.f; y += 1.f / float(AA)) { 197 | #else 198 | const float AA = 1.f, x = 0.f, y = 0.f; 199 | #endif 200 | // vec2 uv = (2.f * (vUv + vec2(x, y)) - u_resolution.xy) / u_resolution.y; 201 | vec2 uv = vUv; 202 | const float shutter_speed = 1.25f; // for motion blur 203 | float dt = fract(texture(iChannel0, float(AA) * (vUv + vec2(x, y)) / iChannelResolution[0].xy).r + u_time) * shutter_speed; 204 | // float dt = fract(hash21(float(AA) * (vUv + vec2(x, y))) + u_time) * shutter_speed; 205 | jTime = mod(u_time - dt * u_timeDelta, 4000.f); 206 | vec3 ro = vec3(0.f, 1, (-20000.f + jTime * speed)); 207 | 208 | #ifdef stereo 209 | ro += stereo * vec3(.2f * (float(uv.x > 0.f) - .5f), 0.f, 0.f); 210 | const float de = .9f; 211 | uv.x = uv.x + .5f * (uv.x > 0.f ? -de : de); 212 | uv *= 2.f; 213 | #endif 214 | 215 | vec3 rd = normalize(vec3(uv, 4.f / 3.f));//vec3(uv,sqrt(1.-dot(uv,uv))); 216 | 217 | vec2 i = intersect(ro, rd); 218 | float d = i.x; 219 | 220 | vec3 ld = normalize(vec3(0, .125f + .05f * sin(.1f * jTime), 1)); 221 | 222 | vec3 fog = d > 0.f ? exp2(-d * vec3(.14f, .1f, .28f)) : vec3(0.f); 223 | vec3 sky = gsky(rd, ld, d < 0.f); 224 | 225 | vec3 p = ro + d * rd; 226 | vec3 n = normalize(grad(p)); 227 | 228 | float diff = dot(n, ld) + .1f * n.y; 229 | vec3 col = vec3(.1f, .11f, .18f) * diff; 230 | 231 | vec3 rfd = reflect(rd, n); 232 | vec3 rfcol = gsky(rfd, ld, true); 233 | 234 | col = mix(col, rfcol, .05f + .95f * pow(max(1.f + dot(rd, n), 0.f), 1.f)); 235 | #ifdef VAPORWAVE 236 | col = mix(col, vec3(.4f, .5f, 1.f), smoothstep(.05f, .0f, i.y)); 237 | col = mix(sky, col, fog); 238 | col = sqrt(col); 239 | #else 240 | col = mix(col, vec3(.18f, .1f, .92f), smoothstep(.05f, .0f, i.y)); 241 | col = mix(sky, col, fog); 242 | //no gamma for that old cg look 243 | #endif 244 | if(d < 0.f) 245 | d = 1e6f; 246 | d = min(d, 10.f); 247 | fragColor += vec4(clamp(col, 0.f, 1.f), d < 0.f ? 0.f : .1f + exp2(-d)); 248 | #ifdef AA 249 | } 250 | } 251 | fragColor /= float(AA * AA); 252 | #endif 253 | } 254 | -------------------------------------------------------------------------------- /www/src/styles/code.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --shiki-foreground: #b2bde8; 3 | --shiki-background: transparent; 4 | --shiki-token-string: #773c00; 5 | --shiki-token-comment: #4d5774; 6 | --shiki-token-keyword: #33a7f4; 7 | --shiki-token-constant: #4685ec; 8 | --shiki-token-function: #ff3b86; 9 | --shiki-token-parameter: #00aaa2; 10 | --shiki-token-string-expression: #89a4e4; 11 | --shiki-token-punctuation: #89a4e4; 12 | --shiki-token-link: #a269cb; 13 | } 14 | 15 | .has-focused .line { 16 | filter: blur(0.095rem); 17 | } 18 | .has-focused .focused { 19 | filter: blur(0); 20 | } 21 | 22 | .has-highlighted { 23 | .line { 24 | opacity: 0.9; 25 | filter: brightness(0.7) saturate(0.7); 26 | 27 | &:has(.highlighted-word) { 28 | opacity: 1; 29 | filter: brightness(1) saturate(1); 30 | 31 | .highlighted-word { 32 | opacity: 1; 33 | filter: brightness(1.25) saturate(1.25); 34 | font-variation-settings: 'wght' 500; 35 | } 36 | 37 | &:not(.highlighted) span:not(.highlighted-word) { 38 | opacity: 0.75; 39 | filter: brightness(0.7) saturate(0.7); 40 | } 41 | } 42 | } 43 | 44 | .highlighted { 45 | opacity: 1; 46 | filter: brightness(1.2) saturate(1.2); 47 | } 48 | } 49 | 50 | pre.astro-code code { 51 | position: relative; 52 | 53 | width: fit-content; 54 | 55 | overflow: visible; 56 | } 57 | 58 | code, 59 | pre { 60 | overflow: visible; 61 | } 62 | 63 | code, 64 | .code { 65 | font-family: var(--font-mono); 66 | a { 67 | color: var(--fg-a); 68 | text-decoration: #22639c dotted underline; 69 | text-decoration-thickness: 1px; 70 | } 71 | } 72 | 73 | :not(pre) > code, 74 | .code { 75 | position: relative; 76 | /* padding: 0.1rem 0.4rem; */ 77 | padding: 0.1rem 0.25rem 0rem 0.25rem; 78 | 79 | background: #1b1e28; 80 | border-radius: 0.2rem; 81 | outline: var(--outline); 82 | 83 | display: inline-flex; 84 | align-items: center; 85 | width: fit-content; 86 | 87 | font-size: var(--font-xs); 88 | line-height: var(--line-height-sm); 89 | vertical-align: middle; 90 | 91 | &:not(.plain):not(.g-red) { 92 | background-image: var(--gradient); 93 | -webkit-background-clip: text; 94 | background-clip: text; 95 | color: transparent; 96 | -webkit-text-fill-color: transparent; 97 | } 98 | 99 | &::before { 100 | /* used for the background fill color since text fill prevents a main background color */ 101 | content: ''; 102 | position: absolute; 103 | inset: 0; 104 | display: inline-block; 105 | z-index: -1; 106 | border-radius: inherit; 107 | width: 100%; 108 | height: 100%; 109 | background-color: var(--bg-a); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /www/src/styles/elements.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: var(--font-a); 3 | background: var(--bg-b); 4 | color: var(--fg-a); 5 | font-size: 1rem; 6 | -webkit-text-size-adjust: 100%; 7 | text-size-adjust: 100%; 8 | /* scroll-behavior: smooth; */ 9 | } 10 | 11 | body { 12 | display: flex; 13 | flex-direction: column; 14 | min-height: 100dvh; 15 | margin: 0; 16 | padding: 0; 17 | width: 100%; 18 | max-width: 100vw; 19 | overflow-x: hidden; 20 | } 21 | 22 | p { 23 | width: clamp(20ch, 42rem, 100%); 24 | margin: auto; 25 | 26 | font-variation-settings: 'wght' 300; 27 | font-size: var(--font); 28 | text-wrap: balance; 29 | text-align: center; 30 | line-height: 1.5; 31 | } 32 | 33 | em { 34 | color: var(--fg-d); 35 | font-variation-settings: 'wght' 300; 36 | } 37 | 38 | main, 39 | section { 40 | position: relative; 41 | display: flex; 42 | flex-direction: column; 43 | 44 | width: 800px; 45 | max-width: calc(100vw - 2rem); 46 | margin: auto; 47 | 48 | color: white; 49 | 50 | z-index: 1; 51 | } 52 | 53 | section { 54 | gap: 1.5rem; 55 | contain: layout; 56 | } 57 | 58 | h1 { 59 | font-family: var(--font-b); 60 | font-size: 4rem; 61 | font-weight: 00; 62 | line-height: 1; 63 | text-align: center; 64 | margin-top: 1rem; 65 | } 66 | 67 | h2 { 68 | scroll-margin-top: 2rem; 69 | 70 | z-index: 1; 71 | 72 | width: fit-content; 73 | margin-top: 4rem; 74 | margin-right: auto; 75 | margin-bottom: 0rem; 76 | margin-left: auto; 77 | 78 | font-size: 3rem; 79 | text-align: center; 80 | font-weight: 500; 81 | font-variation-settings: 'wght' 100; 82 | 83 | a { 84 | text-decoration: transparent solid underline; 85 | transition: 0.2s; 86 | &:hover { 87 | text-decoration-color: var(--bg-d); 88 | } 89 | } 90 | 91 | &:not(:first-of-type) { 92 | margin-top: 8rem; 93 | } 94 | } 95 | 96 | h3 { 97 | scroll-margin-top: 2rem; 98 | 99 | position: relative; 100 | 101 | margin: 0 auto; 102 | 103 | font-size: 2rem; 104 | font-family: var(--font-b); 105 | font-variation-settings: 'wght' 100; 106 | text-align: center; 107 | 108 | &::before { 109 | content: '#'; 110 | color: var(--bg-d); 111 | font-size: 1rem; 112 | position: absolute; 113 | left: -0.75rem; 114 | top: 0.5rem; 115 | } 116 | &:hover::before { 117 | color: color-mix(in lch, var(--bg-d), var(--fg-d)); 118 | } 119 | 120 | a { 121 | text-decoration: transparent solid underline; 122 | text-decoration-thickness: 1px; 123 | 124 | transition: 0.1s; 125 | &:hover { 126 | text-decoration-color: color-mix(in lch, var(--bg-d), var(--fg-d)); 127 | } 128 | } 129 | } 130 | 131 | a { 132 | color: var(--fg-c); 133 | text-decoration: var(--fg-d) dotted underline; 134 | text-decoration-thickness: 1px; 135 | } 136 | 137 | button, 138 | .btn { 139 | min-width: 6rem; 140 | width: fit-content; 141 | margin: auto; 142 | padding: 0.5rem 1rem; 143 | 144 | border: none; 145 | border-radius: 0.75rem; 146 | 147 | font-family: var(--font-a); 148 | font-variation-settings: 'wght' 500; 149 | font-size: 1rem; 150 | 151 | cursor: pointer; 152 | } 153 | 154 | .btn { 155 | position: relative; 156 | color: var(--fg-a); 157 | outline: var(--outline); 158 | 159 | --theme-a: var(--bg-a); 160 | --theme-b: var(--bg-a); 161 | 162 | background: radial-gradient( 163 | in lch circle at -25% 50%, 164 | var(--bg-a) -50%, 165 | var(--theme-b, #a269cb), 166 | var(--theme-a, #33c5f4) 80%, 167 | var(--bg-a) 120% 168 | ) 169 | top right / 200% 200%; 170 | 171 | transition: 172 | background-position 2s cubic-bezier(0.23, 1, 0.32, 1), 173 | background 2s cubic-bezier(0.23, 1, 0.32, 1), 174 | --theme-a 0.5s, 175 | --theme-b 0.5s, 176 | opacity 0.15s, 177 | filter 0.15s; 178 | 179 | font-weight: 900; 180 | 181 | &.active { 182 | --theme-a: #ff0051; 183 | --theme-b: #ff833b; 184 | background-position: 60% 50%; 185 | outline-color: var(--bg-a); 186 | outline-width: 2px; 187 | } 188 | 189 | &:hover { 190 | background-position: 50% 50%; 191 | color: var(--fg-a); 192 | 193 | &:not(.active) { 194 | background-position: 0% 50%; 195 | --theme-a: #33c5f4; 196 | --theme-b: #a269cb; 197 | } 198 | } 199 | 200 | &::before { 201 | content: ''; 202 | position: absolute; 203 | inset: 0; 204 | display: inline-block; 205 | z-index: -1; 206 | border-radius: inherit; 207 | width: 100%; 208 | height: 100%; 209 | padding: 1px; 210 | left: -1px; 211 | top: -1px; 212 | 213 | background-image: var(--gradient); 214 | } 215 | } 216 | 217 | br { 218 | user-select: none; 219 | pointer-events: none; 220 | } 221 | 222 | br { 223 | height: 1rem; 224 | } 225 | br-md { 226 | height: 2rem; 227 | } 228 | br-lg { 229 | height: 3rem; 230 | } 231 | br-xl { 232 | height: 5rem; 233 | } 234 | -------------------------------------------------------------------------------- /www/src/styles/font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Caveat'; 3 | src: url('/fonts/Caveat-VariableFont_wght.ttf') format('truetype'); 4 | font-weight: 100 900; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: 'Fredoka'; 10 | src: url('/fonts/Fredoka-VariableFont_wdth-wght.ttf') format('truetype'); 11 | font-weight: normal; 12 | font-style: normal; 13 | font-display: swap; 14 | } 15 | 16 | @font-face { 17 | font-family: 'Monaspace'; 18 | src: url('/fonts/MonaspaceNeon_wght-wdth-slnt.ttf') format('truetype'); 19 | font-weight: normal; 20 | font-style: normal; 21 | font-display: swap; 22 | } 23 | -------------------------------------------------------------------------------- /www/src/styles/utilities.css: -------------------------------------------------------------------------------- 1 | .text-gradient { 2 | background-image: var(--gradient); 3 | -webkit-background-clip: text; 4 | background-clip: text; 5 | color: transparent; 6 | -webkit-text-fill-color: transparent; 7 | } 8 | 9 | .text-gradient-animated { 10 | background-image: linear-gradient( 11 | var(--angle, 0deg), 12 | var(--theme-a, #33c5f4), 13 | var(--theme-b, #69cb81) 100% 14 | ); 15 | background-clip: text; 16 | -webkit-text-fill-color: transparent; 17 | 18 | animation: gradient-animation 5s infinite alternate; 19 | 20 | /* 21 | Firefox _still_ doesn't support `@property` 22 | https://bugzilla.mozilla.org/show_bug.cgi?id=1273706 23 | */ 24 | @-moz-document url-prefix() { 25 | animation: none; 26 | } 27 | } 28 | 29 | .gradient-outline { 30 | &::before { 31 | content: ''; 32 | position: absolute; 33 | inset: 0; 34 | display: inline-block; 35 | z-index: -1; 36 | border-radius: inherit; 37 | width: 100%; 38 | height: 100%; 39 | padding: 1px; 40 | left: -1px; 41 | top: -1px; 42 | 43 | background-image: linear-gradient( 44 | var(--deg, 45deg), 45 | color-mix(in lch, var(--theme-a, #33c5f4) 25%, transparent), 46 | color-mix(in lch, var(--theme-b, #913bff) 25%, transparent) 100% 47 | ); 48 | 49 | animation: gradient-animation 5s infinite alternate; 50 | } 51 | 52 | &:hover { 53 | background-color: var(--gradient); 54 | } 55 | } 56 | 57 | @keyframes gradient-animation { 58 | from { 59 | --theme-a: #33c5f4; 60 | --angle: 0deg; 61 | } 62 | to { 63 | --theme-a: #f44d4d; 64 | --angle: 360deg; 65 | } 66 | } 67 | 68 | .g-red:not(.plain) { 69 | /* prettier-ignore */ 70 | background-image: var(--ps-gradient); 71 | -webkit-background-clip: text; 72 | background-clip: text; 73 | color: transparent; 74 | -webkit-text-fill-color: transparent; 75 | } 76 | 77 | .g-blue { 78 | background-image: var(--gradient); 79 | -webkit-background-clip: text; 80 | background-clip: text; 81 | color: transparent; 82 | -webkit-text-fill-color: transparent; 83 | } 84 | 85 | .grey { 86 | -webkit-text-fill-color: var(--fg-d); 87 | } 88 | 89 | .card { 90 | width: fit-content; 91 | margin: auto; 92 | padding: 0.75rem 1rem; 93 | 94 | background: var(--bg-a); 95 | outline: var(--outline); 96 | border-radius: var(--radius); 97 | } 98 | -------------------------------------------------------------------------------- /www/src/styles/variables.css: -------------------------------------------------------------------------------- 1 | @property --theme-a { 2 | syntax: ''; 3 | inherits: true; 4 | initial-value: #33c5f4; 5 | } 6 | @property --theme-b { 7 | syntax: ''; 8 | inherits: true; 9 | initial-value: #913bff; 10 | } 11 | @property --angle { 12 | syntax: ''; 13 | inherits: false; 14 | initial-value: 0deg; 15 | } 16 | 17 | :root { 18 | --font-a: 'Fredoka', system-ui, sans-serif; 19 | --font-b: 'Caveat', system-ui, sans-serif; 20 | --font-mono: Monaspace, Fira Code, Menlo, Courier New, monospace; 21 | 22 | /* --font: clamp(0.75rem, calc(0.75rem + 1vw), 1.1rem); */ 23 | --font: 1.1rem; 24 | --font-sm: clamp(10px, calc(8px + 1vw), 14px); 25 | --font-xs: clamp(12px, calc(6px + 1vw), 14px); 26 | --line-height-sm: clamp(1rem, calc(0.5rem + 1vw), 1.2rem); 27 | 28 | --bg-a: black; 29 | --bg-b: #0b0d12; 30 | --bg-c: #1a1e24; 31 | --bg-d: #2a2f36; 32 | 33 | --fg-a: #f5f5f5; 34 | --fg-b: #e0e0e0; 35 | --fg-c: #c6c6c6; 36 | --fg-d: #a8a8a8; 37 | 38 | --outline: 1px solid #fff3; 39 | --radius: 0.5rem; 40 | --gradient: linear-gradient( 41 | in lab var(--deg, 45deg), 42 | var(--theme-a, #33c5f4), 43 | var(--theme-b, #a269cb) 44 | ); 45 | /* prettier-ignore */ 46 | --ps-gradient: linear-gradient( 47 | in lab var(--deg, 45deg), 48 | #913bff, 49 | #f44d4d 50 | ); 51 | /* prettier-ignore */ 52 | --shadow: 0rem 0.0469rem 0.0625rem rgb(0, 0, 0, 0.115), 53 | 0rem 0.1563rem 0.125rem hsla(0, 0%, 0%, 0.075), 54 | 0rem 0.2813rem 0.1875rem hsla(0, 0%, 0%, 0.025), 55 | 0rem 0.3125rem 0.3125rem hsla(0, 0%, 0%, 0.05), 56 | 0rem 0.625rem 0.625rem hsla(0, 0%, 0%, 0.145), 57 | 0rem 0.625rem 1.25rem hsla(0, 0%, 0%, 0.15); 58 | } 59 | -------------------------------------------------------------------------------- /www/src/utils/animations.ts: -------------------------------------------------------------------------------- 1 | const easeOut = 'cubic-bezier(.12,.75,.13,.99)' 2 | 3 | export async function fadeOutUp(el: Element) { 4 | return await el.animate( 5 | [ 6 | { transform: 'translateY(0)', opacity: 1 }, 7 | { transform: 'translateY(-0.2rem)', opacity: 0 }, 8 | ], 9 | { 10 | duration: 200, 11 | easing: easeOut, 12 | fill: 'forwards', 13 | }, 14 | ).finished 15 | } 16 | 17 | export async function fadeInUp(el: Element) { 18 | return await el.animate( 19 | [ 20 | { transform: 'translateY(0.2rem)', opacity: 0 }, 21 | { transform: 'translateY(0)', opacity: 1 }, 22 | ], 23 | { 24 | duration: 200, 25 | easing: easeOut, 26 | fill: 'forwards', 27 | }, 28 | ).finished 29 | } 30 | 31 | /** 32 | * Swaps an element's text with a fade animation. 33 | */ 34 | export async function fadeText(el: Element, text: string) { 35 | const elText = el.textContent 36 | const wrapper = document.createElement('div') 37 | wrapper.textContent = elText 38 | el.innerHTML = '' 39 | el.appendChild(wrapper) 40 | await fadeOutUp(wrapper) 41 | wrapper.textContent = text 42 | await fadeInUp(wrapper) 43 | el.innerHTML = text 44 | } 45 | -------------------------------------------------------------------------------- /www/src/utils/clickOutside.ts: -------------------------------------------------------------------------------- 1 | import type { Action } from 'svelte/action' 2 | 3 | export interface ClickOutsideEventDetail { 4 | target: HTMLElement 5 | } 6 | 7 | /** 8 | * Calls `outclick` when a parent element is clicked. 9 | */ 10 | export type ClickOutsideEvent = CustomEvent 11 | 12 | export interface ClickOutsideOptions { 13 | /** 14 | * Array of classnames. If the click target element has one of these classes, it will not be considered an outclick. 15 | */ 16 | whitelist?: string[] 17 | } 18 | 19 | // Attributes applied to the element that does use:clickOutside 20 | export interface ClickOutsideAttr { 21 | 'on:outclick'?: (event: ClickOutsideEvent) => void 22 | } 23 | 24 | /** 25 | * Calls a function when the user clicks outside the element. 26 | * @example 27 | * ```svelte 28 | *
29 | * ``` 30 | */ 31 | export const clickOutside: Action = ( 32 | node, 33 | options?: ClickOutsideOptions, 34 | ) => { 35 | const handleClick = (event: MouseEvent) => { 36 | let disable = false 37 | 38 | for (const className of options?.whitelist || []) { 39 | if (event.target instanceof Element && event.target.classList.contains(className)) { 40 | disable = true 41 | } 42 | } 43 | 44 | if (!disable && node && !node.contains(event.target as Node) && !event.defaultPrevented) { 45 | node.dispatchEvent( 46 | new CustomEvent('outclick', { 47 | detail: { 48 | target: event.target as HTMLElement, 49 | }, 50 | }), 51 | ) 52 | } 53 | } 54 | 55 | document.addEventListener('click', handleClick, true) 56 | 57 | return { 58 | update: (newOptions) => (options = { ...options, ...newOptions }), 59 | destroy() { 60 | document.removeEventListener('click', handleClick, true) 61 | }, 62 | } 63 | } -------------------------------------------------------------------------------- /www/src/utils/dedent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Trims a multiline string by an amount equal to the least indented line. 3 | * Leading and trailing newlines are removed. 4 | * 5 | * If the first line is _not_ empty, it will be _indented_ by the 6 | * same amount as the rest is _un-indented_. 7 | * 8 | * @returns The dedented string. 9 | */ 10 | export function dedent( 11 | /** 12 | * The string to dedent. 13 | */ 14 | string: string, 15 | options = { 16 | /** 17 | * Whether to trim the last line if its empty. 18 | * @defaultValue `true` 19 | */ 20 | trimEndingNewline: true, 21 | }, 22 | ): string { 23 | const lines = string.split('\n') 24 | const leadingNewline = string[0] !== '\n' 25 | const indent = lines 26 | .filter((line, i) => { 27 | if (leadingNewline && i === 0) { 28 | return false 29 | } 30 | return line.trim() 31 | }) 32 | .map(line => line.match(/^\s*/)?.[0].length) 33 | .filter(indent => indent !== undefined) 34 | // @ts-ignore - Astro is hallucinating errors here and throwing on build 35 | .reduce((a, b) => Math.min(a, b), 9999) 36 | 37 | if (leadingNewline) { 38 | // @ts-ignore - Astro is hallucinating errors here and throwing on build 39 | lines[0] = ' '.repeat(indent) + lines[0] 40 | } else { 41 | lines.shift() 42 | } 43 | 44 | if (options.trimEndingNewline) { 45 | if (lines.at(-1) === '') { 46 | lines.pop() 47 | } 48 | } 49 | 50 | return lines.map(line => line.slice(indent)).join('\n') 51 | } 52 | -------------------------------------------------------------------------------- /www/src/utils/gridColor.ts: -------------------------------------------------------------------------------- 1 | import { quintOut } from 'svelte/easing' 2 | import { tweened } from 'svelte/motion' 3 | 4 | export const gridColors = { 5 | greyscale: [0.1, 0.1, 0.1], 6 | purple: [0.57, 0.23, 1], 7 | cyan: [0.2, 0.77, 0.96], 8 | red: [1, 0.23137254901960785, 0.5254901960784314], 9 | blue: [0.27450980392156865, 0.5215686274509804, 0.9254901960784314], 10 | } 11 | 12 | export const gridColor = tweened(gridColors.greyscale, { 13 | duration: 1500, 14 | easing: quintOut, 15 | }) 16 | 17 | export const gridYeet = tweened(1.0, { 18 | duration: 1500, 19 | easing: quintOut, 20 | }) 21 | -------------------------------------------------------------------------------- /www/src/utils/nanoid.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate a random ID. 3 | * @param length The length of the ID to generate. Default: `21` 4 | */ 5 | export function nanoid(length = 21) { 6 | return crypto 7 | .getRandomValues(new Uint8Array(length)) 8 | .reduce( 9 | (t, e) => 10 | (t += 11 | (e &= 63) < 36 12 | ? e.toString(36) 13 | : e < 62 14 | ? (e - 26).toString(36).toUpperCase() 15 | : e > 62 16 | ? '-' 17 | : '_'), 18 | '', 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /www/src/utils/teleportIntoView.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Like `scrollIntoView({ behavior: 'instant' })` with a quick fade/transform animation. 3 | */ 4 | export function teleportIntoView( 5 | target: Element, 6 | { 7 | yAmount = 0.5, 8 | durationOut = 200, 9 | durationIn = 300, 10 | easingOut = 'ease', 11 | easeIn = 'ease-out', 12 | } = {}, 13 | ) { 14 | // return if the target is already in view 15 | 16 | setTimeout(async () => { 17 | const rect = target.getBoundingClientRect() 18 | 19 | if (rect.top >= -100 && rect.top <= window.innerHeight / 3) { 20 | // The element is in the viewport 21 | return 22 | } 23 | 24 | const direction = rect.top > 0 ? 1 : -1 25 | 26 | await document.querySelector('.page')?.animate( 27 | [ 28 | { opacity: 1, transform: 'translateY(0)' }, 29 | { 30 | opacity: 0, 31 | transform: `translateY(${-direction * yAmount}rem)`, 32 | }, 33 | ], 34 | { 35 | duration: durationOut, 36 | easing: easingOut, 37 | }, 38 | ).finished 39 | target.scrollIntoView({ 40 | behavior: 'instant', 41 | block: 'start', 42 | }) 43 | document.querySelector('.page')?.animate( 44 | [ 45 | { 46 | opacity: 0, 47 | transform: `translateY(${direction * yAmount}rem)`, 48 | }, 49 | { opacity: 1, transform: 'translateY(0)' }, 50 | ], 51 | { 52 | duration: durationIn, 53 | easing: easeIn, 54 | }, 55 | ) 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /www/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@astrojs/svelte'; 2 | 3 | export default { 4 | preprocess: vitePreprocess(), 5 | } 6 | -------------------------------------------------------------------------------- /www/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "strict": true, 6 | "lib": ["DOM", "ESNext"], 7 | "skipLibCheck": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "noEmit": true, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | }, 14 | "exclude": ["node_modules", "dist"] 15 | } 16 | --------------------------------------------------------------------------------