bluedither 1.0.23 → 1.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/commands/install.js +58 -4
- package/dist/bluedither-shader.js +18 -18
- package/fine-tuner/sidecar.js +91 -0
- package/package.json +1 -1
- package/theme/generators/vanilla.md +64 -85
- package/theme/shaders/bluedither-shader.js +7 -1
- package/theme/template/index.html +19 -19
package/cli/commands/install.js
CHANGED
|
@@ -107,6 +107,10 @@ export default async function install(args) {
|
|
|
107
107
|
if (existsSync(middlewarePath)) {
|
|
108
108
|
filesToCopy.push(['fine-tuner/dev-middleware.js', 'dev-middleware.js']);
|
|
109
109
|
}
|
|
110
|
+
const sidecarPath = resolve(BLUEDITHER_ROOT, 'fine-tuner', 'sidecar.js');
|
|
111
|
+
if (existsSync(sidecarPath)) {
|
|
112
|
+
filesToCopy.push(['fine-tuner/sidecar.js', 'sidecar.js']);
|
|
113
|
+
}
|
|
110
114
|
|
|
111
115
|
let copied = 0;
|
|
112
116
|
for (const [src, dest] of filesToCopy) {
|
|
@@ -121,6 +125,26 @@ export default async function install(args) {
|
|
|
121
125
|
|
|
122
126
|
console.log(`Copied ${copied} files to ${bdDir}`);
|
|
123
127
|
|
|
128
|
+
// Also copy tuner assets + tokens to public/bluedither/ for Vite projects
|
|
129
|
+
const publicBdDir = resolve(targetDir, 'public', 'bluedither');
|
|
130
|
+
if (existsSync(resolve(targetDir, 'public')) || existsSync(resolve(targetDir, 'vite.config.js')) || existsSync(resolve(targetDir, 'vite.config.ts'))) {
|
|
131
|
+
mkdirSync(publicBdDir, { recursive: true });
|
|
132
|
+
const publicFiles = [
|
|
133
|
+
[resolve(bdDir, 'bluedither-tuner.js'), 'bluedither-tuner.js'],
|
|
134
|
+
[resolve(bdDir, 'bluedither-tuner.css'), 'bluedither-tuner.css'],
|
|
135
|
+
[resolve(bdDir, 'bluedither-tuner-inject.js'), 'bluedither-tuner-inject.js'],
|
|
136
|
+
[resolve(bdDir, 'tokens.json'), 'tokens.json'],
|
|
137
|
+
[resolve(bdDir, 'tokens.defaults.json'), 'tokens.defaults.json'],
|
|
138
|
+
[resolve(bdDir, 'dev-middleware.js'), 'dev-middleware.js'],
|
|
139
|
+
];
|
|
140
|
+
for (const [srcPath, destName] of publicFiles) {
|
|
141
|
+
if (existsSync(srcPath)) {
|
|
142
|
+
copyFileSync(srcPath, resolve(publicBdDir, destName));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
console.log(`Copied tuner assets to public/bluedither/`);
|
|
146
|
+
}
|
|
147
|
+
|
|
124
148
|
// Auto-wire Vite plugin if vite.config exists
|
|
125
149
|
autoWireVitePlugin(targetDir);
|
|
126
150
|
|
|
@@ -147,10 +171,14 @@ $ARGUMENTS
|
|
|
147
171
|
|
|
148
172
|
To apply the theme, use Claude Code:
|
|
149
173
|
/apply-theme [description of your site]
|
|
150
|
-
|
|
174
|
+
${framework === 'vanilla' ? `
|
|
175
|
+
For vanilla projects, start the dev server:
|
|
176
|
+
node bluedither/sidecar.js [port]
|
|
177
|
+
This serves your files AND handles "Commit Changes" saves.
|
|
178
|
+
` : `
|
|
151
179
|
The tuner panel loads automatically in dev mode.
|
|
152
180
|
Click "Commit Changes" to save tweaks to tokens.json.
|
|
153
|
-
`);
|
|
181
|
+
`} `);
|
|
154
182
|
}
|
|
155
183
|
|
|
156
184
|
function autoWireVitePlugin(targetDir) {
|
|
@@ -260,6 +288,7 @@ async function installFromRegistry(slug, targetDir) {
|
|
|
260
288
|
['fine-tuner/tuner.css', 'bluedither-tuner.css'],
|
|
261
289
|
['fine-tuner/inject.js', 'bluedither-tuner-inject.js'],
|
|
262
290
|
['fine-tuner/dev-middleware.js', 'dev-middleware.js'],
|
|
291
|
+
['fine-tuner/sidecar.js', 'sidecar.js'],
|
|
263
292
|
];
|
|
264
293
|
for (const [src, dest] of tunerFiles) {
|
|
265
294
|
const srcPath = resolve(BLUEDITHER_ROOT, src);
|
|
@@ -269,6 +298,27 @@ async function installFromRegistry(slug, targetDir) {
|
|
|
269
298
|
}
|
|
270
299
|
}
|
|
271
300
|
|
|
301
|
+
// Also copy tuner assets + tokens to public/bluedither/ for Vite projects
|
|
302
|
+
// This saves the AI agent from having to do this step manually
|
|
303
|
+
const publicBdDir = resolve(targetDir, 'public', 'bluedither');
|
|
304
|
+
if (existsSync(resolve(targetDir, 'public')) || existsSync(resolve(targetDir, 'vite.config.js')) || existsSync(resolve(targetDir, 'vite.config.ts'))) {
|
|
305
|
+
mkdirSync(publicBdDir, { recursive: true });
|
|
306
|
+
const publicFiles = [
|
|
307
|
+
[resolve(bdDir, 'bluedither-tuner.js'), 'bluedither-tuner.js'],
|
|
308
|
+
[resolve(bdDir, 'bluedither-tuner.css'), 'bluedither-tuner.css'],
|
|
309
|
+
[resolve(bdDir, 'bluedither-tuner-inject.js'), 'bluedither-tuner-inject.js'],
|
|
310
|
+
[resolve(bdDir, 'tokens.json'), 'tokens.json'],
|
|
311
|
+
[resolve(bdDir, 'tokens.defaults.json'), 'tokens.defaults.json'],
|
|
312
|
+
[resolve(bdDir, 'dev-middleware.js'), 'dev-middleware.js'],
|
|
313
|
+
];
|
|
314
|
+
for (const [srcPath, destName] of publicFiles) {
|
|
315
|
+
if (existsSync(srcPath)) {
|
|
316
|
+
copyFileSync(srcPath, resolve(publicBdDir, destName));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
console.log(` Copied tuner assets to public/bluedither/`);
|
|
320
|
+
}
|
|
321
|
+
|
|
272
322
|
// Detect framework and create Claude Code command
|
|
273
323
|
const { framework, typescript } = detectFramework(targetDir);
|
|
274
324
|
console.log(` Detected framework: ${framework}${typescript ? ' + TypeScript' : ''}`);
|
|
@@ -297,8 +347,12 @@ $ARGUMENTS
|
|
|
297
347
|
|
|
298
348
|
To apply the theme, use Claude Code:
|
|
299
349
|
/apply-theme [description of your site]
|
|
300
|
-
|
|
350
|
+
${framework === 'vanilla' ? `
|
|
351
|
+
For vanilla projects, start the dev server:
|
|
352
|
+
node bluedither/sidecar.js [port]
|
|
353
|
+
This serves your files AND handles "Commit Changes" saves.
|
|
354
|
+
` : `
|
|
301
355
|
The tuner panel loads automatically in dev mode.
|
|
302
356
|
Click "Commit Changes" to save tweaks to tokens.json.
|
|
303
|
-
`);
|
|
357
|
+
`} `);
|
|
304
358
|
}
|
|
@@ -148,7 +148,7 @@ void main() {
|
|
|
148
148
|
|
|
149
149
|
v_imageUV += .5;
|
|
150
150
|
v_imageUV.y = 1. - v_imageUV.y;
|
|
151
|
-
}`,z=8294400,X=class{constructor(e,o,t,i,n=0,s=0,r=2,l=z,m=[]){a(this,"parentElement");a(this,"canvasElement");a(this,"gl");a(this,"program",null);a(this,"uniformLocations",{});a(this,"fragmentShader");a(this,"rafId",null);a(this,"lastRenderTime",0);a(this,"currentFrame",0);a(this,"speed",0);a(this,"currentSpeed",0);a(this,"providedUniforms");a(this,"mipmaps",[]);a(this,"hasBeenDisposed",!1);a(this,"resolutionChanged",!0);a(this,"textures",new Map);a(this,"minPixelRatio");a(this,"maxPixelCount");a(this,"isSafari",oe());a(this,"uniformCache",{});a(this,"textureUnitMap",new Map);a(this,"initProgram",()=>{let e=$(this.gl,Z,this.fragmentShader);e&&(this.program=e)});a(this,"setupPositionAttribute",()=>{let e=this.gl.getAttribLocation(this.program,"a_position"),o=this.gl.createBuffer();this.gl.bindBuffer(this.gl.ARRAY_BUFFER,o);let t=[-1,-1,1,-1,-1,1,-1,1,1,-1,1,1];this.gl.bufferData(this.gl.ARRAY_BUFFER,new Float32Array(t),this.gl.STATIC_DRAW),this.gl.enableVertexAttribArray(e),this.gl.vertexAttribPointer(e,2,this.gl.FLOAT,!1,0,0)});a(this,"setupUniforms",()=>{let e={u_time:this.gl.getUniformLocation(this.program,"u_time"),u_pixelRatio:this.gl.getUniformLocation(this.program,"u_pixelRatio"),u_resolution:this.gl.getUniformLocation(this.program,"u_resolution")};Object.entries(this.providedUniforms).forEach(([o,t])=>{if(e[o]=this.gl.getUniformLocation(this.program,o),t instanceof HTMLImageElement){let i=`${o}AspectRatio`;e[i]=this.gl.getUniformLocation(this.program,i)}}),this.uniformLocations=e});a(this,"renderScale",1);a(this,"parentWidth",0);a(this,"parentHeight",0);a(this,"parentDevicePixelWidth",0);a(this,"parentDevicePixelHeight",0);a(this,"devicePixelsSupported",!1);a(this,"resizeObserver",null);a(this,"setupResizeObserver",()=>{this.resizeObserver=new ResizeObserver(([e])=>{if(e?.borderBoxSize[0]){let o=e.devicePixelContentBoxSize?.[0];o!==void 0&&(this.devicePixelsSupported=!0,this.parentDevicePixelWidth=o.inlineSize,this.parentDevicePixelHeight=o.blockSize),this.parentWidth=e.borderBoxSize[0].inlineSize,this.parentHeight=e.borderBoxSize[0].blockSize}this.handleResize()}),this.resizeObserver.observe(this.parentElement)});a(this,"handleVisualViewportChange",()=>{this.resizeObserver?.disconnect(),this.setupResizeObserver()});a(this,"handleResize",()=>{let e=0,o=0,t=Math.max(1,window.devicePixelRatio),i=visualViewport?.scale??1;if(this.devicePixelsSupported){let u=Math.max(1,this.minPixelRatio/t);e=this.parentDevicePixelWidth*u*i,o=this.parentDevicePixelHeight*u*i}else{let u=Math.max(t,this.minPixelRatio)*i;if(this.isSafari){let d=te();u*=Math.max(1,d)}e=Math.round(this.parentWidth)*u,o=Math.round(this.parentHeight)*u}let n=Math.sqrt(this.maxPixelCount)/Math.sqrt(e*o),s=Math.min(1,n),r=Math.round(e*s),l=Math.round(o*s),m=r/Math.round(this.parentWidth);(this.canvasElement.width!==r||this.canvasElement.height!==l||this.renderScale!==m)&&(this.renderScale=m,this.canvasElement.width=r,this.canvasElement.height=l,this.resolutionChanged=!0,this.gl.viewport(0,0,this.gl.canvas.width,this.gl.canvas.height),this.render(performance.now()))});a(this,"render",e=>{if(this.hasBeenDisposed)return;if(this.program===null){console.warn("Tried to render before program or gl was initialized");return}let o=e-this.lastRenderTime;this.lastRenderTime=e,this.currentSpeed!==0&&(this.currentFrame+=o*this.currentSpeed),this.gl.clear(this.gl.COLOR_BUFFER_BIT),this.gl.useProgram(this.program),this.gl.uniform1f(this.uniformLocations.u_time,this.currentFrame*.001),this.resolutionChanged&&(this.gl.uniform2f(this.uniformLocations.u_resolution,this.gl.canvas.width,this.gl.canvas.height),this.gl.uniform1f(this.uniformLocations.u_pixelRatio,this.renderScale),this.resolutionChanged=!1),this.gl.drawArrays(this.gl.TRIANGLES,0,6),this.currentSpeed!==0?this.requestRender():this.rafId=null});a(this,"requestRender",()=>{this.rafId!==null&&cancelAnimationFrame(this.rafId),this.rafId=requestAnimationFrame(this.render)});a(this,"setTextureUniform",(e,o)=>{if(!o.complete||o.naturalWidth===0)throw new Error(`Paper Shaders: image for uniform ${e} must be fully loaded`);let t=this.textures.get(e);t&&this.gl.deleteTexture(t),this.textureUnitMap.has(e)||this.textureUnitMap.set(e,this.textureUnitMap.size);let i=this.textureUnitMap.get(e);this.gl.activeTexture(this.gl.TEXTURE0+i);let n=this.gl.createTexture();this.gl.bindTexture(this.gl.TEXTURE_2D,n),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGBA,this.gl.RGBA,this.gl.UNSIGNED_BYTE,o),this.mipmaps.includes(e)&&(this.gl.generateMipmap(this.gl.TEXTURE_2D),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR_MIPMAP_LINEAR));let s=this.gl.getError();if(s!==this.gl.NO_ERROR||n===null){console.error("Paper Shaders: WebGL error when uploading texture:",s);return}this.textures.set(e,n);let r=this.uniformLocations[e];if(r){this.gl.uniform1i(r,i);let l=`${e}AspectRatio`,m=this.uniformLocations[l];if(m){let u=o.naturalWidth/o.naturalHeight;this.gl.uniform1f(m,u)}}});a(this,"areUniformValuesEqual",(e,o)=>e===o?!0:Array.isArray(e)&&Array.isArray(o)&&e.length===o.length?e.every((t,i)=>this.areUniformValuesEqual(t,o[i])):!1);a(this,"setUniformValues",e=>{this.gl.useProgram(this.program),Object.entries(e).forEach(([o,t])=>{let i=t;if(t instanceof HTMLImageElement&&(i=`${t.src.slice(0,200)}|${t.naturalWidth}x${t.naturalHeight}`),this.areUniformValuesEqual(this.uniformCache[o],i))return;this.uniformCache[o]=i;let n=this.uniformLocations[o];if(!n){console.warn(`Uniform location for ${o} not found`);return}if(t instanceof HTMLImageElement)this.setTextureUniform(o,t);else if(Array.isArray(t)){let s=null,r=null;if(t[0]!==void 0&&Array.isArray(t[0])){let l=t[0].length;if(t.every(m=>m.length===l))s=t.flat(),r=l;else{console.warn(`All child arrays must be the same length for ${o}`);return}}else s=t,r=s.length;switch(r){case 2:this.gl.uniform2fv(n,s);break;case 3:this.gl.uniform3fv(n,s);break;case 4:this.gl.uniform4fv(n,s);break;case 9:this.gl.uniformMatrix3fv(n,!1,s);break;case 16:this.gl.uniformMatrix4fv(n,!1,s);break;default:console.warn(`Unsupported uniform array length: ${r}`)}}else typeof t=="number"?this.gl.uniform1f(n,t):typeof t=="boolean"?this.gl.uniform1i(n,t?1:0):console.warn(`Unsupported uniform type for ${o}: ${typeof t}`)})});a(this,"getCurrentFrame",()=>this.currentFrame);a(this,"setFrame",e=>{this.currentFrame=e,this.lastRenderTime=performance.now(),this.render(performance.now())});a(this,"setSpeed",(e=1)=>{this.speed=e,this.setCurrentSpeed(document.hidden?0:e)});a(this,"setCurrentSpeed",e=>{this.currentSpeed=e,this.rafId===null&&e!==0&&(this.lastRenderTime=performance.now(),this.rafId=requestAnimationFrame(this.render)),this.rafId!==null&&e===0&&(cancelAnimationFrame(this.rafId),this.rafId=null)});a(this,"setMaxPixelCount",(e=z)=>{this.maxPixelCount=e,this.handleResize()});a(this,"setMinPixelRatio",(e=2)=>{this.minPixelRatio=e,this.handleResize()});a(this,"setUniforms",e=>{this.setUniformValues(e),this.providedUniforms={...this.providedUniforms,...e},this.render(performance.now())});a(this,"handleDocumentVisibilityChange",()=>{this.setCurrentSpeed(document.hidden?0:this.speed)});a(this,"dispose",()=>{this.hasBeenDisposed=!0,this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.gl&&this.program&&(this.textures.forEach(e=>{this.gl.deleteTexture(e)}),this.textures.clear(),this.gl.deleteProgram(this.program),this.program=null,this.gl.bindBuffer(this.gl.ARRAY_BUFFER,null),this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,null),this.gl.bindRenderbuffer(this.gl.RENDERBUFFER,null),this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,null),this.gl.getError()),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),visualViewport?.removeEventListener("resize",this.handleVisualViewportChange),document.removeEventListener("visibilitychange",this.handleDocumentVisibilityChange),this.uniformLocations={},this.canvasElement.remove(),delete this.parentElement.paperShaderMount});if(e instanceof HTMLElement)this.parentElement=e;else throw new Error("Paper Shaders: parent element must be an HTMLElement");if(!document.querySelector("style[data-paper-shader]")){let x=document.createElement("style");x.innerHTML=ee,x.setAttribute("data-paper-shader",""),document.head.prepend(x)}let u=document.createElement("canvas");this.canvasElement=u,this.parentElement.prepend(u),this.fragmentShader=o,this.providedUniforms=t,this.mipmaps=m,this.currentFrame=s,this.minPixelRatio=r,this.maxPixelCount=l;let d=u.getContext("webgl2",i);if(!d)throw new Error("Paper Shaders: WebGL is not supported in this browser");this.gl=d,this.initProgram(),this.setupPositionAttribute(),this.setupUniforms(),this.setUniformValues(this.providedUniforms),this.setupResizeObserver(),visualViewport?.addEventListener("resize",this.handleVisualViewportChange),this.setSpeed(n),this.parentElement.setAttribute("data-paper-shader",""),this.parentElement.paperShaderMount=this,document.addEventListener("visibilitychange",this.handleDocumentVisibilityChange)}};function O(e,o,t){let i=e.createShader(o);return i?(e.shaderSource(i,t),e.compileShader(i),e.getShaderParameter(i,e.COMPILE_STATUS)?i:(console.error("An error occurred compiling the shaders: "+e.getShaderInfoLog(i)),e.deleteShader(i),null)):null}function $(e,o,t){let i=e.getShaderPrecisionFormat(e.FRAGMENT_SHADER,e.MEDIUM_FLOAT),n=i?i.precision:null;n&&n<23&&(o=o.replace(/precision\s+(lowp|mediump)\s+float;/g,"precision highp float;"),t=t.replace(/precision\s+(lowp|mediump)\s+float/g,"precision highp float").replace(/\b(uniform|varying|attribute)\s+(lowp|mediump)\s+(\w+)/g,"$1 highp $3"));let s=O(e,e.VERTEX_SHADER,o),r=O(e,e.FRAGMENT_SHADER,t);if(!s||!r)return null;let l=e.createProgram();return l?(e.attachShader(l,s),e.attachShader(l,r),e.linkProgram(l),e.getProgramParameter(l,e.LINK_STATUS)?(e.detachShader(l,s),e.detachShader(l,r),e.deleteShader(s),e.deleteShader(r),l):(console.error("Unable to initialize the shader program: "+e.getProgramInfoLog(l)),e.deleteProgram(l),e.deleteShader(s),e.deleteShader(r),null)):null}var ee=`@layer paper-shaders {
|
|
151
|
+
}`,z=8294400,X=class{constructor(e,o,t,r,l=0,s=0,n=2,i=z,u=[]){a(this,"parentElement");a(this,"canvasElement");a(this,"gl");a(this,"program",null);a(this,"uniformLocations",{});a(this,"fragmentShader");a(this,"rafId",null);a(this,"lastRenderTime",0);a(this,"currentFrame",0);a(this,"speed",0);a(this,"currentSpeed",0);a(this,"providedUniforms");a(this,"mipmaps",[]);a(this,"hasBeenDisposed",!1);a(this,"resolutionChanged",!0);a(this,"textures",new Map);a(this,"minPixelRatio");a(this,"maxPixelCount");a(this,"isSafari",oe());a(this,"uniformCache",{});a(this,"textureUnitMap",new Map);a(this,"initProgram",()=>{let e=$(this.gl,Z,this.fragmentShader);e&&(this.program=e)});a(this,"setupPositionAttribute",()=>{let e=this.gl.getAttribLocation(this.program,"a_position"),o=this.gl.createBuffer();this.gl.bindBuffer(this.gl.ARRAY_BUFFER,o);let t=[-1,-1,1,-1,-1,1,-1,1,1,-1,1,1];this.gl.bufferData(this.gl.ARRAY_BUFFER,new Float32Array(t),this.gl.STATIC_DRAW),this.gl.enableVertexAttribArray(e),this.gl.vertexAttribPointer(e,2,this.gl.FLOAT,!1,0,0)});a(this,"setupUniforms",()=>{let e={u_time:this.gl.getUniformLocation(this.program,"u_time"),u_pixelRatio:this.gl.getUniformLocation(this.program,"u_pixelRatio"),u_resolution:this.gl.getUniformLocation(this.program,"u_resolution")};Object.entries(this.providedUniforms).forEach(([o,t])=>{if(e[o]=this.gl.getUniformLocation(this.program,o),t instanceof HTMLImageElement){let r=`${o}AspectRatio`;e[r]=this.gl.getUniformLocation(this.program,r)}}),this.uniformLocations=e});a(this,"renderScale",1);a(this,"parentWidth",0);a(this,"parentHeight",0);a(this,"parentDevicePixelWidth",0);a(this,"parentDevicePixelHeight",0);a(this,"devicePixelsSupported",!1);a(this,"resizeObserver",null);a(this,"setupResizeObserver",()=>{this.resizeObserver=new ResizeObserver(([e])=>{if(e?.borderBoxSize[0]){let o=e.devicePixelContentBoxSize?.[0];o!==void 0&&(this.devicePixelsSupported=!0,this.parentDevicePixelWidth=o.inlineSize,this.parentDevicePixelHeight=o.blockSize),this.parentWidth=e.borderBoxSize[0].inlineSize,this.parentHeight=e.borderBoxSize[0].blockSize}this.handleResize()}),this.resizeObserver.observe(this.parentElement)});a(this,"handleVisualViewportChange",()=>{this.resizeObserver?.disconnect(),this.setupResizeObserver()});a(this,"handleResize",()=>{let e=0,o=0,t=Math.max(1,window.devicePixelRatio),r=visualViewport?.scale??1;if(this.devicePixelsSupported){let f=Math.max(1,this.minPixelRatio/t);e=this.parentDevicePixelWidth*f*r,o=this.parentDevicePixelHeight*f*r}else{let f=Math.max(t,this.minPixelRatio)*r;if(this.isSafari){let d=te();f*=Math.max(1,d)}e=Math.round(this.parentWidth)*f,o=Math.round(this.parentHeight)*f}let l=Math.sqrt(this.maxPixelCount)/Math.sqrt(e*o),s=Math.min(1,l),n=Math.round(e*s),i=Math.round(o*s),u=n/Math.round(this.parentWidth);(this.canvasElement.width!==n||this.canvasElement.height!==i||this.renderScale!==u)&&(this.renderScale=u,this.canvasElement.width=n,this.canvasElement.height=i,this.resolutionChanged=!0,this.gl.viewport(0,0,this.gl.canvas.width,this.gl.canvas.height),this.render(performance.now()))});a(this,"render",e=>{if(this.hasBeenDisposed)return;if(this.program===null){console.warn("Tried to render before program or gl was initialized");return}let o=e-this.lastRenderTime;this.lastRenderTime=e,this.currentSpeed!==0&&(this.currentFrame+=o*this.currentSpeed),this.gl.clear(this.gl.COLOR_BUFFER_BIT),this.gl.useProgram(this.program),this.gl.uniform1f(this.uniformLocations.u_time,this.currentFrame*.001),this.resolutionChanged&&(this.gl.uniform2f(this.uniformLocations.u_resolution,this.gl.canvas.width,this.gl.canvas.height),this.gl.uniform1f(this.uniformLocations.u_pixelRatio,this.renderScale),this.resolutionChanged=!1),this.gl.drawArrays(this.gl.TRIANGLES,0,6),this.currentSpeed!==0?this.requestRender():this.rafId=null});a(this,"requestRender",()=>{this.rafId!==null&&cancelAnimationFrame(this.rafId),this.rafId=requestAnimationFrame(this.render)});a(this,"setTextureUniform",(e,o)=>{if(!o.complete||o.naturalWidth===0)throw new Error(`Paper Shaders: image for uniform ${e} must be fully loaded`);let t=this.textures.get(e);t&&this.gl.deleteTexture(t),this.textureUnitMap.has(e)||this.textureUnitMap.set(e,this.textureUnitMap.size);let r=this.textureUnitMap.get(e);this.gl.activeTexture(this.gl.TEXTURE0+r);let l=this.gl.createTexture();this.gl.bindTexture(this.gl.TEXTURE_2D,l),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGBA,this.gl.RGBA,this.gl.UNSIGNED_BYTE,o),this.mipmaps.includes(e)&&(this.gl.generateMipmap(this.gl.TEXTURE_2D),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR_MIPMAP_LINEAR));let s=this.gl.getError();if(s!==this.gl.NO_ERROR||l===null){console.error("Paper Shaders: WebGL error when uploading texture:",s);return}this.textures.set(e,l);let n=this.uniformLocations[e];if(n){this.gl.uniform1i(n,r);let i=`${e}AspectRatio`,u=this.uniformLocations[i];if(u){let f=o.naturalWidth/o.naturalHeight;this.gl.uniform1f(u,f)}}});a(this,"areUniformValuesEqual",(e,o)=>e===o?!0:Array.isArray(e)&&Array.isArray(o)&&e.length===o.length?e.every((t,r)=>this.areUniformValuesEqual(t,o[r])):!1);a(this,"setUniformValues",e=>{this.gl.useProgram(this.program),Object.entries(e).forEach(([o,t])=>{let r=t;if(t instanceof HTMLImageElement&&(r=`${t.src.slice(0,200)}|${t.naturalWidth}x${t.naturalHeight}`),this.areUniformValuesEqual(this.uniformCache[o],r))return;this.uniformCache[o]=r;let l=this.uniformLocations[o];if(!l){console.warn(`Uniform location for ${o} not found`);return}if(t instanceof HTMLImageElement)this.setTextureUniform(o,t);else if(Array.isArray(t)){let s=null,n=null;if(t[0]!==void 0&&Array.isArray(t[0])){let i=t[0].length;if(t.every(u=>u.length===i))s=t.flat(),n=i;else{console.warn(`All child arrays must be the same length for ${o}`);return}}else s=t,n=s.length;switch(n){case 2:this.gl.uniform2fv(l,s);break;case 3:this.gl.uniform3fv(l,s);break;case 4:this.gl.uniform4fv(l,s);break;case 9:this.gl.uniformMatrix3fv(l,!1,s);break;case 16:this.gl.uniformMatrix4fv(l,!1,s);break;default:console.warn(`Unsupported uniform array length: ${n}`)}}else typeof t=="number"?this.gl.uniform1f(l,t):typeof t=="boolean"?this.gl.uniform1i(l,t?1:0):console.warn(`Unsupported uniform type for ${o}: ${typeof t}`)})});a(this,"getCurrentFrame",()=>this.currentFrame);a(this,"setFrame",e=>{this.currentFrame=e,this.lastRenderTime=performance.now(),this.render(performance.now())});a(this,"setSpeed",(e=1)=>{this.speed=e,this.setCurrentSpeed(document.hidden?0:e)});a(this,"setCurrentSpeed",e=>{this.currentSpeed=e,this.rafId===null&&e!==0&&(this.lastRenderTime=performance.now(),this.rafId=requestAnimationFrame(this.render)),this.rafId!==null&&e===0&&(cancelAnimationFrame(this.rafId),this.rafId=null)});a(this,"setMaxPixelCount",(e=z)=>{this.maxPixelCount=e,this.handleResize()});a(this,"setMinPixelRatio",(e=2)=>{this.minPixelRatio=e,this.handleResize()});a(this,"setUniforms",e=>{this.setUniformValues(e),this.providedUniforms={...this.providedUniforms,...e},this.render(performance.now())});a(this,"handleDocumentVisibilityChange",()=>{this.setCurrentSpeed(document.hidden?0:this.speed)});a(this,"dispose",()=>{this.hasBeenDisposed=!0,this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.gl&&this.program&&(this.textures.forEach(e=>{this.gl.deleteTexture(e)}),this.textures.clear(),this.gl.deleteProgram(this.program),this.program=null,this.gl.bindBuffer(this.gl.ARRAY_BUFFER,null),this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,null),this.gl.bindRenderbuffer(this.gl.RENDERBUFFER,null),this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,null),this.gl.getError()),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),visualViewport?.removeEventListener("resize",this.handleVisualViewportChange),document.removeEventListener("visibilitychange",this.handleDocumentVisibilityChange),this.uniformLocations={},this.canvasElement.remove(),delete this.parentElement.paperShaderMount});if(e instanceof HTMLElement)this.parentElement=e;else throw new Error("Paper Shaders: parent element must be an HTMLElement");if(!document.querySelector("style[data-paper-shader]")){let x=document.createElement("style");x.innerHTML=ee,x.setAttribute("data-paper-shader",""),document.head.prepend(x)}let f=document.createElement("canvas");this.canvasElement=f,this.parentElement.prepend(f),this.fragmentShader=o,this.providedUniforms=t,this.mipmaps=u,this.currentFrame=s,this.minPixelRatio=n,this.maxPixelCount=i;let d=f.getContext("webgl2",r);if(!d)throw new Error("Paper Shaders: WebGL is not supported in this browser");this.gl=d,this.initProgram(),this.setupPositionAttribute(),this.setupUniforms(),this.setUniformValues(this.providedUniforms),this.setupResizeObserver(),visualViewport?.addEventListener("resize",this.handleVisualViewportChange),this.setSpeed(l),this.parentElement.setAttribute("data-paper-shader",""),this.parentElement.paperShaderMount=this,document.addEventListener("visibilitychange",this.handleDocumentVisibilityChange)}};function O(e,o,t){let r=e.createShader(o);return r?(e.shaderSource(r,t),e.compileShader(r),e.getShaderParameter(r,e.COMPILE_STATUS)?r:(console.error("An error occurred compiling the shaders: "+e.getShaderInfoLog(r)),e.deleteShader(r),null)):null}function $(e,o,t){let r=e.getShaderPrecisionFormat(e.FRAGMENT_SHADER,e.MEDIUM_FLOAT),l=r?r.precision:null;l&&l<23&&(o=o.replace(/precision\s+(lowp|mediump)\s+float;/g,"precision highp float;"),t=t.replace(/precision\s+(lowp|mediump)\s+float/g,"precision highp float").replace(/\b(uniform|varying|attribute)\s+(lowp|mediump)\s+(\w+)/g,"$1 highp $3"));let s=O(e,e.VERTEX_SHADER,o),n=O(e,e.FRAGMENT_SHADER,t);if(!s||!n)return null;let i=e.createProgram();return i?(e.attachShader(i,s),e.attachShader(i,n),e.linkProgram(i),e.getProgramParameter(i,e.LINK_STATUS)?(e.detachShader(i,s),e.detachShader(i,n),e.deleteShader(s),e.deleteShader(n),i):(console.error("Unable to initialize the shader program: "+e.getProgramInfoLog(i)),e.deleteProgram(i),e.deleteShader(s),e.deleteShader(n),null)):null}var ee=`@layer paper-shaders {
|
|
152
152
|
:where([data-paper-shader]) {
|
|
153
153
|
isolation: isolate;
|
|
154
154
|
position: relative;
|
|
@@ -165,10 +165,10 @@ void main() {
|
|
|
165
165
|
corner-shape: inherit;
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
|
-
}`;function oe(){let e=navigator.userAgent.toLowerCase();return e.includes("safari")&&!e.includes("chrome")&&!e.includes("android")}function te(){let e=visualViewport?.scale??1,o=visualViewport?.width??window.innerWidth,t=window.innerWidth-document.documentElement.clientWidth,
|
|
168
|
+
}`;function oe(){let e=navigator.userAgent.toLowerCase();return e.includes("safari")&&!e.includes("chrome")&&!e.includes("android")}function te(){let e=visualViewport?.scale??1,o=visualViewport?.width??window.innerWidth,t=window.innerWidth-document.documentElement.clientWidth,r=e*o+t,l=outerWidth/r,s=Math.round(100*l);return s%5===0?s/100:s===33?1/3:s===67?2/3:s===133?4/3:l}var c=`
|
|
169
169
|
#define TWO_PI 6.28318530718
|
|
170
170
|
#define PI 3.14159265358979323846
|
|
171
|
-
`,
|
|
171
|
+
`,m=`
|
|
172
172
|
vec2 rotate(vec2 uv, float th) {
|
|
173
173
|
return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv;
|
|
174
174
|
}
|
|
@@ -279,7 +279,7 @@ in vec2 v_objectUV;
|
|
|
279
279
|
out vec4 fragColor;
|
|
280
280
|
|
|
281
281
|
${c}
|
|
282
|
-
${
|
|
282
|
+
${m}
|
|
283
283
|
${g}
|
|
284
284
|
|
|
285
285
|
float valueNoise(vec2 st) {
|
|
@@ -505,7 +505,7 @@ in vec2 v_patternUV;
|
|
|
505
505
|
|
|
506
506
|
out vec4 fragColor;
|
|
507
507
|
|
|
508
|
-
${
|
|
508
|
+
${m}
|
|
509
509
|
|
|
510
510
|
float neuroShape(vec2 uv, float t) {
|
|
511
511
|
vec2 sine_acc = vec2(0.);
|
|
@@ -575,7 +575,7 @@ in vec2 v_patternUV;
|
|
|
575
575
|
out vec4 fragColor;
|
|
576
576
|
|
|
577
577
|
${c}
|
|
578
|
-
${
|
|
578
|
+
${m}
|
|
579
579
|
${_}
|
|
580
580
|
${E}
|
|
581
581
|
|
|
@@ -1293,7 +1293,7 @@ in vec2 v_patternUV;
|
|
|
1293
1293
|
out vec4 fragColor;
|
|
1294
1294
|
|
|
1295
1295
|
${c}
|
|
1296
|
-
${
|
|
1296
|
+
${m}
|
|
1297
1297
|
float randomG(vec2 p) {
|
|
1298
1298
|
vec2 uv = floor(p) / 100. + .5;
|
|
1299
1299
|
return texture(u_noiseTexture, fract(uv)).g;
|
|
@@ -1404,7 +1404,7 @@ in vec2 v_objectUV;
|
|
|
1404
1404
|
out vec4 fragColor;
|
|
1405
1405
|
|
|
1406
1406
|
${c}
|
|
1407
|
-
${
|
|
1407
|
+
${m}
|
|
1408
1408
|
${_}
|
|
1409
1409
|
float valueNoise(vec2 st) {
|
|
1410
1410
|
vec2 i = floor(st);
|
|
@@ -1588,7 +1588,7 @@ out vec4 fragColor;
|
|
|
1588
1588
|
|
|
1589
1589
|
${c}
|
|
1590
1590
|
${h}
|
|
1591
|
-
${
|
|
1591
|
+
${m}
|
|
1592
1592
|
|
|
1593
1593
|
void main() {
|
|
1594
1594
|
vec2 shape_uv = v_objectUV;
|
|
@@ -1930,7 +1930,7 @@ out vec4 fragColor;
|
|
|
1930
1930
|
|
|
1931
1931
|
${c}
|
|
1932
1932
|
${h}
|
|
1933
|
-
${
|
|
1933
|
+
${m}
|
|
1934
1934
|
${_}
|
|
1935
1935
|
|
|
1936
1936
|
float valueNoiseR(vec2 st) {
|
|
@@ -2607,7 +2607,7 @@ in vec2 v_objectUV;
|
|
|
2607
2607
|
out vec4 fragColor;
|
|
2608
2608
|
|
|
2609
2609
|
${c}
|
|
2610
|
-
${
|
|
2610
|
+
${m}
|
|
2611
2611
|
${g}
|
|
2612
2612
|
|
|
2613
2613
|
float valueNoise(vec2 st) {
|
|
@@ -2721,7 +2721,7 @@ in vec2 v_objectUV;
|
|
|
2721
2721
|
out vec4 fragColor;
|
|
2722
2722
|
|
|
2723
2723
|
${c}
|
|
2724
|
-
${
|
|
2724
|
+
${m}
|
|
2725
2725
|
${g}
|
|
2726
2726
|
|
|
2727
2727
|
float valueNoise(vec2 st) {
|
|
@@ -2909,7 +2909,7 @@ float getUvFrame(vec2 uv) {
|
|
|
2909
2909
|
}
|
|
2910
2910
|
|
|
2911
2911
|
${c}
|
|
2912
|
-
${
|
|
2912
|
+
${m}
|
|
2913
2913
|
${_}
|
|
2914
2914
|
float valueNoise(vec2 st) {
|
|
2915
2915
|
vec2 i = floor(st);
|
|
@@ -3125,7 +3125,7 @@ in vec2 v_imageUV;
|
|
|
3125
3125
|
out vec4 fragColor;
|
|
3126
3126
|
|
|
3127
3127
|
${c}
|
|
3128
|
-
${
|
|
3128
|
+
${m}
|
|
3129
3129
|
${h}
|
|
3130
3130
|
|
|
3131
3131
|
float getUvFrame(vec2 uv) {
|
|
@@ -3247,7 +3247,7 @@ in vec2 v_imageUV;
|
|
|
3247
3247
|
out vec4 fragColor;
|
|
3248
3248
|
|
|
3249
3249
|
${c}
|
|
3250
|
-
${
|
|
3250
|
+
${m}
|
|
3251
3251
|
${g}
|
|
3252
3252
|
|
|
3253
3253
|
float valueNoise(vec2 st) {
|
|
@@ -4003,7 +4003,7 @@ in vec2 v_imageUV;
|
|
|
4003
4003
|
out vec4 fragColor;
|
|
4004
4004
|
|
|
4005
4005
|
${c}
|
|
4006
|
-
${
|
|
4006
|
+
${m}
|
|
4007
4007
|
${h}
|
|
4008
4008
|
|
|
4009
4009
|
float getColorChanges(float c1, float c2, float stripe_p, vec3 w, float blur, float bump, float tint) {
|
|
@@ -4322,7 +4322,7 @@ in vec2 v_imageUV;
|
|
|
4322
4322
|
out vec4 fragColor;
|
|
4323
4323
|
|
|
4324
4324
|
${c}
|
|
4325
|
-
${
|
|
4325
|
+
${m}
|
|
4326
4326
|
${g}
|
|
4327
4327
|
|
|
4328
4328
|
float valueNoise(vec2 st) {
|
|
@@ -4864,4 +4864,4 @@ void main() {
|
|
|
4864
4864
|
|
|
4865
4865
|
fragColor = vec4(color, opacity);
|
|
4866
4866
|
}
|
|
4867
|
-
`;function A(e){if(Array.isArray(e))return e.length===4?e:e.length===3?[...e,1]:M;if(typeof e!="string")return M;let o,t,
|
|
4867
|
+
`;function A(e){if(Array.isArray(e))return e.length===4?e:e.length===3?[...e,1]:M;if(typeof e!="string")return M;let o,t,r,l=1;if(e.startsWith("#"))[o,t,r,l]=ie(e);else if(e.startsWith("rgb"))[o,t,r,l]=re(e);else if(e.startsWith("hsl"))[o,t,r,l]=le(se(e));else return console.error("Unsupported color format",e),M;return[C(o,0,1),C(t,0,1),C(r,0,1),C(l,0,1)]}function ie(e){e=e.replace(/^#/,""),e.length===3&&(e=e.split("").map(s=>s+s).join("")),e.length===6&&(e=e+"ff");let o=parseInt(e.slice(0,2),16)/255,t=parseInt(e.slice(2,4),16)/255,r=parseInt(e.slice(4,6),16)/255,l=parseInt(e.slice(6,8),16)/255;return[o,t,r,l]}function re(e){let o=e.match(/^rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([0-9.]+))?\s*\)$/i);return o?[parseInt(o[1]??"0")/255,parseInt(o[2]??"0")/255,parseInt(o[3]??"0")/255,o[4]===void 0?1:parseFloat(o[4])]:[0,0,0,1]}function se(e){let o=e.match(/^hsla?\s*\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*(?:,\s*([0-9.]+))?\s*\)$/i);return o?[parseInt(o[1]??"0"),parseInt(o[2]??"0"),parseInt(o[3]??"0"),o[4]===void 0?1:parseFloat(o[4])]:[0,0,0,1]}function le(e){let[o,t,r,l]=e,s=o/360,n=t/100,i=r/100,u,f,d;if(t===0)u=f=d=i;else{let x=(y,V,v)=>(v<0&&(v+=1),v>1&&(v-=1),v<.16666666666666666?y+(V-y)*6*v:v<.5?V:v<.6666666666666666?y+(V-y)*(.6666666666666666-v)*6:y),b=i<.5?i*(1+n):i+n-i*n,U=2*i-b;u=x(U,b,s+1/3),f=x(U,b,s),d=x(U,b,s-1/3)}return[u,f,d,l]}var C=(e,o,t)=>Math.min(Math.max(e,o),t),M=[0,0,0,1];function Qe(e,o){let t=B[o.shape]??B.warp,r=S[o.type]??S["2x2"],l={u_colorFront:A(o.colorFront),u_colorBack:A(o.colorBack),u_shape:t,u_type:r,u_pxSize:o.size,u_scale:o.scale,u_rotation:parseFloat(o.rotation)||180,u_originX:.5,u_originY:.5,u_worldWidth:0,u_worldHeight:0,u_fit:0,u_offsetX:0,u_offsetY:0},s=new X(e,J,l,{alpha:!0,premultipliedAlpha:!1},o.speed,0,2),n={updateParams(i){let u={};i.colorFront!==void 0&&(u.u_colorFront=A(i.colorFront)),i.colorBack!==void 0&&(u.u_colorBack=A(i.colorBack)),i.shape!==void 0&&(u.u_shape=B[i.shape]??t),i.type!==void 0&&(u.u_type=S[i.type]??r),i.size!==void 0&&(u.u_pxSize=i.size),i.scale!==void 0&&(u.u_scale=i.scale),i.rotation!==void 0&&(u.u_rotation=parseFloat(i.rotation)||180),i.speed!==void 0&&s.setSpeed(i.speed),Object.keys(u).length>0&&s.setUniforms(u)},destroy(){s.dispose(),window.__BD_SHADER__===n&&(window.__BD_SHADER__=null)}};return window.__BD_SHADER__=n,n}export{Qe as createDitheringRenderer};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BlueDither Sidecar — Lightweight dev server for vanilla projects.
|
|
3
|
+
*
|
|
4
|
+
* Serves static files from the current directory and handles
|
|
5
|
+
* POST /__bluedither/commit to save tokens.json to disk.
|
|
6
|
+
*
|
|
7
|
+
* Usage: node bluedither/sidecar.js [port]
|
|
8
|
+
* Default port: 3456
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createServer } from 'http';
|
|
12
|
+
import { readFileSync, writeFileSync, existsSync, statSync } from 'fs';
|
|
13
|
+
import { resolve, join, extname } from 'path';
|
|
14
|
+
|
|
15
|
+
const PORT = parseInt(process.argv[2] || '3456', 10);
|
|
16
|
+
const ROOT = process.cwd();
|
|
17
|
+
|
|
18
|
+
const MIME = {
|
|
19
|
+
'.html': 'text/html',
|
|
20
|
+
'.css': 'text/css',
|
|
21
|
+
'.js': 'application/javascript',
|
|
22
|
+
'.mjs': 'application/javascript',
|
|
23
|
+
'.json': 'application/json',
|
|
24
|
+
'.png': 'image/png',
|
|
25
|
+
'.jpg': 'image/jpeg',
|
|
26
|
+
'.jpeg': 'image/jpeg',
|
|
27
|
+
'.gif': 'image/gif',
|
|
28
|
+
'.svg': 'image/svg+xml',
|
|
29
|
+
'.ico': 'image/x-icon',
|
|
30
|
+
'.woff': 'font/woff',
|
|
31
|
+
'.woff2': 'font/woff2',
|
|
32
|
+
'.ttf': 'font/ttf',
|
|
33
|
+
'.otf': 'font/otf',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function findTokensPath() {
|
|
37
|
+
const candidates = [
|
|
38
|
+
resolve(ROOT, 'bluedither', 'tokens.json'),
|
|
39
|
+
resolve(ROOT, 'tokens.json'),
|
|
40
|
+
];
|
|
41
|
+
for (const p of candidates) {
|
|
42
|
+
if (existsSync(p)) return p;
|
|
43
|
+
}
|
|
44
|
+
return candidates[0]; // default to bluedither/tokens.json
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const server = createServer((req, res) => {
|
|
48
|
+
// Handle commit endpoint
|
|
49
|
+
if (req.method === 'POST' && req.url === '/__bluedither/commit') {
|
|
50
|
+
let body = '';
|
|
51
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
52
|
+
req.on('end', () => {
|
|
53
|
+
try {
|
|
54
|
+
JSON.parse(body); // validate JSON
|
|
55
|
+
const tokensPath = findTokensPath();
|
|
56
|
+
writeFileSync(tokensPath, body);
|
|
57
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
58
|
+
res.end(JSON.stringify({ ok: true, path: tokensPath }));
|
|
59
|
+
console.log(` Saved tokens to ${tokensPath}`);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
62
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Serve static files
|
|
69
|
+
let filePath = join(ROOT, decodeURIComponent(req.url.split('?')[0]));
|
|
70
|
+
if (filePath.endsWith('/') || filePath === ROOT) {
|
|
71
|
+
filePath = join(filePath, 'index.html');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!existsSync(filePath) || !statSync(filePath).isFile()) {
|
|
75
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
76
|
+
res.end('Not Found');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const ext = extname(filePath).toLowerCase();
|
|
81
|
+
const mime = MIME[ext] || 'application/octet-stream';
|
|
82
|
+
const content = readFileSync(filePath);
|
|
83
|
+
res.writeHead(200, { 'Content-Type': mime });
|
|
84
|
+
res.end(content);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
server.listen(PORT, () => {
|
|
88
|
+
console.log(`\n BlueDither dev server running at http://localhost:${PORT}/\n`);
|
|
89
|
+
console.log(` Commit endpoint: POST /__bluedither/commit`);
|
|
90
|
+
console.log(` Serving files from: ${ROOT}\n`);
|
|
91
|
+
});
|
package/package.json
CHANGED
|
@@ -2,124 +2,83 @@
|
|
|
2
2
|
|
|
3
3
|
When the target project is **plain HTML/CSS/JS** (no framework detected), follow these patterns.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
Generate a single `index.html` file containing:
|
|
8
|
-
1. Inline `<style>` with CSS custom properties from tokens + all `.bd-*` class rules
|
|
9
|
-
2. Semantic HTML matching `structure.json` exactly
|
|
10
|
-
3. `<script type="module">` at the end of `<body>` that:
|
|
11
|
-
- Imports `bluedither-shader.js` (or the bundled version)
|
|
12
|
-
- Initializes `ShaderMount` on `#bd-shader-parent`
|
|
5
|
+
## Dev Server
|
|
13
6
|
|
|
14
|
-
|
|
7
|
+
Vanilla projects use the BlueDither sidecar as their dev server. It serves static files AND handles the "Commit Changes" endpoint.
|
|
15
8
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
clamp(max*0.55 rem, px/designWidth*100 vw, px/16 rem)
|
|
9
|
+
```bash
|
|
10
|
+
node bluedither/sidecar.js
|
|
19
11
|
```
|
|
20
12
|
|
|
21
|
-
|
|
13
|
+
This starts a server at `http://localhost:3456/`. The sidecar handles `POST /__bluedither/commit` to save tokens.json to disk.
|
|
22
14
|
|
|
23
|
-
##
|
|
15
|
+
## Output Structure
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
URL-encode the font family name.
|
|
17
|
+
Generate a single `index.html` file containing:
|
|
18
|
+
1. Inline `<style>` with CSS custom properties from tokens + all `.bd-*` class rules
|
|
19
|
+
2. Semantic HTML matching `structure.json` exactly
|
|
20
|
+
3. `<script type="module">` at the end of `<body>` that imports and initializes the shader
|
|
30
21
|
|
|
31
22
|
## CRITICAL: File Placement Rules
|
|
32
23
|
|
|
33
|
-
**In project root
|
|
24
|
+
**In project root:**
|
|
34
25
|
- `index.html` — the rendered page
|
|
35
|
-
- `
|
|
26
|
+
- `bluedither-shader.js` — copied from `bluedither/shaders/bluedither-shader.js`
|
|
27
|
+
- `paper-shaders-bundle.js` — copied from `bluedither/shaders/paper-shaders-bundle.js`
|
|
36
28
|
|
|
37
|
-
**In `
|
|
29
|
+
**In `bluedither/` (already placed by install):**
|
|
38
30
|
- `bluedither-tuner.js` — tuner panel
|
|
39
31
|
- `bluedither-tuner.css` — tuner styles
|
|
40
32
|
- `bluedither-tuner-inject.js` — tuner loader
|
|
41
33
|
- `tokens.json` — design tokens (read at runtime by tuner)
|
|
42
34
|
- `tokens.defaults.json` — default tokens
|
|
43
|
-
- `
|
|
35
|
+
- `sidecar.js` — dev server
|
|
44
36
|
|
|
45
|
-
Copy
|
|
37
|
+
Copy shader files to project root:
|
|
46
38
|
```bash
|
|
47
|
-
|
|
48
|
-
cp bluedither/
|
|
49
|
-
cp bluedither/bluedither-tuner.css public/bluedither/
|
|
50
|
-
cp bluedither/bluedither-tuner-inject.js public/bluedither/
|
|
51
|
-
cp bluedither/tokens.json public/bluedither/
|
|
52
|
-
cp bluedither/tokens.defaults.json public/bluedither/
|
|
53
|
-
cp bluedither/dev-middleware.js public/bluedither/
|
|
39
|
+
cp bluedither/shaders/bluedither-shader.js ./bluedither-shader.js
|
|
40
|
+
cp bluedither/shaders/paper-shaders-bundle.js ./paper-shaders-bundle.js
|
|
54
41
|
```
|
|
55
42
|
|
|
56
43
|
## Shader Module
|
|
57
44
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
Import and initialize in a `<script type="module">` at the end of `<body>`. **CRITICAL:** You MUST create a `window.__BD_SHADER__` wrapper with an `updateParams()` method — the tuner calls `updateParams()` but ShaderMount only has `setUniforms()` and `setSpeed()`.
|
|
45
|
+
Import `createDitheringRenderer` from `bluedither-shader.js`. It auto-assigns `window.__BD_SHADER__` so the tuner can update shader params live.
|
|
61
46
|
|
|
62
47
|
```js
|
|
63
|
-
import {
|
|
48
|
+
import { createDitheringRenderer } from './bluedither-shader.js';
|
|
64
49
|
|
|
65
50
|
const parent = document.getElementById('bd-shader-parent');
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
u_fit: 0, u_offsetX: 0, u_offsetY: 0,
|
|
77
|
-
}, { alpha: true, premultipliedAlpha: false }, 0.41, 0, 2);
|
|
78
|
-
|
|
79
|
-
// CRITICAL: wrapper with updateParams for the tuner
|
|
80
|
-
window.__BD_SHADER__ = {
|
|
81
|
-
updateParams(params) {
|
|
82
|
-
const uniforms = {};
|
|
83
|
-
if (params.colorFront !== undefined) uniforms.u_colorFront = getShaderColorFromString(params.colorFront);
|
|
84
|
-
if (params.colorBack !== undefined) uniforms.u_colorBack = getShaderColorFromString(params.colorBack);
|
|
85
|
-
if (params.shape !== undefined) uniforms.u_shape = DitheringShapes[params.shape] ?? DitheringShapes.warp;
|
|
86
|
-
if (params.type !== undefined) uniforms.u_type = DitheringTypes[params.type] ?? DitheringTypes['2x2'];
|
|
87
|
-
if (params.size !== undefined) uniforms.u_pxSize = params.size;
|
|
88
|
-
if (params.scale !== undefined) uniforms.u_scale = params.scale;
|
|
89
|
-
if (params.rotation !== undefined) uniforms.u_rotation = parseFloat(params.rotation) || 180;
|
|
90
|
-
if (Object.keys(uniforms).length > 0) mount.setUniforms(uniforms);
|
|
91
|
-
if (params.speed !== undefined) mount.setSpeed(params.speed);
|
|
92
|
-
},
|
|
93
|
-
};
|
|
51
|
+
createDitheringRenderer(parent, {
|
|
52
|
+
colorFront: '#005A6A',
|
|
53
|
+
colorBack: '#00000000',
|
|
54
|
+
shape: 'ripple',
|
|
55
|
+
type: '2x2',
|
|
56
|
+
size: 4.2,
|
|
57
|
+
scale: 1,
|
|
58
|
+
speed: 0.41,
|
|
59
|
+
rotation: 180,
|
|
60
|
+
});
|
|
94
61
|
```
|
|
95
62
|
|
|
96
|
-
|
|
63
|
+
Replace the param values with the actual values from `tokens.json` shader + colors sections.
|
|
97
64
|
|
|
98
|
-
|
|
99
|
-
```html
|
|
100
|
-
<script type="module">
|
|
101
|
-
if (import.meta.env.DEV) {
|
|
102
|
-
const s = document.createElement('script');
|
|
103
|
-
s.src = '/bluedither/bluedither-tuner-inject.js';
|
|
104
|
-
document.body.appendChild(s);
|
|
105
|
-
}
|
|
106
|
-
</script>
|
|
107
|
-
```
|
|
65
|
+
## Tuner Loading
|
|
108
66
|
|
|
109
|
-
|
|
67
|
+
Load the tuner inject script. For vanilla projects served by the sidecar, use localhost detection:
|
|
110
68
|
```html
|
|
111
|
-
<script
|
|
112
|
-
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
|
|
113
|
-
const s = document.createElement('script');
|
|
114
|
-
s.src = '/bluedither/bluedither-tuner-inject.js';
|
|
115
|
-
document.body.appendChild(s);
|
|
116
|
-
}
|
|
117
|
-
</script>
|
|
69
|
+
<script src="./bluedither/bluedither-tuner-inject.js"></script>
|
|
118
70
|
```
|
|
119
71
|
|
|
72
|
+
This loads the tuner on every page load. The tuner applies saved tokens from `tokens.json` and shows the overlay panel.
|
|
73
|
+
|
|
120
74
|
## CSS Custom Properties
|
|
121
75
|
|
|
122
|
-
|
|
76
|
+
Compute `clamp()` values from all `referencePx` and spacing tokens using:
|
|
77
|
+
```
|
|
78
|
+
clamp(max*0.55 rem, px/designWidth*100 vw, px/16 rem)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The CSS variable names MUST match exactly what the tuner expects. **DO NOT use long names like `--bd-color-background` — use the short names below:**
|
|
123
82
|
|
|
124
83
|
**Colors:** `--bd-bg`, `--bd-primary`, `--bd-text`, `--bd-cta-bg`, `--bd-cta-text`
|
|
125
84
|
**Typography:** `--bd-font-primary`, `--bd-font-secondary`, `--bd-font-primary-weight`, `--bd-font-secondary-weight`, `--bd-headline-size`, `--bd-headline-lh`, `--bd-sub-size`, `--bd-sub-lh`, `--bd-sub-transform`, `--bd-logo-size`, `--bd-logo-lh`, `--bd-nav-size`, `--bd-nav-lh`, `--bd-cta-size`, `--bd-cta-lh`
|
|
@@ -128,13 +87,33 @@ The CSS variable names MUST match exactly what the tuner expects:
|
|
|
128
87
|
|
|
129
88
|
Using different variable names will break live updates from the tuner.
|
|
130
89
|
|
|
90
|
+
## CSS Rule References
|
|
91
|
+
|
|
92
|
+
In your CSS rules, reference the SHORT variable names:
|
|
93
|
+
```css
|
|
94
|
+
.bd-root { background-color: var(--bd-bg); }
|
|
95
|
+
.bd-header { background-color: var(--bd-primary); }
|
|
96
|
+
.bd-logo { color: var(--bd-text); }
|
|
97
|
+
.bd-cta { background-color: var(--bd-cta-bg); }
|
|
98
|
+
.bd-cta-text { color: var(--bd-cta-text); }
|
|
99
|
+
.bd-subheadline { font-size: var(--bd-sub-size); line-height: var(--bd-sub-lh); text-transform: var(--bd-sub-transform); }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Google Fonts
|
|
103
|
+
|
|
104
|
+
Include `<link>` tags for `typography.primaryFont` and `typography.secondaryFont` via Google Fonts CDN:
|
|
105
|
+
```html
|
|
106
|
+
<link href="https://fonts.googleapis.com/css2?family=FONT_NAME:wght@400;700&display=swap" rel="stylesheet">
|
|
107
|
+
```
|
|
108
|
+
URL-encode the font family name.
|
|
109
|
+
|
|
131
110
|
## File Output
|
|
132
111
|
|
|
133
112
|
| File | Contents |
|
|
134
113
|
|------|----------|
|
|
135
114
|
| `index.html` | Complete rendered page |
|
|
136
|
-
| `bluedither-shader.js` | Shader module (copied) |
|
|
137
|
-
| `paper-shaders-bundle.js` | Shader library bundle (copied) |
|
|
115
|
+
| `bluedither-shader.js` | Shader wrapper module (copied from bluedither/shaders/) |
|
|
116
|
+
| `paper-shaders-bundle.js` | Shader library bundle (copied from bluedither/shaders/) |
|
|
138
117
|
|
|
139
118
|
## Content Handling
|
|
140
119
|
|
|
@@ -52,7 +52,7 @@ export function createDitheringRenderer(parentEl, params) {
|
|
|
52
52
|
2, // minPixelRatio
|
|
53
53
|
);
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
const wrapper = {
|
|
56
56
|
updateParams(newParams) {
|
|
57
57
|
const updatedUniforms = {};
|
|
58
58
|
|
|
@@ -87,6 +87,12 @@ export function createDitheringRenderer(parentEl, params) {
|
|
|
87
87
|
},
|
|
88
88
|
destroy() {
|
|
89
89
|
mount.dispose();
|
|
90
|
+
if (window.__BD_SHADER__ === wrapper) window.__BD_SHADER__ = null;
|
|
90
91
|
}
|
|
91
92
|
};
|
|
93
|
+
|
|
94
|
+
// Auto-assign so the tuner can find it without extra wiring
|
|
95
|
+
window.__BD_SHADER__ = wrapper;
|
|
96
|
+
|
|
97
|
+
return wrapper;
|
|
92
98
|
}
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
<style>
|
|
12
12
|
:root {
|
|
13
13
|
/* Colors */
|
|
14
|
-
--bd-
|
|
15
|
-
--bd-
|
|
16
|
-
--bd-
|
|
17
|
-
--bd-
|
|
18
|
-
--bd-
|
|
14
|
+
--bd-bg: {{colors.background}};
|
|
15
|
+
--bd-primary: {{colors.primary}};
|
|
16
|
+
--bd-text: {{colors.text}};
|
|
17
|
+
--bd-cta-bg: {{colors.ctaBackground}};
|
|
18
|
+
--bd-cta-text: {{colors.ctaText}};
|
|
19
19
|
|
|
20
20
|
/* Typography */
|
|
21
21
|
--bd-font-primary: "{{typography.primaryFont}}", system-ui, sans-serif;
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
|
|
26
26
|
--bd-headline-size: {{clamp.headline.fontSize}};
|
|
27
27
|
--bd-headline-lh: {{clamp.headline.lineHeight}};
|
|
28
|
-
--bd-
|
|
29
|
-
--bd-
|
|
30
|
-
--bd-
|
|
28
|
+
--bd-sub-size: {{clamp.subHeadline.fontSize}};
|
|
29
|
+
--bd-sub-lh: {{clamp.subHeadline.lineHeight}};
|
|
30
|
+
--bd-sub-transform: {{typography.subHeadline.textTransform}};
|
|
31
31
|
--bd-logo-size: {{clamp.logo.fontSize}};
|
|
32
32
|
--bd-logo-lh: {{clamp.logo.lineHeight}};
|
|
33
33
|
--bd-nav-size: {{clamp.navItem.fontSize}};
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
flex-direction: column;
|
|
60
60
|
width: 100%;
|
|
61
61
|
min-height: 100vh;
|
|
62
|
-
background-color: var(--bd-
|
|
62
|
+
background-color: var(--bd-bg);
|
|
63
63
|
overflow: clip;
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
width: 100%;
|
|
74
74
|
padding-inline: var(--bd-header-px);
|
|
75
75
|
padding-block: var(--bd-header-py);
|
|
76
|
-
background-color: var(--bd-
|
|
76
|
+
background-color: var(--bd-primary);
|
|
77
77
|
overflow: clip;
|
|
78
78
|
flex-shrink: 0;
|
|
79
79
|
}
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
font-weight: var(--bd-font-primary-weight);
|
|
91
91
|
font-size: var(--bd-logo-size);
|
|
92
92
|
line-height: var(--bd-logo-lh);
|
|
93
|
-
color: var(--bd-
|
|
93
|
+
color: var(--bd-text);
|
|
94
94
|
white-space: pre;
|
|
95
95
|
flex-shrink: 0;
|
|
96
96
|
}
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
font-weight: var(--bd-font-primary-weight);
|
|
109
109
|
font-size: var(--bd-nav-size);
|
|
110
110
|
line-height: var(--bd-nav-lh);
|
|
111
|
-
color: var(--bd-
|
|
111
|
+
color: var(--bd-text);
|
|
112
112
|
white-space: pre;
|
|
113
113
|
text-decoration: none;
|
|
114
114
|
cursor: pointer;
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
.bd-cta {
|
|
118
118
|
display: flex;
|
|
119
119
|
align-items: start;
|
|
120
|
-
background-color: var(--bd-
|
|
120
|
+
background-color: var(--bd-cta-bg);
|
|
121
121
|
border-radius: var(--bd-cta-radius);
|
|
122
122
|
padding-inline: var(--bd-cta-px);
|
|
123
123
|
padding-block: var(--bd-cta-py);
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
font-weight: var(--bd-font-primary-weight);
|
|
133
133
|
font-size: var(--bd-cta-size);
|
|
134
134
|
line-height: var(--bd-cta-lh);
|
|
135
|
-
color: var(--bd-
|
|
135
|
+
color: var(--bd-cta-text);
|
|
136
136
|
white-space: pre;
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -188,7 +188,7 @@
|
|
|
188
188
|
font-weight: var(--bd-font-primary-weight);
|
|
189
189
|
font-size: var(--bd-headline-size);
|
|
190
190
|
line-height: var(--bd-headline-lh);
|
|
191
|
-
color: var(--bd-
|
|
191
|
+
color: var(--bd-text);
|
|
192
192
|
white-space: pre;
|
|
193
193
|
width: fit-content;
|
|
194
194
|
flex-shrink: 0;
|
|
@@ -197,10 +197,10 @@
|
|
|
197
197
|
.bd-subheadline {
|
|
198
198
|
font-family: var(--bd-font-secondary);
|
|
199
199
|
font-weight: var(--bd-font-secondary-weight);
|
|
200
|
-
font-size: var(--bd-
|
|
201
|
-
line-height: var(--bd-
|
|
202
|
-
text-transform: var(--bd-
|
|
203
|
-
color: var(--bd-
|
|
200
|
+
font-size: var(--bd-sub-size);
|
|
201
|
+
line-height: var(--bd-sub-lh);
|
|
202
|
+
text-transform: var(--bd-sub-transform);
|
|
203
|
+
color: var(--bd-text);
|
|
204
204
|
width: fit-content;
|
|
205
205
|
flex-shrink: 0;
|
|
206
206
|
}
|