COmpREss.html
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8" />
  5. <meta name="viewport" content="width=device-width,initial-scale=1" />
  6. <title>21-bit to Integer Converter — output is post-subtract binary (LSB disamb, leading 1-bit stripped)</title>
  7. <style>
  8. body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; margin: 24px; color:#111; }
  9. h1 { margin-bottom: 8px; }
  10. h2 { margin-top: 24px; margin-bottom: 8px; font-size: 20px; }
  11. .card { border:1px solid #e1e4e8; border-radius:8px; padding:16px; max-width:920px; background:#fff; box-shadow:0 1px 2px rgba(0,0,0,0.03); margin-bottom: 16px; }
  12. label { display:block; margin-bottom:8px; font-weight:600; }
  13. input[type="text"], input[type="number"] { width:100%; padding:10px 12px; font-size:16px; border:1px solid #cbd5e1; border-radius:6px; box-sizing:border-box; }
  14. button { margin-top:12px; padding:8px 12px; font-size:15px; border-radius:6px; border:0; background:#0366d6; color:#fff; cursor:pointer; }
  15. button.secondary { background:#6b7280; }
  16. .error { color:#b91c1c; margin-top:8px; }
  17. .success { color:#059669; margin-top:8px; font-weight:600; }
  18. pre { background:#f6f8fa; padding:12px; border-radius:6px; overflow:auto; }
  19. .steps { margin-top:12px; }
  20. .result { margin-top:12px; font-weight:700; font-size:18px; }
  21. .small { font-size:13px; color:#374151; }
  22. .row { display:flex; gap:8px; align-items:center; margin-top:8px; }
  23. .copy { background:#10b981; }
  24. .inline { display:inline-block; width:auto; }
  25. .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono", "Courier New", monospace; }
  26. </style>
  27. </head>
  28. <body>
  29. <div class="card">
  30. <h1>21-bit to Integer Converter</h1>
  31.  
  32. <div class="small">
  33. Enter exactly 21 bits. The calculator follows your rule set and <strong>subtracts 1,048,576</strong>.
  34. <strong>Output</strong> is the binary representation of the value <em>after</em> that subtraction, with the leading 1-bit stripped (decompressor adds it back), and a single
  35. deterministic disambiguation bit appended as the LSB. All UI elements and trial statistics use that same total length (core bits without leading 1 + 1 LSB).
  36. </div>
  37.  
  38. <label for="bits">Bits</label>
  39. <input id="bits" type="text" placeholder="e.g. 010101001110000111000" maxlength="21" />
  40.  
  41. <div class="row">
  42. <button id="compute">Compute</button>
  43. <button id="random" class="secondary">Random</button>
  44. <button id="example" class="secondary">Fill Example</button>
  45. <button id="copy" class="copy">Copy Result</button>
  46. </div>
  47.  
  48. <div style="margin-top:12px;">
  49. <label for="count">Run Random Tests (count)</label>
  50. <div class="row">
  51. <input id="count" type="number" min="1" max="100000" value="100" style="width:120px;" />
  52. <button id="runTests">Run</button>
  53. </div>
  54. </div>
  55.  
  56. <div id="error" class="error" role="status" aria-live="polite"></div>
  57. <div id="output" class="steps" aria-live="polite"></div>
  58. </div>
  59.  
  60. <div class="card">
  61. <h2>Decompressor (Test Reversibility)</h2>
  62.  
  63. <div class="small">
  64. Enter a compressed payload (binary with leading 1-bit stripped + LSB disambiguation bit) to decompress it back to the original 21-bit input.
  65. </div>
  66.  
  67. <label for="compressed">Compressed Payload</label>
  68. <input id="compressed" type="text" placeholder="e.g. 01010100111000011100" />
  69.  
  70. <div class="row">
  71. <button id="decompress">Decompress</button>
  72. <button id="testRoundtrip" class="secondary">Test Roundtrip</button>
  73. </div>
  74.  
  75. <div id="decompressError" class="error" role="status" aria-live="polite"></div>
  76. <div id="decompressOutput" class="steps" aria-live="polite"></div>
  77. </div>
  78.  
  79. <script>
  80. const bitsInput = document.getElementById('bits');
  81. const computeBtn = document.getElementById('compute');
  82. const randomBtn = document.getElementById('random');
  83. const exampleBtn = document.getElementById('example');
  84. const copyBtn = document.getElementById('copy');
  85. const runTestsBtn = document.getElementById('runTests');
  86. const countInput = document.getElementById('count');
  87. const errorEl = document.getElementById('error');
  88. const outputEl = document.getElementById('output');
  89.  
  90. const compressedInput = document.getElementById('compressed');
  91. const decompressBtn = document.getElementById('decompress');
  92. const testRoundtripBtn = document.getElementById('testRoundtrip');
  93. const decompressErrorEl = document.getElementById('decompressError');
  94. const decompressOutputEl = document.getElementById('decompressOutput');
  95.  
  96. function validateBits(s) {
  97. if (!s) return 'Input is empty';
  98. if (s.length !== 21) return 'Input must be exactly 21 bits';
  99. if (!/^[01]{21}$/.test(s)) return 'Input must contain only 0 and 1';
  100. return '';
  101. }
  102.  
  103. function validateCompressed(s) {
  104. if (!s) return 'Input is empty';
  105. if (!/^[01]+$/.test(s)) return 'Input must contain only 0 and 1';
  106. if (s.length < 2) return 'Input must be at least 2 bits (core + LSB)';
  107. return '';
  108. }
  109.  
  110. const SUBTRACT = 1024n * 1024n; // 1,048,576n
  111.  
  112. /**
  113. * computeFromBits:
  114. * - runs the stepwise rule to produce valueBeforeSubtract
  115. * - computes valueAfterSubtract = valueBeforeSubtract - SUBTRACT
  116. * - produces binary representation of valueAfterSubtract (fullBinaryAfter)
  117. * - strips the leading 1-bit from fullBinaryAfter to get coreBinaryAfter
  118. * - appends disambiguation bit (LSB) which is the original first input bit
  119. *
  120. * Important: the UI and trial mode treat the "compressed-with-disamb" as:
  121. * compressedWithDisambLSB = coreBinaryAfter + disambiguationBit
  122. *
  123. * The total length in bits reported to the user counts only the binary digits (without leading 1) plus the 1 disambiguation bit.
  124. */
  125. function computeFromBits(bitString) {
  126. const bits = bitString.split('').map(b => b === '1' ? 1 : 0);
  127.  
  128. // initial value
  129. let value = bits[0] === 0 ? 2n : 3n;
  130. const trace = [];
  131. trace.push({ step: 0, bit: bits[0], op: `start`, value: value.toString() });
  132.  
  133. for (let i = 1; i < bits.length; i++) {
  134. const b = bits[i];
  135. if (b === 0) {
  136. value = value * 2n;
  137. trace.push({ step: i, bit: 0, op: `×2`, value: value.toString() });
  138. } else {
  139. value = value * 2n - 1n;
  140. trace.push({ step: i, bit: 1, op: `×2 − 1`, value: value.toString() });
  141. }
  142. }
  143.  
  144. const valueBeforeSubtract = value;
  145. const valueAfterSubtract = valueBeforeSubtract - SUBTRACT;
  146.  
  147. // full binary is the binary representation of the value AFTER subtraction
  148. const fullBinaryAfter = valueAfterSubtract.toString(2);
  149.  
  150. // strip the leading 1-bit (decompressor will add it back)
  151. const coreBinaryAfter = fullBinaryAfter.slice(1);
  152. const coreBitsLength = coreBinaryAfter.length;
  153.  
  154. // deterministic disambiguation bit: original first input bit
  155. const disambiguationBit = String(bits[0]);
  156.  
  157. // compressed-with-disambiguation (LSB placement) is coreBinaryAfter (without leading 1) + disambiguation bit
  158. const compressedWithDisambLSB = coreBinaryAfter + disambiguationBit;
  159.  
  160. // total length in bits used for statistics: coreBitsLength + 1 (LSB disambiguation)
  161. const totalLengthBits = coreBitsLength + 1;
  162.  
  163. return {
  164. trace,
  165. valueBeforeSubtract: valueBeforeSubtract.toString(),
  166. valueAfterSubtract: valueAfterSubtract.toString(),
  167. fullBinaryAfter,
  168. coreBinaryAfter,
  169. coreBitsLength,
  170. disambiguationBit,
  171. compressedWithDisambLSB,
  172. totalLengthBits,
  173. originalBits: bitString
  174. };
  175. }
  176.  
  177. /**
  178. * decompressPayload:
  179. * - takes compressed payload (core binary without leading 1 + LSB disambiguation)
  180. * - extracts LSB disambiguation bit
  181. * - prepends "1" to restore full binary
  182. * - parses to get valueAfterSubtract
  183. * - adds SUBTRACT to get valueBeforeSubtract
  184. * - reverses the encoding algorithm to recover original 21 bits
  185. */
  186. function decompressPayload(compressedPayload) {
  187. // Extract LSB (last bit)
  188. const disambiguationBit = compressedPayload[compressedPayload.length - 1];
  189.  
  190. // Extract core binary (all but last bit)
  191. const coreBinary = compressedPayload.slice(0, -1);
  192.  
  193. // Restore full binary by prepending "1"
  194. const fullBinary = '1' + coreBinary;
  195.  
  196. // Parse to get valueAfterSubtract
  197. const valueAfterSubtract = BigInt('0b' + fullBinary);
  198.  
  199. // Add SUBTRACT to get valueBeforeSubtract
  200. const valueBeforeSubtract = valueAfterSubtract + SUBTRACT;
  201.  
  202. // Now reverse the encoding algorithm to recover the original 21 bits
  203. // We need to work backwards from valueBeforeSubtract
  204. const recovered21Bits = reverseEncode(valueBeforeSubtract);
  205.  
  206. return {
  207. disambiguationBit,
  208. coreBinary,
  209. fullBinary,
  210. valueAfterSubtract: valueAfterSubtract.toString(),
  211. valueBeforeSubtract: valueBeforeSubtract.toString(),
  212. recovered21Bits
  213. };
  214. }
  215.  
  216. /**
  217. * reverseEncode:
  218. * Given a valueBeforeSubtract, reverse the encoding algorithm to recover the original 21 bits.
  219. *
  220. * The encoding algorithm is:
  221. * - Start with value = 2 (if first bit is 0) or 3 (if first bit is 1)
  222. * - For each subsequent bit:
  223. * - If bit is 0: value = value * 2
  224. * - If bit is 1: value = value * 2 - 1
  225. *
  226. * To reverse:
  227. * - We work backwards from the final value
  228. * - For each step, determine if the previous operation was *2 or *2-1
  229. * - If current value is even: previous was *2, so bit is 0, prev_value = value / 2
  230. * - If current value is odd: previous was *2-1, so bit is 1, prev_value = (value + 1) / 2
  231. */
  232. function reverseEncode(finalValue) {
  233. const bits = [];
  234. let value = finalValue;
  235.  
  236. // Work backwards for 20 steps (bits 20 down to 1)
  237. for (let i = 0; i < 20; i++) {
  238. if (value % 2n === 0n) {
  239. // Even: previous operation was *2, so bit is 0
  240. bits.unshift(0);
  241. value = value / 2n;
  242. } else {
  243. // Odd: previous operation was *2-1, so bit is 1
  244. bits.unshift(1);
  245. value = (value + 1n) / 2n;
  246. }
  247. }
  248.  
  249. // Now value should be either 2 or 3, which tells us the first bit
  250. if (value === 2n) {
  251. bits.unshift(0);
  252. } else if (value === 3n) {
  253. bits.unshift(1);
  254. } else {
  255. throw new Error('Decompression failed: invalid starting value');
  256. }
  257.  
  258. return bits.join('');
  259. }
  260.  
  261. function renderResult(resultObj) {
  262. const {
  263. trace,
  264. valueBeforeSubtract,
  265. valueAfterSubtract,
  266. fullBinaryAfter,
  267. coreBinaryAfter,
  268. coreBitsLength,
  269. disambiguationBit,
  270. compressedWithDisambLSB,
  271. totalLengthBits
  272. } = resultObj;
  273.  
  274. let html = '';
  275. html += '<div class="small"><strong>Trace (step-by-step building of value before subtract)</strong></div>';
  276. html += '<pre class="mono">';
  277. html += 'Step Bit Operation Value\n';
  278. html += '---- --- --------- ----------------\n';
  279. trace.forEach(t => {
  280. const step = String(t.step).padEnd(4, ' ');
  281. const bit = String(t.bit).padEnd(3, ' ');
  282. const op = String(t.op).padEnd(9, ' ');
  283. const val = t.value;
  284. html += `${step} ${bit} ${op} ${val}\n`;
  285. });
  286. html += '</pre>';
  287.  
  288. html += `<div class="result"><strong>Value before subtracting 1,048,576</strong>: ${valueBeforeSubtract}</div>`;
  289. html += `<div class="result"><strong>Value after subtracting 1,048,576</strong>: ${valueAfterSubtract}</div>`;
  290.  
  291. html += '<hr />';
  292.  
  293. html += '<div class="small"><strong>Full binary of value after subtraction</strong></div>';
  294. html += `<pre class="mono">${fullBinaryAfter}</pre>`;
  295.  
  296. html += '<div class="small" style="margin-top:8px;"><strong>Compressed output (leading 1-bit stripped)</strong></div>';
  297. html += `<pre class="mono">${coreBinaryAfter}</pre>`;
  298.  
  299. html += '<div class="small" style="margin-top:8px;"><strong>Disambiguation bit (LSB)</strong></div>';
  300. html += `<div class="mono">disambiguationBit (LSB) = <strong>${disambiguationBit}</strong> (original first input bit)</div>`;
  301.  
  302. html += '<div style="margin-top:8px;" class="small"><strong>Final transmitted payload (core binary without leading 1 + LSB disambiguation)</strong></div>';
  303. html += `<pre class="mono">${compressedWithDisambLSB}</pre>`;
  304.  
  305. html += `<div style="margin-top:8px;" class="small"><strong>Total length used for payload (for statistics)</strong></div>`;
  306. html += `<div class="mono"><strong>${totalLengthBits}</strong> bits (core binary digits without leading 1: ${coreBitsLength} + 1 LSB disambiguation)</div>`;
  307.  
  308. html += '<div style="margin-top:8px;" class="small"><strong>Decompressor recipe (full and accurate)</strong></div>';
  309. html += '<pre class="mono">';
  310. html += '1) Read the last bit of the payload => disambiguation bit (LSB).\n';
  311. html += '2) Take the remaining bits: the value-after-subtract binary with its leading 1 stripped.\n';
  312. html += '3) Prepend "1" to restore the full binary of valueAfterSubtract.\n';
  313. html += '4) Parse it to the integer valueAfterSubtract.\n';
  314. html += '5) valueBeforeSubtract = valueAfterSubtract + 1,048,576.\n';
  315. html += '6) Recover the 21 bits by undoing the build. Repeat 20 times:\n';
  316. html += ' if the value is even, the bit was 0 and value = value / 2;\n';
  317. html += ' if the value is odd, the bit was 1 and value = (value + 1) / 2.\n';
  318. html += '7) After 20 undo-steps the value is 2 or 3; that is the first bit (2 => 0, 3 => 1).\n';
  319. html += '\n';
  320. html += 'Note: step 7 recovers the first bit from the value itself, so the LSB disambiguation bit\n';
  321. html += 'read in step 1 is never consulted. It is currently a placeholder and carries no information;\n';
  322. html += 'the value alone already determines all 21 original bits.\n';
  323. html += '\n';
  324. html += 'Example (decompressor):\n';
  325. html += `received payload: ${compressedWithDisambLSB}\n`;
  326. html += `disambiguation (LSB): ${disambiguationBit} (not used by the decoder)\n`;
  327. html += `core binary (leading 1 stripped): ${coreBinaryAfter}\n`;
  328. html += `restored full binary: ${fullBinaryAfter}\n`;
  329. html += `parsed valueAfterSubtract: ${valueAfterSubtract}\n`;
  330. html += `recovered valueBeforeSubtract: ${valueBeforeSubtract}\n`;
  331. html += `recovered 21 bits (via steps 6-7): ${resultObj.originalBits}\n`;
  332. html += '</pre>';
  333.  
  334. outputEl.innerHTML = html;
  335. }
  336.  
  337. function renderDecompressResult(decompressObj, originalBits = null) {
  338. const {
  339. disambiguationBit,
  340. coreBinary,
  341. fullBinary,
  342. valueAfterSubtract,
  343. valueBeforeSubtract,
  344. recovered21Bits
  345. } = decompressObj;
  346.  
  347. let html = '';
  348. html += '<div class="small"><strong>Decompression Steps</strong></div>';
  349. html += '<pre class="mono">';
  350. html += `1) LSB disambiguation bit: ${disambiguationBit}\n`;
  351. html += `2) Core binary (leading 1 stripped): ${coreBinary}\n`;
  352. html += `3) Restored full binary: ${fullBinary}\n`;
  353. html += `4) Parsed valueAfterSubtract: ${valueAfterSubtract}\n`;
  354. html += `5) Recovered valueBeforeSubtract: ${valueBeforeSubtract}\n`;
  355. html += '</pre>';
  356.  
  357. html += `<div class="result"><strong>Recovered 21-bit input</strong>: ${recovered21Bits}</div>`;
  358.  
  359. if (originalBits) {
  360. if (originalBits === recovered21Bits) {
  361. html += '<div class="success">✓ Roundtrip successful! Original and recovered bits match.</div>';
  362. } else {
  363. html += '<div class="error">✗ Roundtrip failed! Bits do not match.</div>';
  364. html += `<div class="small">Original: ${originalBits}</div>`;
  365. html += `<div class="small">Recovered: ${recovered21Bits}</div>`;
  366. }
  367. }
  368.  
  369. decompressOutputEl.innerHTML = html;
  370. }
  371.  
  372. computeBtn.addEventListener('click', () => {
  373. errorEl.textContent = '';
  374. outputEl.innerHTML = '';
  375. const s = bitsInput.value.trim();
  376. const v = validateBits(s);
  377. if (v) { errorEl.textContent = v; return; }
  378. try {
  379. const result = computeFromBits(s);
  380. renderResult(result);
  381. // Auto-fill the compressed input for easy testing
  382. compressedInput.value = result.compressedWithDisambLSB;
  383. } catch (err) {
  384. errorEl.textContent = 'Computation error';
  385. console.error(err);
  386. }
  387. });
  388.  
  389. decompressBtn.addEventListener('click', () => {
  390. decompressErrorEl.textContent = '';
  391. decompressOutputEl.innerHTML = '';
  392. const s = compressedInput.value.trim();
  393. const v = validateCompressed(s);
  394. if (v) { decompressErrorEl.textContent = v; return; }
  395. try {
  396. const result = decompressPayload(s);
  397. renderDecompressResult(result);
  398. } catch (err) {
  399. decompressErrorEl.textContent = 'Decompression error: ' + err.message;
  400. console.error(err);
  401. }
  402. });
  403.  
  404. testRoundtripBtn.addEventListener('click', () => {
  405. errorEl.textContent = '';
  406. outputEl.innerHTML = '';
  407. decompressErrorEl.textContent = '';
  408. decompressOutputEl.innerHTML = '';
  409.  
  410. // Use current bits input or generate random
  411. let originalBits = bitsInput.value.trim();
  412. if (!originalBits || originalBits.length !== 21) {
  413. originalBits = randomBits21();
  414. bitsInput.value = originalBits;
  415. }
  416.  
  417. try {
  418. // Compress
  419. const compressResult = computeFromBits(originalBits);
  420. renderResult(compressResult);
  421.  
  422. // Decompress
  423. const decompressResult = decompressPayload(compressResult.compressedWithDisambLSB);
  424. renderDecompressResult(decompressResult, originalBits);
  425.  
  426. compressedInput.value = compressResult.compressedWithDisambLSB;
  427. } catch (err) {
  428. errorEl.textContent = 'Roundtrip test error: ' + err.message;
  429. console.error(err);
  430. }
  431. });
  432.  
  433. exampleBtn.addEventListener('click', () => {
  434. bitsInput.value = '010101001110000111000';
  435. errorEl.textContent = '';
  436. outputEl.innerHTML = '';
  437. });
  438.  
  439. randomBtn.addEventListener('click', () => {
  440. bitsInput.value = randomBits21();
  441. errorEl.textContent = '';
  442. outputEl.innerHTML = '';
  443. computeBtn.click();
  444. });
  445.  
  446. copyBtn.addEventListener('click', async () => {
  447. const out = outputEl.innerText || '';
  448. if (!out) { errorEl.textContent = 'Nothing to copy'; return; }
  449. try {
  450. await navigator.clipboard.writeText(out);
  451. errorEl.textContent = 'Result copied to clipboard';
  452. setTimeout(() => { errorEl.textContent = ''; }, 2000);
  453. } catch (e) {
  454. errorEl.textContent = 'Copy failed';
  455. }
  456. });
  457.  
  458. function randomBits21() {
  459. let s = '';
  460. for (let i = 0; i < 21; i++) {
  461. s += (Math.random() < 0.5 ? '0' : '1');
  462. }
  463. return s;
  464. }
  465.  
  466. // Run many random tests and show summary
  467. runTestsBtn.addEventListener('click', () => {
  468. errorEl.textContent = '';
  469. outputEl.innerHTML = '';
  470. const count = Math.max(1, Math.min(100000, Number(countInput.value) || 100));
  471.  
  472. // Track bit-length distribution
  473. const lengthCounts = new Map();
  474.  
  475. let min = null;
  476. let max = null;
  477. let sum = 0n;
  478.  
  479. for (let i = 0; i < count; i++) {
  480. const bits = randomBits21();
  481. const res = computeFromBits(bits);
  482. const valAfter = BigInt(res.valueAfterSubtract);
  483.  
  484. if (min === null || valAfter < min) min = valAfter;
  485. if (max === null || valAfter > max) max = valAfter;
  486. sum += valAfter;
  487.  
  488. const len = res.totalLengthBits;
  489. lengthCounts.set(len, (lengthCounts.get(len) || 0) + 1);
  490. }
  491.  
  492. const avg = sum / BigInt(count);
  493.  
  494. // Sort lengths for display
  495. const sortedLengths = Array.from(lengthCounts.keys()).sort((a, b) => a - b);
  496.  
  497. let html = '<div class="small"><strong>Random tests summary (using post-subtract binary with leading 1-bit stripped + 1-bit LSB disambiguation)</strong></div>';
  498. html += '<pre>';
  499. html += `Total samples: ${count}\n`;
  500. html += `Min valueAfterSubtract: ${min.toString()}\n`;
  501. html += `Max valueAfterSubtract: ${max.toString()}\n`;
  502. html += `Average valueAfterSubtract: ${avg.toString()}\n`;
  503. html += '\n';
  504. html += '<strong>Payload Length Distribution (bits):</strong>\n';
  505. html += 'Bits Count Percentage\n';
  506. html += '---- --------- ----------\n';
  507.  
  508. sortedLengths.forEach(len => {
  509. const cnt = lengthCounts.get(len);
  510. const pct = ((cnt / count) * 100).toFixed(2);
  511. const lenStr = String(len).padEnd(4, ' ');
  512. const cntStr = String(cnt).padStart(9, ' ');
  513. const pctStr = String(pct).padStart(6, ' ') + '%';
  514. html += `${lenStr} ${cntStr} ${pctStr}\n`;
  515. });
  516.  
  517. // Calculate statistics
  518. const minLen = sortedLengths[0];
  519. const maxLen = sortedLengths[sortedLengths.length - 1];
  520. let weightedSum = 0;
  521. sortedLengths.forEach(len => {
  522. weightedSum += len * lengthCounts.get(len);
  523. });
  524. const avgLen = (weightedSum / count).toFixed(2);
  525.  
  526. html += '\n';
  527. html += `Min payload length: ${minLen} bits\n`;
  528. html += `Max payload length: ${maxLen} bits\n`;
  529. html += `Average payload length: ${avgLen} bits\n`;
  530. html += `Compression ratio: ${avgLen}/21 = ${(avgLen / 21).toFixed(4)} (${((avgLen / 21) * 100).toFixed(2)}%)\n`;
  531.  
  532. html += '</pre>';
  533.  
  534. outputEl.innerHTML = html;
  535. });
  536.  
  537. // allow Enter to compute
  538. bitsInput.addEventListener('keydown', (e) => {
  539. if (e.key === 'Enter') { computeBtn.click(); }
  540. });
  541.  
  542. compressedInput.addEventListener('keydown', (e) => {
  543. if (e.key === 'Enter') { decompressBtn.click(); }
  544. });
  545. </script>
  546. </body>
  547. </html>
  548.  
Pasted 2026-08-20 19:45:21

Short link: