| 1 |
( function () { |
| 2 |
var el = wp.element.createElement; |
| 3 |
var Fragment = wp.element.Fragment; |
| 4 |
var __ = wp.i18n.__; |
| 5 |
var registerBlockType = wp.blocks.registerBlockType; |
| 6 |
var InspectorControls = wp.blockEditor.InspectorControls; |
| 7 |
var useBlockProps = wp.blockEditor.useBlockProps; |
| 8 |
var PanelColorSettings = wp.blockEditor.PanelColorSettings; |
| 9 |
var PanelBody = wp.components.PanelBody; |
| 10 |
var TextControl = wp.components.TextControl; |
| 11 |
var ToggleControl = wp.components.ToggleControl; |
| 12 |
var RangeControl = wp.components.RangeControl; |
| 13 |
var SelectControl = wp.components.SelectControl; |
| 14 |
var useRef = wp.element.useRef; |
| 15 |
var useEffect = wp.element.useEffect; |
| 16 |
|
| 17 |
var _tc, _tv; |
| 18 |
function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); } |
| 19 |
function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); } |
| 20 |
|
| 21 |
var CHAR_SETS = [ |
| 22 |
{ label: 'Matrix (Katakana + digits)', value: 'matrix' }, |
| 23 |
{ label: 'Binary (0 and 1)', value: 'binary' }, |
| 24 |
{ label: 'Hexadecimal', value: 'hex' }, |
| 25 |
{ label: 'Latin alphabet', value: 'latin' }, |
| 26 |
{ label: 'Custom', value: 'custom' }, |
| 27 |
]; |
| 28 |
|
| 29 |
var COLOR_STYLES = [ |
| 30 |
{ label: '🟢 Classic Green / White head', value: 'classic-green' }, |
| 31 |
{ label: '🔵 Cyan / White head', value: 'cyan' }, |
| 32 |
{ label: '🔴 Red / Pink head', value: 'red' }, |
| 33 |
{ label: '🟡 Gold / White head', value: 'gold' }, |
| 34 |
{ label: '🟣 Purple / White head', value: 'purple' }, |
| 35 |
{ label: '🌈 Rainbow columns', value: 'rainbow' }, |
| 36 |
{ label: '🎨 Custom colors', value: 'custom' }, |
| 37 |
]; |
| 38 |
|
| 39 |
var STYLE_COLORS = { |
| 40 |
'classic-green': { primary: '#00ff41', head: '#ffffff', bg: '#000000' }, |
| 41 |
'cyan': { primary: '#00e5ff', head: '#ffffff', bg: '#000a0d' }, |
| 42 |
'red': { primary: '#ff0040', head: '#ffaaaa', bg: '#0d0000' }, |
| 43 |
'gold': { primary: '#ffcc00', head: '#ffffff', bg: '#080600' }, |
| 44 |
'purple': { primary: '#cc44ff', head: '#ffffff', bg: '#060008' }, |
| 45 |
'rainbow': { primary: '#00ff41', head: '#ffffff', bg: '#000000' }, |
| 46 |
'custom': null, |
| 47 |
}; |
| 48 |
|
| 49 |
function applyStyle(val, set) { |
| 50 |
var c = STYLE_COLORS[val]; |
| 51 |
if (c) set({ colorStyle: val, primaryColor: c.primary, headColor: c.head, bgColor: c.bg }); |
| 52 |
else set({ colorStyle: val }); |
| 53 |
} |
| 54 |
|
| 55 |
/* live canvas preview in editor */ |
| 56 |
function MatrixCanvas(props) { |
| 57 |
var a = props.attributes; |
| 58 |
var canRef = useRef(null); |
| 59 |
|
| 60 |
useEffect(function () { |
| 61 |
var canvas = canRef.current; |
| 62 |
if (!canvas) return; |
| 63 |
var ctx = canvas.getContext('2d'); |
| 64 |
canvas.width = canvas.offsetWidth || 600; |
| 65 |
canvas.height = a.height; |
| 66 |
|
| 67 |
var cols = Math.floor(canvas.width / (a.fontSize || 16)); |
| 68 |
var drops = []; |
| 69 |
for (var i = 0; i < cols; i++) { |
| 70 |
drops[i] = a.randomStart ? Math.floor(Math.random() * -canvas.height / a.fontSize) : 0; |
| 71 |
} |
| 72 |
|
| 73 |
var chars = getChars(a); |
| 74 |
var raf; |
| 75 |
var lastTime = 0; |
| 76 |
|
| 77 |
function draw(ts) { |
| 78 |
var interval = 1000 / ((a.speed || 50) / 10 + 5); |
| 79 |
if (ts - lastTime > interval) { |
| 80 |
lastTime = ts; |
| 81 |
ctx.fillStyle = hexToRgba(a.bgColor || '#000', (a.bgOpacity || 8) / 100); |
| 82 |
ctx.fillRect(0, 0, canvas.width, canvas.height); |
| 83 |
for (var i = 0; i < drops.length; i++) { |
| 84 |
var ch = chars[Math.floor(Math.random() * chars.length)]; |
| 85 |
var y = drops[i] * (a.fontSize || 16); |
| 86 |
if (y > 0) { |
| 87 |
ctx.font = (a.fontSize || 16) + 'px monospace'; |
| 88 |
ctx.fillStyle = a.headColor || '#ffffff'; |
| 89 |
ctx.fillText(ch, i * (a.fontSize || 16), y); |
| 90 |
} |
| 91 |
if (Math.random() > 0.975) drops[i] = 0; |
| 92 |
drops[i]++; |
| 93 |
} |
| 94 |
} |
| 95 |
raf = requestAnimationFrame(draw); |
| 96 |
} |
| 97 |
raf = requestAnimationFrame(draw); |
| 98 |
return function () { cancelAnimationFrame(raf); }; |
| 99 |
}, [a.height, a.fontSize, a.speed, a.bgColor, a.bgOpacity, a.primaryColor, a.headColor, a.charSet, a.customChars, a.randomStart]); |
| 100 |
|
| 101 |
return el('canvas', { ref: canRef, style: { width: '100%', height: a.height + 'px', display: 'block', borderRadius: '0' } }); |
| 102 |
} |
| 103 |
|
| 104 |
function getChars(a) { |
| 105 |
if (a.charSet === 'binary') return ['0','1']; |
| 106 |
if (a.charSet === 'hex') return '0123456789ABCDEF'.split(''); |
| 107 |
if (a.charSet === 'latin') return 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.split(''); |
| 108 |
if (a.charSet === 'custom') return (a.customChars || '01').split(''); |
| 109 |
/* matrix: katakana + digits */ |
| 110 |
var kata = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン'; |
| 111 |
return (kata + '0123456789').split(''); |
| 112 |
} |
| 113 |
|
| 114 |
function hexToRgba(hex, alpha) { |
| 115 |
var r = 0, g = 0, b = 0; |
| 116 |
if (hex && hex.startsWith('#')) { |
| 117 |
var h = hex.slice(1); |
| 118 |
if (h.length === 3) h = h[0]+h[0]+h[1]+h[1]+h[2]+h[2]; |
| 119 |
r = parseInt(h.slice(0,2),16); |
| 120 |
g = parseInt(h.slice(2,4),16); |
| 121 |
b = parseInt(h.slice(4,6),16); |
| 122 |
} |
| 123 |
return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')'; |
| 124 |
} |
| 125 |
|
| 126 |
registerBlockType('blockenberg/matrix-rain', { |
| 127 |
edit: function (props) { |
| 128 |
var a = props.attributes; |
| 129 |
var set = props.setAttributes; |
| 130 |
|
| 131 |
return el(Fragment, null, |
| 132 |
el(InspectorControls, null, |
| 133 |
/* Canvas */ |
| 134 |
el(PanelBody, { title: 'Canvas', initialOpen: true }, |
| 135 |
el(RangeControl, { __nextHasNoMarginBottom: true, label: 'Height (px)', value: a.height, min: 100, max: 900, step: 20, onChange: function (v) { set({ height: v }); } }), |
| 136 |
el(RangeControl, { __nextHasNoMarginBottom: true, label: 'Speed', value: a.speed, min: 1, max: 100, onChange: function (v) { set({ speed: v }); } }), |
| 137 |
el(RangeControl, { __nextHasNoMarginBottom: true, label: 'Density (%)', value: a.density, min: 10, max: 100, onChange: function (v) { set({ density: v }); } }), |
| 138 |
el(RangeControl, { __nextHasNoMarginBottom: true, label: 'Fade trail length', value: a.fadeLength, min: 1, max: 80, onChange: function (v) { set({ fadeLength: v }); } }), |
| 139 |
el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Glow effect', checked: a.glowEffect, onChange: function (v) { set({ glowEffect: v }); } }), |
| 140 |
el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Random start positions', checked: a.randomStart, onChange: function (v) { set({ randomStart: v }); } }), |
| 141 |
), |
| 142 |
/* Characters */ |
| 143 |
el(PanelBody, { title: 'Characters', initialOpen: false }, |
| 144 |
el(SelectControl, { __nextHasNoMarginBottom: true, label: 'Character set', value: a.charSet, options: CHAR_SETS, onChange: function (v) { set({ charSet: v }); } }), |
| 145 |
a.charSet === 'custom' && el(TextControl, { __nextHasNoMarginBottom: true, label: 'Custom characters', value: a.customChars, onChange: function (v) { set({ customChars: v }); }, help: 'Type the characters to use (no spaces needed)' }), |
| 146 |
), |
| 147 |
/* Colors */ |
| 148 |
|
| 149 |
el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false }, |
| 150 |
el(RangeControl, { __nextHasNoMarginBottom: true, label: 'Canvas font size (px)', value: a.fontSize, min: 8, max: 40, onChange: function (v) { set({ fontSize: v }); } }), |
| 151 |
a.showOverlay && getTypoControl() && el(getTypoControl(), { label: 'Overlay Title', value: a.overlayTypo || {}, onChange: function (v) { set({ overlayTypo: v }); } }), |
| 152 |
a.showOverlay && getTypoControl() && el(getTypoControl(), { label: 'Overlay Subtext', value: a.subtextTypo || {}, onChange: function (v) { set({ subtextTypo: v }); } }) |
| 153 |
), |
| 154 |
el(PanelBody, { title: 'Colors', initialOpen: false }, |
| 155 |
el(SelectControl, { __nextHasNoMarginBottom: true, label: 'Color preset', value: a.colorStyle, options: COLOR_STYLES, onChange: function (v) { applyStyle(v, set); } }), |
| 156 |
el(RangeControl, { __nextHasNoMarginBottom: true, label: 'Background opacity (fade trail)', value: a.bgOpacity, min: 1, max: 40, onChange: function (v) { set({ bgOpacity: v }); } }), |
| 157 |
), |
| 158 |
el(PanelColorSettings, { |
| 159 |
title: 'Custom Colors', |
| 160 |
initialOpen: false, |
| 161 |
colorSettings: [ |
| 162 |
{ value: a.primaryColor, onChange: function (v) { set({ primaryColor: v || '#00ff41', colorStyle: 'custom' }); }, label: 'Rain color' }, |
| 163 |
{ value: a.headColor, onChange: function (v) { set({ headColor: v || '#ffffff', colorStyle: 'custom' }); }, label: 'Head (leading char) color' }, |
| 164 |
{ value: a.bgColor, onChange: function (v) { set({ bgColor: v || '#000000', colorStyle: 'custom' }); }, label: 'Background color' }, |
| 165 |
], |
| 166 |
}), |
| 167 |
/* Overlay */ |
| 168 |
el(PanelBody, { title: 'Text Overlay', initialOpen: false }, |
| 169 |
el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Show text overlay', checked: a.showOverlay, onChange: function (v) { set({ showOverlay: v }); } }), |
| 170 |
a.showOverlay && el(TextControl, { __nextHasNoMarginBottom: true, label: 'Main text', value: a.overlayText, onChange: function (v) { set({ overlayText: v }); } }), |
| 171 |
a.showOverlay && el(TextControl, { __nextHasNoMarginBottom: true, label: 'Subtext', value: a.overlaySubtext, onChange: function (v) { set({ overlaySubtext: v }); } }), |
| 172 |
), |
| 173 |
a.showOverlay && el(PanelColorSettings, { |
| 174 |
title: 'Overlay Colors', |
| 175 |
initialOpen: false, |
| 176 |
colorSettings: [ |
| 177 |
{ value: a.overlayTextColor, onChange: function (v) { set({ overlayTextColor: v || '#00ff41' }); }, label: 'Overlay text color' }, |
| 178 |
{ value: a.overlayBg, onChange: function (v) { set({ overlayBg: v || 'rgba(0,0,0,0.35)' }); }, label: 'Overlay background' }, |
| 179 |
], |
| 180 |
}), |
| 181 |
/* Interaction */ |
| 182 |
el(PanelBody, { title: 'Interaction', initialOpen: false }, |
| 183 |
el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Click to pause/resume', checked: a.clickToPause, onChange: function (v) { set({ clickToPause: v }); } }), |
| 184 |
el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Pause on hover', checked: a.pauseOnHover, onChange: function (v) { set({ pauseOnHover: v }); } }), |
| 185 |
), |
| 186 |
), |
| 187 |
el('div', useBlockProps((function () { |
| 188 |
var tv = getTypoCssVars(); |
| 189 |
var s = { position: 'relative', overflow: 'hidden' }; |
| 190 |
Object.assign(s, tv(a.overlayTypo, '--bkbg-mr-ot-')); |
| 191 |
Object.assign(s, tv(a.subtextTypo, '--bkbg-mr-os-')); |
| 192 |
return { style: s }; |
| 193 |
})()), |
| 194 |
el(MatrixCanvas, { attributes: a }), |
| 195 |
a.showOverlay && el('div', { |
| 196 |
className: 'bkbg-mr-overlay', |
| 197 |
style: { |
| 198 |
position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', |
| 199 |
alignItems: 'center', justifyContent: 'center', |
| 200 |
background: a.overlayBg, pointerEvents: 'none', |
| 201 |
}, |
| 202 |
}, |
| 203 |
el('h2', { className: 'bkbg-mr-overlay-title', style: { color: a.overlayTextColor, margin: '0 0 12px' } }, |
| 204 |
a.overlayText), |
| 205 |
a.overlaySubtext && el('p', { className: 'bkbg-mr-overlay-sub', style: { color: a.overlayTextColor, opacity: 0.7, margin: 0 } }, |
| 206 |
a.overlaySubtext) |
| 207 |
) |
| 208 |
) |
| 209 |
); |
| 210 |
}, |
| 211 |
save: function (props) { |
| 212 |
var a = props.attributes; |
| 213 |
return el('div', useBlockProps.save((function () { |
| 214 |
var tv = getTypoCssVars(); |
| 215 |
var s = {}; |
| 216 |
Object.assign(s, tv(a.overlayTypo, '--bkbg-mr-ot-')); |
| 217 |
Object.assign(s, tv(a.subtextTypo, '--bkbg-mr-os-')); |
| 218 |
return { style: s }; |
| 219 |
})()), |
| 220 |
el('div', { className: 'bkbg-mr-app', 'data-opts': JSON.stringify(a) }) |
| 221 |
); |
| 222 |
}, |
| 223 |
}); |
| 224 |
}() ); |
| 225 |
|