<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Radio stuff from M5NCW on Nick Craig-Wood's home page</title><link>https://ncw.github.io/www/radio/</link><description>Radio stuff from M5NCW</description><language>en-US</language><copyright>Copyright (c) 1994-2026, Nick Craig-Wood; all rights reserved.</copyright><atom:link href="https://ncw.github.io/www/radio/index.xml" rel="self" type="application/rss+xml"/><lastBuildDate>Tue, 12 Aug 2025 00:00:00 +0000</lastBuildDate><item><title>Unicode Block-Style Morse Encoder</title><link>https://ncw.github.io/www/radio/block_morse/</link><pubDate>Tue, 12 Aug 2025 00:00:00 +0000</pubDate><author>nick@craig-wood.com (Nick Craig-Wood)</author><guid>https://ncw.github.io/www/radio/block_morse/</guid><description>&lt;style&gt;
fieldset { border: 1px solid #3b4261; border-radius: 8px; padding: 1rem; }
label { display: block; font-weight: 600; margin-bottom: 0.25rem; }
#input { width: 100%; padding: 0.5rem; font-size: 1rem; background: #1f2335; color: #c0caf5; border: 1px solid #3b4261; border-radius: 4px; }
#output { width: 100%; height: 260px; padding: 0.5rem; background: #1f2335; color: #c0caf5; border: 1px solid #3b4261; border-radius: 4px; }
.buttons { margin-top: 0.5rem; display: flex; gap: 0.5rem; }
.buttons button { padding: 0.5rem 0.8rem; border-radius: 8px; border: 1px solid #3b4261; background: #24283b; color: #c0caf5; cursor: pointer; }
.buttons button:hover { background: #3b4261; }
.buttons button:active { transform: translateY(1px); }
.help { margin-top: 1rem; font-size: 0.95rem; }
@media (prefers-color-scheme: light) {
fieldset { border-color: #e7e5e4; }
#input, #output { background: #f3f2ef; color: #1c1917; border-color: #e7e5e4; }
.buttons button { background: #f3f2ef; color: #1c1917; border-color: #e7e5e4; }
.buttons button:hover { background: #e7e5e4; }
}
&lt;/style&gt;
&lt;fieldset&gt;
&lt;label for="input"&gt;Text&lt;/label&gt;
&lt;input id="input" type="text" placeholder="Type text, e.g. SOS" autocomplete="off" /&gt;
&lt;div class="buttons"&gt;
&lt;button id="convert"&gt;Convert ⏎&lt;/button&gt;
&lt;button id="clear" type="button"&gt;Clear&lt;/button&gt;
&lt;/div&gt;
&lt;/fieldset&gt;
&lt;div class="help"&gt;
&lt;p&gt;&lt;strong&gt;How:&lt;/strong&gt; Type text and press &lt;kbd&gt;Enter&lt;/kbd&gt; or &lt;em&gt;Convert&lt;/em&gt;. Each conversion appends to the output below. Unsupported characters are skipped.&lt;/p&gt;
&lt;p&gt;Output uses special block characters (U+1FB03, U+1FB0B) and spaces (U+2001) so it stays properly spaced in both mono and proportional fonts.&lt;/p&gt;
&lt;/div&gt;
&lt;label for="output" style="display:block;margin-top:1rem;font-weight:600;"&gt;Output&lt;/label&gt;
&lt;textarea id="output" readonly&gt;&lt;/textarea&gt;
&lt;script&gt;
(() =&gt; {
"use strict";
// Unicode block glyphs
const DOT = "🬃"; // U+1FB03 (2 dits)
const DASH = "🬋🬃"; // U+1FB0B + U+1FB03 (4 dits)
const EN = "\u2001"; // U+2001 EM QUAD SPACE (2 dits)
// ITU Morse (A–Z, 0–9, basic punctuation)
const MORSE = {
'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.',
'F': '..-.', 'G': '--.', 'H': '....', 'I': '..', 'J': '.---',
'K': '-.-', 'L': '.-..', 'M': '--', 'N': '-.', 'O': '---',
'P': '.--.', 'Q': '--.-', 'R': '.-.', 'S': '...', 'T': '-',
'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-', 'Y': '-.--',
'Z': '--..',
'0': '-----', '1': '.----', '2': '..---', '3': '...--', '4': '....-',
'5': '.....', '6': '-....', '7': '--...', '8': '---..', '9': '----.',
'.': '.-.-.-', ',': '--..--', '?': '..--..', "'": '.----.',
'!': '-.-.--', '/': '-..-.', '(': '-.--.', ')': '-.--.-',
'&amp;': '.-...', ':': '---...', ';': '-.-.-.', '=': '-...-',
'+': '.-.-.', '-': '-....-', '_': '..--.-', '"': '.-..-.',
'$': '...-..-','@': '.--.-.'
};
const encodeSymbol = sym =&gt; (sym === '.') ? DOT : DASH;
const encodeLetter = ch =&gt; {
const pat = MORSE[ch.toUpperCase()];
if (!pat) return '';
const symbols = [...pat].map(encodeSymbol).join('');
return symbols + EN; // add 2‑dit spacing at the end of the letter
};
const textToBlockMorse = text =&gt; {
const parts = [];
const words = text.split(' ');
for (const word of words) {
for (const letter of word) {
const enc = encodeLetter(letter);
if (enc) parts.push(enc);
}
// word gap = 7 dits; we already have 3, so add 4 more (2 × EN)
parts.push(EN + EN);
}
return parts.join('');
};
// UI wiring
const $in = document.getElementById('input');
const $out = document.getElementById('output');
const $convert = document.getElementById('convert');
const $clear = document.getElementById('clear');
const appendOutput = str =&gt; {
if (!str) return;
$out.value += ($out.value ? "\n" : "") + str;
$out.scrollTop = $out.scrollHeight;
};
const handleConvert = () =&gt; {
const raw = $in.value.trim();
if (!raw) return;
const encoded = textToBlockMorse(raw);
appendOutput(encoded);
$in.value = '';
$in.focus();
};
$convert.addEventListener('click', handleConvert);
$clear.addEventListener('click', () =&gt; { $out.value = ''; $in.focus(); });
// Enter submits (Shift+Enter to insert a newline if needed)
$in.addEventListener('keydown', e =&gt; {
if (e.key === 'Enter' &amp;&amp; !e.shiftKey) {
e.preventDefault();
handleConvert();
}
});
})();
&lt;/script&gt;</description></item></channel></rss>