| 1 |
( function () { |
| 2 |
var el = React.createElement; |
| 3 |
var registerBlockType = wp.blocks.registerBlockType; |
| 4 |
var useBlockProps = wp.blockEditor.useBlockProps; |
| 5 |
var InspectorControls = wp.blockEditor.InspectorControls; |
| 6 |
var MediaUpload = wp.blockEditor.MediaUpload; |
| 7 |
var PanelBody = wp.components.PanelBody; |
| 8 |
var RangeControl = wp.components.RangeControl; |
| 9 |
var SelectControl = wp.components.SelectControl; |
| 10 |
var TextControl = wp.components.TextControl; |
| 11 |
var TextareaControl = wp.components.TextareaControl; |
| 12 |
var ToggleControl = wp.components.ToggleControl; |
| 13 |
var Button = wp.components.Button; |
| 14 |
var PanelColorSettings = wp.blockEditor.PanelColorSettings; |
| 15 |
var useRef = wp.element.useRef; |
| 16 |
var useEffect = wp.element.useEffect; |
| 17 |
|
| 18 |
var _tc, _tv; |
| 19 |
function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); } |
| 20 |
function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); } |
| 21 |
|
| 22 |
var TAG_OPTS = ['h1','h2','h3','h4','p'].map(function(t){return{value:t,label:t.toUpperCase()};}); |
| 23 |
var BG_OPTS = [{value:'solid',label:'Solid Color'},{value:'gradient',label:'Gradient'},{value:'image',label:'Image'}]; |
| 24 |
var ALIGN_OPTS = [{value:'left',label:'Left'},{value:'center',label:'Center'},{value:'right',label:'Right'}]; |
| 25 |
var TINT_OPTS = [{value:'dark',label:'Dark (shadows)'},{value:'light',label:'Light (halation)'},{value:'colored',label:'Colored (tinted)'}]; |
| 26 |
|
| 27 |
function buildBg(a) { |
| 28 |
if (a.bgType === 'gradient') return 'linear-gradient('+a.bgAngle+'deg, '+(a.bgColor||'#0a0a0f')+', '+(a.bgColor2||'#1e1b4b')+')'; |
| 29 |
return a.bgColor || '#0a0a0f'; |
| 30 |
} |
| 31 |
|
| 32 |
registerBlockType('blockenberg/noise-section', { |
| 33 |
title: 'Noise / Grain Section', |
| 34 |
icon: 'image-flip-horizontal', |
| 35 |
category: 'bkbg-effects', |
| 36 |
|
| 37 |
edit: function (props) { |
| 38 |
var attr = props.attributes; var setAttr = props.setAttributes; |
| 39 |
var previewRef = useRef(null); |
| 40 |
var canvasRef = useRef(null); |
| 41 |
var rafRef = useRef(null); |
| 42 |
|
| 43 |
useEffect(function () { |
| 44 |
var previewEl = previewRef.current; |
| 45 |
var canvas = canvasRef.current; |
| 46 |
if (!previewEl || !canvas) return; |
| 47 |
|
| 48 |
var ctx = canvas.getContext('2d'); |
| 49 |
if (!ctx) return; |
| 50 |
|
| 51 |
var pixelRatio = window.devicePixelRatio || 1; |
| 52 |
var fps = Math.max(1, attr.noiseFps || 12); |
| 53 |
var frameInterval = 1000 / fps; |
| 54 |
var scale = Math.max(0.5, attr.noiseScale || 1); |
| 55 |
var opacity = attr.noiseOpacity !== undefined ? attr.noiseOpacity : 0.3; |
| 56 |
var alpha = Math.max(0, Math.min(255, Math.round(opacity * 255))); |
| 57 |
var lastFrame = 0; |
| 58 |
|
| 59 |
var tintR = 0, tintG = 0, tintB = 0; |
| 60 |
if (attr.noiseTint === 'light') { tintR = 255; tintG = 255; tintB = 220; } |
| 61 |
else if (attr.noiseTint === 'colored') { tintR = 120; tintG = 80; tintB = 200; } |
| 62 |
|
| 63 |
function resizeCanvas() { |
| 64 |
var w = previewEl.offsetWidth || 800; |
| 65 |
var h = previewEl.offsetHeight || (attr.sectionHeight || 600); |
| 66 |
canvas.width = Math.ceil(w * pixelRatio / scale); |
| 67 |
canvas.height = Math.ceil(h * pixelRatio / scale); |
| 68 |
canvas.style.width = w + 'px'; |
| 69 |
canvas.style.height = h + 'px'; |
| 70 |
} |
| 71 |
|
| 72 |
function drawNoise(now) { |
| 73 |
if (now - lastFrame < frameInterval) return; |
| 74 |
lastFrame = now; |
| 75 |
|
| 76 |
var w = canvas.width; |
| 77 |
var h = canvas.height; |
| 78 |
var imageData = ctx.createImageData(w, h); |
| 79 |
var data = imageData.data; |
| 80 |
|
| 81 |
for (var i = 0; i < data.length; i += 4) { |
| 82 |
var v = Math.random() * 255 | 0; |
| 83 |
data[i] = tintR || v; |
| 84 |
data[i + 1] = tintG || v; |
| 85 |
data[i + 2] = tintB || v; |
| 86 |
data[i + 3] = alpha; |
| 87 |
} |
| 88 |
ctx.putImageData(imageData, 0, 0); |
| 89 |
} |
| 90 |
|
| 91 |
function loop(now) { |
| 92 |
drawNoise(now); |
| 93 |
rafRef.current = requestAnimationFrame(loop); |
| 94 |
} |
| 95 |
|
| 96 |
function stopLoop() { |
| 97 |
if (rafRef.current) { |
| 98 |
cancelAnimationFrame(rafRef.current); |
| 99 |
rafRef.current = null; |
| 100 |
} |
| 101 |
} |
| 102 |
|
| 103 |
resizeCanvas(); |
| 104 |
stopLoop(); |
| 105 |
rafRef.current = requestAnimationFrame(loop); |
| 106 |
window.addEventListener('resize', resizeCanvas); |
| 107 |
|
| 108 |
return function () { |
| 109 |
stopLoop(); |
| 110 |
window.removeEventListener('resize', resizeCanvas); |
| 111 |
}; |
| 112 |
}, [attr.noiseOpacity, attr.noiseTint, attr.noiseFps, attr.noiseScale, attr.sectionHeight, attr.bgType, attr.bgImage, attr.bgImageOpacity, attr.bgColor, attr.bgColor2, attr.bgAngle]); |
| 113 |
|
| 114 |
var preview = el('div', { |
| 115 |
ref: previewRef, |
| 116 |
style: { |
| 117 |
position:'relative', |
| 118 |
minHeight:(attr.sectionHeight||600)+'px', |
| 119 |
background: buildBg(attr), |
| 120 |
display:'flex', alignItems:'center', justifyContent: attr.textAlign==='center'?'center':attr.textAlign==='right'?'flex-end':'flex-start', |
| 121 |
padding:(attr.paddingTop||100)+'px clamp(24px,5vw,80px) '+(attr.paddingBottom||100)+'px', |
| 122 |
overflow:'hidden', |
| 123 |
borderRadius:8 |
| 124 |
} |
| 125 |
}, |
| 126 |
/* Background image layer */ |
| 127 |
attr.bgType === 'image' && attr.bgImage && el('div', { |
| 128 |
style: { position:'absolute', inset:0, backgroundImage:'url('+attr.bgImage+')', backgroundSize:'cover', backgroundPosition:'center', opacity:attr.bgImageOpacity||0.5, zIndex:0 } |
| 129 |
}), |
| 130 |
/* Grain overlay preview (static version) */ |
| 131 |
el('canvas', { |
| 132 |
ref: canvasRef, |
| 133 |
style: { position:'absolute', inset:0, zIndex:1, pointerEvents:'none' } |
| 134 |
}), |
| 135 |
/* Content */ |
| 136 |
el('div', { className: 'bkbg-ns-content', style: { position:'relative', zIndex:2, maxWidth:(attr.maxWidth||800)+'px', textAlign:attr.textAlign||'center', width:'100%' } }, |
| 137 |
el(attr.tag||'h1', { |
| 138 |
className: 'bkbg-ns-heading', |
| 139 |
style: { color:attr.textColor||'#fff', margin:'0 0 24px' } |
| 140 |
}, attr.heading), |
| 141 |
attr.subtext && el('p', { className: 'bkbg-ns-subtext', style: { color:attr.subtextColor||'#94a3b8', margin:'0 0 36px' } }, attr.subtext), |
| 142 |
el('div', { style: { display:'flex', gap:12, flexWrap:'wrap', justifyContent:attr.textAlign==='center'?'center':'flex-start', alignItems:'center' } }, |
| 143 |
attr.showCta && el('a', { href:attr.ctaUrl||'#', style: { background:attr.ctaBg||'#fff', color:attr.ctaColor||'#0a0a0f', padding:'14px 32px', borderRadius:8, fontWeight:700, fontSize:16, textDecoration:'none' } }, attr.ctaLabel||'Get Started'), |
| 144 |
attr.showCta2 && el('a', { href:attr.ctaUrl2||'#', style: { background:attr.ctaBg2||'transparent', color:attr.ctaColor2||'#fff', border:'1px solid '+(attr.ctaBorder2||'rgba(255,255,255,0.4)'), padding:'14px 32px', borderRadius:8, fontWeight:700, fontSize:16, textDecoration:'none' } }, attr.ctaLabel2||'Learn More') |
| 145 |
) |
| 146 |
) |
| 147 |
); |
| 148 |
|
| 149 |
var blockProps = useBlockProps((function () { |
| 150 |
var _tvf = getTypoCssVars(); |
| 151 |
var s = {}; |
| 152 |
if (_tvf) { Object.assign(s, _tvf(attr.headingTypo, '--bkbg-ns-hd-')); Object.assign(s, _tvf(attr.subtextTypo, '--bkbg-ns-st-')); } |
| 153 |
return { style: s }; |
| 154 |
})()); |
| 155 |
|
| 156 |
return el(React.Fragment, null, |
| 157 |
el(InspectorControls, null, |
| 158 |
el(PanelBody, {title:'Content', initialOpen:true}, |
| 159 |
el(TextControl,{__nextHasNoMarginBottom:true,label:'Heading',value:attr.heading,onChange:function(v){setAttr({heading:v});}}), |
| 160 |
el(SelectControl,{__nextHasNoMarginBottom:true,label:'Heading Tag',value:attr.tag,options:TAG_OPTS,onChange:function(v){setAttr({tag:v});}}), |
| 161 |
el(TextareaControl,{__nextHasNoMarginBottom:true,label:'Subtext',value:attr.subtext,onChange:function(v){setAttr({subtext:v});}}), |
| 162 |
el(ToggleControl,{__nextHasNoMarginBottom:true,label:'Primary CTA',checked:attr.showCta,onChange:function(v){setAttr({showCta:v});}}), |
| 163 |
attr.showCta && el(TextControl,{__nextHasNoMarginBottom:true,label:'CTA Label',value:attr.ctaLabel,onChange:function(v){setAttr({ctaLabel:v});}}), |
| 164 |
attr.showCta && el(TextControl,{__nextHasNoMarginBottom:true,label:'CTA URL',value:attr.ctaUrl,onChange:function(v){setAttr({ctaUrl:v});}}), |
| 165 |
el(ToggleControl,{__nextHasNoMarginBottom:true,label:'Secondary CTA',checked:attr.showCta2,onChange:function(v){setAttr({showCta2:v});}}), |
| 166 |
attr.showCta2 && el(TextControl,{__nextHasNoMarginBottom:true,label:'Secondary Label',value:attr.ctaLabel2,onChange:function(v){setAttr({ctaLabel2:v});}}), |
| 167 |
attr.showCta2 && el(TextControl,{__nextHasNoMarginBottom:true,label:'Secondary URL',value:attr.ctaUrl2,onChange:function(v){setAttr({ctaUrl2:v});}}) |
| 168 |
), |
| 169 |
el(PanelBody, {title:'Background', initialOpen:true}, |
| 170 |
el(SelectControl,{__nextHasNoMarginBottom:true,label:'Background Type',value:attr.bgType,options:BG_OPTS,onChange:function(v){setAttr({bgType:v});}}), |
| 171 |
attr.bgType === 'image' && el(MediaUpload,{ |
| 172 |
onSelect:function(m){setAttr({bgImage:m.url,bgImageId:m.id});}, |
| 173 |
allowedTypes:['image'],value:attr.bgImageId, |
| 174 |
render:function(p){ return el(Button,{variant:'secondary',onClick:p.open},attr.bgImage?'Change Image':'Select Image'); } |
| 175 |
}), |
| 176 |
attr.bgType === 'image' && attr.bgImage && el(RangeControl,{__nextHasNoMarginBottom:true,label:'Image Opacity',value:attr.bgImageOpacity,min:0,max:1,step:0.05,onChange:function(v){setAttr({bgImageOpacity:v});}}), |
| 177 |
attr.bgType === 'gradient' && el(RangeControl,{__nextHasNoMarginBottom:true,label:'Gradient Angle',value:attr.bgAngle,min:0,max:360,onChange:function(v){setAttr({bgAngle:v});}}) |
| 178 |
), |
| 179 |
el(PanelBody, {title:'Noise Settings', initialOpen:true}, |
| 180 |
el(RangeControl,{__nextHasNoMarginBottom:true,label:'Noise Opacity',value:attr.noiseOpacity !== undefined ? attr.noiseOpacity : 0.3,min:0,max:0.5,step:0.01,onChange:function(v){setAttr({noiseOpacity:v});}}), |
| 181 |
el(SelectControl,{__nextHasNoMarginBottom:true,label:'Noise Tint',value:attr.noiseTint,options:TINT_OPTS,onChange:function(v){setAttr({noiseTint:v});}}), |
| 182 |
el(RangeControl,{__nextHasNoMarginBottom:true,label:'FPS (animation speed)',value:attr.noiseFps,min:1,max:60,onChange:function(v){setAttr({noiseFps:v});}}), |
| 183 |
el(RangeControl,{__nextHasNoMarginBottom:true,label:'Grain Scale',value:attr.noiseScale,min:0.5,max:4,step:0.5,onChange:function(v){setAttr({noiseScale:v});}}) |
| 184 |
), |
| 185 |
el(PanelBody, {title:'Typography', initialOpen:false}, |
| 186 |
el(SelectControl,{__nextHasNoMarginBottom:true,label:'Text Align',value:attr.textAlign,options:ALIGN_OPTS,onChange:function(v){setAttr({textAlign:v});}}), |
| 187 |
getTypoControl() && el(getTypoControl(), { label: 'Heading Typography', value: attr.headingTypo || {}, onChange: function (v) { setAttr({ headingTypo: v }); } }), |
| 188 |
getTypoControl() && el(getTypoControl(), { label: 'Subtext Typography', value: attr.subtextTypo || {}, onChange: function (v) { setAttr({ subtextTypo: v }); } }) |
| 189 |
), |
| 190 |
el(PanelBody, {title:'Layout', initialOpen:false}, |
| 191 |
el(RangeControl,{__nextHasNoMarginBottom:true,label:'Section Height (px)',value:attr.sectionHeight,min:200,max:1200,onChange:function(v){setAttr({sectionHeight:v});}}), |
| 192 |
el(RangeControl,{__nextHasNoMarginBottom:true,label:'Content Max Width (px)',value:attr.maxWidth,min:300,max:1600,onChange:function(v){setAttr({maxWidth:v});}}), |
| 193 |
el(RangeControl,{__nextHasNoMarginBottom:true,label:'Padding Top (px)',value:attr.paddingTop,min:0,max:300,onChange:function(v){setAttr({paddingTop:v});}}), |
| 194 |
el(RangeControl,{__nextHasNoMarginBottom:true,label:'Padding Bottom (px)',value:attr.paddingBottom,min:0,max:300,onChange:function(v){setAttr({paddingBottom:v});}}) |
| 195 |
), |
| 196 |
el(PanelColorSettings, { |
| 197 |
title:'Colors',initialOpen:false, |
| 198 |
colorSettings:[ |
| 199 |
{value:attr.bgColor, onChange:function(v){setAttr({bgColor: v||'#0a0a0f'});},label:'Background / Gradient From'}, |
| 200 |
attr.bgType==='gradient' && {value:attr.bgColor2,onChange:function(v){setAttr({bgColor2:v||'#1e1b4b'});},label:'Gradient To'}, |
| 201 |
{value:attr.textColor, onChange:function(v){setAttr({textColor: v||'#ffffff'});},label:'Heading'}, |
| 202 |
{value:attr.subtextColor, onChange:function(v){setAttr({subtextColor: v||'#94a3b8'});},label:'Subtext'}, |
| 203 |
attr.showCta && {value:attr.ctaBg, onChange:function(v){setAttr({ctaBg: v||'#fff'});},label:'CTA Bg'}, |
| 204 |
attr.showCta && {value:attr.ctaColor, onChange:function(v){setAttr({ctaColor: v||'#0a0a0f'});},label:'CTA Text'}, |
| 205 |
attr.showCta2 && {value:attr.ctaBg2, onChange:function(v){setAttr({ctaBg2: v||'transparent'});},label:'CTA 2 Bg'}, |
| 206 |
attr.showCta2 && {value:attr.ctaColor2,onChange:function(v){setAttr({ctaColor2:v||'#fff'});},label:'CTA 2 Text'} |
| 207 |
].filter(Boolean) |
| 208 |
}) |
| 209 |
), |
| 210 |
el('div', blockProps, preview) |
| 211 |
); |
| 212 |
}, |
| 213 |
|
| 214 |
save: function (props) { |
| 215 |
return el('div', useBlockProps.save(), |
| 216 |
el('div', { className:'bkbg-ns-app', 'data-opts':JSON.stringify(props.attributes) }) |
| 217 |
); |
| 218 |
} |
| 219 |
}); |
| 220 |
}() ); |
| 221 |
|