Files
2026-09-05 11:54:22 -04:00

397 lines
19 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ARRL Welfare Radiogram (Packet Radio)</title>
<style>
* { box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace; background: #f4f6f8; color: #222; padding: 20px; margin: 0; }
.container { max-width: 880px; margin: 0 auto; background: #fff; padding: 24px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.header { background: #0b5394; color: white; padding: 12px; border-radius: 6px; text-align: center; margin-bottom: 18px; }
.header h2 { margin: 0; font-size: 1.3rem; letter-spacing: 1px; }
.header p { margin: 4px 0 0 0; font-size: 0.85rem; opacity: 0.9; }
fieldset { border: 1px solid #ccc; border-radius: 6px; padding: 14px; margin-bottom: 16px; }
legend { font-weight: bold; padding: 0 6px; font-size: 0.9em; color: #0b5394; }
.legend-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.row { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 10px; }
.col { flex: 1; min-width: 130px; display: flex; flex-direction: column; }
.label-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; }
label { font-size: 0.8rem; font-weight: 600; color: #444; }
input[type="text"] { padding: 7px 8px; font-size: 0.9rem; border: 1px solid #aaa; border-radius: 4px; text-transform: uppercase; font-family: inherit; width: 100%; }
.grid-5 { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; margin-bottom: 8px; }
.word-box { display: flex; align-items: center; gap: 4px; }
.word-box span { font-size: 0.75rem; font-weight: bold; width: 22px; text-align: right; color: #666; }
.word-box input { width: 100%; padding: 6px 4px; text-align: center; }
.actions { display: flex; gap: 12px; margin: 20px 0; }
button { padding: 10px 14px; font-size: 0.95rem; font-weight: bold; border: none; border-radius: 6px; cursor: pointer; }
.btn-now { background: #4a5568; color: #fff; padding: 4px 10px; font-size: 0.75rem; font-weight: normal; border-radius: 4px; }
.btn-now:hover { background: #2d3748; }
.btn-generate { background: #0b5394; color: #fff; flex: 1; }
.btn-generate:hover { background: #083c6c; }
.btn-copy { background: #2e7d32; color: #fff; flex: 1; }
.btn-copy:hover { background: #1b5e20; }
.btn-clear { background: #d32f2f; color: #fff; flex: 0.4; }
.btn-clear:hover { background: #b71c1c; }
pre { background: #1e1e1e; color: #00ff66; padding: 16px; border-radius: 6px; overflow-x: auto; font-family: "Courier New", Courier, monospace; font-size: 0.85rem; line-height: 1.35; white-space: pre; min-height: 150px; }
.nav-bar { margin-bottom: 12px; font-size: 0.85rem; }
.nav-bar a { color: #0b5394; text-decoration: none; font-weight: bold; }
/* Modal Save Dialog Window */
.save-modal-backdrop {
display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.55); z-index: 99999; justify-content: center; align-items: center;
}
.save-modal-backdrop.active { display: flex; }
.save-modal-window {
background: #ffffff; padding: 24px 28px; border-radius: 10px; width: 92%; max-width: 480px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3); border: 1px solid #cbd5e1; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.save-modal-window h3 { margin: 0 0 8px 0; font-size: 1.25rem; color: #1e293b; font-weight: bold; }
.save-modal-window p { margin: 0 0 16px 0; font-size: 0.88rem; color: #64748b; line-height: 1.4; }
.save-modal-window input[type="text"] {
width: 100%; padding: 10px 12px; font-size: 0.95rem; border: 2px solid #94a3b8; border-radius: 6px;
margin-bottom: 20px; font-family: inherit; box-sizing: border-box; text-transform: none;
}
.save-modal-window input[type="text"]:focus { border-color: #2563eb; outline: none; }
.save-modal-actions { display: flex; gap: 10px; justify-content: flex-end; }
.save-modal-btn { padding: 9px 18px; font-size: 0.9rem; font-weight: bold; border-radius: 6px; border: none; cursor: pointer; }
.save-modal-btn-confirm { background: #2563eb; color: #ffffff; }
.save-modal-btn-confirm:hover { background: #1d4ed8; }
.save-modal-btn-cancel { background: #e2e8f0; color: #334155; }
.save-modal-btn-cancel:hover { background: #cbd5e1; }
</style>
</head>
<body>
<div class="container">
<div class="nav-bar"><a href="../index.html">&larr; Back to Packet Forms Hub</a></div>
<div class="header">
<h2>HEALTH & WELFARE RADIOGRAM</h2>
<p>ARRL / NTS Standard Format for Packet Radio</p>
</div>
<form id="radiogramForm" onsubmit="event.preventDefault(); return false;">
<fieldset>
<legend>PREAMBLE</legend>
<div class="legend-bar">
<span style="font-size: 0.85rem; color: #666; font-weight: bold;">Message Header</span>
<button type="button" class="btn-now" id="btnRadioNow">Set Current UTC</button>
</div>
<div class="row">
<div class="col" style="max-width: 100px;"><label for="msgNum">Number</label><input type="text" id="msgNum" placeholder="101"></div>
<div class="col" style="max-width: 90px;"><label for="prec">Precedence</label><input type="text" id="prec" value="W" readonly style="background:#eee;"></div>
<div class="col" style="max-width: 90px;"><label for="hx">HX</label><input type="text" id="hx" placeholder="HXC"></div>
<div class="col"><label for="originStation">Station of Origin</label><input type="text" id="originStation" placeholder="CALLSIGN"></div>
<div class="col" style="max-width: 90px;"><label for="check">Check</label><input type="text" id="check" readonly style="background:#eee;" placeholder="AUTO"></div>
</div>
<div class="row">
<div class="col"><label for="placeOrigin">Place of Origin</label><input type="text" id="placeOrigin" placeholder="CITY, STATE"></div>
<div class="col"><label for="timeFiled">Time Filed</label><input type="text" id="timeFiled" placeholder="1430Z"></div>
<div class="col"><label for="dateFiled">Date</label><input type="text" id="dateFiled" placeholder="SEP 04"></div>
</div>
</fieldset>
<fieldset>
<legend>TO (Recipient Info)</legend>
<div class="row">
<div class="col"><label for="toName">Name</label><input type="text" id="toName"></div>
<div class="col"><label for="toPhone">Phone</label><input type="text" id="toPhone"></div>
</div>
<div class="row">
<div class="col"><label for="toAddress">Address</label><input type="text" id="toAddress"></div>
<div class="col"><label for="toEmail">Email</label><input type="text" id="toEmail"></div>
</div>
<div class="row">
<div class="col" style="flex: 2;"><label for="toCity">City</label><input type="text" id="toCity"></div>
<div class="col"><label for="toState">State</label><input type="text" id="toState"></div>
<div class="col"><label for="toZip">Zip</label><input type="text" id="toZip"></div>
</div>
</fieldset>
<fieldset>
<legend>TEXT (5 Words Per Line, Max 25 Words - No Standard Punctuation)</legend>
<div id="wordGrid"></div>
</fieldset>
<fieldset>
<legend>SIGNATURE & HANDLING</legend>
<div class="row">
<div class="col"><label for="sigName">Signature (Name / Callsign)</label><input type="text" id="sigName"></div>
</div>
<div class="row">
<div class="col"><label for="rcvdFrom">Rec'd From</label><input type="text" id="rcvdFrom"></div>
<div class="col"><label for="rcvdTime">Time</label><input type="text" id="rcvdTime"></div>
<div class="col"><label for="rcvdDate">Date</label><input type="text" id="rcvdDate"></div>
<div class="col"><label for="sentTo">Sent To</label><input type="text" id="sentTo"></div>
<div class="col"><label for="sentTime">Time</label><input type="text" id="sentTime"></div>
<div class="col"><label for="sentDate">Date</label><input type="text" id="sentDate"></div>
</div>
</fieldset>
<div class="actions">
<button type="button" class="btn-generate" id="btnGen">Generate ASCII</button>
<button type="button" class="btn-save" id="btnSaveHtml" style="background:#5c6bc0; color:#fff; flex:1;">Save as HTML</button>
<button type="button" class="btn-copy" id="btnCopy">Copy Text</button>
<button type="button" class="btn-clear" id="btnClear">Clear</button>
</div>
</form>
<label style="font-weight:bold;">Formatted ASCII Output (Ready for Packet / BBS):</label>
<pre id="outputArea">Form output will appear here...</pre>
</div>
<!-- Custom File Name Modal Window -->
<div id="saveModalBackdrop" class="save-modal-backdrop">
<div class="save-modal-window">
<h3>Save Form as HTML</h3>
<p>Enter a name for the saved offline HTML file:</p>
<input type="text" id="saveModalFileName" spellcheck="false">
<div class="save-modal-actions">
<button type="button" class="save-modal-btn save-modal-btn-cancel" id="saveModalCancelBtn">Cancel</button>
<button type="button" class="save-modal-btn save-modal-btn-confirm" id="saveModalSaveBtn">Save File</button>
</div>
</div>
</div>
<script>
const gridContainer = document.getElementById('wordGrid');
for (let r = 0; r < 5; r++) {
const rowDiv = document.createElement('div');
rowDiv.className = 'grid-5';
for (let c = 1; c <= 5; c++) {
const idx = r * 5 + c;
const box = document.createElement('div');
box.className = 'word-box';
box.innerHTML = '<span>' + idx + '.</span><input type="text" id="w' + idx + '" oninput="cleanWord(this)">';
rowDiv.appendChild(box);
}
gridContainer.appendChild(rowDiv);
}
function setCurrentDateTime() {
const now = new Date();
const hours = String(now.getUTCHours()).padStart(2, '0');
const minutes = String(now.getUTCMinutes()).padStart(2, '0');
document.getElementById('timeFiled').value = hours + minutes + 'Z';
const months = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
const month = months[now.getUTCMonth()];
const day = String(now.getUTCDate()).padStart(2, '0');
document.getElementById('dateFiled').value = month + ' ' + day;
}
function cleanWord(el) {
el.value = el.value.replace(/[^a-zA-Z0-9\/]/g, '').toUpperCase();
updateCheckCount();
}
function updateCheckCount() {
let count = 0;
for (let i = 1; i <= 25; i++) {
const el = document.getElementById('w' + i);
if (el && el.value.trim() !== '') count++;
}
document.getElementById('check').value = count > 0 ? count : '';
}
function pad(str, len) {
str = (str || '').toString().toUpperCase();
return str + ' '.repeat(Math.max(0, len - str.length));
}
function generateOutput() {
try {
const num = pad(document.getElementById('msgNum').value, 6);
const prec = "W";
const hx = pad(document.getElementById('hx').value, 6);
const origin = pad(document.getElementById('originStation').value, 9);
const check = pad(document.getElementById('check').value, 6);
const place = pad(document.getElementById('placeOrigin').value, 22);
const time = pad(document.getElementById('timeFiled').value, 9);
const date = pad(document.getElementById('dateFiled').value, 17);
const name = pad(document.getElementById('toName').value, 38);
const phone = pad(document.getElementById('toPhone').value, 34);
const addr = pad(document.getElementById('toAddress').value, 38);
const email = pad(document.getElementById('toEmail').value, 34);
const city = pad(document.getElementById('toCity').value, 38);
const st = pad(document.getElementById('toState').value, 11);
const zip = pad(document.getElementById('toZip').value, 15);
const sig = pad(document.getElementById('sigName').value, 84);
const rFrom = pad(document.getElementById('rcvdFrom').value, 14);
const rTime = pad(document.getElementById('rcvdTime').value, 9);
const rDate = pad(document.getElementById('rcvdDate').value, 8);
const sTo = pad(document.getElementById('sentTo').value, 14);
const sTime = pad(document.getElementById('sentTime').value, 9);
const sDate = pad(document.getElementById('sentDate').value, 8);
const divLine = "_".repeat(100);
let textLines = [];
for (let r = 0; r < 5; r++) {
let rowParts = [];
for (let c = 1; c <= 5; c++) {
const idx = r * 5 + c;
const val = pad(document.getElementById('w' + idx).value, 17);
const prefix = idx < 10 ? idx + '. ' : idx + '.';
rowParts.push(prefix + ' ' + val);
}
textLines.push(rowParts.join(' '));
}
const output =
divLine + '\n' +
'PREAMBLE: \n\n' +
'| Number: ' + num + ' | Precedence: ' + prec + ' | HX: ' + hx + ' | Station of Origin: ' + origin + ' | Check: ' + check + ' | \n' +
'| Place of Origin: ' + place + ' | Time Filed: ' + time + ' | Date: ' + date + ' |\n' +
divLine + '\n' +
'TO (Recipient Info):\n' +
'Name: ' + name + ' Phone: ' + phone + '\n' +
'Address: ' + addr + ' Email: ' + email + '\n' +
'City: ' + city + ' State: ' + st + ' Zip: ' + zip + '\n' +
divLine + '\n' +
'TEXT (Write exactly 5 words per line, max 25 words. All uppercase, no standard punctuation):\n\n' +
textLines[0] + '\n\n' +
textLines[1] + '\n\n' +
textLines[2] + '\n\n' +
textLines[3] + '\n\n' +
textLines[4] + '\n' +
divLine + '\n' +
'SIGNATURE (Sender Info):\n' +
'Name/Callsign: ' + sig + '\n' +
'Received From: ' + rFrom + ' Time: ' + rTime + ' Date: ' + rDate + ' | Sent To: ' + sTo + ' Time: ' + sTime + ' Date: ' + sDate + '\n' +
divLine;
document.getElementById('outputArea').textContent = output;
} catch(err) {
alert("Error generating output: " + err.message);
}
}
function copyOutput() {
const text = document.getElementById('outputArea').textContent;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(function() {
alert("Radiogram copied to clipboard!");
}).catch(function() {
fallbackCopy();
});
} else {
fallbackCopy();
}
}
function fallbackCopy() {
const pre = document.getElementById('outputArea');
const range = document.createRange();
range.selectNode(pre);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
document.execCommand('copy');
alert("Copied to clipboard!");
}
function clearForm() {
if (confirm("Clear all form fields?")) {
document.getElementById('radiogramForm').reset();
document.getElementById('prec').value = 'W';
document.getElementById('outputArea').textContent = 'Form output will appear here...';
}
}
document.getElementById('btnRadioNow').addEventListener('click', setCurrentDateTime);
document.getElementById('btnGen').addEventListener('click', generateOutput);
document.getElementById('btnCopy').addEventListener('click', copyOutput);
document.getElementById('btnClear').addEventListener('click', clearForm);
// --- File Name Modal Window & HTML Exporter ---
function openSaveModal() {
var now = new Date();
var dStr = now.toISOString().slice(0, 10);
var defName = 'welfare_radiogram_' + dStr + '.html';
var modal = document.getElementById('saveModalBackdrop');
var input = document.getElementById('saveModalFileName');
if (input) input.value = defName;
if (modal) modal.classList.add('active');
setTimeout(function() {
if (input) { input.focus(); input.select(); }
}, 50);
}
function closeSaveModal() {
var modal = document.getElementById('saveModalBackdrop');
if (modal) modal.classList.remove('active');
}
function executeSaveHTML() {
var input = document.getElementById('saveModalFileName');
var chosenName = input ? input.value.trim() : '';
if (!chosenName) return;
if (!chosenName.toLowerCase().endsWith('.html') && !chosenName.toLowerCase().endsWith('.htm')) {
chosenName += '.html';
}
closeSaveModal();
document.querySelectorAll('input[type="text"]').forEach(function(el) { el.setAttribute('value', el.value); });
document.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(function(el) {
if (el.checked) el.setAttribute('checked', 'checked');
else el.removeAttribute('checked');
});
document.querySelectorAll('textarea').forEach(function(el) { el.textContent = el.value; });
document.querySelectorAll('select').forEach(function(el) {
Array.from(el.options).forEach(function(opt) {
if (opt.value === el.value) opt.setAttribute('selected', 'selected');
else opt.removeAttribute('selected');
});
});
var pre = document.getElementById('ascii-report') || document.getElementById('outputPre') || document.getElementById('preview') || document.getElementById('outputArea');
if (pre) pre.textContent = pre.textContent;
var clonedDoc = document.documentElement.cloneNode(true);
var modalInClone = clonedDoc.querySelector('#saveModalBackdrop');
if (modalInClone) modalInClone.classList.remove('active');
var htmlContent = '<!DOCTYPE html>\n' + clonedDoc.outerHTML;
var blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = chosenName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// Initialize event listeners safely after DOM loads
window.addEventListener('DOMContentLoaded', function() {
var cancelBtn = document.getElementById('saveModalCancelBtn');
var confirmBtn = document.getElementById('saveModalSaveBtn');
var modalInput = document.getElementById('saveModalFileName');
var modalBackdrop = document.getElementById('saveModalBackdrop');
if (cancelBtn) cancelBtn.addEventListener('click', closeSaveModal);
if (confirmBtn) confirmBtn.addEventListener('click', executeSaveHTML);
if (modalInput) {
modalInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') { e.preventDefault(); executeSaveHTML(); }
if (e.key === 'Escape') { e.preventDefault(); closeSaveModal(); }
});
}
if (modalBackdrop) {
modalBackdrop.addEventListener('click', function(e) {
if (e.target === modalBackdrop) closeSaveModal();
});
}
var btnSaveMain = document.getElementById('btnSaveHtml');
if (btnSaveMain) {
btnSaveMain.addEventListener('click', openSaveModal);
}
});
</script>
</body>
</html>