bluedither 1.0.21 → 1.0.22
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/dist/bluedither-tuner.js +7 -7
- package/fine-tuner/inject.js +2 -2
- package/fine-tuner/tuner.js +1 -1
- package/package.json +1 -1
- package/theme/generators/react.md +8 -1
- package/theme/generators/vanilla.md +92 -7
- package/theme/generators/vue.md +182 -22
package/dist/bluedither-tuner.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
(()=>{if(!window.__BD_TUNER_LOADED__){let w=function(n,r,e){let i=r.split("."),t=n;for(let d=0;d<i.length-1;d++)t=t[i[d]];t[i[i.length-1]]=e},L=function(n,r){return r.split(".").reduce((e,i)=>e?.[i],n)},
|
|
1
|
+
(()=>{if(!window.__BD_TUNER_LOADED__){let w=function(n,r,e){let i=r.split("."),t=n;for(let d=0;d<i.length-1;d++)t=t[i[d]];t[i[i.length-1]]=e},L=function(n,r){return r.split(".").reduce((e,i)=>e?.[i],n)},v=function(n,r){document.documentElement.style.setProperty(n,r)},S=function(n,r){let e=window.__BD_SHADER__;e&&e.updateParams({[n]:r})},U=function(n){let r=b.layout.designWidth,e=n/16,i=n/r*100;return`clamp(${(e*.55).toFixed(4)}rem, ${i.toFixed(4)}vw, ${e.toFixed(4)}rem)`},$=function(n){let r="bd-gf-"+n.replace(/\s+/g,"-").toLowerCase();if(document.getElementById(r))return;let e=document.createElement("link");e.id=r,e.rel="stylesheet",e.href=`https://fonts.googleapis.com/css2?family=${encodeURIComponent(n)}:wght@400;700&display=swap`,document.head.appendChild(e)},_=function(n){let r=document.createElement("div");return r.className="bd-tuner-section",r.innerHTML=`<div class="bd-tuner-section-label">${n}</div>`,f.appendChild(r),r},C=function(n,r,e,i,t){let d=L(b,e)||"#000000",a=document.createElement("div");a.className="bd-tuner-row",a.innerHTML=`
|
|
2
2
|
<span class="bd-tuner-label">${r}</span>
|
|
3
3
|
<span class="bd-tuner-input"><input type="color" value="${d.substring(0,7)}"></span>
|
|
4
4
|
<span class="bd-tuner-value">${d.substring(0,7)}</span>
|
|
5
|
-
`;let s=a.querySelector("input"),c=a.querySelector(".bd-tuner-value");return s.addEventListener("input",l=>{let
|
|
5
|
+
`;let s=a.querySelector("input"),c=a.querySelector(".bd-tuner-value");return s.addEventListener("input",l=>{let m=l.target.value;w(b,e,m),c.textContent=m,i&&v(i,m),t&&t(m)}),n.appendChild(a),{setValue(l){w(b,e,l),s.value=l.substring(0,7),c.textContent=l.substring(0,7),i&&v(i,l)}}},g=function(n,r,e,i,t,d,a){let s=L(b,e),c=document.createElement("div");c.className="bd-tuner-row bd-tuner-row-stacked",c.innerHTML=`
|
|
6
6
|
<div class="bd-tuner-row-top">
|
|
7
7
|
<span class="bd-tuner-label">${r}</span>
|
|
8
8
|
<div class="bd-tuner-px-input-wrap">
|
|
@@ -11,24 +11,24 @@
|
|
|
11
11
|
</div>
|
|
12
12
|
</div>
|
|
13
13
|
<input type="range" class="bd-tuner-slider" min="${i}" max="${t}" step="${d}" value="${s}">
|
|
14
|
-
`;let l=c.querySelector(".bd-tuner-px-num"),
|
|
14
|
+
`;let l=c.querySelector(".bd-tuner-px-num"),m=c.querySelector(".bd-tuner-slider");function u(o){if(o=Math.max(i,Math.min(t,o)),w(b,e,o),l.value=o,m.value=o,a){let k=U(o);(Array.isArray(a)?a:[a]).forEach(h=>v(h,k))}}l.addEventListener("input",o=>u(parseFloat(o.target.value)||0)),m.addEventListener("input",o=>u(parseFloat(o.target.value))),l.addEventListener("keydown",o=>{if(o.key==="ArrowUp"||o.key==="ArrowDown"){o.preventDefault();let k=o.shiftKey?10:1,h=(o.key==="ArrowUp"?d:-d)*k;u(parseFloat(l.value)+h)}}),n.appendChild(c)},E=function(n,r,e,i,t,d,a){let s=L(b,e),c=document.createElement("div");c.className="bd-tuner-row bd-tuner-row-stacked",c.innerHTML=`
|
|
15
15
|
<div class="bd-tuner-row-top">
|
|
16
16
|
<span class="bd-tuner-label">${r}</span>
|
|
17
17
|
<span class="bd-tuner-value">${s}</span>
|
|
18
18
|
</div>
|
|
19
19
|
<input type="range" class="bd-tuner-slider" min="${i}" max="${t}" step="${d}" value="${s}">
|
|
20
|
-
`;let l=c.querySelector(".bd-tuner-slider"),
|
|
20
|
+
`;let l=c.querySelector(".bd-tuner-slider"),m=c.querySelector(".bd-tuner-value");l.addEventListener("input",u=>{let o=parseFloat(u.target.value);w(b,e,o),m.textContent=o,a&&a(o)}),n.appendChild(c)},D=function(n,r,e,i){let t=L(b,e),d=document.createElement("div");d.className="bd-tuner-row",d.innerHTML=`
|
|
21
21
|
<span class="bd-tuner-label">${r}</span>
|
|
22
22
|
<span class="bd-tuner-input bd-tuner-font-input">
|
|
23
23
|
<input type="text" class="bd-tuner-font-search" value="${t}" placeholder="Search fonts...">
|
|
24
24
|
<div class="bd-tuner-font-dropdown"></div>
|
|
25
25
|
</span>
|
|
26
|
-
`;let a=d.querySelector(".bd-tuner-font-search"),s=d.querySelector(".bd-tuner-font-dropdown"),c=
|
|
26
|
+
`;let a=d.querySelector(".bd-tuner-font-search"),s=d.querySelector(".bd-tuner-font-dropdown"),c=m=>z.some(u=>u.toLowerCase()===m.toLowerCase());function l(m=""){let u=q.filter(o=>o.toLowerCase().includes(m.toLowerCase())).slice(0,20);s.innerHTML=u.map(o=>{let k=c(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}${k}</div>`}).join(""),u.filter(o=>!c(o)).forEach($),s.querySelectorAll(".bd-tuner-font-option").forEach(o=>{o.addEventListener("mousedown",k=>{k.preventDefault();let h=o.dataset.font;a.value=h,w(b,e,h),c(h)||$(h),v(i,`"${h}", 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&&(w(b,e,u),c(u)||$(u),v(i,`"${u}", system-ui, sans-serif`),s.classList.remove("open"),a.blur())}}),n.appendChild(d)},B=function(n,r,e,i,t){let d=L(b,e),a=document.createElement("div");a.className="bd-tuner-row bd-tuner-row-stacked",a.innerHTML=`
|
|
27
27
|
<span class="bd-tuner-label">${r}</span>
|
|
28
28
|
<div class="bd-tuner-segmented">
|
|
29
29
|
${i.map(s=>`<button class="bd-tuner-seg-btn${s===d?" active":""}" data-val="${s}">${s}</button>`).join("")}
|
|
30
30
|
</div>
|
|
31
|
-
`,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;w(b,e,c),t&&t(c)})}),n.appendChild(a)};window.__BD_TUNER_LOADED__=!0;let b=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||"{}")),H=!!(window.__BD_TOKENS__&&window.__BD_DEFAULTS__)||!!window.__BD_TUNER_SERVER_MODE__,z=["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"],j=["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"],q=[...new Set([...z,...j])].sort(),f=document.createElement("div");f.id="bd-tuner";let
|
|
31
|
+
`,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;w(b,e,c),t&&t(c)})}),n.appendChild(a)};window.__BD_TUNER_LOADED__=!0;let b=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||"{}")),H=!!(window.__BD_TOKENS__&&window.__BD_DEFAULTS__)||!!window.__BD_TUNER_SERVER_MODE__,z=["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"],j=["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"],q=[...new Set([...z,...j])].sort(),f=document.createElement("div");f.id="bd-tuner";let P=document.createElement("button");P.id="bd-tuner-toggle",P.textContent="Tuner",P.onclick=()=>f.classList.remove("collapsed"),document.body.appendChild(f),document.body.appendChild(P),f.innerHTML=`
|
|
32
32
|
<div class="bd-tuner-title">
|
|
33
33
|
<span>BlueDither Tuner</span>
|
|
34
34
|
<div style="display:flex;gap:8px;align-items:center;">
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
<button id="bd-tuner-close" title="Close">×</button>
|
|
37
37
|
</div>
|
|
38
38
|
</div>
|
|
39
|
-
`,f.querySelector("#bd-tuner-close").onclick=()=>f.classList.add("collapsed"),f.querySelector("#bd-tuner-reset").onclick=async()=>{confirm("Reset all tokens to defaults?")&&(Object.assign(b,structuredClone(O)),await R(),location.reload())};let T=_("Colors");C(T,"Background","colors.background","--bd-bg");let N;C(T,"Primary","colors.primary","--bd-primary",n=>{N.setValue(n),
|
|
39
|
+
`,f.querySelector("#bd-tuner-close").onclick=()=>f.classList.add("collapsed"),f.querySelector("#bd-tuner-reset").onclick=async()=>{confirm("Reset all tokens to defaults?")&&(Object.assign(b,structuredClone(O)),await R(),location.reload())};let T=_("Colors");C(T,"Background","colors.background","--bd-bg");let N;C(T,"Primary","colors.primary","--bd-primary",n=>{N.setValue(n),S("colorFront",n)}),C(T,"Text","colors.text","--bd-text"),C(T,"CTA Background","colors.ctaBackground","--bd-cta-bg"),C(T,"CTA Text","colors.ctaText","--bd-cta-text"),N=C(T,"Shader Front","colors.shaderFront",null,n=>S("colorFront",n));let y=_("Typography");D(y,"Primary Font","typography.primaryFont","--bd-font-primary"),D(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-sub-size"),g(y,"Sub LH","typography.subHeadline.lineHeightPx",12,80,1,"--bd-sub-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");let x=_("Spacing");g(x,"Header Pad X","spacing.headerPaddingX",0,80,1,"--bd-header-px"),g(x,"Header Pad Y","spacing.headerPaddingY",0,60,1,"--bd-header-py"),g(x,"Hero Pad Top","spacing.heroPaddingTop",0,120,1,"--bd-hero-pt"),g(x,"Hero Pad Bottom","spacing.heroPaddingBottom",0,120,1,"--bd-hero-pb"),g(x,"Hero Pad X","spacing.heroPaddingX",0,120,1,"--bd-hero-px"),g(x,"Nav Gap","spacing.navGap",0,100,1,"--bd-nav-gap"),g(x,"CTA Pad X","spacing.ctaPaddingX",0,60,1,"--bd-cta-px"),g(x,"CTA Pad Y","spacing.ctaPaddingY",0,30,1,"--bd-cta-py"),E(x,"CTA Radius","spacing.ctaBorderRadius",0,32,1,n=>v("--bd-cta-radius",`${(n/16).toFixed(4)}rem`));let F=_("Shader");B(F,"Shape","shader.shape",["warp","simplex","dots","wave","ripple","swirl","sphere"],n=>S("shape",n)),B(F,"Dither Type","shader.type",["random","2x2","4x4","8x8"],n=>S("type",n)),E(F,"Speed","shader.speed",0,2,.01,n=>S("speed",n)),E(F,"Scale","shader.scale",.1,5,.01,n=>S("scale",n)),E(F,"Dither Size","shader.size",.5,10,.1,n=>S("size",n));let I=_("Opacity");E(I,"Nav Links","opacity.navLinks",0,1,.01,n=>v("--bd-nav-opacity",n));let p=document.createElement("button");p.id="bd-tuner-commit",p.textContent="Commit Changes";let M=null;async function R(){let n=JSON.stringify(b,null,2);try{let t=await fetch("/__bluedither/commit",{method:"POST",headers:{"Content-Type":"application/json"},body:n});if(t.ok&&(await t.json()).ok)return"saved"}catch{}try{let t=await fetch("http://localhost:3344/commit",{method:"POST",headers:{"Content-Type":"application/json"},body:n});if(t.ok&&(await t.json()).ok)return"saved"}catch{}if(H)try{let t=await fetch("/commit",{method:"POST",headers:{"Content-Type":"application/json"},body:n});if(t.ok&&(await t.json()).ok)return"saved"}catch{}if(window.showSaveFilePicker)try{M||(M=await window.showSaveFilePicker({suggestedName:"tokens.json",types:[{description:"JSON",accept:{"application/json":[".json"]}}]}));let t=await M.createWritable();return await t.write(n),await t.close(),"saved"}catch(t){if(t.name==="AbortError")return"cancelled";M=null}let r=new Blob([n],{type:"application/json"}),e=URL.createObjectURL(r),i=document.createElement("a");return i.href=e,i.download="tokens.json",i.click(),URL.revokeObjectURL(e),"downloaded"}p.onclick=async()=>{p.textContent="Saving...",p.disabled=!0;try{let n=await R();if(n==="cancelled"){p.textContent="Commit Changes",p.disabled=!1;return}p.textContent=n==="saved"?"Saved!":"Downloaded!",p.classList.add("success"),setTimeout(()=>{p.textContent="Commit Changes",p.classList.remove("success"),p.disabled=!1},2e3)}catch(n){p.textContent="Error: "+n.message,p.disabled=!1,setTimeout(()=>{p.textContent="Commit Changes"},3e3)}},f.appendChild(p)}var A=document.createElement("style");A.textContent=`/* =============================================
|
|
40
40
|
BlueDither Fine-Tuner \u2014 Overlay Panel v2
|
|
41
41
|
============================================= */
|
|
42
42
|
|
package/fine-tuner/inject.js
CHANGED
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
|
|
60
60
|
// Typography
|
|
61
61
|
if (t.typography) {
|
|
62
|
-
if (t.typography.primaryFont) root.style.setProperty('--bd-font-primary', t.typography.primaryFont);
|
|
63
|
-
if (t.typography.secondaryFont) root.style.setProperty('--bd-font-secondary', t.typography.secondaryFont);
|
|
62
|
+
if (t.typography.primaryFont) root.style.setProperty('--bd-font-primary', `"${t.typography.primaryFont}", system-ui, sans-serif`);
|
|
63
|
+
if (t.typography.secondaryFont) root.style.setProperty('--bd-font-secondary', `"${t.typography.secondaryFont}", system-ui, sans-serif`);
|
|
64
64
|
if (t.typography.headline) {
|
|
65
65
|
root.style.setProperty('--bd-headline-size', pxToClamp(t.typography.headline.referencePx));
|
|
66
66
|
root.style.setProperty('--bd-headline-lh', pxToClamp(t.typography.headline.lineHeightPx));
|
package/fine-tuner/tuner.js
CHANGED
|
@@ -379,7 +379,7 @@ addPxField(spacingSection, 'Hero Pad X', 'spacing.heroPaddingX', 0, 120, 1, '--b
|
|
|
379
379
|
addPxField(spacingSection, 'Nav Gap', 'spacing.navGap', 0, 100, 1, '--bd-nav-gap');
|
|
380
380
|
addPxField(spacingSection, 'CTA Pad X', 'spacing.ctaPaddingX', 0, 60, 1, '--bd-cta-px');
|
|
381
381
|
addPxField(spacingSection, 'CTA Pad Y', 'spacing.ctaPaddingY', 0, 30, 1, '--bd-cta-py');
|
|
382
|
-
|
|
382
|
+
addRange(spacingSection, 'CTA Radius', 'spacing.ctaBorderRadius', 0, 32, 1, (v) => setCSSVar('--bd-cta-radius', `${(v / 16).toFixed(4)}rem`));
|
|
383
383
|
|
|
384
384
|
// ── Shader ──
|
|
385
385
|
const shaderSection = addSection('Shader');
|
package/package.json
CHANGED
|
@@ -130,7 +130,14 @@ Note: The shader uses `ShaderMount` directly from `paper-shaders-bundle.js` —
|
|
|
130
130
|
The `window.__BD_SHADER__` assignment is **required** — the tuner calls `updateParams()` on it for live shader editing. The ShaderMount instance has `setUniforms()` and `setSpeed()` methods. The tuner's `updateParams` maps token names to uniform names (e.g., `colorFront` → `u_colorFront`).
|
|
131
131
|
|
|
132
132
|
### `src/bluedither.css`
|
|
133
|
-
All CSS custom properties and `.bd-*` class rules.
|
|
133
|
+
All CSS custom properties and `.bd-*` class rules. The CSS variable names MUST match exactly:
|
|
134
|
+
|
|
135
|
+
**Colors:** `--bd-bg`, `--bd-primary`, `--bd-text`, `--bd-cta-bg`, `--bd-cta-text`
|
|
136
|
+
**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`
|
|
137
|
+
**Spacing:** `--bd-header-px`, `--bd-header-py`, `--bd-hero-pt`, `--bd-hero-pb`, `--bd-hero-px`, `--bd-nav-gap`, `--bd-cta-px`, `--bd-cta-py`, `--bd-cta-radius`
|
|
138
|
+
**Opacity:** `--bd-nav-opacity`
|
|
139
|
+
|
|
140
|
+
Using different variable names will break live updates from the tuner.
|
|
134
141
|
|
|
135
142
|
## Vite Config
|
|
136
143
|
|
|
@@ -28,20 +28,105 @@ Include `<link>` tags for `typography.primaryFont` and `typography.secondaryFont
|
|
|
28
28
|
```
|
|
29
29
|
URL-encode the font family name.
|
|
30
30
|
|
|
31
|
+
## CRITICAL: File Placement Rules
|
|
32
|
+
|
|
33
|
+
**In project root (or `src/`):**
|
|
34
|
+
- `index.html` — the rendered page
|
|
35
|
+
- `paper-shaders-bundle.js` — copied from `bluedither/shaders/`
|
|
36
|
+
|
|
37
|
+
**In `public/bluedither/` (for Vite) or alongside `index.html` (for non-Vite):**
|
|
38
|
+
- `bluedither-tuner.js` — tuner panel
|
|
39
|
+
- `bluedither-tuner.css` — tuner styles
|
|
40
|
+
- `bluedither-tuner-inject.js` — tuner loader
|
|
41
|
+
- `tokens.json` — design tokens (read at runtime by tuner)
|
|
42
|
+
- `tokens.defaults.json` — default tokens
|
|
43
|
+
- `dev-middleware.js` — Vite commit endpoint
|
|
44
|
+
|
|
45
|
+
Copy tuner assets:
|
|
46
|
+
```bash
|
|
47
|
+
mkdir -p public/bluedither
|
|
48
|
+
cp bluedither/bluedither-tuner.js public/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/
|
|
54
|
+
```
|
|
55
|
+
|
|
31
56
|
## Shader Module
|
|
32
57
|
|
|
33
|
-
Copy `
|
|
58
|
+
Copy `bluedither/shaders/paper-shaders-bundle.js` to the project root (or `src/`).
|
|
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()`.
|
|
34
61
|
|
|
35
|
-
Import and initialize inline. **CRITICAL**: Assign to `window.__BD_SHADER__` so the tuner can update shader params live:
|
|
36
62
|
```js
|
|
37
|
-
import { ShaderMount, ditheringFragmentShader, getShaderColorFromString } from './paper-shaders-bundle.js';
|
|
63
|
+
import { ShaderMount, ditheringFragmentShader, DitheringShapes, DitheringTypes, getShaderColorFromString } from './paper-shaders-bundle.js';
|
|
64
|
+
|
|
65
|
+
const parent = document.getElementById('bd-shader-parent');
|
|
66
|
+
const mount = new ShaderMount(parent, ditheringFragmentShader, {
|
|
67
|
+
u_colorFront: getShaderColorFromString('#005A6A'),
|
|
68
|
+
u_colorBack: getShaderColorFromString('#00000000'),
|
|
69
|
+
u_shape: DitheringShapes.ripple,
|
|
70
|
+
u_type: DitheringTypes['2x2'],
|
|
71
|
+
u_pxSize: 4.2,
|
|
72
|
+
u_scale: 1,
|
|
73
|
+
u_rotation: 180,
|
|
74
|
+
u_originX: 0.5, u_originY: 0.5,
|
|
75
|
+
u_worldWidth: 0, u_worldHeight: 0,
|
|
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
|
+
};
|
|
94
|
+
```
|
|
38
95
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
96
|
+
## Tuner Loading (Dev-Only)
|
|
97
|
+
|
|
98
|
+
Add this after the shader script to load the tuner in dev mode:
|
|
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>
|
|
42
107
|
```
|
|
43
108
|
|
|
44
|
-
|
|
109
|
+
For non-Vite vanilla projects (opened via file:// or a simple HTTP server), use localhost detection instead:
|
|
110
|
+
```html
|
|
111
|
+
<script type="module">
|
|
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>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## CSS Custom Properties
|
|
121
|
+
|
|
122
|
+
The CSS variable names MUST match exactly what the tuner expects:
|
|
123
|
+
|
|
124
|
+
**Colors:** `--bd-bg`, `--bd-primary`, `--bd-text`, `--bd-cta-bg`, `--bd-cta-text`
|
|
125
|
+
**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`
|
|
126
|
+
**Spacing:** `--bd-header-px`, `--bd-header-py`, `--bd-hero-pt`, `--bd-hero-pb`, `--bd-hero-px`, `--bd-nav-gap`, `--bd-cta-px`, `--bd-cta-py`, `--bd-cta-radius`
|
|
127
|
+
**Opacity:** `--bd-nav-opacity`
|
|
128
|
+
|
|
129
|
+
Using different variable names will break live updates from the tuner.
|
|
45
130
|
|
|
46
131
|
## File Output
|
|
47
132
|
|
package/theme/generators/vue.md
CHANGED
|
@@ -2,6 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
When the target project uses **Vue** (detected via `vue` in package.json dependencies), follow these patterns.
|
|
4
4
|
|
|
5
|
+
## CRITICAL: File Placement Rules
|
|
6
|
+
|
|
7
|
+
**In `src/` (importable source files):**
|
|
8
|
+
- All `.vue` component files
|
|
9
|
+
- `bluedither.css` — CSS custom properties + rules
|
|
10
|
+
- `paper-shaders-bundle.js` — copied from `bluedither/shaders/`
|
|
11
|
+
- Any other JS modules that are `import`-ed
|
|
12
|
+
|
|
13
|
+
**In `public/bluedither/` (static assets loaded at runtime):**
|
|
14
|
+
- `bluedither-tuner.js` — tuner panel
|
|
15
|
+
- `bluedither-tuner.css` — tuner styles
|
|
16
|
+
- `bluedither-tuner-inject.js` — tuner loader
|
|
17
|
+
- `tokens.json` — design tokens (read at runtime by tuner)
|
|
18
|
+
- `tokens.defaults.json` — default tokens
|
|
19
|
+
- `dev-middleware.js` — Vite commit endpoint
|
|
20
|
+
|
|
21
|
+
**NEVER import JS files from `public/` — Vite will error.** Shader files MUST be in `src/`.
|
|
22
|
+
|
|
23
|
+
Copy shader files during generation:
|
|
24
|
+
```bash
|
|
25
|
+
cp bluedither/shaders/paper-shaders-bundle.js src/paper-shaders-bundle.js
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Copy tuner assets to public:
|
|
29
|
+
```bash
|
|
30
|
+
mkdir -p public/bluedither
|
|
31
|
+
cp bluedither/bluedither-tuner.js public/bluedither/
|
|
32
|
+
cp bluedither/bluedither-tuner.css public/bluedither/
|
|
33
|
+
cp bluedither/bluedither-tuner-inject.js public/bluedither/
|
|
34
|
+
cp bluedither/tokens.json public/bluedither/
|
|
35
|
+
cp bluedither/tokens.defaults.json public/bluedither/
|
|
36
|
+
cp bluedither/dev-middleware.js public/bluedither/
|
|
37
|
+
```
|
|
38
|
+
|
|
5
39
|
## Detection
|
|
6
40
|
|
|
7
41
|
Vue is detected when `package.json` contains `vue` in `dependencies` or `devDependencies`. Also check for:
|
|
@@ -10,14 +44,26 @@ Vue is detected when `package.json` contains `vue` in `dependencies` or `devDepe
|
|
|
10
44
|
|
|
11
45
|
## Component Structure
|
|
12
46
|
|
|
13
|
-
Generate Vue Single File Components (SFCs) using `<script setup>` syntax (Vue 3)
|
|
47
|
+
Generate Vue Single File Components (SFCs) using `<script setup>` syntax (Vue 3).
|
|
48
|
+
|
|
49
|
+
### `src/App.vue` (or modify existing)
|
|
50
|
+
Root component wrapping the full layout. Loads tuner in dev mode.
|
|
14
51
|
|
|
15
|
-
### `BlueDitherTheme.vue`
|
|
16
|
-
Root component:
|
|
17
52
|
```vue
|
|
18
53
|
<script setup>
|
|
19
|
-
import
|
|
20
|
-
import BlueDitherHero from './BlueDitherHero.vue';
|
|
54
|
+
import { onMounted } from 'vue';
|
|
55
|
+
import BlueDitherHero from './components/BlueDitherHero.vue';
|
|
56
|
+
import BlueDitherHeader from './components/BlueDitherHeader.vue';
|
|
57
|
+
import './bluedither.css';
|
|
58
|
+
|
|
59
|
+
// Load tuner in dev mode only
|
|
60
|
+
onMounted(() => {
|
|
61
|
+
if (import.meta.env.DEV) {
|
|
62
|
+
const s = document.createElement('script');
|
|
63
|
+
s.src = '/bluedither/bluedither-tuner-inject.js';
|
|
64
|
+
document.body.appendChild(s);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
21
67
|
</script>
|
|
22
68
|
|
|
23
69
|
<template>
|
|
@@ -26,17 +72,16 @@ import BlueDitherHero from './BlueDitherHero.vue';
|
|
|
26
72
|
<BlueDitherHeader />
|
|
27
73
|
</div>
|
|
28
74
|
</template>
|
|
29
|
-
|
|
30
|
-
<style src="./bluedither.css"></style>
|
|
31
75
|
```
|
|
32
76
|
|
|
33
|
-
### `BlueDitherHeader.vue`
|
|
77
|
+
### `src/components/BlueDitherHeader.vue`
|
|
34
78
|
Navbar with logo, nav items, CTA:
|
|
35
79
|
```vue
|
|
36
80
|
<script setup>
|
|
81
|
+
// Content from tokens.json content section
|
|
82
|
+
const companyName = 'COMPANYLOGO';
|
|
37
83
|
const navItems = ['HOME', 'FEATURES', 'FAQ', 'CONTACT'];
|
|
38
84
|
const ctaText = 'SIGN UP';
|
|
39
|
-
const companyName = 'COMPANYLOGO';
|
|
40
85
|
</script>
|
|
41
86
|
|
|
42
87
|
<template>
|
|
@@ -54,25 +99,100 @@ const companyName = 'COMPANYLOGO';
|
|
|
54
99
|
</template>
|
|
55
100
|
```
|
|
56
101
|
|
|
57
|
-
### `BlueDitherHero.vue`
|
|
58
|
-
Hero section with shader initialization
|
|
102
|
+
### `src/components/BlueDitherHero.vue`
|
|
103
|
+
Hero section with shader initialization. **CRITICAL:** The `window.__BD_SHADER__` wrapper is required — the tuner calls `updateParams()` on it, but ShaderMount only has `setUniforms()` and `setSpeed()`. You MUST create the translation wrapper.
|
|
104
|
+
|
|
59
105
|
```vue
|
|
60
106
|
<script setup>
|
|
61
107
|
import { ref, onMounted, onUnmounted } from 'vue';
|
|
62
108
|
|
|
109
|
+
// Content from tokens.json
|
|
110
|
+
const headline = 'AN ABSOLUTELY\nFANTASTIC HEADLINE.';
|
|
111
|
+
const subHeadline = "Here's a fantastic sub-headline that is sure to impressive your friends.";
|
|
112
|
+
|
|
113
|
+
// Shader params from tokens.json
|
|
114
|
+
const SHADER_PARAMS = {
|
|
115
|
+
colorFront: '#005A6A',
|
|
116
|
+
colorBack: '#00000000',
|
|
117
|
+
shape: 'ripple',
|
|
118
|
+
type: '2x2',
|
|
119
|
+
size: 4.2,
|
|
120
|
+
scale: 1,
|
|
121
|
+
speed: 0.41,
|
|
122
|
+
rotation: 180,
|
|
123
|
+
};
|
|
124
|
+
|
|
63
125
|
const shaderParent = ref(null);
|
|
64
126
|
let shaderMount = null;
|
|
65
127
|
|
|
66
128
|
onMounted(async () => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
129
|
+
if (!shaderParent.value) return;
|
|
130
|
+
const {
|
|
131
|
+
ShaderMount,
|
|
132
|
+
ditheringFragmentShader,
|
|
133
|
+
DitheringShapes,
|
|
134
|
+
DitheringTypes,
|
|
135
|
+
getShaderColorFromString,
|
|
136
|
+
} = await import('../paper-shaders-bundle.js');
|
|
137
|
+
|
|
138
|
+
shaderMount = new ShaderMount(
|
|
139
|
+
shaderParent.value,
|
|
140
|
+
ditheringFragmentShader,
|
|
141
|
+
{
|
|
142
|
+
u_colorFront: getShaderColorFromString(SHADER_PARAMS.colorFront),
|
|
143
|
+
u_colorBack: getShaderColorFromString(SHADER_PARAMS.colorBack),
|
|
144
|
+
u_shape: DitheringShapes[SHADER_PARAMS.shape] ?? DitheringShapes.warp,
|
|
145
|
+
u_type: DitheringTypes[SHADER_PARAMS.type] ?? DitheringTypes['2x2'],
|
|
146
|
+
u_pxSize: SHADER_PARAMS.size,
|
|
147
|
+
u_scale: SHADER_PARAMS.scale,
|
|
148
|
+
u_rotation: SHADER_PARAMS.rotation,
|
|
149
|
+
u_originX: 0.5,
|
|
150
|
+
u_originY: 0.5,
|
|
151
|
+
u_worldWidth: 0,
|
|
152
|
+
u_worldHeight: 0,
|
|
153
|
+
u_fit: 0,
|
|
154
|
+
u_offsetX: 0,
|
|
155
|
+
u_offsetY: 0,
|
|
156
|
+
},
|
|
157
|
+
{ alpha: true, premultipliedAlpha: false },
|
|
158
|
+
SHADER_PARAMS.speed,
|
|
159
|
+
0,
|
|
160
|
+
2
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// CRITICAL: expose wrapper with updateParams for the tuner
|
|
164
|
+
// ShaderMount has setUniforms() and setSpeed() — NOT updateParams()
|
|
165
|
+
// The tuner calls updateParams() with token names, this translates to uniform names
|
|
166
|
+
window.__BD_SHADER__ = {
|
|
167
|
+
updateParams(params) {
|
|
168
|
+
const uniforms = {};
|
|
169
|
+
if (params.colorFront !== undefined)
|
|
170
|
+
uniforms.u_colorFront = getShaderColorFromString(params.colorFront);
|
|
171
|
+
if (params.colorBack !== undefined)
|
|
172
|
+
uniforms.u_colorBack = getShaderColorFromString(params.colorBack);
|
|
173
|
+
if (params.shape !== undefined)
|
|
174
|
+
uniforms.u_shape = DitheringShapes[params.shape] ?? DitheringShapes.warp;
|
|
175
|
+
if (params.type !== undefined)
|
|
176
|
+
uniforms.u_type = DitheringTypes[params.type] ?? DitheringTypes['2x2'];
|
|
177
|
+
if (params.size !== undefined) uniforms.u_pxSize = params.size;
|
|
178
|
+
if (params.scale !== undefined) uniforms.u_scale = params.scale;
|
|
179
|
+
if (params.rotation !== undefined)
|
|
180
|
+
uniforms.u_rotation = parseFloat(params.rotation) || 180;
|
|
181
|
+
if (Object.keys(uniforms).length > 0) shaderMount.setUniforms(uniforms);
|
|
182
|
+
if (params.speed !== undefined) shaderMount.setSpeed(params.speed);
|
|
183
|
+
},
|
|
184
|
+
dispose() {
|
|
185
|
+
shaderMount?.dispose();
|
|
186
|
+
},
|
|
187
|
+
};
|
|
70
188
|
});
|
|
71
189
|
|
|
72
190
|
onUnmounted(() => {
|
|
73
|
-
|
|
191
|
+
window.__BD_SHADER__?.dispose();
|
|
74
192
|
window.__BD_SHADER__ = null;
|
|
75
193
|
});
|
|
194
|
+
|
|
195
|
+
const headlineLines = headline.split('\n');
|
|
76
196
|
</script>
|
|
77
197
|
|
|
78
198
|
<template>
|
|
@@ -80,14 +200,40 @@ onUnmounted(() => {
|
|
|
80
200
|
<div class="bd-hero-inner">
|
|
81
201
|
<div ref="shaderParent" class="bd-shader-layer"></div>
|
|
82
202
|
<div class="bd-hero-content">
|
|
83
|
-
<div class="bd-headline"
|
|
84
|
-
|
|
203
|
+
<div class="bd-headline">
|
|
204
|
+
<template v-for="(line, i) in headlineLines" :key="i">
|
|
205
|
+
<br v-if="i > 0" />{{ line }}
|
|
206
|
+
</template>
|
|
207
|
+
</div>
|
|
208
|
+
<div class="bd-subheadline">{{ subHeadline }}</div>
|
|
85
209
|
</div>
|
|
86
210
|
</div>
|
|
87
211
|
</div>
|
|
88
212
|
</template>
|
|
89
213
|
```
|
|
90
214
|
|
|
215
|
+
Note: The shader uses `ShaderMount` directly from `paper-shaders-bundle.js` — do NOT create a separate shader wrapper module. Import `ShaderMount`, `ditheringFragmentShader`, `DitheringShapes`, `DitheringTypes`, and `getShaderColorFromString` directly.
|
|
216
|
+
|
|
217
|
+
The `window.__BD_SHADER__` wrapper is **required** — the tuner calls `updateParams()` on it for live shader editing. The ShaderMount instance has `setUniforms()` and `setSpeed()` methods. The wrapper translates token names to uniform names (e.g., `colorFront` → `u_colorFront`).
|
|
218
|
+
|
|
219
|
+
### `src/bluedither.css`
|
|
220
|
+
All CSS custom properties and `.bd-*` class rules. See `bluedither/template/index.html` for the exact variable names and class styles. The CSS variable names MUST match exactly:
|
|
221
|
+
|
|
222
|
+
**Colors:** `--bd-bg`, `--bd-primary`, `--bd-text`, `--bd-cta-bg`, `--bd-cta-text`
|
|
223
|
+
**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`
|
|
224
|
+
**Spacing:** `--bd-header-px`, `--bd-header-py`, `--bd-hero-pt`, `--bd-hero-pb`, `--bd-hero-px`, `--bd-nav-gap`, `--bd-cta-px`, `--bd-cta-py`, `--bd-cta-radius`
|
|
225
|
+
**Opacity:** `--bd-nav-opacity`
|
|
226
|
+
|
|
227
|
+
Using different variable names will break live updates from the tuner.
|
|
228
|
+
|
|
229
|
+
## Vite Config
|
|
230
|
+
|
|
231
|
+
The `npx bluedither install` command auto-wires the Vite plugin. If it wasn't auto-wired, add manually:
|
|
232
|
+
```js
|
|
233
|
+
import { bluedither } from './bluedither/dev-middleware.js'
|
|
234
|
+
export default defineConfig({ plugins: [vue(), bluedither()] })
|
|
235
|
+
```
|
|
236
|
+
|
|
91
237
|
## TypeScript
|
|
92
238
|
|
|
93
239
|
If TypeScript is detected, add `lang="ts"` to `<script setup lang="ts">`. Type the template ref:
|
|
@@ -95,12 +241,26 @@ If TypeScript is detected, add `lang="ts"` to `<script setup lang="ts">`. Type t
|
|
|
95
241
|
const shaderParent = ref<HTMLDivElement | null>(null);
|
|
96
242
|
```
|
|
97
243
|
|
|
244
|
+
## Nuxt
|
|
245
|
+
|
|
246
|
+
If Nuxt is detected:
|
|
247
|
+
- Place components in `components/` directory (auto-imported)
|
|
248
|
+
- Use `useHead()` for font links
|
|
249
|
+
- Use `process.client` for shader initialization
|
|
250
|
+
- Use `process.dev` for tuner loading
|
|
251
|
+
|
|
252
|
+
## Content Handling
|
|
253
|
+
|
|
254
|
+
- Inject content tokens directly as Vue template text
|
|
255
|
+
- Headline newlines: split and use `v-for` with `<br>` (see example above)
|
|
256
|
+
- Nav items: `v-for="item in navItems"`
|
|
257
|
+
|
|
98
258
|
## File Output
|
|
99
259
|
|
|
100
260
|
| File | Contents |
|
|
101
261
|
|------|----------|
|
|
102
|
-
| `
|
|
103
|
-
| `BlueDitherHeader.vue` | Navbar SFC |
|
|
104
|
-
| `BlueDitherHero.vue` | Hero + shader SFC |
|
|
105
|
-
| `bluedither.css` | CSS custom properties + rules |
|
|
106
|
-
| `paper-shaders-bundle.js` | Shader library (copied) |
|
|
262
|
+
| `src/App.vue` | Root layout SFC (or modify existing) |
|
|
263
|
+
| `src/components/BlueDitherHeader.vue` | Navbar SFC |
|
|
264
|
+
| `src/components/BlueDitherHero.vue` | Hero + shader SFC |
|
|
265
|
+
| `src/bluedither.css` | CSS custom properties + rules |
|
|
266
|
+
| `src/paper-shaders-bundle.js` | Shader library (copied from bluedither/shaders/) |
|