add footer, contact button
All checks were successful
Deploy Pages / deploy (push) Successful in 24s
All checks were successful
Deploy Pages / deploy (push) Successful in 24s
This commit is contained in:
40
index.html
40
index.html
@ -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
236
link-aes.js
Normal 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'));
|
||||
}
|
Reference in New Issue
Block a user