| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
var _typoKeys = { |
| 5 |
family:'font-family', weight:'font-weight', style:'font-style', |
| 6 |
decoration:'text-decoration', transform:'text-transform', |
| 7 |
sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m', |
| 8 |
lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m', |
| 9 |
letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m', |
| 10 |
wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m' |
| 11 |
}; |
| 12 |
function typoCssVarsForEl(el, obj, prefix) { |
| 13 |
if (!obj || typeof obj !== 'object') return; |
| 14 |
Object.keys(_typoKeys).forEach(function (k) { |
| 15 |
var v = obj[k]; |
| 16 |
if (v === undefined || v === '' || v === null) return; |
| 17 |
if (k === 'sizeDesktop' || k === 'sizeTablet' || k === 'sizeMobile') v = v + (obj.sizeUnit || 'px'); |
| 18 |
else if (k === 'lineHeightDesktop' || k === 'lineHeightTablet' || k === 'lineHeightMobile') v = v + (obj.lineHeightUnit || ''); |
| 19 |
else if (k === 'letterSpacingDesktop' || k === 'letterSpacingTablet' || k === 'letterSpacingMobile') v = v + (obj.letterSpacingUnit || 'px'); |
| 20 |
else if (k === 'wordSpacingDesktop' || k === 'wordSpacingTablet' || k === 'wordSpacingMobile') v = v + (obj.wordSpacingUnit || 'px'); |
| 21 |
el.style.setProperty(prefix + _typoKeys[k], String(v)); |
| 22 |
}); |
| 23 |
} |
| 24 |
|
| 25 |
var MORSE_MAP = { |
| 26 |
'A':'.-','B':'-...','C':'-.-.','D':'-..','E':'.','F':'..-.','G':'--.','H':'....','I':'..','J':'.---', |
| 27 |
'K':'-.-','L':'.-..','M':'--','N':'-.','O':'---','P':'.--.','Q':'--.-','R':'.-.','S':'...','T':'-', |
| 28 |
'U':'..-','V':'...-','W':'.--','X':'-..-','Y':'-.--','Z':'--..', |
| 29 |
'0':'-----','1':'.----','2':'..---','3':'...--','4':'....-','5':'.....','6':'-....','7':'--...','8':'---..','9':'----.', |
| 30 |
' ':'/' |
| 31 |
}; |
| 32 |
var REVERSE_MAP = {}; |
| 33 |
Object.keys(MORSE_MAP).forEach(function(k){ REVERSE_MAP[MORSE_MAP[k]] = k; }); |
| 34 |
|
| 35 |
function toMorse(text) { |
| 36 |
return text.toUpperCase().split('').map(function(c){ return MORSE_MAP[c] || ''; }).join(' ').replace(/ +/g,' ').trim(); |
| 37 |
} |
| 38 |
function fromMorse(morse) { |
| 39 |
return morse.trim().split(' / ').map(function(word){ |
| 40 |
return word.trim().split(' ').map(function(code){ return REVERSE_MAP[code.trim()] || ''; }).join(''); |
| 41 |
}).join(' '); |
| 42 |
} |
| 43 |
|
| 44 |
function showToast(msg) { |
| 45 |
var t = document.querySelector('.bkbg-mcc-toast'); |
| 46 |
if (!t) { t = document.createElement('div'); t.className = 'bkbg-mcc-toast'; document.body.appendChild(t); } |
| 47 |
t.textContent = msg; t.classList.add('show'); |
| 48 |
setTimeout(function(){ t.classList.remove('show'); }, 1800); |
| 49 |
} |
| 50 |
|
| 51 |
// Web Audio API Morse playback |
| 52 |
function playMorse(morseStr) { |
| 53 |
if (!window.AudioContext && !window.webkitAudioContext) { showToast('Audio not supported'); return; } |
| 54 |
var ctx = new (window.AudioContext || window.webkitAudioContext)(); |
| 55 |
var wpm = 15; |
| 56 |
var dot = 1200 / wpm / 1000; |
| 57 |
var t = ctx.currentTime + 0.1; |
| 58 |
var freq = 600; |
| 59 |
|
| 60 |
function beep(dur) { |
| 61 |
var osc = ctx.createOscillator(); |
| 62 |
var gain = ctx.createGain(); |
| 63 |
osc.connect(gain); gain.connect(ctx.destination); |
| 64 |
osc.frequency.value = freq; |
| 65 |
gain.gain.setValueAtTime(0.3, t); |
| 66 |
osc.start(t); osc.stop(t + dur); |
| 67 |
t += dur; |
| 68 |
} |
| 69 |
function pause(dur) { t += dur; } |
| 70 |
|
| 71 |
morseStr.split('').forEach(function(ch) { |
| 72 |
if (ch === '.') { beep(dot); pause(dot); } |
| 73 |
else if (ch === '-') { beep(dot * 3); pause(dot); } |
| 74 |
else if (ch === ' ') { pause(dot * 2); } |
| 75 |
else if (ch === '/') { pause(dot * 4); } |
| 76 |
}); |
| 77 |
} |
| 78 |
|
| 79 |
var CHARS = Object.keys(MORSE_MAP).filter(function(k){ return k !== ' '; }); |
| 80 |
|
| 81 |
function initBlock(app) { |
| 82 |
var opts = {}; |
| 83 |
try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch(e) {} |
| 84 |
|
| 85 |
var accent = opts.accentColor || '#6c3fb5'; |
| 86 |
var outputBg = opts.outputBg || '#f5f3ff'; |
| 87 |
var outputBorder = opts.outputBorder || '#ede9fe'; |
| 88 |
var outputColor = opts.outputColor || '#3b0764'; |
| 89 |
var tabActiveBg = opts.tabActiveBg || accent; |
| 90 |
var tabActiveClr = opts.tabActiveColor || '#fff'; |
| 91 |
var tabInactiveBg = opts.tabInactiveBg || '#f3f4f6'; |
| 92 |
var tabInactiveClr= opts.tabInactiveColor|| '#374151'; |
| 93 |
var refBg = opts.refBg || '#f9fafb'; |
| 94 |
var refBorder = opts.refBorder || '#e5e7eb'; |
| 95 |
var inputBorder = opts.inputBorder || '#e5e7eb'; |
| 96 |
var labelColor = opts.labelColor || '#374151'; |
| 97 |
var cardRadius = (opts.cardRadius !== undefined ? opts.cardRadius : 16) + 'px'; |
| 98 |
var inputRadius = (opts.inputRadius !== undefined ? opts.inputRadius : 8) + 'px'; |
| 99 |
var outputFS = (opts.outputFontSize || 18) + 'px'; |
| 100 |
|
| 101 |
var state = { mode: opts.defaultMode || 'encode' }; |
| 102 |
|
| 103 |
// Wrap |
| 104 |
var wrap = document.createElement('div'); |
| 105 |
wrap.className = 'bkbg-mcc-wrap'; |
| 106 |
wrap.style.borderRadius = cardRadius; |
| 107 |
if (opts.cardBg) wrap.style.background = opts.cardBg; |
| 108 |
app.appendChild(wrap); |
| 109 |
|
| 110 |
// Header |
| 111 |
if (opts.showTitle || opts.showSubtitle) { |
| 112 |
var hdr = document.createElement('div'); hdr.className = 'bkbg-mcc-header'; |
| 113 |
if (opts.showTitle && opts.title) { |
| 114 |
var ti = document.createElement('div'); ti.className = 'bkbg-mcc-title'; |
| 115 |
if (opts.titleColor) ti.style.color = opts.titleColor; |
| 116 |
ti.textContent = opts.title; hdr.appendChild(ti); |
| 117 |
} |
| 118 |
if (opts.showSubtitle && opts.subtitle) { |
| 119 |
var su = document.createElement('div'); su.className = 'bkbg-mcc-subtitle'; |
| 120 |
if (opts.subtitleColor) su.style.color = opts.subtitleColor; |
| 121 |
su.textContent = opts.subtitle; hdr.appendChild(su); |
| 122 |
} |
| 123 |
wrap.appendChild(hdr); |
| 124 |
} |
| 125 |
|
| 126 |
// Tabs |
| 127 |
var tabWrap = document.createElement('div'); tabWrap.className = 'bkbg-mcc-tabs'; |
| 128 |
var tabEncode = document.createElement('button'); tabEncode.className = 'bkbg-mcc-tab'; tabEncode.type = 'button'; tabEncode.textContent = 'Text → Morse'; |
| 129 |
var tabDecode = document.createElement('button'); tabDecode.className = 'bkbg-mcc-tab'; tabDecode.type = 'button'; tabDecode.textContent = 'Morse → Text'; |
| 130 |
tabWrap.appendChild(tabEncode); tabWrap.appendChild(tabDecode); wrap.appendChild(tabWrap); |
| 131 |
|
| 132 |
function styleTab(btn, active) { |
| 133 |
btn.style.background = active ? tabActiveBg : tabInactiveBg; |
| 134 |
btn.style.color = active ? tabActiveClr : tabInactiveClr; |
| 135 |
btn.classList.toggle('active', active); |
| 136 |
} |
| 137 |
|
| 138 |
// Label |
| 139 |
var inputLabel = document.createElement('label'); inputLabel.className = 'bkbg-mcc-label'; inputLabel.style.color = labelColor; |
| 140 |
// Textarea |
| 141 |
var textarea = document.createElement('textarea'); textarea.className = 'bkbg-mcc-textarea'; |
| 142 |
textarea.style.borderRadius = inputRadius; textarea.style.borderColor = inputBorder; |
| 143 |
textarea.rows = 3; |
| 144 |
var inputWrap = document.createElement('div'); inputWrap.style.marginBottom = '12px'; |
| 145 |
inputWrap.appendChild(inputLabel); inputWrap.appendChild(textarea); wrap.appendChild(inputWrap); |
| 146 |
|
| 147 |
// Output |
| 148 |
var outputBox = document.createElement('div'); outputBox.className = 'bkbg-mcc-output'; |
| 149 |
outputBox.style.background = outputBg; outputBox.style.borderColor = outputBorder; outputBox.style.borderRadius = inputRadius; |
| 150 |
var outputLabel = document.createElement('div'); outputLabel.className = 'bkbg-mcc-output-label'; outputLabel.style.color = labelColor; |
| 151 |
var outputText = document.createElement('div'); outputText.className = 'bkbg-mcc-output-text'; outputText.style.color = outputColor; |
| 152 |
outputBox.appendChild(outputLabel); outputBox.appendChild(outputText); wrap.appendChild(outputBox); |
| 153 |
|
| 154 |
// Buttons |
| 155 |
var actionsRow = document.createElement('div'); actionsRow.className = 'bkbg-mcc-actions'; actionsRow.style.marginBottom = '20px'; |
| 156 |
if (opts.showCopyButton !== false) { |
| 157 |
var copyBtn = document.createElement('button'); copyBtn.className = 'bkbg-mcc-btn-copy'; copyBtn.type = 'button'; copyBtn.textContent = '📋 Copy'; copyBtn.style.background = accent; |
| 158 |
copyBtn.addEventListener('click', function(){ |
| 159 |
var val = outputText.textContent; |
| 160 |
if (!val) return; |
| 161 |
if (navigator.clipboard) { navigator.clipboard.writeText(val).then(function(){ showToast('Copied!'); }); } |
| 162 |
else { showToast('Copy not supported'); } |
| 163 |
}); |
| 164 |
actionsRow.appendChild(copyBtn); |
| 165 |
} |
| 166 |
if (opts.showPlayButton !== false) { |
| 167 |
var playBtn = document.createElement('button'); playBtn.className = 'bkbg-mcc-btn-play'; playBtn.type = 'button'; playBtn.textContent = '🔊 Play Audio'; |
| 168 |
playBtn.addEventListener('click', function(){ |
| 169 |
var morse = state.mode === 'encode' ? outputText.textContent : textarea.value; |
| 170 |
playMorse(morse); |
| 171 |
}); |
| 172 |
actionsRow.appendChild(playBtn); |
| 173 |
} |
| 174 |
wrap.appendChild(actionsRow); |
| 175 |
|
| 176 |
// Reference table |
| 177 |
if (opts.showReference !== false) { |
| 178 |
var refDetails = document.createElement('details'); refDetails.className = 'bkbg-mcc-ref'; |
| 179 |
var refSummary = document.createElement('summary'); refSummary.textContent = 'Morse Code Reference (A–Z, 0–9)'; refSummary.style.background = refBg; |
| 180 |
var refGrid = document.createElement('div'); refGrid.className = 'bkbg-mcc-ref-grid'; refGrid.style.background = refBg; |
| 181 |
CHARS.forEach(function(c){ |
| 182 |
var cell = document.createElement('div'); cell.className = 'bkbg-mcc-ref-cell'; cell.style.borderColor = refBorder; |
| 183 |
var charEl = document.createElement('div'); charEl.className = 'bkbg-mcc-ref-char'; charEl.style.color = accent; charEl.textContent = c; |
| 184 |
var codeEl = document.createElement('div'); codeEl.className = 'bkbg-mcc-ref-code'; codeEl.textContent = MORSE_MAP[c]; |
| 185 |
cell.appendChild(charEl); cell.appendChild(codeEl); refGrid.appendChild(cell); |
| 186 |
}); |
| 187 |
refDetails.appendChild(refSummary); refDetails.appendChild(refGrid); wrap.appendChild(refDetails); |
| 188 |
} |
| 189 |
|
| 190 |
function updateOutput() { |
| 191 |
var val = textarea.value; |
| 192 |
var result; |
| 193 |
if (state.mode === 'encode') { |
| 194 |
result = toMorse(val); |
| 195 |
outputLabel.textContent = 'Morse Code'; |
| 196 |
inputLabel.textContent = 'Enter Text'; |
| 197 |
} else { |
| 198 |
result = fromMorse(val); |
| 199 |
outputLabel.textContent = 'Decoded Text'; |
| 200 |
inputLabel.textContent = 'Enter Morse Code (use / between words)'; |
| 201 |
} |
| 202 |
if (result) { |
| 203 |
outputText.textContent = result; |
| 204 |
outputText.classList.remove('empty'); |
| 205 |
} else { |
| 206 |
outputText.textContent = 'Output will appear here…'; |
| 207 |
outputText.classList.add('empty'); |
| 208 |
} |
| 209 |
} |
| 210 |
|
| 211 |
// Set typography CSS vars on the blockProps wrapper |
| 212 |
var bpWrap = app.parentNode; |
| 213 |
typoCssVarsForEl(bpWrap, opts.titleTypo, '--bkbg-mcc-tt-'); |
| 214 |
typoCssVarsForEl(bpWrap, opts.subtitleTypo, '--bkbg-mcc-st-'); |
| 215 |
typoCssVarsForEl(bpWrap, opts.outputTypo, '--bkbg-mcc-ot-'); |
| 216 |
|
| 217 |
function setMode(mode) { |
| 218 |
state.mode = mode; |
| 219 |
var defText = opts.defaultText || 'Hello World'; |
| 220 |
textarea.value = mode === 'encode' ? defText : toMorse(defText); |
| 221 |
styleTab(tabEncode, mode === 'encode'); |
| 222 |
styleTab(tabDecode, mode === 'decode'); |
| 223 |
updateOutput(); |
| 224 |
} |
| 225 |
|
| 226 |
tabEncode.addEventListener('click', function(){ setMode('encode'); }); |
| 227 |
tabDecode.addEventListener('click', function(){ setMode('decode'); }); |
| 228 |
textarea.addEventListener('input', updateOutput); |
| 229 |
|
| 230 |
setMode(state.mode); |
| 231 |
} |
| 232 |
|
| 233 |
function init() { document.querySelectorAll('.bkbg-mcc-app').forEach(initBlock); } |
| 234 |
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } |
| 235 |
})(); |
| 236 |
|