Upload files to "General"

This commit is contained in:
2026-09-05 10:17:30 -04:00
parent 992ec209ee
commit 8898a54389
5 changed files with 2167 additions and 0 deletions
+563
View File
@@ -0,0 +1,563 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Winlink Field Situation Report (FSR) - Full Version</title>
<style>
* { box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace; background: #eef1f5; color: #222; padding: 20px; margin: 0; }
.container { max-width: 960px; margin: 0 auto; background: #fff; padding: 24px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.header { background: #b71c1c; 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 #ef9a9a; border-radius: 6px; padding: 14px; margin-bottom: 14px; background: #fffbfb; }
legend { font-weight: bold; padding: 0 8px; font-size: 0.85rem; color: #b71c1c; }
.row { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 10px; }
.col { flex: 1; min-width: 140px; 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: #333; }
input[type="text"], textarea, select { padding: 7px 8px; font-size: 0.88rem; border: 1px solid #999; border-radius: 4px; font-family: inherit; width: 100%; }
textarea { resize: vertical; min-height: 50px; }
.btn-now { background: #4a5568; color: #fff; padding: 4px 8px; font-size: 0.72rem; border-radius: 4px; border: none; cursor: pointer; }
.btn-now:hover { background: #2d3748; }
.lifeline-block { background: #fafafa; border: 1px solid #e0e0e0; border-radius: 5px; padding: 10px; margin-bottom: 8px; }
.lifeline-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
.lifeline-header span { font-size: 0.85rem; font-weight: bold; color: #333; }
.lifeline-header select { width: 170px; font-weight: bold; }
.actions { display: flex; gap: 10px; margin: 18px 0; }
button.main-btn { flex: 1; padding: 12px; font-size: 0.95rem; font-weight: bold; border: none; border-radius: 6px; cursor: pointer; }
.btn-generate { background: #b71c1c; color: #fff; }
.btn-generate:hover { background: #7f0000; }
.btn-save { background: #5c6bc0; color: #fff; }
.btn-save:hover { background: #3f51b5; }
.btn-copy { background: #2e7d32; color: #fff; }
.btn-copy:hover { background: #1b5e20; }
.btn-clear { background: #555; color: #fff; flex: 0.35; }
.btn-clear:hover { background: #333; }
pre { background: #181c20; color: #39ff14; padding: 14px; border-radius: 6px; overflow-x: auto; font-family: "Courier New", Courier, monospace; font-size: 0.82rem; line-height: 1.32; min-height: 150px; }
.nav-bar { margin-bottom: 12px; font-size: 0.85rem; }
.nav-bar a { color: #b71c1c; 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>FIELD SITUATION REPORT (FSR)</h2>
<p>Official Winlink Emergency Infrastructure & Lifeline Assessment</p>
</div>
<form id="fsrForm" onsubmit="event.preventDefault(); return false;">
<fieldset>
<legend>ROUTING & MESSAGE HEADER</legend>
<div class="row">
<div class="col" style="max-width: 140px;">
<label for="prec">Precedence</label>
<select id="prec">
<option value="R/ Routine" selected>R/ Routine</option>
<option value="P/ Priority">P/ Priority</option>
<option value="O/ Immediate">O/ Immediate</option>
<option value="Z/ Flash">Z/ Flash</option>
</select>
</div>
<div class="col">
<div class="label-row">
<label for="dtZulu">Date / Time (UTC)</label>
<button type="button" class="btn-now" id="btnZuluNow">Current UTC</button>
</div>
<input type="text" id="dtZulu" placeholder="YYYY-MM-DD HHMMZ">
</div>
<div class="col" style="max-width: 140px;"><label for="taskNum">Task / Msg #</label><input type="text" id="taskNum" placeholder="e.g. 001"></div>
<div class="col"><label for="msgSender">From (Callsign)</label><input type="text" id="msgSender" placeholder="N0CALL"></div>
</div>
<div class="row">
<div class="col"><label for="msgTo">To (Callsigns or Emails separated by semicolon ;)</label><input type="text" id="msgTo" placeholder="NCS; REDCROSS; [email protected]"></div>
<div class="col"><label for="msgCc">Info (CC)</label><input type="text" id="msgCc" placeholder="Optional CC recipients"></div>
</div>
</fieldset>
<fieldset id="safetyFieldset">
<legend>1. LIFE SAFETY / CRITICAL STATUS</legend>
<div class="row" style="align-items: center;">
<div class="col" style="max-width: 320px;">
<label for="lifeSafety" style="color: #b71c1c;">Is there an EMERGENT / LIFE SAFETY Need?</label>
<select id="lifeSafety" style="font-weight:bold;">
<option value="NO" selected>NO - Standard Situation</option>
<option value="YES" style="background:#ffcdd2; color:#b71c1c;">YES - LIFE CRITICAL NEED</option>
</select>
</div>
<div class="col">
<label for="lifeSafetyDesc">Emergency Details & Residential Address (if Life Critical)</label>
<input type="text" id="lifeSafetyDesc" placeholder="Describe life-safety hazard, trapped persons, residential address...">
</div>
</div>
</fieldset>
<fieldset>
<legend>2-3. LOCATION & GEOCOORDINATES</legend>
<div class="row">
<div class="col"><label for="city">City</label><input type="text" id="city"></div>
<div class="col"><label for="county">County</label><input type="text" id="county"></div>
<div class="col" style="max-width: 90px;"><label for="state">State</label><input type="text" id="state" placeholder="TX" maxlength="2"></div>
<div class="col"><label for="territory">Territory / Region</label><input type="text" id="territory"></div>
</div>
<div class="row">
<div class="col"><label for="lat">Latitude (Decimal)</label><input type="text" id="lat" placeholder="e.g. 30.2672"></div>
<div class="col"><label for="lon">Longitude (Decimal)</label><input type="text" id="lon" placeholder="e.g. -97.7431"></div>
<div class="col"><label for="mgrs">MGRS Coordinates</label><input type="text" id="mgrs" placeholder="e.g. 14RPU12345678"></div>
<div class="col" style="max-width: 120px;"><label for="grid">Grid Square</label><input type="text" id="grid" placeholder="EM10dj"></div>
</div>
</fieldset>
<fieldset>
<legend>4-11. INFRASTRUCTURE & LIFELINE ASSESSMENTS</legend>
<!-- 4a & 4b Phone -->
<div class="lifeline-block">
<div class="lifeline-header">
<span>4a. POTS Landlines Functioning?</span>
<select id="s_pots"><option value="YES">YES</option><option value="NO">NO</option><option value="Unknown - N/A" selected>Unknown - N/A</option></select>
</div>
<input type="text" id="c_pots" placeholder="If NO, state provider or area details...">
</div>
<div class="lifeline-block">
<div class="lifeline-header">
<span>4b. VoIP Landlines Functioning?</span>
<select id="s_voip"><option value="YES">YES</option><option value="NO">NO</option><option value="Unknown - N/A" selected>Unknown - N/A</option></select>
</div>
<input type="text" id="c_voip" placeholder="If NO, state provider or area details...">
</div>
<!-- 5a & 5b Cell -->
<div class="lifeline-block">
<div class="lifeline-header">
<span>5a. Cell Phone Voice Calls Functioning?</span>
<select id="s_cell_v"><option value="YES">YES</option><option value="NO">NO</option><option value="Unknown - N/A" selected>Unknown - N/A</option></select>
</div>
<input type="text" id="c_cell_v" placeholder="If NO, state carrier (AT&T, Verizon, T-Mobile)...">
</div>
<div class="lifeline-block">
<div class="lifeline-header">
<span>5b. Cell Phone Texts (SMS) Functioning?</span>
<select id="s_cell_t"><option value="YES">YES</option><option value="NO">NO</option><option value="Unknown - N/A" selected>Unknown - N/A</option></select>
</div>
<input type="text" id="c_cell_t" placeholder="If NO, state carrier or notes...">
</div>
<!-- 6 Broadcast Radio -->
<div class="lifeline-block">
<div class="lifeline-header">
<span>6. AM/FM Broadcast Stations Functioning?</span>
<select id="s_amfm"><option value="YES">YES</option><option value="NO">NO</option><option value="Unknown - N/A" selected>Unknown - N/A</option></select>
</div>
<input type="text" id="c_amfm" placeholder="If NO, provide broadcast station callsign / frequency off the air...">
</div>
<!-- 7 TV -->
<div class="lifeline-block">
<div class="lifeline-header">
<span>7a. Over-the-Air (OTA) TV Functioning?</span>
<select id="s_otatv"><option value="YES">YES</option><option value="NO">NO</option><option value="Unknown - N/A" selected>Unknown - N/A</option></select>
</div>
<input type="text" id="c_otatv" placeholder="If NO, identify TV station / transmitter...">
</div>
<div class="lifeline-block">
<div class="lifeline-header">
<span>7b. Satellite TV Functioning?</span>
<select id="s_sattv"><option value="YES">YES</option><option value="NO">NO</option><option value="Unknown - N/A" selected>Unknown - N/A</option></select>
</div>
<input type="text" id="c_sattv" placeholder="If NO, state provider...">
</div>
<div class="lifeline-block">
<div class="lifeline-header">
<span>7c. Cable TV Functioning?</span>
<select id="s_cabletv"><option value="YES">YES</option><option value="NO">NO</option><option value="Unknown - N/A" selected>Unknown - N/A</option></select>
</div>
<input type="text" id="c_cabletv" placeholder="If NO, state provider...">
</div>
<!-- 8 Water -->
<div class="lifeline-block">
<div class="lifeline-header">
<span>8. Public Water Works Functioning?</span>
<select id="s_water"><option value="YES">YES</option><option value="NO">NO</option><option value="Unknown - N/A" selected>Unknown - N/A</option></select>
</div>
<input type="text" id="c_water" placeholder="If NO, describe outage or boil-water notice...">
</div>
<!-- 9 Power & Gas -->
<div class="lifeline-block">
<div class="lifeline-header">
<span>9a. Commercial Power Functioning?</span>
<select id="s_pwr"><option value="YES">YES</option><option value="NO">NO</option><option value="Unknown - N/A" selected>Unknown - N/A</option></select>
</div>
<input type="text" id="c_pwr" placeholder="If NO, state electric utility provider...">
</div>
<div class="lifeline-block">
<div class="lifeline-header">
<span>9b. Commercial Power Stable?</span>
<select id="s_pwr_stab"><option value="YES">YES</option><option value="NO">NO (Brownouts / Fluctuating)</option><option value="Unknown - N/A" selected>Unknown - N/A</option></select>
</div>
<input type="text" id="c_pwr_stab" placeholder="Comments on power stability...">
</div>
<div class="lifeline-block">
<div class="lifeline-header">
<span>9c. Natural Gas Supply Functioning?</span>
<select id="s_gas"><option value="YES">YES</option><option value="NO">NO</option><option value="Unknown - N/A" selected>Unknown - N/A</option></select>
</div>
<input type="text" id="c_gas" placeholder="If NO, state gas provider...">
</div>
<!-- 10 Internet -->
<div class="lifeline-block">
<div class="lifeline-header">
<span>10. Internet Functioning?</span>
<select id="s_net"><option value="YES">YES</option><option value="NO">NO</option><option value="Unknown - N/A" selected>Unknown - N/A</option></select>
</div>
<input type="text" id="c_net" placeholder="Indicate Fiber, Cable, DSL, Wireless, Satellite and ISP...">
</div>
<!-- 11 NOAA Weather Radio -->
<div class="lifeline-block">
<div class="lifeline-header">
<span>11a. NOAA Weather Radio Functioning?</span>
<select id="s_noaa"><option value="YES">YES</option><option value="NO">NO</option><option value="Unknown - N/A" selected>Unknown - N/A</option></select>
</div>
<input type="text" id="c_noaa" placeholder="Identify NOAA Transmitter frequency, callsign, or location...">
</div>
<div class="lifeline-block">
<div class="lifeline-header">
<span>11b. NOAA Weather Radio Audio Degraded?</span>
<select id="s_noaa_deg"><option value="YES">YES (Degraded / Static)</option><option value="NO">NO (Clear Audio)</option><option value="Unknown - N/A" selected>Unknown - N/A</option></select>
</div>
<input type="text" id="c_noaa_deg" placeholder="Comments on audio degradation...">
</div>
</fieldset>
<fieldset>
<legend>12-13. SUMMARY & POINT OF CONTACT</legend>
<div class="row">
<div class="col">
<label for="comments">12. Additional Comments / Current Observations</label>
<textarea id="comments" rows="3" placeholder="Brief summary of situation, observed damage, local road access..."></textarea>
</div>
</div>
<div class="row">
<div class="col"><label for="poc">13. Point of Contact (POC)</label><input type="text" id="poc" placeholder="Name, Callsign, Phone / Email"></div>
</div>
</fieldset>
<div class="actions">
<button type="button" class="main-btn btn-generate" id="btnGen">Generate Packet ASCII</button>
<button type="button" class="main-btn btn-save" id="btnSaveHtml">Save as HTML</button>
<button type="button" class="main-btn btn-copy" id="btnCopy">Copy ASCII</button>
<button type="button" class="main-btn btn-clear" id="btnClear">Reset</button>
</div>
</form>
<label style="font-weight:bold;">Formatted ASCII for Packet / BBS Transmission:</label>
<pre id="outputPre">Fill in the fields above and click "Generate Packet ASCII"...</pre>
</div>
<script>
function setUTC() {
const now = new Date();
const y = now.getUTCFullYear();
const m = String(now.getUTCMonth()+1).padStart(2,'0');
const d = String(now.getUTCDate()).padStart(2,'0');
const hh = String(now.getUTCHours()).padStart(2,'0');
const mm = String(now.getUTCMinutes()).padStart(2,'0');
document.getElementById('dtZulu').value = y + '-' + m + '-' + d + ' ' + hh + mm + 'Z';
}
function clean(id) {
const el = document.getElementById(id);
return el ? (el.value || '').trim() : '';
}
function pad(str, len) {
str = (str || '').toString();
return str + ' '.repeat(Math.max(0, len - str.length));
}
function wrapText(str, width) {
if (!str) return [];
let lines = [];
str.split('\n').forEach(function(paragraph) {
let words = paragraph.split(' ');
let cur = '';
words.forEach(function(w) {
if ((cur + (cur ? ' ' : '') + w).length <= width) {
cur += (cur ? ' ' : '') + w;
} else {
if (cur) lines.push(cur);
cur = w;
}
});
if (cur) lines.push(cur);
});
return lines;
}
function generateASCII() {
try {
const div = '='.repeat(78);
const sub = '-'.repeat(78);
const prec = clean('prec');
const dt = clean('dtZulu') || 'N/A';
const task = clean('taskNum') || 'N/A';
const from = clean('msgSender').toUpperCase() || 'N/A';
const to = clean('msgTo').toUpperCase() || 'N/A';
const cc = clean('msgCc').toUpperCase() || 'NONE';
const safety = clean('lifeSafety');
const safetyD = clean('lifeSafetyDesc').toUpperCase();
const city = clean('city').toUpperCase();
const county = clean('county').toUpperCase();
const st = clean('state').toUpperCase();
const terr = clean('territory').toUpperCase();
const lat = clean('lat');
const lon = clean('lon');
const mgrs = clean('mgrs').toUpperCase();
const grid = clean('grid').toUpperCase();
const cmt = clean('comments').toUpperCase() || 'NONE';
const poc = clean('poc').toUpperCase() || 'N/A';
let items = [
["04a. POTS LANDLINES", clean('s_pots'), clean('c_pots')],
["04b. VOIP LANDLINES", clean('s_voip'), clean('c_voip')],
["05a. CELL VOICE", clean('s_cell_v'), clean('c_cell_v')],
["05b. CELL TEXTS", clean('s_cell_t'), clean('c_cell_t')],
["06. AM/FM BROADCAST",clean('s_amfm'), clean('c_amfm')],
["07a. OTA TV", clean('s_otatv'), clean('c_otatv')],
["07b. SATELLITE TV", clean('s_sattv'), clean('c_sattv')],
["07c. CABLE TV", clean('s_cabletv'), clean('c_cabletv')],
["08. WATER WORKS", clean('s_water'), clean('c_water')],
["09a. COMM POWER", clean('s_pwr'), clean('c_pwr')],
["09b. POWER STABLE", clean('s_pwr_stab'), clean('c_pwr_stab')],
["09c. NATURAL GAS", clean('s_gas'), clean('c_gas')],
["10. INTERNET", clean('s_net'), clean('c_net')],
["11a. NOAA WX RADIO", clean('s_noaa'), clean('c_noaa')],
["11b. NOAA AUDIO DEG", clean('s_noaa_deg'), clean('c_noaa_deg')]
];
let lifelineLines = [];
items.forEach(function(it) {
let line = ' ' + pad(it[0], 21) + ': ' + pad(it[1], 15);
if (it[2]) line += ' [' + it[2].toUpperCase() + ']';
lifelineLines.push(line);
});
let wrappedCmt = wrapText(cmt, 72).map(function(l){ return ' ' + l; }).join('\n');
const out = div + '\n' +
'FIELD SITUATION REPORT (FSR) - WINLINK / PACKET ASCII\n' +
div + '\n' +
'PRECEDENCE: ' + pad(prec, 16) + ' TASK #: ' + pad(task, 12) + ' DATE/TIME: ' + dt + '\n' +
'FROM : ' + from + '\n' +
'TO : ' + to + '\n' +
'INFO (CC) : ' + cc + '\n' +
sub + '\n' +
'1. LIFE SAFETY CRITICAL : ' + safety + (safetyD ? ' - ' + safetyD : '') + '\n' +
sub + '\n' +
'2. LOCATION: ' + (city ? city + ', ' : '') + (county ? county + ' CO, ' : '') + st + (terr ? ' (' + terr + ')' : '') + '\n' +
'3. COORDINATES:\n' +
' LAT: ' + pad(lat || 'N/A', 12) + ' LON: ' + pad(lon || 'N/A', 12) + ' MGRS: ' + pad(mgrs || 'N/A', 16) + ' GRID: ' + (grid || 'N/A') + '\n' +
sub + '\n' +
'INFRASTRUCTURE & LIFELINE STATUS:\n' +
lifelineLines.join('\n') + '\n' +
sub + '\n' +
'12. ADDITIONAL COMMENTS:\n' + wrappedCmt + '\n' +
sub + '\n' +
'13. POINT OF CONTACT: ' + poc + '\n' +
div;
document.getElementById('outputPre').textContent = out;
} catch(err) {
alert("Error generating text: " + err.message);
}
}
function copyASCII() {
const txt = document.getElementById('outputPre').textContent;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(txt).then(function() {
alert("FSR copied to clipboard!");
}).catch(function() {
fallbackCopy();
});
} else {
fallbackCopy();
}
}
function fallbackCopy() {
const pre = document.getElementById('outputPre');
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("Reset Field Situation Report form?")) {
document.getElementById('fsrForm').reset();
document.getElementById('outputPre').textContent = 'Fill in the fields above and click "Generate Packet ASCII"...';
}
}
document.getElementById('btnZuluNow').addEventListener('click', setUTC);
document.getElementById('btnGen').addEventListener('click', generateASCII);
document.getElementById('btnSaveHtml').addEventListener('click', openSaveModal);
document.getElementById('btnCopy').addEventListener('click', copyASCII);
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 = 'fsr_' + 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);
}
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) {
// Clear existing inline handlers just in case
btnSaveMain.onclick = null;
btnSaveMain.addEventListener('click', openSaveModal);
}
});
</script>
<!-- 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>
</body>
</html>
+334
View File
@@ -0,0 +1,334 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Winlink Simple Bulletin (Packet Radio)</title>
<style>
* { box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace; background: #fdfaf6; color: #222; padding: 20px; margin: 0; }
.container { max-width: 900px; margin: 0 auto; background: #fff; padding: 24px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.header { background: #d84315; 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 #ffccbc; border-radius: 6px; padding: 14px; margin-bottom: 14px; background: #fffbf9; }
legend { font-weight: bold; padding: 0 8px; font-size: 0.85rem; color: #bf360c; }
.row { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 10px; }
.col { flex: 1; min-width: 140px; 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: #333; }
input[type="text"], textarea, select { padding: 7px 8px; font-size: 0.9rem; border: 1px solid #999; border-radius: 4px; font-family: inherit; width: 100%; }
textarea { resize: vertical; min-height: 120px; }
.btn-now { background: #4a5568; color: #fff; padding: 4px 8px; font-size: 0.72rem; border-radius: 4px; border: none; cursor: pointer; }
.btn-now:hover { background: #2d3748; }
.actions { display: flex; gap: 10px; margin: 18px 0; }
button.main-btn { flex: 1; padding: 12px; font-size: 0.95rem; font-weight: bold; border: none; border-radius: 6px; cursor: pointer; }
.btn-generate { background: #d84315; color: #fff; }
.btn-generate:hover { background: #bf360c; }
.btn-save { background: #5c6bc0; color: #fff; }
.btn-save:hover { background: #3f51b5; }
.btn-copy { background: #2e7d32; color: #fff; }
.btn-copy:hover { background: #1b5e20; }
.btn-clear { background: #c62828; color: #fff; flex: 0.35; }
.btn-clear:hover { background: #8e0000; }
pre { background: #181c20; color: #39ff14; padding: 14px; border-radius: 6px; overflow-x: auto; font-family: "Courier New", Courier, monospace; font-size: 0.85rem; line-height: 1.35; min-height: 150px; }
.nav-bar { margin-bottom: 12px; font-size: 0.85rem; }
.nav-bar a { color: #d84315; 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>BULLETIN (WINLINK / PACKET RADIO)</h2>
<p>Official Winlink Simple Bulletin Template</p>
</div>
<form id="bulletinForm" onsubmit="event.preventDefault(); return false;">
<fieldset>
<legend>BULLETIN HEADER & ROUTING</legend>
<div class="row">
<div class="col" style="flex: 2;"><label for="forGroup">For (Name / Group)</label><input type="text" id="forGroup" placeholder="Name or Group this bulletin is for (e.g. ALL ARES STATIONS)"></div>
<div class="col"><label for="bullNr">Bulletin Nr.</label><input type="text" id="bullNr" placeholder="Optional number / identifier"></div>
</div>
<div class="row">
<div class="col" style="flex: 2;"><label for="fromGroup">From (Name / Group)</label><input type="text" id="fromGroup" placeholder="Your name or agency / group"></div>
<div class="col">
<div class="label-row">
<label for="dtZulu">Date / Time</label>
<button type="button" class="btn-now" id="btnBltnNow">Current UTC</button>
</div>
<input type="text" id="dtZulu" placeholder="YYYY-MM-DD HHMMZ">
</div>
</div>
<div class="row">
<div class="col" style="max-width: 170px;">
<label for="precedence">Precedence</label>
<select id="precedence">
<option value="Routine" selected>Routine</option>
<option value="Priority">Priority</option>
<option value="Immediate">Immediate</option>
<option value="Flash">Flash</option>
</select>
</div>
<div class="col"><label for="subject">Subject</label><input type="text" id="subject" placeholder="Bulletin subject line"></div>
</div>
</fieldset>
<fieldset>
<legend>BULLETIN MESSAGE BODY</legend>
<textarea id="message" rows="8" placeholder="Insert your bulletin text..."></textarea>
</fieldset>
<div class="actions">
<button type="button" class="main-btn btn-generate" id="btnGen">Generate Packet ASCII</button>
<button type="button" class="main-btn btn-save" id="btnSaveHtml">Save as HTML</button>
<button type="button" class="main-btn btn-copy" id="btnCopy">Copy ASCII</button>
<button type="button" class="main-btn btn-clear" id="btnClear">Reset</button>
</div>
</form>
<label style="font-weight:bold;">Formatted ASCII Output (Ready for Packet / BBS):</label>
<pre id="outputPre">Fill in the fields above and click "Generate Packet ASCII"...</pre>
</div>
<script>
function setUTC() {
const now = new Date();
const y = now.getUTCFullYear();
const m = String(now.getUTCMonth()+1).padStart(2,'0');
const d = String(now.getUTCDate()).padStart(2,'0');
const hh = String(now.getUTCHours()).padStart(2,'0');
const mm = String(now.getUTCMinutes()).padStart(2,'0');
document.getElementById('dtZulu').value = y + '-' + m + '-' + d + ' ' + hh + mm + 'Z';
}
function clean(id) {
const el = document.getElementById(id);
return el ? (el.value || '').trim() : '';
}
function wrapText(str, width) {
if (!str) return [];
let lines = [];
str.split('\n').forEach(function(paragraph) {
let words = paragraph.split(' ');
let cur = '';
words.forEach(function(w) {
if ((cur + (cur ? ' ' : '') + w).length <= width) {
cur += (cur ? ' ' : '') + w;
} else {
if (cur) lines.push(cur);
cur = w;
}
});
if (cur) lines.push(cur);
});
return lines;
}
function generateASCII() {
try {
const div = '='.repeat(74);
const sub = '-'.repeat(74);
const target = clean('forGroup').toUpperCase() || 'ALL';
const nr = clean('bullNr').toUpperCase() || 'N/A';
const from = clean('fromGroup').toUpperCase() || 'N/A';
const dt = clean('dtZulu') || 'N/A';
const prec = clean('precedence').toUpperCase();
const subj = clean('subject').toUpperCase() || 'GENERAL BULLETIN';
const body = clean('message').toUpperCase() || 'NO BULLETIN TEXT ENTERED';
let wrappedBody = wrapText(body, 70).map(function(l){ return ' ' + l; }).join('\n');
const out = div + '\n' +
'WINLINK / PACKET RADIO BULLETIN\n' +
div + '\n' +
'FOR (TARGET) : ' + target + ' BULLETIN #: ' + nr + '\n' +
'FROM : ' + from + ' PRECEDENCE: ' + prec + '\n' +
'DATE/TIME : ' + dt + '\n' +
'SUBJECT : ' + subj + '\n' +
sub + '\n' +
'BULLETIN TEXT:\n' +
wrappedBody + '\n' +
div;
document.getElementById('outputPre').textContent = out;
} catch(err) {
alert("Error generating text: " + err.message);
}
}
function copyASCII() {
const txt = document.getElementById('outputPre').textContent;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(txt).then(function() {
alert("Bulletin copied to clipboard!");
}).catch(function() {
fallbackCopy();
});
} else {
fallbackCopy();
}
}
function fallbackCopy() {
const pre = document.getElementById('outputPre');
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("Reset Bulletin form?")) {
document.getElementById('bulletinForm').reset();
document.getElementById('outputPre').textContent = 'Fill in the fields above and click "Generate Packet ASCII"...';
}
}
document.getElementById('btnBltnNow').addEventListener('click', setUTC);
document.getElementById('btnGen').addEventListener('click', generateASCII);
document.getElementById('btnSaveHtml').addEventListener('click', openSaveModal);
document.getElementById('btnCopy').addEventListener('click', copyASCII);
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 = 'general_bulletin_' + 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);
}
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) {
// Clear existing inline handlers just in case
btnSaveMain.onclick = null;
btnSaveMain.addEventListener('click', openSaveModal);
}
});
</script>
<!-- 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>
</body>
</html>
+455
View File
@@ -0,0 +1,455 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Net Check-In & Roster (Packet Radio)</title>
<style>
* { box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace; background: #eef2f7; color: #222; padding: 20px; margin: 0; }
.container { max-width: 940px; margin: 0 auto; background: #fff; padding: 24px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.header { background: #00695c; 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 #80cbc4; border-radius: 6px; padding: 14px; margin-bottom: 14px; background: #f4fbf9; }
legend { font-weight: bold; padding: 0 8px; font-size: 0.85rem; color: #004d40; }
.row { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 10px; }
.col { flex: 1; min-width: 140px; 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: #333; }
input[type="text"], select { padding: 7px 8px; font-size: 0.9rem; border: 1px solid #999; border-radius: 4px; font-family: inherit; width: 100%; }
.btn-now { background: #4a5568; color: #fff; padding: 4px 8px; font-size: 0.72rem; border-radius: 4px; border: none; cursor: pointer; }
.btn-now:hover { background: #2d3748; }
.roster-row { display: flex; gap: 6px; margin-bottom: 6px; align-items: center; }
.roster-row input, .roster-row select { font-size: 0.82rem; padding: 6px 5px; }
.actions { display: flex; gap: 10px; margin: 18px 0; }
button.main-btn { flex: 1; padding: 12px; font-size: 0.95rem; font-weight: bold; border: none; border-radius: 6px; cursor: pointer; }
.btn-generate { background: #00695c; color: #fff; }
.btn-generate:hover { background: #004d40; }
.btn-save { background: #5c6bc0; color: #fff; }
.btn-save:hover { background: #3f51b5; }
.btn-copy { background: #2e7d32; color: #fff; }
.btn-copy:hover { background: #1b5e20; }
.btn-clear { background: #c62828; color: #fff; flex: 0.35; }
.btn-clear:hover { background: #8e0000; }
pre { background: #181c20; color: #39ff14; padding: 14px; border-radius: 6px; overflow-x: auto; font-family: "Courier New", Courier, monospace; font-size: 0.83rem; line-height: 1.32; min-height: 150px; }
.nav-bar { margin-bottom: 12px; font-size: 0.85rem; }
.nav-bar a { color: #00695c; 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>NET CHECK-IN LOG / ROSTER</h2>
<p>Net Control Station (NCS) Multi-Station Check-In & Traffic Log (Packet ASCII)</p>
</div>
<form id="netRosterForm" onsubmit="event.preventDefault(); return false;">
<fieldset>
<legend>NET SESSION HEADER</legend>
<div class="row">
<div class="col" style="flex: 2;"><label for="netTitle">Net Name / Purpose</label><input type="text" id="netTitle" placeholder="e.g. DISTRICT 3 EMERGENCY ARES NET"></div>
<div class="col"><label for="netFreq">Frequency / Mode</label><input type="text" id="netFreq" placeholder="146.520 Simplex / AX.25"></div>
</div>
<div class="row">
<div class="col">
<div class="label-row">
<label for="netDateTime">Net Date / Time</label>
<button type="button" class="btn-now" id="btnNetNow">Current UTC</button>
</div>
<input type="text" id="netDateTime" placeholder="YYYY-MM-DD HHMMZ">
</div>
<div class="col"><label for="ncsCall">NCS Callsign</label><input type="text" id="ncsCall" placeholder="N0CALL"></div>
<div class="col"><label for="ncsOp">NCS Operator Name</label><input type="text" id="ncsOp"></div>
</div>
</fieldset>
<fieldset>
<legend>NET CHECK-IN STATIONS</legend>
<div style="font-size: 0.75rem; font-weight: bold; margin-bottom: 6px; color: #555;">
# | Time (Z) | Callsign | Name / Tactical | Location / Grid | Power / Mode | Traffic / Remarks
</div>
<div id="rosterContainer">
<div class="roster-row">
<input type="text" style="width: 38px;" value="1" readonly>
<input type="text" style="width: 75px;" placeholder="TIME" class="r-time">
<input type="text" style="width: 100px;" placeholder="CALLSIGN" class="r-call">
<input type="text" style="width: 130px;" placeholder="NAME / TACTICAL" class="r-name">
<input type="text" style="width: 130px;" placeholder="LOCATION / GRID" class="r-loc">
<select style="width: 115px;" class="r-pwr">
<option value="COMM">COMM/GRID</option>
<option value="BATT">BATTERY</option>
<option value="GEN">GENERATOR</option>
<option value="SOLAR">SOLAR</option>
</select>
<input type="text" style="flex: 1;" placeholder="No Traffic / QRU / Msg on hand" class="r-remarks">
</div>
<div class="roster-row">
<input type="text" style="width: 38px;" value="2" readonly>
<input type="text" style="width: 75px;" placeholder="TIME" class="r-time">
<input type="text" style="width: 100px;" placeholder="CALLSIGN" class="r-call">
<input type="text" style="width: 130px;" placeholder="NAME / TACTICAL" class="r-name">
<input type="text" style="width: 130px;" placeholder="LOCATION / GRID" class="r-loc">
<select style="width: 115px;" class="r-pwr">
<option value="COMM">COMM/GRID</option>
<option value="BATT">BATTERY</option>
<option value="GEN">GENERATOR</option>
<option value="SOLAR">SOLAR</option>
</select>
<input type="text" style="flex: 1;" placeholder="No Traffic / QRU / Msg on hand" class="r-remarks">
</div>
<div class="roster-row">
<input type="text" style="width: 38px;" value="3" readonly>
<input type="text" style="width: 75px;" placeholder="TIME" class="r-time">
<input type="text" style="width: 100px;" placeholder="CALLSIGN" class="r-call">
<input type="text" style="width: 130px;" placeholder="NAME / TACTICAL" class="r-name">
<input type="text" style="width: 130px;" placeholder="LOCATION / GRID" class="r-loc">
<select style="width: 115px;" class="r-pwr">
<option value="COMM">COMM/GRID</option>
<option value="BATT">BATTERY</option>
<option value="GEN">GENERATOR</option>
<option value="SOLAR">SOLAR</option>
</select>
<input type="text" style="flex: 1;" placeholder="No Traffic / QRU / Msg on hand" class="r-remarks">
</div>
<div class="roster-row">
<input type="text" style="width: 38px;" value="4" readonly>
<input type="text" style="width: 75px;" placeholder="TIME" class="r-time">
<input type="text" style="width: 100px;" placeholder="CALLSIGN" class="r-call">
<input type="text" style="width: 130px;" placeholder="NAME / TACTICAL" class="r-name">
<input type="text" style="width: 130px;" placeholder="LOCATION / GRID" class="r-loc">
<select style="width: 115px;" class="r-pwr">
<option value="COMM">COMM/GRID</option>
<option value="BATT">BATTERY</option>
<option value="GEN">GENERATOR</option>
<option value="SOLAR">SOLAR</option>
</select>
<input type="text" style="flex: 1;" placeholder="No Traffic / QRU / Msg on hand" class="r-remarks">
</div>
<div class="roster-row">
<input type="text" style="width: 38px;" value="5" readonly>
<input type="text" style="width: 75px;" placeholder="TIME" class="r-time">
<input type="text" style="width: 100px;" placeholder="CALLSIGN" class="r-call">
<input type="text" style="width: 130px;" placeholder="NAME / TACTICAL" class="r-name">
<input type="text" style="width: 130px;" placeholder="LOCATION / GRID" class="r-loc">
<select style="width: 115px;" class="r-pwr">
<option value="COMM">COMM/GRID</option>
<option value="BATT">BATTERY</option>
<option value="GEN">GENERATOR</option>
<option value="SOLAR">SOLAR</option>
</select>
<input type="text" style="flex: 1;" placeholder="No Traffic / QRU / Msg on hand" class="r-remarks">
</div>
</div>
<button type="button" class="btn-now" id="btnAddCheckin" style="margin-top: 6px; padding: 6px 12px; font-size: 0.8rem;">+ Add Station Check-in</button>
</fieldset>
<fieldset>
<legend>SESSION SUMMARY</legend>
<div class="row">
<div class="col"><label for="netClose">Net Close Time</label><input type="text" id="netClose" placeholder="e.g. 2130Z"></div>
<div class="col"><label for="totalCheckins">Total Stations</label><input type="text" id="totalCheckins" placeholder="AUTO" readonly style="background:#eee;"></div>
<div class="col"><label for="totalTraffic">Formal Messages Handled</label><input type="text" id="totalTraffic" placeholder="0"></div>
</div>
</fieldset>
<div class="actions">
<button type="button" class="main-btn btn-generate" id="btnGen">Generate Packet ASCII</button>
<button type="button" class="main-btn btn-save" id="btnSaveHtml">Save as HTML</button>
<button type="button" class="main-btn btn-copy" id="btnCopy">Copy ASCII</button>
<button type="button" class="main-btn btn-clear" id="btnClear">Reset</button>
</div>
</form>
<label style="font-weight:bold;">Formatted ASCII for Packet / BBS Transmission:</label>
<pre id="outputPre">Fill in the fields above and click "Generate Packet ASCII"...</pre>
</div>
<script>
function addCheckinRow() {
const container = document.getElementById('rosterContainer');
const count = container.querySelectorAll('.roster-row').length + 1;
const div = document.createElement('div');
div.className = 'roster-row';
div.innerHTML = '<input type="text" style="width: 38px;" value="' + count + '" readonly>' +
'<input type="text" style="width: 75px;" placeholder="TIME" class="r-time">' +
'<input type="text" style="width: 100px;" placeholder="CALLSIGN" class="r-call">' +
'<input type="text" style="width: 130px;" placeholder="NAME / TACTICAL" class="r-name">' +
'<input type="text" style="width: 130px;" placeholder="LOCATION / GRID" class="r-loc">' +
'<select style="width: 115px;" class="r-pwr">' +
'<option value="COMM">COMM/GRID</option>' +
'<option value="BATT">BATTERY</option>' +
'<option value="GEN">GENERATOR</option>' +
'<option value="SOLAR">SOLAR</option>' +
'</select>' +
'<input type="text" style="flex: 1;" placeholder="No Traffic / Remarks" class="r-remarks">';
container.appendChild(div);
}
function setUTC() {
const now = new Date();
const y = now.getUTCFullYear();
const m = String(now.getUTCMonth()+1).padStart(2,'0');
const d = String(now.getUTCDate()).padStart(2,'0');
const hh = String(now.getUTCHours()).padStart(2,'0');
const mm = String(now.getUTCMinutes()).padStart(2,'0');
document.getElementById('netDateTime').value = y + '-' + m + '-' + d + ' ' + hh + mm + 'Z';
}
function clean(id) {
const el = document.getElementById(id);
return el ? (el.value || '').trim() : '';
}
function pad(str, len) {
str = (str || '').toString();
return str + ' '.repeat(Math.max(0, len - str.length));
}
function generateASCII() {
try {
const div = '='.repeat(78);
const sub = '-'.repeat(78);
const net = clean('netTitle').toUpperCase() || 'NET SESSION';
const freq = clean('netFreq').toUpperCase() || 'N/A';
const dt = clean('netDateTime').toUpperCase() || 'N/A';
const ncs = clean('ncsCall').toUpperCase() || 'N/A';
const op = clean('ncsOp').toUpperCase() || 'N/A';
const closeT = clean('netClose').toUpperCase() || 'OPEN';
const traf = clean('totalTraffic') || '0';
let lines = [];
let activeCount = 0;
const rows = document.querySelectorAll('.roster-row');
rows.forEach(function(r, idx) {
const time = pad((r.querySelector('.r-time').value || '').trim().toUpperCase(), 5);
const call = pad((r.querySelector('.r-call').value || '').trim().toUpperCase(), 9);
const name = pad((r.querySelector('.r-name').value || '').trim().toUpperCase(), 13);
const loc = pad((r.querySelector('.r-loc').value || '').trim().toUpperCase(), 13);
const pwr = pad((r.querySelector('.r-pwr').value || 'COMM'), 6);
const rem = (r.querySelector('.r-remarks').value || '').trim().toUpperCase();
if (call.trim()) {
activeCount++;
const numStr = pad(String(activeCount), 3);
lines.push(numStr + '| ' + time + ' | ' + call + ' | ' + name + ' | ' + loc + ' | ' + pwr + ' | ' + (rem || 'QRU'));
}
});
document.getElementById('totalCheckins').value = activeCount;
if (lines.length === 0) {
lines.push(' NO STATIONS LOGGED');
}
const out = div + '\n' +
'NET CHECK-IN ROSTER & TRAFFIC LOG (PACKET ASCII)\n' +
div + '\n' +
'NET NAME : ' + net + '\n' +
'FREQ / MODE: ' + freq + ' DATE/TIME: ' + dt + '\n' +
'NCS CALL : ' + pad(ncs, 10) + ' NCS OPERATOR: ' + op + '\n' +
sub + '\n' +
'# | TIME | CALLSIGN | NAME/TACTICAL | LOCATION/GRID | POWER | TRAFFIC/REMARKS\n' +
sub + '\n' +
lines.join('\n') + '\n' +
sub + '\n' +
'TOTAL STATIONS: ' + activeCount + ' NET CLOSED: ' + closeT + ' MESSAGES: ' + traf + '\n' +
div;
document.getElementById('outputPre').textContent = out;
} catch(err) {
alert("Error generating text: " + err.message);
}
}
});
});
const pre = document.getElementById('outputPre');
if (pre) pre.textContent = pre.textContent;
const doctype = '<!DOCTYPE html>\n';
const htmlContent = doctype + document.documentElement.outerHTML;
const blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' });
const url = URL.createObjectURL(blob);
const now = new Date();
const dStr = now.toISOString().slice(0, 10);
const defaultName = 'net_roster_' + dStr + '.html';
let chosenName = prompt('Enter filename to save as:', defaultName);
if (!chosenName) return; // User cancelled
if (!chosenName.toLowerCase().endsWith('.html') && !chosenName.toLowerCase().endsWith('.txt')) {
chosenName += '.html';
}
function copyASCII() {
const txt = document.getElementById('outputPre').textContent;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(txt).then(function() {
alert("Net check-in log copied to clipboard!");
}).catch(function() {
fallbackCopy();
});
} else {
fallbackCopy();
}
}
function fallbackCopy() {
const pre = document.getElementById('outputPre');
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("Reset Net Roster form?")) {
document.getElementById('netRosterForm').reset();
document.getElementById('outputPre').textContent = 'Fill in the fields above and click "Generate Packet ASCII"...';
}
}
document.getElementById('btnNetNow').addEventListener('click', setUTC);
document.getElementById('btnAddCheckin').addEventListener('click', addCheckinRow);
document.getElementById('btnGen').addEventListener('click', generateASCII);
document.getElementById('btnSaveHtml').addEventListener('click', openSaveModal);
document.getElementById('btnCopy').addEventListener('click', copyASCII);
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 = 'net_checkin_' + 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);
}
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) {
// Clear existing inline handlers just in case
btnSaveMain.onclick = null;
btnSaveMain.addEventListener('click', openSaveModal);
}
});
</script>
<!-- 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>
</body>
</html>
+433
View File
@@ -0,0 +1,433 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TPRFN / RRI Radiogram Generator</title>
<style>
:root { --primary: #2e7d32; --secondary: #1976d2; --danger: #c62828; --warning: #f57c00; --dark: #111; --light: #f9f9f9; --info: #546e7a; --success: #388e3c; }
html, body { height: auto; margin: 0; padding: 0; background-color: #eceff1; }
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; padding: 15px; line-height: 1.6; }
.nav-bar { max-width: 1000px; margin: 0 auto 10px auto; font-size: 0.88rem; }
.nav-bar a { color: var(--primary); text-decoration: none; font-weight: bold; }
.radiogram-container {
font-family: 'Courier New', Courier, monospace;
width: 98%; max-width: 1000px; margin: 0 auto 20px auto; padding: 35px;
border: 2px solid #333; background: var(--light);
box-shadow: 0 15px 35px rgba(0,0,0,0.3); border-radius: 12px;
box-sizing: border-box; height: auto; overflow: visible;
}
.header { text-align: center; border-bottom: 3px solid #333; margin-bottom: 25px; padding-bottom: 15px; }
.header h2 { margin: 0; color: var(--primary); font-size: clamp(22px, 6vw, 32px); letter-spacing: 1px; }
.section-title { font-size: 14px; font-weight: bold; color: var(--primary); margin: 30px 0 15px 0; text-transform: uppercase; border-left: 6px solid var(--primary); padding: 8px 15px; background: #e8f5e9; }
.row { display: flex; margin-bottom: 15px; align-items: center; flex-wrap: wrap; gap: 10px; }
.row label { flex: 0 0 220px; font-weight: bold; font-size: 15px; color: #333; }
.row input, .row select { flex: 1 1 300px; padding: 12px; border: 1px solid #aaa; border-radius: 5px; text-transform: uppercase; font-family: inherit; font-size: 15px; box-sizing: border-box; }
.multi-input { display: flex; gap: 10px; flex: 1 1 300px; flex-wrap: wrap; align-items: center; }
.btn-inline { padding: 12px 20px; background: #607d8b; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 13px; transition: 0.3s; }
.btn-inline:hover { background: #455a64; }
textarea { width: 100%; height: 200px; margin-top: 15px; text-transform: uppercase; font-family: inherit; padding: 15px; border: 1px solid #aaa; border-radius: 5px; box-sizing: border-box; resize: vertical; font-size: 16px; }
.btns-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 30px 0; }
button { padding: 15px; font-weight: bold; cursor: pointer; color: white; border: none; border-radius: 6px; font-size: 14px; transition: 0.2s; }
.btn-gen { background: var(--primary); grid-column: span 2; font-size: 18px; }
.btn-gen:hover { background: #1b5e20; }
.btn-copy { background: var(--secondary); }
.btn-copy:hover { background: #0d47a1; }
.btn-save-txt { background: var(--warning); color: #000; }
.btn-save-txt:hover { background: #e65100; color: #fff; }
.btn-save-html { background: #5c6bc0; color: #fff; }
.btn-save-html:hover { background: #3949ab; }
.btn-clear { background: var(--danger); }
.btn-clear:hover { background: #b71c1c; }
.btn-external { background: var(--secondary); font-size: 14px; text-decoration: none; display: flex; align-items: center; justify-content: center; text-align: center; margin-bottom: 20px; width: 100%; cursor: pointer; border-radius: 4px; }
.btn-external:hover { background: #1565c0; }
#preview {
width: 100%; min-height: 380px; height: auto;
background: var(--dark); color: #00ff41;
border: 3px solid #444; padding: 25px;
white-space: pre-wrap; word-wrap: break-word; font-size: 15px;
margin-top: 15px; border-radius: 6px; box-sizing: border-box;
box-shadow: inset 0 0 20px #000; overflow: visible;
}
.footer-sig { margin-top: 40px; text-align: center; font-size: 13px; color: #888; border-top: 1px solid #ddd; padding-top: 20px; font-family: sans-serif; font-weight: bold; letter-spacing: 2px; }
@media (max-width: 700px) {
.row label { flex: 1 1 100%; }
.btns-grid { grid-template-columns: 1fr; }
.btn-gen { grid-column: span 1; }
}
/* 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 onload="initForm()">
<div class="nav-bar"><a href="../index.html">&larr; Back to Packet Forms Hub</a></div>
<div class="radiogram-container">
<div class="header">
<h2>TPRFN / RRI RADIOGRAM GENERATOR</h2>
<div style="font-weight:bold; margin-top:8px; font-size: 14px; color: #555;">NTS DIGITAL TRAFFIC BENCH</div>
</div>
<button type="button" class="btn-external" onclick="window.open('https://www.tprfn.net/tprfn-lets-get-connected/tprfn-radiogram-instructions', '_blank')">🔗 RADIOGRAM INSTRUCTIONS (OPENS IN NEW WINDOW)</button>
<form id="tprfnForm" onsubmit="event.preventDefault(); return false;">
<div class="section-title">1. Station of Origin</div>
<div class="row"><label for="s_name">Op Name:</label><input type="text" id="s_name" placeholder="FULL NAME"></div>
<div class="row"><label for="s_call">Station Callsign:</label><input type="text" id="s_call" placeholder="N3MEL"></div>
<div class="row"><label for="s_place">Place of Origin:</label><input type="text" id="s_place" placeholder="CITY STATE"></div>
<div class="section-title">2. Preamble (The Header)</div>
<div class="row"><label for="num">Message Number:</label><input type="text" id="num" placeholder="1"></div>
<div class="row"><label for="prec">Precedence:</label>
<select id="prec">
<option value="R">ROUTINE (R)</option>
<option value="W">WELFARE (W)</option>
<option value="P">PRIORITY (P)</option>
<option value="EMERGENCY">EMERGENCY</option>
</select>
</div>
<div class="row"><label for="hx">Handling (HX):</label>
<select id="hx">
<option value="">NONE</option>
<option value="HXA">HXA</option>
<option value="HXB">HXB</option>
<option value="HXC">HXC</option>
<option value="HXD">HXD</option>
<option value="HXE">HXE</option>
<option value="HXF">HXF</option>
<option value="HXG">HXG</option>
</select>
</div>
<div class="row"><label for="date_val">Date Filed:</label><input type="text" id="date_val" placeholder="MMM DD"></div>
<div class="row">
<label for="time_val">Time Filed (UTC):</label>
<div class="multi-input">
<input type="text" id="time_val" placeholder="0000" style="flex: 1;">
<button type="button" class="btn-inline" id="btnNowZulu">🕒 NOW</button>
</div>
</div>
<div class="section-title">3. The Address (Recipient)</div>
<div class="row"><label for="r_name">Recipient Name:</label><input type="text" id="r_name"></div>
<div class="row"><label for="r_call">Recipient Callsign:</label><input type="text" id="r_call"></div>
<div class="row"><label for="addr1">Street Address:</label><input type="text" id="addr1"></div>
<div class="row"><label for="addr2">Apt / Suite / Box:</label><input type="text" id="addr2"></div>
<div class="row">
<label for="city">City ST Zip:</label>
<div class="multi-input">
<input type="text" id="city" placeholder="TOWN" style="flex:2">
<select id="state" style="flex:1;">
<option value="">ST</option>
<option value="AL">AL</option><option value="AK">AK</option><option value="AZ">AZ</option><option value="AR">AR</option>
<option value="CA">CA</option><option value="CO">CO</option><option value="CT">CT</option><option value="DE">DE</option>
<option value="DC">DC</option><option value="FL">FL</option><option value="GA">GA</option><option value="HI">HI</option>
<option value="ID">ID</option><option value="IL">IL</option><option value="IN">IN</option><option value="IA">IA</option>
<option value="KS">KS</option><option value="KY">KY</option><option value="LA">LA</option><option value="ME">ME</option>
<option value="MD">MD</option><option value="MA">MA</option><option value="MI">MI</option><option value="MN">MN</option>
<option value="MS">MS</option><option value="MO">MO</option><option value="MT">MT</option><option value="NE">NE</option>
<option value="NV">NV</option><option value="NH">NH</option><option value="NJ">NJ</option><option value="NM">NM</option>
<option value="NY">NY</option><option value="NC">NC</option><option value="ND">ND</option><option value="OH">OH</option>
<option value="OK">OK</option><option value="OR">OR</option><option value="PA">PA</option><option value="RI">RI</option>
<option value="SC">SC</option><option value="SD">SD</option><option value="TN">TN</option><option value="TX">TX</option>
<option value="UT">UT</option><option value="VT">VT</option><option value="VA">VA</option><option value="WA">WA</option>
<option value="WV">WV</option><option value="WI">WI</option><option value="WY">WY</option>
</select>
<input type="text" id="zip" placeholder="ZIP" style="flex:1.5">
</div>
</div>
<div class="row"><label for="phone">Phone Number:</label><input type="text" id="phone"></div>
<div class="row"><label for="email">Email Address:</label><input type="text" id="email"></div>
<div class="section-title">4. The Text (Message Body)</div>
<textarea id="msg" placeholder="DASH BECOMES 'DASH', # BECOMES 'NR', .COM BECOMES 'DOT COM'."></textarea>
<div class="btns-grid">
<button type="button" class="btn-gen" id="btnGen">GENERATE RRI MESSAGE</button>
<button type="button" class="btn-copy" id="btnCopy">COPY FOR TERMINAL</button>
<button type="button" class="btn-save-txt" id="btnSaveTxt">SAVE AS .TXT</button>
<button type="button" class="btn-save-html" id="btnSaveHtml">SAVE AS HTML</button>
<button type="button" class="btn-clear" id="btnClear">RESET RECIPIENT/MSG</button>
</div>
</form>
<div class="section-title">5. RRI / ARRL ASCII Output</div>
<pre id="preview">Ready for input...</pre>
<div class="footer-sig">CODE CREATED BY N3MEL &bull; PACKET RADIO EMCOMM ADAPTATION</div>
</div>
<textarea id="hidden_copy" style="position:absolute; left:-9999px;"></textarea>
<script>
function initForm() { setZuluTime(); }
function setZuluTime() {
const now = new Date();
const months = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
document.getElementById('date_val').value = months[now.getUTCMonth()] + " " + String(now.getUTCDate()).padStart(2, '0');
document.getElementById('time_val').value = String(now.getUTCHours()).padStart(2, '0') + String(now.getUTCMinutes()).padStart(2, '0');
}
function generate() {
try {
const toAsc = (t) => (t || "").trim().toUpperCase().replace(/[^\x00-\x7F]/g, "");
const strip = (t) => {
let work = toAsc(t);
work = work.replace(/'/g, "").replace(/#/g, " NR ").replace(/-/g, " DASH ");
return work.replace(/[.,\/!$%\^&\*;:{}=_`~()]/g, " ");
};
const getVal = (id) => strip(document.getElementById(id).value).replace(/\s+/g, " ");
let s_name = getVal('s_name'), s_call = getVal('s_call'), s_place = getVal('s_place');
let num = document.getElementById('num').value.trim().replace(/^0+/, "") || "1";
let prec = document.getElementById('prec').value, hx = getVal('hx');
let date_f = getVal('date_val'), time_f = getVal('time_val');
let r_name = getVal('r_name'), r_call = getVal('r_call'), addr1 = getVal('addr1'), addr2 = getVal('addr2');
let r_city = getVal('city'), r_state = document.getElementById('state').value, zip = getVal('zip');
let rawPhone = document.getElementById('phone').value;
let formattedPhone = rawPhone.replace(/[^\d]/g, " ").replace(/\s+/g, " ").trim();
let email = document.getElementById('email').value.toUpperCase();
let msg = document.getElementById('msg').value.toUpperCase();
const msgFmt = (t) => {
let work = t.replace(/'/g, "");
work = work.replace(/\.NET/g, " DOT NET ").replace(/\.COM/g, " DOT COM ").replace(/\.ORG/g, " DOT ORG ").replace(/\.EDU/g, " DOT EDU ").replace(/\.GOV/g, " DOT GOV ");
work = work.replace(/-/g, " DASH ").replace(/#/g, " NR ").replace(/\./g, " X ").replace(/@/g, " ATSIGN ").replace(/_/g, " UNDERSCORE ");
return work.replace(/[!$%\^&\*;{}=~()]/g, " ");
};
const emailFmt = (t) => t.replace(/@/g, " ATSIGN ").replace(/\./g, " DOT ").replace(/_/g, " UNDERSCORE ").replace(/-/g, " DASH ");
let processedMsg = msgFmt(msg);
let finalWords = processedMsg.split(/\s+/).filter(w => w.length > 0);
let out = "";
if(zip && r_state) out += "ST " + zip + "@NTS" + r_state + "\n";
out += (r_city || "DESTINATION") + (r_call ? " " + r_call : "") + "\n\n";
out += num + " " + prec + " " + (hx ? hx + " " : "") + (s_call || "ORIGIN") + " " + finalWords.length + " " + (s_place || "ORIGIN") + " " + (time_f ? time_f + "Z " : "") + date_f + "\n";
out += (r_name || "RECIPIENT") + (r_call ? " " + r_call : "") + "\n";
if(addr1) out += addr1 + "\n";
if(addr2) out += addr2 + "\n";
if(r_city || r_state || zip) out += (r_city ? r_city + " " : "") + (r_state ? r_state + " " : "") + (zip || "") + "\n";
if(formattedPhone) out += formattedPhone + "\n";
if(email) out += emailFmt(email).replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "") + "\n";
out += "BT\n";
for (let i = 0; i < finalWords.length; i += 5) {
out += finalWords.slice(i, i + 5).join(' ') + "\n";
}
out += "BT\n" + (s_name ? s_name + " " : "") + (s_call || "") + "\n/EX";
document.getElementById('preview').innerText = out;
} catch(err) {
alert("Error generating radiogram: " + err.message);
}
}
function copyClean() {
const t = document.getElementById('preview').innerText;
if(t.includes("Ready")) {
alert("Please click GENERATE RRI MESSAGE first.");
return;
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(t).then(function() {
showCopyNotice();
}).catch(function() {
fallbackCopy(t);
});
} else {
fallbackCopy(t);
}
}
function fallbackCopy(t) {
const h = document.getElementById('hidden_copy');
h.value = t;
h.select();
document.execCommand('copy');
showCopyNotice();
}
function showCopyNotice() {
const b = document.getElementById('btnCopy');
const orig = b.innerText;
b.innerText = "✅ COPIED!";
setTimeout(() => { b.innerText = orig; }, 2000);
}
function saveToFile() {
const t = document.getElementById('preview').innerText;
if(t.includes("Ready")) {
alert("Please generate the message before saving.");
return;
}
const b = new Blob([t], { type: "text/plain;charset=us-ascii" });
const a = document.createElement("a");
let defTxt = "Radiogram_" + (document.getElementById('num').value || "1") + ".txt";
let chosenTxt = prompt('Enter filename to save as (.txt):', defTxt);
if (!chosenTxt) return;
if (!chosenTxt.toLowerCase().endsWith('.txt')) chosenTxt += '.txt';
a.download = chosenTxt;
a.href = window.URL.createObjectURL(b);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
function clearForm() {
if(confirm("Clear current recipient and message data?")) {
['num','r_name','r_call','addr1','addr2','city','zip','phone','email','msg','time_val'].forEach(id => document.getElementById(id).value = "");
document.getElementById('state').selectedIndex = 0;
setZuluTime();
document.getElementById('preview').innerText = "Ready for input...";
}
}
document.getElementById('btnNowZulu').addEventListener('click', setZuluTime);
document.getElementById('btnGen').addEventListener('click', generate);
document.getElementById('btnCopy').addEventListener('click', copyClean);
document.getElementById('btnSaveTxt').addEventListener('click', saveToFile);
document.getElementById('btnSaveHtml').addEventListener('click', openSaveModal);
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 = 'tprfn_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);
}
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) {
// Clear existing inline handlers just in case
btnSaveMain.onclick = null;
btnSaveMain.addEventListener('click', openSaveModal);
}
});
</script>
<!-- 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>
</body>
</html>
+382
View File
@@ -0,0 +1,382 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Winlink Check-in (Official Format - Packet Radio)</title>
<style>
* { box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace; background: #f0f4f8; color: #222; padding: 20px; margin: 0; }
.container { max-width: 920px; margin: 0 auto; background: #fff; padding: 24px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.header { background: #137333; 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 #a8dab5; border-radius: 6px; padding: 14px; margin-bottom: 14px; background: #f9fdfa; }
legend { font-weight: bold; padding: 0 8px; font-size: 0.85rem; color: #0d5a27; }
.row { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 10px; }
.col { flex: 1; min-width: 140px; 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: #333; }
input[type="text"], textarea, select { padding: 7px 8px; font-size: 0.88rem; border: 1px solid #999; border-radius: 4px; font-family: inherit; width: 100%; }
textarea { resize: vertical; min-height: 70px; }
.btn-now { background: #4a5568; color: #fff; padding: 4px 8px; font-size: 0.72rem; border-radius: 4px; border: none; cursor: pointer; }
.btn-now:hover { background: #2d3748; }
.actions { display: flex; gap: 10px; margin: 18px 0; }
button.main-btn { flex: 1; padding: 12px; font-size: 0.95rem; font-weight: bold; border: none; border-radius: 6px; cursor: pointer; }
.btn-generate { background: #137333; color: #fff; }
.btn-generate:hover { background: #0d5a27; }
.btn-save { background: #5c6bc0; color: #fff; }
.btn-save:hover { background: #3f51b5; }
.btn-copy { background: #2e7d32; color: #fff; }
.btn-copy:hover { background: #1b5e20; }
.btn-clear { background: #c62828; color: #fff; flex: 0.35; }
pre { background: #181c20; color: #39ff14; padding: 14px; border-radius: 6px; overflow-x: auto; font-family: "Courier New", Courier, monospace; font-size: 0.84rem; line-height: 1.35; min-height: 150px; }
.nav-bar { margin-bottom: 12px; font-size: 0.85rem; }
.nav-bar a { color: #137333; 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>WINLINK CHECK-IN</h2>
<p>Official Winlink Check-in Form (Packet / BBS ASCII Format)</p>
</div>
<form id="checkinForm" onsubmit="event.preventDefault(); return false;">
<fieldset>
<legend>1. STATION INFORMATION</legend>
<div class="row">
<div class="col">
<div class="label-row">
<label for="dtZulu">a. Date / Time (UTC)</label>
<button type="button" class="btn-now" id="btnDateNow">Current UTC</button>
</div>
<input type="text" id="dtZulu" placeholder="YYYY-MM-DD HHMMZ">
</div>
<div class="col" style="flex: 2;"><label for="msgTo">b. To (Callsigns / Emails; semicolon separated)</label><input type="text" id="msgTo" placeholder="NCS; WINLINKNET"></div>
</div>
<div class="row">
<div class="col"><label for="msgSender">c. From (Callsign)</label><input type="text" id="msgSender" placeholder="N0CALL"></div>
<div class="col"><label for="contactName">d. Station Contact Name</label><input type="text" id="contactName" placeholder="Operator Name / Authority"></div>
<div class="col"><label for="assigned">e. Initial Operator(s)</label><input type="text" id="assigned" placeholder="Callsign(s) on site"></div>
<div class="col"><label for="exId">Optional Exercise ID</label><input type="text" id="exId" placeholder="e.g. SHAKEOUT"></div>
</div>
</fieldset>
<fieldset>
<legend>2. SESSION CONFIGURATION</legend>
<div class="row">
<div class="col">
<label for="sessType">a. Type</label>
<select id="sessType">
<option value="EXERCISE" selected>EXERCISE</option>
<option value="REAL EVENT">REAL EVENT</option>
</select>
</div>
<div class="col">
<label for="service">b. Service</label>
<select id="service">
<option value="AMATEUR" selected>AMATEUR (FCC Part 97)</option>
<option value="SHARES">SHARES (NTIA)</option>
</select>
</div>
<div class="col">
<label for="band">c. Band</label>
<select id="band">
<option value="NA">NA / Not Applicable</option>
<option value="Telnet" selected>Telnet</option>
<option value="HF">HF (3-30 MHz)</option>
<option value="VHF">VHF (30-300 MHz)</option>
<option value="UHF">UHF (300-3000 MHz)</option>
<option value="SHF">SHF (3 GHz+)</option>
</select>
</div>
<div class="col">
<label for="session">d. Mode / Session</label>
<select id="session">
<option value="Packet (AX.25)">Packet (AX.25)</option>
<option value="VARA FM">VARA FM</option>
<option value="VARA HF">VARA HF</option>
<option value="Telnet" selected>Telnet</option>
<option value="Pactor">Pactor</option>
<option value="Ardop">Ardop</option>
<option value="Robust Packet">Robust Packet</option>
<option value="Mesh (AREDN)">Mesh (AREDN)</option>
<option value="Iridium Go">Iridium Go</option>
</select>
</div>
</div>
</fieldset>
<fieldset>
<legend>3. LOCATION</legend>
<div class="row">
<div class="col" style="flex: 2;"><label for="location">a. Location Descriptor</label><input type="text" id="location" placeholder="e.g. Travis County EOC, Shelter Parking Lot"></div>
</div>
<div class="row">
<div class="col"><label for="lat">b. Latitude (Decimal)</label><input type="text" id="lat" placeholder="e.g. 30.2672"></div>
<div class="col"><label for="lon">c. Longitude (Decimal)</label><input type="text" id="lon" placeholder="e.g. -97.7431"></div>
<div class="col"><label for="mgrs">d. MGRS</label><input type="text" id="mgrs" placeholder="e.g. 14RPU12345678"></div>
<div class="col" style="max-width: 120px;"><label for="grid">e. Grid Square</label><input type="text" id="grid" placeholder="EM10dj"></div>
</div>
</fieldset>
<fieldset>
<legend>4. COMMENTS (Max 500 characters)</legend>
<textarea id="comments" rows="3" maxlength="500" placeholder="Power source (battery/generator), traffic on hand, weather status, relief schedule..."></textarea>
</fieldset>
<div class="actions">
<button type="button" class="main-btn btn-generate" id="btnGen">Generate Packet ASCII</button>
<button type="button" class="main-btn btn-save" id="btnSaveHtml">Save as HTML</button>
<button type="button" class="main-btn btn-copy" id="btnCopy">Copy ASCII</button>
<button type="button" class="main-btn btn-clear" id="btnClear">Reset</button>
</div>
</form>
<label style="font-weight:bold;">Formatted ASCII for Packet / BBS Transmission:</label>
<pre id="outputPre">Fill in the fields above and click "Generate Packet ASCII"...</pre>
</div>
<script>
function setUTC() {
const now = new Date();
const y = now.getUTCFullYear();
const m = String(now.getUTCMonth()+1).padStart(2,'0');
const d = String(now.getUTCDate()).padStart(2,'0');
const hh = String(now.getUTCHours()).padStart(2,'0');
const mm = String(now.getUTCMinutes()).padStart(2,'0');
document.getElementById('dtZulu').value = y + '-' + m + '-' + d + ' ' + hh + mm + 'Z';
}
function clean(id) {
const el = document.getElementById(id);
return el ? (el.value || '').trim() : '';
}
function pad(str, len) {
str = (str || '').toString();
return str + ' '.repeat(Math.max(0, len - str.length));
}
function generateASCII() {
try {
const div = '='.repeat(74);
const sub = '-'.repeat(74);
const dt = clean('dtZulu') || 'N/A';
const to = clean('msgTo').toUpperCase() || 'N/A';
const from = clean('msgSender').toUpperCase() || 'N/A';
const contact = clean('contactName').toUpperCase() || 'N/A';
const op = clean('assigned').toUpperCase() || 'N/A';
const ex = clean('exId').toUpperCase() || 'NONE';
const sType = clean('sessType').toUpperCase();
const sServ = clean('service').toUpperCase();
const sBand = clean('band').toUpperCase();
const sMode = clean('session').toUpperCase();
const loc = clean('location').toUpperCase() || 'N/A';
const lat = clean('lat') || 'N/A';
const lon = clean('lon') || 'N/A';
const mgrs = clean('mgrs').toUpperCase() || 'N/A';
const grid = clean('grid').toUpperCase() || 'N/A';
const cmt = clean('comments').toUpperCase() || 'NORMAL OPERATIONS - NO TRAFFIC';
const out = div + '\n' +
'WINLINK CHECK-IN REPORT (PACKET ASCII)\n' +
div + '\n' +
'STATUS / TYPE : ' + pad(sType, 16) + ' SERVICE: ' + sServ + '\n' +
'DATE / TIME : ' + pad(dt, 20) + ' EXERCISE ID: ' + ex + '\n' +
'TO : ' + to + '\n' +
'FROM : ' + pad(from, 16) + ' CONTACT: ' + contact + '\n' +
'INITIAL OPS : ' + op + '\n' +
sub + '\n' +
'SESSION INFO : BAND: ' + pad(sBand, 10) + ' MODE: ' + sMode + '\n' +
sub + '\n' +
'LOCATION : ' + loc + '\n' +
'COORDINATES : LAT: ' + pad(lat, 12) + ' LON: ' + lon + '\n' +
'MGRS / GRID : MGRS: ' + pad(mgrs, 16) + ' GRID: ' + grid + '\n' +
sub + '\n' +
'COMMENTS:\n ' + cmt + '\n' +
div;
document.getElementById('outputPre').textContent = out;
} catch(err) {
alert("Error generating text: " + err.message);
}
}
function copyASCII() {
const txt = document.getElementById('outputPre').textContent;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(txt).then(function() {
alert("Check-in copied to clipboard!");
}).catch(function() {
fallbackCopy();
});
} else {
fallbackCopy();
}
}
function fallbackCopy() {
const pre = document.getElementById('outputPre');
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("Reset form?")) {
document.getElementById('checkinForm').reset();
document.getElementById('outputPre').textContent = 'Fill in the fields above and click "Generate Packet ASCII"...';
}
}
document.getElementById('btnDateNow').addEventListener('click', setUTC);
document.getElementById('btnGen').addEventListener('click', generateASCII);
document.getElementById('btnSaveHtml').addEventListener('click', openSaveModal);
document.getElementById('btnCopy').addEventListener('click', copyASCII);
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 = 'winlink_checkin_' + 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);
}
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) {
// Clear existing inline handlers just in case
btnSaveMain.onclick = null;
btnSaveMain.addEventListener('click', openSaveModal);
}
});
</script>
<!-- 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>
</body>
</html>