bluedither 1.0.12 → 1.0.13

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.
@@ -103,6 +103,10 @@ export default async function install(args) {
103
103
  if (existsSync(injectPath)) {
104
104
  filesToCopy.push(['fine-tuner/inject.js', 'bluedither-tuner-inject.js']);
105
105
  }
106
+ const middlewarePath = resolve(BLUEDITHER_ROOT, 'fine-tuner', 'dev-middleware.js');
107
+ if (existsSync(middlewarePath)) {
108
+ filesToCopy.push(['fine-tuner/dev-middleware.js', 'dev-middleware.js']);
109
+ }
106
110
 
107
111
  let copied = 0;
108
112
  for (const [src, dest] of filesToCopy) {
@@ -203,6 +207,7 @@ async function installFromRegistry(slug, targetDir) {
203
207
  ['dist/bluedither-tuner.js', 'bluedither-tuner.js'],
204
208
  ['fine-tuner/tuner.css', 'bluedither-tuner.css'],
205
209
  ['fine-tuner/inject.js', 'bluedither-tuner-inject.js'],
210
+ ['fine-tuner/dev-middleware.js', 'dev-middleware.js'],
206
211
  ];
207
212
  for (const [src, dest] of tunerFiles) {
208
213
  const srcPath = resolve(BLUEDITHER_ROOT, src);
@@ -1,4 +1,4 @@
1
- (()=>{var u=structuredClone(window.__BD_TOKENS__||JSON.parse(document.querySelector('script[type="application/json"][data-bluedither-tokens]')?.textContent||"{}")),O=structuredClone(window.__BD_DEFAULTS__||JSON.parse(document.querySelector('script[type="application/json"][data-bluedither-defaults]')?.textContent||"{}")),N=!!(window.__BD_TOKENS__&&window.__BD_DEFAULTS__)||!!window.__BD_TUNER_SERVER_MODE__,P=["Arial","Arial Black","Bebas Neue Pro","Consolas","Courier New","Georgia","Helvetica","Impact","Lucida Console","Segoe UI","Tahoma","Times New Roman","Trebuchet MS","Verdana","SF Pro Display","SF Mono","Cascadia Code","Menlo","Monaco"],H=["Bebas Neue","Space Mono","Inter","Roboto","Roboto Mono","Roboto Condensed","Open Sans","Montserrat","Lato","Oswald","Raleway","Poppins","Nunito","Playfair Display","Merriweather","Source Sans 3","Source Code Pro","PT Sans","PT Serif","PT Mono","Ubuntu","Ubuntu Mono","Fira Sans","Fira Code","Fira Mono","Work Sans","Noto Sans","Noto Serif","DM Sans","DM Serif Display","DM Mono","IBM Plex Sans","IBM Plex Mono","IBM Plex Serif","JetBrains Mono","Inconsolata","Space Grotesk","Archivo","Archivo Black","Barlow","Barlow Condensed","Lexend","Outfit","Sora","Manrope","Bitter","Crimson Text","Libre Baskerville","Abril Fatface","Anton","Permanent Marker","Righteous","Orbitron","Teko","Rubik","Quicksand","Cabin","Karla","Josefin Sans","Comfortaa","Fredoka","Geologica","Instrument Sans","Instrument Serif"],A=[...new Set([...P,...H])].sort();function v(n,t,e){let a=t.split("."),l=n;for(let s=0;s<a.length-1;s++)l=l[a[s]];l[a[a.length-1]]=e}function E(n,t){return t.split(".").reduce((e,a)=>e?.[a],n)}function k(n,t){document.documentElement.style.setProperty(n,t)}function S(n,t){let e=window.__BD_SHADER__;e&&e.updateParams({[n]:t})}function j(n){let t=u.layout.designWidth,e=n/16,a=n/t*100;return`clamp(${(e*.55).toFixed(4)}rem, ${a.toFixed(4)}vw, ${e.toFixed(4)}rem)`}function D(n){let t="bd-gf-"+n.replace(/\s+/g,"-").toLowerCase();if(document.getElementById(t))return;let e=document.createElement("link");e.id=t,e.rel="stylesheet",e.href=`https://fonts.googleapis.com/css2?family=${encodeURIComponent(n)}:wght@400;700&display=swap`,document.head.appendChild(e)}var x=document.createElement("div");x.id="bd-tuner";var M=document.createElement("button");M.id="bd-tuner-toggle";M.textContent="Tuner";M.onclick=()=>x.classList.remove("collapsed");document.body.appendChild(x);document.body.appendChild(M);x.innerHTML=`
1
+ (()=>{var p=structuredClone(window.__BD_TOKENS__||JSON.parse(document.querySelector('script[type="application/json"][data-bluedither-tokens]')?.textContent||"{}")),N=structuredClone(window.__BD_DEFAULTS__||JSON.parse(document.querySelector('script[type="application/json"][data-bluedither-defaults]')?.textContent||"{}")),j=!!(window.__BD_TOKENS__&&window.__BD_DEFAULTS__)||!!window.__BD_TUNER_SERVER_MODE__,$=["Arial","Arial Black","Bebas Neue Pro","Consolas","Courier New","Georgia","Helvetica","Impact","Lucida Console","Segoe UI","Tahoma","Times New Roman","Trebuchet MS","Verdana","SF Pro Display","SF Mono","Cascadia Code","Menlo","Monaco"],H=["Bebas Neue","Space Mono","Inter","Roboto","Roboto Mono","Roboto Condensed","Open Sans","Montserrat","Lato","Oswald","Raleway","Poppins","Nunito","Playfair Display","Merriweather","Source Sans 3","Source Code Pro","PT Sans","PT Serif","PT Mono","Ubuntu","Ubuntu Mono","Fira Sans","Fira Code","Fira Mono","Work Sans","Noto Sans","Noto Serif","DM Sans","DM Serif Display","DM Mono","IBM Plex Sans","IBM Plex Mono","IBM Plex Serif","JetBrains Mono","Inconsolata","Space Grotesk","Archivo","Archivo Black","Barlow","Barlow Condensed","Lexend","Outfit","Sora","Manrope","Bitter","Crimson Text","Libre Baskerville","Abril Fatface","Anton","Permanent Marker","Righteous","Orbitron","Teko","Rubik","Quicksand","Cabin","Karla","Josefin Sans","Comfortaa","Fredoka","Geologica","Instrument Sans","Instrument Serif"],A=[...new Set([...$,...H])].sort();function v(n,t,e){let i=t.split("."),o=n;for(let d=0;d<i.length-1;d++)o=o[i[d]];o[i[i.length-1]]=e}function E(n,t){return t.split(".").reduce((e,i)=>e?.[i],n)}function k(n,t){document.documentElement.style.setProperty(n,t)}function S(n,t){let e=window.__BD_SHADER__;e&&e.updateParams({[n]:t})}function U(n){let t=p.layout.designWidth,e=n/16,i=n/t*100;return`clamp(${(e*.55).toFixed(4)}rem, ${i.toFixed(4)}vw, ${e.toFixed(4)}rem)`}function R(n){let t="bd-gf-"+n.replace(/\s+/g,"-").toLowerCase();if(document.getElementById(t))return;let e=document.createElement("link");e.id=t,e.rel="stylesheet",e.href=`https://fonts.googleapis.com/css2?family=${encodeURIComponent(n)}:wght@400;700&display=swap`,document.head.appendChild(e)}var x=document.createElement("div");x.id="bd-tuner";var M=document.createElement("button");M.id="bd-tuner-toggle";M.textContent="Tuner";M.onclick=()=>x.classList.remove("collapsed");document.body.appendChild(x);document.body.appendChild(M);x.innerHTML=`
2
2
  <div class="bd-tuner-title">
3
3
  <span>BlueDither Tuner</span>
4
4
  <div style="display:flex;gap:8px;align-items:center;">
@@ -6,37 +6,37 @@
6
6
  <button id="bd-tuner-close" title="Close">&times;</button>
7
7
  </div>
8
8
  </div>
9
- `;x.querySelector("#bd-tuner-close").onclick=()=>x.classList.add("collapsed");x.querySelector("#bd-tuner-reset").onclick=()=>{confirm("Reset all tokens to defaults?")&&(Object.assign(u,structuredClone(O)),window.__BD_SERVER_MODE__?fetch("/commit",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(u,null,2)}).then(()=>location.reload()):location.reload())};function T(n){let t=document.createElement("div");return t.className="bd-tuner-section",t.innerHTML=`<div class="bd-tuner-section-label">${n}</div>`,x.appendChild(t),t}function C(n,t,e,a,l){let s=E(u,e)||"#000000",r=document.createElement("div");r.className="bd-tuner-row",r.innerHTML=`
9
+ `;x.querySelector("#bd-tuner-close").onclick=()=>x.classList.add("collapsed");x.querySelector("#bd-tuner-reset").onclick=async()=>{confirm("Reset all tokens to defaults?")&&(Object.assign(p,structuredClone(N)),await I(),location.reload())};function T(n){let t=document.createElement("div");return t.className="bd-tuner-section",t.innerHTML=`<div class="bd-tuner-section-label">${n}</div>`,x.appendChild(t),t}function C(n,t,e,i,o){let d=E(p,e)||"#000000",a=document.createElement("div");a.className="bd-tuner-row",a.innerHTML=`
10
10
  <span class="bd-tuner-label">${t}</span>
11
- <span class="bd-tuner-input"><input type="color" value="${s.substring(0,7)}"></span>
12
- <span class="bd-tuner-value">${s.substring(0,7)}</span>
13
- `;let i=r.querySelector("input"),d=r.querySelector(".bd-tuner-value");return i.addEventListener("input",c=>{let m=c.target.value;v(u,e,m),d.textContent=m,a&&k(a,m),l&&l(m)}),n.appendChild(r),{setValue(c){v(u,e,c),i.value=c.substring(0,7),d.textContent=c.substring(0,7),a&&k(a,c)}}}function g(n,t,e,a,l,s,r){let i=E(u,e),d=document.createElement("div");d.className="bd-tuner-row bd-tuner-row-stacked",d.innerHTML=`
11
+ <span class="bd-tuner-input"><input type="color" value="${d.substring(0,7)}"></span>
12
+ <span class="bd-tuner-value">${d.substring(0,7)}</span>
13
+ `;let s=a.querySelector("input"),c=a.querySelector(".bd-tuner-value");return s.addEventListener("input",l=>{let m=l.target.value;v(p,e,m),c.textContent=m,i&&k(i,m),o&&o(m)}),n.appendChild(a),{setValue(l){v(p,e,l),s.value=l.substring(0,7),c.textContent=l.substring(0,7),i&&k(i,l)}}}function f(n,t,e,i,o,d,a){let s=E(p,e),c=document.createElement("div");c.className="bd-tuner-row bd-tuner-row-stacked",c.innerHTML=`
14
14
  <div class="bd-tuner-row-top">
15
15
  <span class="bd-tuner-label">${t}</span>
16
16
  <div class="bd-tuner-px-input-wrap">
17
- <input type="number" class="bd-tuner-px-num" value="${i}" min="${a}" max="${l}" step="${s}">
17
+ <input type="number" class="bd-tuner-px-num" value="${s}" min="${i}" max="${o}" step="${d}">
18
18
  <span class="bd-tuner-unit">px</span>
19
19
  </div>
20
20
  </div>
21
- <input type="range" class="bd-tuner-slider" min="${a}" max="${l}" step="${s}" value="${i}">
22
- `;let c=d.querySelector(".bd-tuner-px-num"),m=d.querySelector(".bd-tuner-slider");function b(o){if(o=Math.max(a,Math.min(l,o)),v(u,e,o),c.value=o,m.value=o,r){let w=j(o);(Array.isArray(r)?r:[r]).forEach(f=>k(f,w))}}c.addEventListener("input",o=>b(parseFloat(o.target.value)||0)),m.addEventListener("input",o=>b(parseFloat(o.target.value))),c.addEventListener("keydown",o=>{if(o.key==="ArrowUp"||o.key==="ArrowDown"){o.preventDefault();let w=o.shiftKey?10:1,f=(o.key==="ArrowUp"?s:-s)*w;b(parseFloat(c.value)+f)}}),n.appendChild(d)}function F(n,t,e,a,l,s,r){let i=E(u,e),d=document.createElement("div");d.className="bd-tuner-row bd-tuner-row-stacked",d.innerHTML=`
21
+ <input type="range" class="bd-tuner-slider" min="${i}" max="${o}" step="${d}" value="${s}">
22
+ `;let l=c.querySelector(".bd-tuner-px-num"),m=c.querySelector(".bd-tuner-slider");function u(r){if(r=Math.max(i,Math.min(o,r)),v(p,e,r),l.value=r,m.value=r,a){let w=U(r);(Array.isArray(a)?a:[a]).forEach(g=>k(g,w))}}l.addEventListener("input",r=>u(parseFloat(r.target.value)||0)),m.addEventListener("input",r=>u(parseFloat(r.target.value))),l.addEventListener("keydown",r=>{if(r.key==="ArrowUp"||r.key==="ArrowDown"){r.preventDefault();let w=r.shiftKey?10:1,g=(r.key==="ArrowUp"?d:-d)*w;u(parseFloat(l.value)+g)}}),n.appendChild(c)}function P(n,t,e,i,o,d,a){let s=E(p,e),c=document.createElement("div");c.className="bd-tuner-row bd-tuner-row-stacked",c.innerHTML=`
23
23
  <div class="bd-tuner-row-top">
24
24
  <span class="bd-tuner-label">${t}</span>
25
- <span class="bd-tuner-value">${i}</span>
25
+ <span class="bd-tuner-value">${s}</span>
26
26
  </div>
27
- <input type="range" class="bd-tuner-slider" min="${a}" max="${l}" step="${s}" value="${i}">
28
- `;let c=d.querySelector(".bd-tuner-slider"),m=d.querySelector(".bd-tuner-value");c.addEventListener("input",b=>{let o=parseFloat(b.target.value);v(u,e,o),m.textContent=o,r&&r(o)}),n.appendChild(d)}function $(n,t,e,a){let l=E(u,e),s=document.createElement("div");s.className="bd-tuner-row",s.innerHTML=`
27
+ <input type="range" class="bd-tuner-slider" min="${i}" max="${o}" step="${d}" value="${s}">
28
+ `;let l=c.querySelector(".bd-tuner-slider"),m=c.querySelector(".bd-tuner-value");l.addEventListener("input",u=>{let r=parseFloat(u.target.value);v(p,e,r),m.textContent=r,a&&a(r)}),n.appendChild(c)}function B(n,t,e,i){let o=E(p,e),d=document.createElement("div");d.className="bd-tuner-row",d.innerHTML=`
29
29
  <span class="bd-tuner-label">${t}</span>
30
30
  <span class="bd-tuner-input bd-tuner-font-input">
31
- <input type="text" class="bd-tuner-font-search" value="${l}" placeholder="Search fonts...">
31
+ <input type="text" class="bd-tuner-font-search" value="${o}" placeholder="Search fonts...">
32
32
  <div class="bd-tuner-font-dropdown"></div>
33
33
  </span>
34
- `;let r=s.querySelector(".bd-tuner-font-search"),i=s.querySelector(".bd-tuner-font-dropdown"),d=m=>P.some(b=>b.toLowerCase()===m.toLowerCase());function c(m=""){let b=A.filter(o=>o.toLowerCase().includes(m.toLowerCase())).slice(0,20);i.innerHTML=b.map(o=>{let w=d(o)?' <span style="opacity:0.4;font-size:9px">SYSTEM</span>':"";return`<div class="bd-tuner-font-option" data-font="${o}" style="font-family:'${o}',system-ui">${o}${w}</div>`}).join(""),b.filter(o=>!d(o)).forEach(D),i.querySelectorAll(".bd-tuner-font-option").forEach(o=>{o.addEventListener("mousedown",w=>{w.preventDefault();let f=o.dataset.font;r.value=f,v(u,e,f),d(f)||D(f),k(a,`"${f}", system-ui, sans-serif`),i.classList.remove("open")})})}r.addEventListener("focus",()=>{c(r.value),i.classList.add("open")}),r.addEventListener("input",()=>{c(r.value),i.classList.add("open")}),r.addEventListener("blur",()=>{setTimeout(()=>i.classList.remove("open"),150)}),r.addEventListener("keydown",m=>{if(m.key==="Enter"){let b=r.value.trim();b&&(v(u,e,b),d(b)||D(b),k(a,`"${b}", system-ui, sans-serif`),i.classList.remove("open"),r.blur())}}),n.appendChild(s)}function R(n,t,e,a,l){let s=E(u,e),r=document.createElement("div");r.className="bd-tuner-row bd-tuner-row-stacked",r.innerHTML=`
34
+ `;let a=d.querySelector(".bd-tuner-font-search"),s=d.querySelector(".bd-tuner-font-dropdown"),c=m=>$.some(u=>u.toLowerCase()===m.toLowerCase());function l(m=""){let u=A.filter(r=>r.toLowerCase().includes(m.toLowerCase())).slice(0,20);s.innerHTML=u.map(r=>{let w=c(r)?' <span style="opacity:0.4;font-size:9px">SYSTEM</span>':"";return`<div class="bd-tuner-font-option" data-font="${r}" style="font-family:'${r}',system-ui">${r}${w}</div>`}).join(""),u.filter(r=>!c(r)).forEach(R),s.querySelectorAll(".bd-tuner-font-option").forEach(r=>{r.addEventListener("mousedown",w=>{w.preventDefault();let g=r.dataset.font;a.value=g,v(p,e,g),c(g)||R(g),k(i,`"${g}", system-ui, sans-serif`),s.classList.remove("open")})})}a.addEventListener("focus",()=>{l(a.value),s.classList.add("open")}),a.addEventListener("input",()=>{l(a.value),s.classList.add("open")}),a.addEventListener("blur",()=>{setTimeout(()=>s.classList.remove("open"),150)}),a.addEventListener("keydown",m=>{if(m.key==="Enter"){let u=a.value.trim();u&&(v(p,e,u),c(u)||R(u),k(i,`"${u}", system-ui, sans-serif`),s.classList.remove("open"),a.blur())}}),n.appendChild(d)}function D(n,t,e,i,o){let d=E(p,e),a=document.createElement("div");a.className="bd-tuner-row bd-tuner-row-stacked",a.innerHTML=`
35
35
  <span class="bd-tuner-label">${t}</span>
36
36
  <div class="bd-tuner-segmented">
37
- ${a.map(i=>`<button class="bd-tuner-seg-btn${i===s?" active":""}" data-val="${i}">${i}</button>`).join("")}
37
+ ${i.map(s=>`<button class="bd-tuner-seg-btn${s===d?" active":""}" data-val="${s}">${s}</button>`).join("")}
38
38
  </div>
39
- `,r.querySelectorAll(".bd-tuner-seg-btn").forEach(i=>{i.addEventListener("click",()=>{r.querySelectorAll(".bd-tuner-seg-btn").forEach(c=>c.classList.remove("active")),i.classList.add("active");let d=i.dataset.val;v(u,e,d),l&&l(d)})}),n.appendChild(r)}var _=T("Colors");C(_,"Background","colors.background","--bd-color-background");var B;C(_,"Primary","colors.primary","--bd-color-primary",n=>{B.setValue(n),S("colorFront",n)});C(_,"Text","colors.text","--bd-color-text");C(_,"CTA Background","colors.ctaBackground","--bd-color-cta-bg");C(_,"CTA Text","colors.ctaText","--bd-color-cta-text");B=C(_,"Shader Front","colors.shaderFront",null,n=>S("colorFront",n));var y=T("Typography");$(y,"Primary Font","typography.primaryFont","--bd-font-primary");$(y,"Secondary Font","typography.secondaryFont","--bd-font-secondary");g(y,"Headline Size","typography.headline.referencePx",32,300,1,"--bd-headline-size");g(y,"Headline LH","typography.headline.lineHeightPx",24,280,1,"--bd-headline-lh");g(y,"Sub Size","typography.subHeadline.referencePx",10,48,1,"--bd-subheadline-size");g(y,"Sub LH","typography.subHeadline.lineHeightPx",12,80,1,"--bd-subheadline-lh");g(y,"Logo Size","typography.logo.referencePx",12,80,1,"--bd-logo-size");g(y,"Nav Size","typography.navItem.referencePx",10,48,1,"--bd-nav-size");var h=T("Spacing");g(h,"Header Pad X","spacing.headerPaddingX",0,80,1,"--bd-header-px");g(h,"Header Pad Y","spacing.headerPaddingY",0,60,1,"--bd-header-py");g(h,"Hero Pad Top","spacing.heroPaddingTop",0,120,1,"--bd-hero-pt");g(h,"Hero Pad Bottom","spacing.heroPaddingBottom",0,120,1,"--bd-hero-pb");g(h,"Hero Pad X","spacing.heroPaddingX",0,120,1,"--bd-hero-px");g(h,"Nav Gap","spacing.navGap",0,100,1,"--bd-nav-gap");g(h,"CTA Pad X","spacing.ctaPaddingX",0,60,1,"--bd-cta-px");g(h,"CTA Pad Y","spacing.ctaPaddingY",0,30,1,"--bd-cta-py");g(h,"CTA Radius","spacing.ctaBorderRadius",0,32,1,"--bd-cta-radius");var L=T("Shader");R(L,"Shape","shader.shape",["warp","simplex","dots","wave","ripple","swirl","sphere"],n=>S("shape",n));R(L,"Dither Type","shader.type",["random","2x2","4x4","8x8"],n=>S("type",n));F(L,"Speed","shader.speed",0,2,.01,n=>S("speed",n));F(L,"Scale","shader.scale",.1,5,.01,n=>S("scale",n));F(L,"Dither Size","shader.size",.5,10,.1,n=>S("size",n));var q=T("Opacity");F(q,"Nav Links","opacity.navLinks",0,1,.01,n=>k("--bd-nav-opacity",n));var p=document.createElement("button");p.id="bd-tuner-commit";p.textContent="Commit Changes";var I=N?"Commit Changes":"Export Tokens";p.textContent=I;p.onclick=async()=>{p.textContent="Exporting...",p.disabled=!0;try{if(window.__BD_SERVER_MODE__){let t=await(await fetch("/commit",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(u,null,2)})).json();if(!t.ok)throw new Error(t.error);p.textContent="Committed!"}else{let n=new Blob([JSON.stringify(u,null,2)],{type:"application/json"}),t=URL.createObjectURL(n),e=document.createElement("a");e.href=t,e.download="tokens.json",e.click(),URL.revokeObjectURL(t),p.textContent="Downloaded!"}p.classList.add("success"),setTimeout(()=>{p.textContent=window.__BD_SERVER_MODE__?"Commit Changes":"Export Tokens",p.classList.remove("success"),p.disabled=!1},2e3)}catch(n){p.textContent="Error: "+n.message,p.disabled=!1,setTimeout(()=>{p.textContent=window.__BD_SERVER_MODE__?"Commit Changes":"Export Tokens"},3e3)}};window.__BD_SERVER_MODE__||(p.textContent="Export Tokens");x.appendChild(p);var z=document.createElement("style");z.textContent=`/* =============================================
39
+ `,a.querySelectorAll(".bd-tuner-seg-btn").forEach(s=>{s.addEventListener("click",()=>{a.querySelectorAll(".bd-tuner-seg-btn").forEach(l=>l.classList.remove("active")),s.classList.add("active");let c=s.dataset.val;v(p,e,c),o&&o(c)})}),n.appendChild(a)}var _=T("Colors");C(_,"Background","colors.background","--bd-color-background");var O;C(_,"Primary","colors.primary","--bd-color-primary",n=>{O.setValue(n),S("colorFront",n)});C(_,"Text","colors.text","--bd-color-text");C(_,"CTA Background","colors.ctaBackground","--bd-color-cta-bg");C(_,"CTA Text","colors.ctaText","--bd-color-cta-text");O=C(_,"Shader Front","colors.shaderFront",null,n=>S("colorFront",n));var y=T("Typography");B(y,"Primary Font","typography.primaryFont","--bd-font-primary");B(y,"Secondary Font","typography.secondaryFont","--bd-font-secondary");f(y,"Headline Size","typography.headline.referencePx",32,300,1,"--bd-headline-size");f(y,"Headline LH","typography.headline.lineHeightPx",24,280,1,"--bd-headline-lh");f(y,"Sub Size","typography.subHeadline.referencePx",10,48,1,"--bd-subheadline-size");f(y,"Sub LH","typography.subHeadline.lineHeightPx",12,80,1,"--bd-subheadline-lh");f(y,"Logo Size","typography.logo.referencePx",12,80,1,"--bd-logo-size");f(y,"Nav Size","typography.navItem.referencePx",10,48,1,"--bd-nav-size");var h=T("Spacing");f(h,"Header Pad X","spacing.headerPaddingX",0,80,1,"--bd-header-px");f(h,"Header Pad Y","spacing.headerPaddingY",0,60,1,"--bd-header-py");f(h,"Hero Pad Top","spacing.heroPaddingTop",0,120,1,"--bd-hero-pt");f(h,"Hero Pad Bottom","spacing.heroPaddingBottom",0,120,1,"--bd-hero-pb");f(h,"Hero Pad X","spacing.heroPaddingX",0,120,1,"--bd-hero-px");f(h,"Nav Gap","spacing.navGap",0,100,1,"--bd-nav-gap");f(h,"CTA Pad X","spacing.ctaPaddingX",0,60,1,"--bd-cta-px");f(h,"CTA Pad Y","spacing.ctaPaddingY",0,30,1,"--bd-cta-py");f(h,"CTA Radius","spacing.ctaBorderRadius",0,32,1,"--bd-cta-radius");var L=T("Shader");D(L,"Shape","shader.shape",["warp","simplex","dots","wave","ripple","swirl","sphere"],n=>S("shape",n));D(L,"Dither Type","shader.type",["random","2x2","4x4","8x8"],n=>S("type",n));P(L,"Speed","shader.speed",0,2,.01,n=>S("speed",n));P(L,"Scale","shader.scale",.1,5,.01,n=>S("scale",n));P(L,"Dither Size","shader.size",.5,10,.1,n=>S("size",n));var q=T("Opacity");P(q,"Nav Links","opacity.navLinks",0,1,.01,n=>k("--bd-nav-opacity",n));var b=document.createElement("button");b.id="bd-tuner-commit";b.textContent="Commit Changes";var F=null;async function I(){let n=JSON.stringify(p,null,2);try{let o=await fetch("/__bluedither/commit",{method:"POST",headers:{"Content-Type":"application/json"},body:n});if(o.ok&&(await o.json()).ok)return"saved"}catch{}try{let o=await fetch("http://localhost:3344/commit",{method:"POST",headers:{"Content-Type":"application/json"},body:n});if(o.ok&&(await o.json()).ok)return"saved"}catch{}if(j)try{let o=await fetch("/commit",{method:"POST",headers:{"Content-Type":"application/json"},body:n});if(o.ok&&(await o.json()).ok)return"saved"}catch{}if(window.showSaveFilePicker)try{F||(F=await window.showSaveFilePicker({suggestedName:"tokens.json",types:[{description:"JSON",accept:{"application/json":[".json"]}}]}));let o=await F.createWritable();return await o.write(n),await o.close(),"saved"}catch(o){if(o.name==="AbortError")return"cancelled";F=null}let t=new Blob([n],{type:"application/json"}),e=URL.createObjectURL(t),i=document.createElement("a");return i.href=e,i.download="tokens.json",i.click(),URL.revokeObjectURL(e),"downloaded"}b.onclick=async()=>{b.textContent="Exporting...",b.disabled=!0;try{if(window.__BD_SERVER_MODE__){let t=await(await fetch("/commit",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p,null,2)})).json();if(!t.ok)throw new Error(t.error);b.textContent="Committed!"}else{let n=new Blob([JSON.stringify(p,null,2)],{type:"application/json"}),t=URL.createObjectURL(n),e=document.createElement("a");e.href=t,e.download="tokens.json",e.click(),URL.revokeObjectURL(t),b.textContent="Downloaded!"}b.classList.add("success"),setTimeout(()=>{b.textContent=window.__BD_SERVER_MODE__?"Commit Changes":"Export Tokens",b.classList.remove("success"),b.disabled=!1},2e3)}catch(n){b.textContent="Error: "+n.message,b.disabled=!1,setTimeout(()=>{b.textContent=window.__BD_SERVER_MODE__?"Commit Changes":"Export Tokens"},3e3)}};window.__BD_SERVER_MODE__||(b.textContent="Export Tokens");x.appendChild(b);var z=document.createElement("style");z.textContent=`/* =============================================
40
40
  BlueDither Fine-Tuner \u2014 Overlay Panel v2
41
41
  ============================================= */
42
42
 
@@ -0,0 +1,110 @@
1
+ /**
2
+ * BlueDither Dev Middleware
3
+ *
4
+ * Universal middleware that handles POST /___bluedither/commit
5
+ * to write tokens.json to disk. Works with any Node.js dev server.
6
+ *
7
+ * Exports:
8
+ * bluedither() — Vite plugin
9
+ * blueditherMiddleware() — Express/Connect middleware
10
+ * blueditherHandler() — Raw Node http handler (for custom servers)
11
+ *
12
+ * The grab-theme command auto-wires the right export for the detected framework.
13
+ */
14
+
15
+ import { writeFileSync, existsSync } from 'fs';
16
+ import { resolve, dirname } from 'path';
17
+
18
+ const ENDPOINT = '/__bluedither/commit';
19
+
20
+ function findTokensPath() {
21
+ // Search from cwd for bluedither/tokens.json
22
+ const cwd = process.cwd();
23
+ const paths = [
24
+ resolve(cwd, 'bluedither', 'tokens.json'),
25
+ resolve(cwd, 'public', 'bluedither', 'tokens.json'),
26
+ resolve(cwd, 'theme', 'tokens.json'),
27
+ ];
28
+ for (const p of paths) {
29
+ if (existsSync(p)) return p;
30
+ }
31
+ // Default to bluedither/tokens.json even if it doesn't exist yet
32
+ return resolve(cwd, 'bluedither', 'tokens.json');
33
+ }
34
+
35
+ function handleRequest(req, res) {
36
+ // CORS preflight
37
+ if (req.method === 'OPTIONS' && req.url === ENDPOINT) {
38
+ res.writeHead(204, {
39
+ 'Access-Control-Allow-Origin': '*',
40
+ 'Access-Control-Allow-Methods': 'POST, OPTIONS',
41
+ 'Access-Control-Allow-Headers': 'Content-Type',
42
+ });
43
+ res.end();
44
+ return true;
45
+ }
46
+
47
+ if (req.method === 'POST' && req.url === ENDPOINT) {
48
+ let body = '';
49
+ req.on('data', chunk => { body += chunk; });
50
+ req.on('end', () => {
51
+ try {
52
+ // Validate it's JSON
53
+ JSON.parse(body);
54
+ const tokensPath = findTokensPath();
55
+ writeFileSync(tokensPath, body);
56
+ res.writeHead(200, {
57
+ 'Content-Type': 'application/json',
58
+ 'Access-Control-Allow-Origin': '*',
59
+ });
60
+ res.end('{"ok":true}');
61
+ } catch (e) {
62
+ res.writeHead(500, {
63
+ 'Content-Type': 'application/json',
64
+ 'Access-Control-Allow-Origin': '*',
65
+ });
66
+ res.end(JSON.stringify({ error: e.message }));
67
+ }
68
+ });
69
+ return true;
70
+ }
71
+
72
+ return false;
73
+ }
74
+
75
+ /**
76
+ * Vite plugin — add to vite.config.js:
77
+ * import { bluedither } from './bluedither/dev-middleware.js'
78
+ * export default { plugins: [bluedither()] }
79
+ */
80
+ export function bluedither() {
81
+ return {
82
+ name: 'bluedither-tuner',
83
+ configureServer(server) {
84
+ server.middlewares.use((req, res, next) => {
85
+ if (!handleRequest(req, res)) next();
86
+ });
87
+ },
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Express/Connect middleware — add to your server:
93
+ * import { blueditherMiddleware } from './bluedither/dev-middleware.js'
94
+ * app.use(blueditherMiddleware())
95
+ */
96
+ export function blueditherMiddleware() {
97
+ return (req, res, next) => {
98
+ if (!handleRequest(req, res)) next();
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Raw handler for custom Node http servers:
104
+ * import { blueditherHandler } from './bluedither/dev-middleware.js'
105
+ * // In your request handler:
106
+ * if (!blueditherHandler(req, res)) { /* your normal handling */ }
107
+ */
108
+ export { handleRequest as blueditherHandler };
109
+
110
+ export default bluedither;
@@ -3,13 +3,10 @@
3
3
  *
4
4
  * Drop this script into ANY page to get the tuner overlay.
5
5
  * Reads tokens from bluedither/tokens.json, updates CSS vars live.
6
- * Saves via sidecar (if running) or downloads the file.
6
+ * "Commit Changes" saves via dev middleware → sidecar File System API download.
7
7
  *
8
8
  * Usage:
9
9
  * <script src="./bluedither/bluedither-tuner-inject.js"></script>
10
- *
11
- * The sidecar save server:
12
- * npx bluedither tune --save-only
13
10
  */
14
11
 
15
12
  (async function () {
@@ -38,30 +35,8 @@
38
35
  window.__BD_TOKENS__ = tokens;
39
36
  window.__BD_DEFAULTS__ = defaults;
40
37
 
41
- // Override server mode use sidecar instead of /commit
42
- const SIDECAR_PORT = 3344;
43
- let sidecarAvailable = false;
44
-
45
- try {
46
- const probe = await fetch(`http://localhost:${SIDECAR_PORT}/ping`, { method: 'GET', signal: AbortSignal.timeout(500) });
47
- sidecarAvailable = probe.ok;
48
- } catch {
49
- // Sidecar not running
50
- }
51
-
52
- // Patch the commit mechanism
53
- if (sidecarAvailable) {
54
- // Override fetch for /commit to redirect to sidecar
55
- const origFetch = window.fetch;
56
- window.fetch = function (url, opts) {
57
- if (url === '/commit' && opts?.method === 'POST') {
58
- return origFetch.call(this, `http://localhost:${SIDECAR_PORT}/commit`, opts);
59
- }
60
- return origFetch.apply(this, arguments);
61
- };
62
- // Signal to tuner that server mode is available
63
- window.__BD_TUNER_SERVER_MODE__ = true;
64
- }
38
+ // Signal that we're in injectable mode (tuner will use commitTokens cascade)
39
+ window.__BD_TUNER_SERVER_MODE__ = true;
65
40
 
66
41
  // Load tuner CSS
67
42
  try {
@@ -112,18 +112,11 @@ panel.innerHTML = `
112
112
  </div>
113
113
  `;
114
114
  panel.querySelector('#bd-tuner-close').onclick = () => panel.classList.add('collapsed');
115
- panel.querySelector('#bd-tuner-reset').onclick = () => {
115
+ panel.querySelector('#bd-tuner-reset').onclick = async () => {
116
116
  if (!confirm('Reset all tokens to defaults?')) return;
117
117
  Object.assign(tokens, structuredClone(defaults));
118
- if (__serverMode) {
119
- fetch('/commit', {
120
- method: 'POST',
121
- headers: { 'Content-Type': 'application/json' },
122
- body: JSON.stringify(tokens, null, 2)
123
- }).then(() => location.reload());
124
- } else {
125
- location.reload();
126
- }
118
+ await commitTokens();
119
+ location.reload();
127
120
  };
128
121
 
129
122
  // ── Section builders ──
@@ -404,42 +397,105 @@ addRange(opacitySection, 'Nav Links', 'opacity.navLinks', 0, 1, 0.01, (v) => set
404
397
  const commitBtn = document.createElement('button');
405
398
  commitBtn.id = 'bd-tuner-commit';
406
399
  commitBtn.textContent = 'Commit Changes';
407
- const commitLabel = __serverMode ? 'Commit Changes' : 'Export Tokens';
408
- commitBtn.textContent = commitLabel;
409
- commitBtn.onclick = async () => {
410
- commitBtn.textContent = __serverMode ? 'Committing...' : 'Exporting...';
411
- commitBtn.disabled = true;
400
+
401
+ // Try to commit via dev middleware, then sidecar, then File System Access API, then download
402
+ let __fileHandle = null; // Cached File System Access API handle
403
+
404
+ async function commitTokens() {
405
+ const payload = JSON.stringify(tokens, null, 2);
406
+
407
+ // Strategy 1: Dev middleware (Vite plugin, Express middleware, etc.)
412
408
  try {
413
- if (__serverMode) {
409
+ const res = await fetch('/__bluedither/commit', {
410
+ method: 'POST',
411
+ headers: { 'Content-Type': 'application/json' },
412
+ body: payload,
413
+ });
414
+ if (res.ok) {
415
+ const data = await res.json();
416
+ if (data.ok) return 'saved';
417
+ }
418
+ } catch {}
419
+
420
+ // Strategy 2: Sidecar on port 3344 (if running)
421
+ try {
422
+ const res = await fetch('http://localhost:3344/commit', {
423
+ method: 'POST',
424
+ headers: { 'Content-Type': 'application/json' },
425
+ body: payload,
426
+ });
427
+ if (res.ok) {
428
+ const data = await res.json();
429
+ if (data.ok) return 'saved';
430
+ }
431
+ } catch {}
432
+
433
+ // Strategy 3: Legacy /commit endpoint (BlueDither's own dev server)
434
+ if (__serverMode) {
435
+ try {
414
436
  const res = await fetch('/commit', {
415
437
  method: 'POST',
416
438
  headers: { 'Content-Type': 'application/json' },
417
- body: JSON.stringify(tokens, null, 2)
439
+ body: payload,
418
440
  });
419
- const data = await res.json();
420
- if (!data.ok) throw new Error(data.error);
421
- commitBtn.textContent = 'Committed!';
422
- } else {
423
- // Standalone: download tokens as JSON file
424
- const blob = new Blob([JSON.stringify(tokens, null, 2)], { type: 'application/json' });
425
- const url = URL.createObjectURL(blob);
426
- const a = document.createElement('a');
427
- a.href = url;
428
- a.download = 'tokens.json';
429
- a.click();
430
- URL.revokeObjectURL(url);
431
- commitBtn.textContent = 'Downloaded!';
441
+ if (res.ok) {
442
+ const data = await res.json();
443
+ if (data.ok) return 'saved';
444
+ }
445
+ } catch {}
446
+ }
447
+
448
+ // Strategy 4: File System Access API (Chrome/Edge — no server needed)
449
+ if (window.showSaveFilePicker) {
450
+ try {
451
+ if (!__fileHandle) {
452
+ __fileHandle = await window.showSaveFilePicker({
453
+ suggestedName: 'tokens.json',
454
+ types: [{ description: 'JSON', accept: { 'application/json': ['.json'] } }],
455
+ });
456
+ }
457
+ const writable = await __fileHandle.createWritable();
458
+ await writable.write(payload);
459
+ await writable.close();
460
+ return 'saved';
461
+ } catch (e) {
462
+ if (e.name === 'AbortError') return 'cancelled';
463
+ __fileHandle = null;
464
+ }
465
+ }
466
+
467
+ // Strategy 5: Download as last resort
468
+ const blob = new Blob([payload], { type: 'application/json' });
469
+ const url = URL.createObjectURL(blob);
470
+ const a = document.createElement('a');
471
+ a.href = url;
472
+ a.download = 'tokens.json';
473
+ a.click();
474
+ URL.revokeObjectURL(url);
475
+ return 'downloaded';
476
+ }
477
+
478
+ commitBtn.onclick = async () => {
479
+ commitBtn.textContent = 'Saving...';
480
+ commitBtn.disabled = true;
481
+ try {
482
+ const result = await commitTokens();
483
+ if (result === 'cancelled') {
484
+ commitBtn.textContent = 'Commit Changes';
485
+ commitBtn.disabled = false;
486
+ return;
432
487
  }
488
+ commitBtn.textContent = result === 'saved' ? 'Saved!' : 'Downloaded!';
433
489
  commitBtn.classList.add('success');
434
490
  setTimeout(() => {
435
- commitBtn.textContent = commitLabel;
491
+ commitBtn.textContent = 'Commit Changes';
436
492
  commitBtn.classList.remove('success');
437
493
  commitBtn.disabled = false;
438
494
  }, 2000);
439
495
  } catch (e) {
440
496
  commitBtn.textContent = 'Error: ' + e.message;
441
497
  commitBtn.disabled = false;
442
- setTimeout(() => { commitBtn.textContent = commitLabel; }, 3000);
498
+ setTimeout(() => { commitBtn.textContent = 'Commit Changes'; }, 3000);
443
499
  }
444
500
  };
445
501
  panel.appendChild(commitBtn);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bluedither",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "A bold, dithered-shader hero theme for Claude Code — skill + fine-tuner",
5
5
  "type": "module",
6
6
  "bin": {