add footer, contact button
All checks were successful
Deploy Pages / deploy (push) Successful in 24s

This commit is contained in:
2025-04-25 15:10:43 -04:00
parent 5236a97d1c
commit 9d7c3f0122
2 changed files with 272 additions and 4 deletions

View File

@ -41,6 +41,9 @@
<meta name="msapplication-TileImage" content="favicon.png?3">
<meta name="theme-color" content="#67EA94">
<!-- Scripts -->
<script src="link-aes.js" defer></script>
<title>CT Mesh</title>
<style>
@ -72,8 +75,8 @@
.splash-box {
background-color: #EBEBEB;
border-radius: 30px;
padding: 3em;
margin: 1em;
padding: 3em 3em 2em 3em;
margin: 1em auto 0 auto;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
color: #000000;
position: relative;
@ -88,7 +91,7 @@
justify-content: center;
gap: 1em;
width: 100%;
padding-bottom: 35px;
padding-bottom: 15px;
}
.logo {
width: 200px;
@ -149,6 +152,31 @@
flex-direction: column;
gap: 1em;
}
.btn-small {
display: inline-block;
margin-top: 0.5em;
color: #fff;
background-color: #2C2D3C;
font-weight: bold;
text-decoration: none;
padding: 0.5em 1.25em;
border-radius: 15px;
}
.site-footer {
margin-top: 2em;
padding-top: 2em;
text-align: center;
font-size: 0.875rem;
color: #2C2D3C;
border-top: 1px solid #ccc;
}
.site-footer a {
color: #757684;
text-decoration: none;
}
.site-footer a:hover {
text-decoration: underline;
}
/* Adjustments for devices with a viewport width of 800px or less */
@media (max-width: 800px) {
@ -212,7 +240,7 @@
<div class="text-content">
<h1>CT Mesh</h1>
<p class="sub-header">Connecticut Meshtastic User Group</p>
<p>Join the local community!</p>
<a href="Ws9mCBVzpuQdAEBDZLxJxNbKOflt64pOog" rel="nofollow noindex" class="btn-small link-aes">Contact</a>
</div>
</div>
<div class="btn-groups">
@ -252,6 +280,10 @@
</a>
</div>
</div>
<footer class="site-footer">
<p><a href="https://ctmesh.org/">CT Mesh</a> is a volunteer-run user group for <a href="https://meshtastic.org" target="_blank">Meshtastic</a> enthusiasts in Connecticut.</p>
<p><a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank" rel="noopener">Content licensed CC BY-SA 4.0</a></p>
</footer>
</div>
</body>
</html>

236
link-aes.js Normal file
View File

@ -0,0 +1,236 @@
'use strict';
document.addEventListener('DOMContentLoaded', function ()
{
const keyText = '1CnBWwQtMAwRP8E0mZLkRxywSisGedf3XzyHmCIK1BM';
const onGetKey = function (key)
{
const coder = new AesLinkCoder(key);
document.querySelectorAll('.link-aes')
.forEach(coder.decode.bind(coder));
};
if ('subtle' in window.crypto) {
Aes.getKey(keyText).then(onGetKey);
} else {
console.log('AES encryption is only available in secure contexts (such as over https or localhost)');
}
});
// Aes
const Aes = {
'KEY_BITS': 256, // 128 (SECRET) | 192 (TOP SECRET) | 256 (TOP SECRET)
'NONCE_BYTES': 3, // 3 bytes = 16,777,216 possible nonces
'BLOCK_BYTES': 16, // Required by AES
'BITS_PER_BYTE': 8 // Conversion factor
};
Aes.newKeyText = async function ()
{
const options = {
'name': 'AES-CTR',
'length': Aes.KEY_BITS
};
const key = await window.crypto.subtle.generateKey(options, true, ['encrypt', 'decrypt']);
const keyBuffer = await window.crypto.subtle.exportKey('raw', key);
const keyBytes = new Uint8Array(keyBuffer);
const keyText = Base64.text(keyBytes);
return keyText;
}
Aes.getKey = async function (keyText)
{
const options = {
'name': 'AES-CTR'
};
const keyBytes = Base64.bytes(keyText);
const key = window.crypto.subtle.importKey('raw', keyBytes, options, false, ['encrypt', 'decrypt']);
return key;
}
Aes.encrypt = async function (plainText, key)
{
const nonceBytes = Aes.__newNonce();
const counterBytes = Aes.__newCounter(nonceBytes);
const options = Aes.__newOptions(counterBytes);
const encoder = new TextEncoder();
const plainBytes = encoder.encode(plainText);
const codeBuffer = await window.crypto.subtle.encrypt(options, key, plainBytes);
const codeBytes = new Uint8Array(codeBuffer);
const outputBytes = new Uint8Array(nonceBytes.length + codeBytes.length);
outputBytes.set(nonceBytes);
outputBytes.set(codeBytes, nonceBytes.length);
const outputText = Base64.text(outputBytes);
return outputText;
}
Aes.__newNonce = function ()
{
const nonce = new Uint8Array(Aes.NONCE_BYTES);
window.crypto.getRandomValues(nonce);
return nonce;
}
Aes.__newCounter = function (nonce)
{
const counter = new Uint8Array(Aes.BLOCK_BYTES);
counter.set(nonce);
return counter;
}
Aes.__newOptions = function (counter)
{
return {
'name': 'AES-CTR',
'counter': counter,
'length': (Aes.BLOCK_BYTES - Aes.NONCE_BYTES) * Aes.BITS_PER_BYTE
};
}
Aes.decrypt = async function (inputText, key)
{
const inputBytes = Base64.bytes(inputText);
const nonceBytes = inputBytes.slice(0, Aes.NONCE_BYTES);
const codeBytes = inputBytes.slice(Aes.NONCE_BYTES);
const counterBytes = Aes.__newCounter(nonceBytes);
const options = Aes.__newOptions(counterBytes);
const plainBytes = await window.crypto.subtle.decrypt(options, key, codeBytes);
const decoder = new TextDecoder();
const plainText = decoder.decode(plainBytes);
return plainText;
}
// Base64
const Base64 = {};
Base64.bytes = function (text)
{
const values = Base64.__getValues(text);
const bytes = Base64.__getBytes(values);
return bytes;
}
Base64.__getValues = function (text)
{
const values = new Uint8Array(text.length);
for (let i = 0; i < text.length; ++i) {
const code = text.charCodeAt(i);
values[i] = Base64.__getValue(code);
}
return values;
}
Base64.__getValue = function (code)
{
if (code === 45) {
return 63;
}
if (code < 58) {
return code - 48;
}
if (code < 91) {
return code - 29;
}
if (code === 95) {
return 62;
}
return code - 87;
}
Base64.__getBytes = function (values)
{
const length = Math.floor(3 * values.length / 4);
const bytes = new Uint8Array(length);
for (let i = 0, j = 0; i < values.length; ++i) {
const r = 2 * (i % 4);
if (r !== 0) {
bytes[j++] = ((values[i-1] << r) & 0xff) | (values[i] >> (6 - r));
}
}
return bytes;
}
Base64.text = function (bytes)
{
let text = '';
for (let i = 0; i < bytes.length; ++i) {
const r = i % 3;
if (r === 0) {
text += Base64.__getSymbol(bytes[i] >> 2) + Base64.__getSymbol(((bytes[i] << 4) & 63) | (bytes[i+1] >> 4));
} else if (r === 1) {
text += Base64.__getSymbol(((bytes[i] << 2) & 63) | (bytes[i+1] >> 6));
} else {
text += Base64.__getSymbol(bytes[i] & 63);
}
}
return text;
}
Base64.__getSymbol = function (value)
{
if (value < 10) {
return String.fromCharCode(48 + value);
}
if (value < 36) {
return String.fromCharCode(87 + value);
}
if (value < 62) {
return String.fromCharCode(29 + value);
}
if (value === 62) {
return '_';
}
return '-';
}
// AesLinkCoder
function AesLinkCoder (key)
{
this.key = key;
}
AesLinkCoder.prototype.decode = function (node)
{
Aes.decrypt(node.getAttribute('href'), this.key)
.then(node.setAttribute.bind(node, 'href'));
}
AesLinkCoder.prototype.encode = function (node)
{
Aes.encrypt(node.getAttribute('href'), this.key)
.then(node.setAttribute.bind(node, 'href'));
}