add scrolling grid bg

update copyright year
remove old libs
update to v0.12.0.
This commit is contained in:
Eric Woodward 2024-04-09 23:39:41 -04:00
parent 5f14032aee
commit b2d2fb6d34
13 changed files with 270 additions and 308 deletions

View File

@ -1,19 +0,0 @@
{
/*
*/
/** TACO Express Default Options **/
/*
The function used to log output (console.log, morgan, etc).
Should take one (or more) strings as arguments.
*/
logFunction: null,
build: {},
site: {},
serve: {
port: 5000,
},
}

View File

@ -1,43 +0,0 @@
module.exports = (opts, envKey) => {
const
fs = require('fs'),
path = require('path'),
json5 = require('json5'),
{ convertCamelToUpperSnakeCase, readJsonIfExists } = require('./utils'),
{ cwd, env } = process,
def = readJsonIfExists(path.resolve(__dirname, 'defaults.json5')),
// gets value from ENV || options || defaults (in that order)
getVal = (envName) => {
const snakeEnvName = `${envKey}_${convertCamelToUpperSnakeCase(envName)}`;
if (env[snakeEnvName]) return env[snakeEnvName];
if (opts[envName]) return opts[envName];
return def[envName];
},
// gets array from ENV || options || defaults (in that order)
getArray = (envName, optName = '') => {
if (optName === '') {
optName = envName;
envName = convertCamelToUpperSnakeCase(envName);
}
envName = `${envKey}_${envName}`;
if (env[envName]) return env[envName].split(path.delimiter);
if (Array.isArray(opts[optName]) && opts[optName].length) return opts[optName];
return def[optName];
};
return {
...Object.keys(def).reduce((acc, curr) => {
if (Array.isArray(def[curr])) acc[curr] = getArray(curr);
else acc[curr] = getVal(curr);
return acc;
}, {}),
};
};

View File

@ -1,26 +0,0 @@
module.exports = async (config) => {
let isReady = false;
const
http = require('http'),
address = require('network-address'),
handler = require('serve-handler'),
build = require('./build'),
{ build: buildOpts, logFunction: log = () => {}, serve: serveOpts } = config || {},
{ outputPath, srcPath } = buildOpts || {},
{ port = 5000 } = serveOpts || {},
server = http.createServer((request, response) => {
// You pass two more arguments for config and middleware
// More details here: https://github.com/vercel/serve-handler#options
return handler(request, response, { public: outputPath });
});
await build(config);
server.listen(port, async () => {
log(`Running at http://${address()}:${port} / http://localhost:${port}`);
});
};

View File

@ -1,57 +0,0 @@
module.exports = (() => {
const
chalk = require('chalk'),
getTime = () => {
const
now = new Date(),
tzo = -now.getTimezoneOffset(),
dif = tzo >= 0 ? '+' : '-',
pad = (num) => {
const norm = Math.floor(Math.abs(num));
return `${norm < 10 ? '0' : ''}${norm}`;
};
return [
now.getFullYear(),
'-',
pad(now.getMonth() + 1),
'-',
pad(now.getDate()),
'T',
pad(now.getHours()),
':',
pad(now.getMinutes()),
':',
pad(now.getSeconds()),
dif,
pad(tzo / 60),
':',
pad(tzo % 60)
].join('');
};
return {
convertCamelToUpperSnakeCase:
str => str.replace(/[A-Z]/g, letter => `_${letter}`).toUpperCase(),
getTime,
log: (msg) => console.log(`${chalk.grey(`${getTime()}:`)} ${msg}`),
readJsonIfExists: (filePath) => {
const
fs = require('fs'),
json5 = require('json5');
try {
return json5.parse(fs.readFileSync(filePath, {encoding: 'utf8'}));
} catch (err) {
if (err.code === 'ENOENT') return {};
throw err;
}
},
};
})();

View File

@ -1,58 +0,0 @@
module.exports = async (config) => {
let isReady = false;
const
http = require('http'),
chokidar = require('chokidar'),
address = require('network-address'),
handler = require('serve-handler'),
build = require('./build'),
rebuild = (cfg) => {
isReady = false;
build({ ...cfg, isRebuild: true });
isReady = true;
},
{ build: buildOpts, logFunction: log = () => {}, serve: serveOpts } = config || {},
{ outputPath, srcPath } = buildOpts || {},
{ port = 5000 } = serveOpts || {},
watcher = chokidar.watch([srcPath, '*.json'], {
ignored: /(^|[\/\\])\../, // ignore dotfiles
persistent: true
})
.on('add', (path) => {
if (isReady) {
log(`File ${path} has been added`)
rebuild(config);
}
})
.on('change', (path) => {
if (isReady) {
log(`File ${path} has been changed`)
rebuild(config);
}
})
.on('ready', () => {
isReady = true;
})
.on('unlink', (path) => {
if (isReady) {
log(`File ${path} has been removed`)
rebuild(config);
}
}),
server = http.createServer((request, response) => {
// You pass two more arguments for config and middleware
// More details here: https://github.com/vercel/serve-handler#options
return handler(request, response, { public: outputPath });
});
await build(config);
server.listen(port, () => {
log(`Running at http://${address()}:${port} / http://localhost:${port}`);
});
};

8
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "iew-site-builder",
"version": "0.10.0",
"name": "iew-site",
"version": "0.11.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "iew-site-builder",
"version": "0.10.0",
"name": "iew-site",
"version": "0.11.0",
"license": "MIT",
"devDependencies": {
"web-weevr": "git+ssh://git@git.itsericwoodward.com:eric/web-weevr.git"

View File

@ -1,12 +1,16 @@
{
"name": "iew-site",
"version": "0.11.0",
"version": "0.12.0",
"description": "",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "Eric Woodward (https://www.itsericwoodward.com)",
"license": "MIT",
"engines": {
"npm": ">=9",
"node": ">=18"
},
"devDependencies": {
"web-weevr": "git+ssh://git@git.itsericwoodward.com:eric/web-weevr.git"
}

View File

@ -1,9 +0,0 @@
// @license magnet:?xt=urn:btih:e95b018ef3580986a04669f1b5879592219e2a7a&dn=public-domain.txt Unlicense
/*
classList.js v1.2.20171210
https://github.com/eligrey/classList.js
The Unlicense
*/
"document"in self&&(("classList"in document.createElement("_")&&(!document.createElementNS||"classList"in document.createElementNS("http://www.w3.org/2000/svg","g")))||!(function(t){"use strict";if("Element"in t){var e="classList",n="prototype",i=t.Element[n],s=Object,r=String[n].trim||function(){return this.replace(/^\s+|\s+$/g,"")},o=Array[n].indexOf||function(t){for(var e=0,n=this.length;n>e;e+=1){if(e in this&&this[e]===t){return e}}return -1},c=function(t,e){(this.name=t),(this.code=DOMException[t]),(this.message=e)},a=function(t,e){if(""===e){throw new c("SYNTAX_ERR","The token must not be empty.")}if(/\s/.test(e)){throw new c("INVALID_CHARACTER_ERR","The token must not contain space characters.")}return o.call(t,e)},l=function(t){for(var e=r.call(t.getAttribute("class")||""),n=e?e.split(/\s+/):[],i=0,s=n.length;s>i;i+=1){this.push(n[i])}this._updateClassName=function(){t.setAttribute("class",this.toString())}},u=(l[n]=[]),h=function(){return new l(this)};if(((c[n]=Error[n]),(u.item=function(t){return this[t]||null}),(u.contains=function(t){return~a(this,t+"")}),(u.add=function(){var t,e=arguments,n=0,i=e.length,s=!1;do{(t=e[n]+""),~a(this,t)||(this.push(t),(s=!0))}while(++n<i);s&&this._updateClassName()}),(u.remove=function(){var t,e,n=arguments,i=0,s=n.length,r=!1;do{for(t=n[i]+"",e=a(this,t);~e;){this.splice(e,1),(r=!0),(e=a(this,t))}}while(++i<s);r&&this._updateClassName()}),(u.toggle=function(t,e){var n=this.contains(t),i=n?e!==!0&&"remove":e!==!1&&"add";return i&&this[i](t),e===!0||e===!1?e:!n}),(u.replace=function(t,e){var n=a(t+"");~n&&(this.splice(n,1,e),this._updateClassName())}),(u.toString=function(){return this.join(" ")}),s.defineProperty)){var f={get:h,enumerable:!0,configurable:!0};try{s.defineProperty(i,e,f)}catch(p){(void 0!==p.number&&-2146823252!==p.number)||((f.enumerable=!1),s.defineProperty(i,e,f))}}else{s[n].__defineGetter__&&i.__defineGetter__(e,h)}}})(self),(function(){"use strict";var t=document.createElement("_");if((t.classList.add("c1","c2"),!t.classList.contains("c2"))){var e=function(t){var e=DOMTokenList.prototype[t];DOMTokenList.prototype[t]=function(t){var n,i=arguments.length;for(n=0;i>n;n+=1){(t=arguments[n]),e.call(this,t)};;};e("add"),e("remove")}if((t.classList.toggle("c3",!1),t.classList.contains("c3"))){var n=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(t,e){return 1 in arguments&&!this.contains(t)==!e?e:n.call(this,t)}}"replace"in document.createElement("_").classList||(DOMTokenList.prototype.replace=function(t,e){var n=this.toString().split(" "),i=n.indexOf(t+"");~i&&((n=n.slice(i)),this.remove.apply(this,n),this.add(e),this.add.apply(this,n.slice(1)))}),(t=null)})());
// @license-end

View File

@ -61,6 +61,62 @@
});
}
// default to no scrolling on devices under 600 px w
const isMobile = window.matchMedia(
"only screen and (max-width: 600px)"
).matches;
// add background scrolling
const hasScrollToggle =
Cookies.get("scrollToggle") === true ||
Cookies.get("scrollToggle") === "true" ||
(Cookies.get("scrollToggle") === undefined && !isMobile);
const scrollToggle = document.createElement("div");
scrollToggle.innerHTML = [
'<label class="toggleSwitchV3" for="scrollingToggle">',
'<div class="toggleSwitchV3-description">',
"Background",
"</div>",
`<input class="toggleSwitchV3-checkbox" ${
hasScrollToggle ? "checked" : ""
} type="checkbox" id="scrollingToggle" />`,
'<div class="toggleSwitchV3-status" data-ts-on="SCROLLING" data-ts-off="STATIC"></div>',
"</label>",
].join("\n");
scrollToggle.classList.add("js-scrollToggle", "scrollToggle");
const actionBox = document
// .getElementById("footer")
// .querySelector("#footer > .pageFooter-inner")
.querySelector("#footer .actionBox");
actionBox.append(scrollToggle);
actionBox.classList.add("js-actionBox");
// add toggle event
document
.getElementById("scrollingToggle")
.addEventListener("click", function (e) {
const theList =
document.getElementById("gridContainer").classList;
theList.toggle("js-isAnimated");
Cookies.set(
"scrollToggle",
!!theList.contains("js-isAnimated")
);
});
// add class if active at startup
if (hasScrollToggle)
document
.getElementById("gridContainer")
.classList.add("js-isAnimated");
// TODO: maybe put toggle behind collapsable "settings" menu that
// lives on bottom left of screen on mobile?
// can eventually toggle color schemes here, too?
if (document.documentElement.className.indexOf("is404") > -1) {
document.getElementById("searchQuery").value =
window.location.pathname

View File

@ -20,8 +20,7 @@
*/
body {
background: #0d1852; /* Old browsers */
background: url("/images/code-bg.jpg") top fixed;
background: #040308; /* Old browsers */
color: #9aa8bc;
font-family: sans-serif;
font-size: 100%;
@ -35,8 +34,8 @@ a {
font-weight: bold;
padding: 0 0.2rem;
text-decoration: none;
-webkit-transition: 0.3s background-color, 0.3s color, 0.3s border-radius;
transition: 0.3s background-color, 0.3s color, 0.3s border-radius;
-webkit-transition: 0.3s background-color, 0.3s color, 0.3s border-radius;
}
a[target="_blank"]::after {
@ -45,8 +44,8 @@ a[target="_blank"]::after {
}
a:hover {
border: 1px solid #25baba;
background-color: #25baba;
border: 1px solid #25baba;
border-radius: 0.3rem;
color: #040308;
text-decoration: none;
@ -57,7 +56,6 @@ a:hover:visited {
}
a:visited {
color: #094192;
color: #1e6e58;
}
@ -86,8 +84,8 @@ figcaption {
}
figure {
margin: 1em auto;
display: block;
margin: 1em auto;
text-align: center;
}
@ -191,6 +189,27 @@ samp {
font-size: 0.9em;
}
.actionBox {
align-items: stretch;
display: flex;
flex-direction: row;
justify-content: space-evenly;
margin: 1rem 0;
}
.actionBox.js-actionBox > .topLink {
align-items: center;
border: 1px solid #726f6a;
border-radius: 0.3em;
display: flex;
flex-wrap: wrap;
margin: 0;
max-width: 5rem;
padding: 0.5em;
text-align: center;
width: auto;
}
.anaCardImg {
height: auto;
margin: 0;
@ -284,8 +303,8 @@ samp {
border: 1px solid transparent;
display: inline-block;
padding: 0.5em 0.5em 0.3em;
-webkit-transition: 0.3s background-color, 0.3s color, 0.3s border-radius;
transition: 0.3s background-color, 0.3s color, 0.3s border-radius;
-webkit-transition: 0.3s background-color, 0.3s color, 0.3s border-radius;
}
.asideBio-div-link {
@ -395,6 +414,10 @@ samp {
background-color: #25baba;
}
.container {
color: white;
}
.dataTable td {
border-left: 1px solid #9aa8bc;
border-right: 1px solid #9aa8bc;
@ -457,8 +480,8 @@ samp {
}
.embedSwitch {
text-align: right;
margin-bottom: 1em;
text-align: right;
}
.embedTwitter {
@ -519,9 +542,47 @@ samp {
max-width: 100%;
}
.heroImage {
.gridContainer::after {
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
background-clip: content-box;
-webkit-background-clip: content-box;
background-position: center bottom;
background-size: 20px 20px;
background-image: linear-gradient(to right, #04778f 2px, transparent 2px), linear-gradient(to bottom, #04778f 2px, transparent 2px);
bottom: 0;
content: "";
display: block;
height: 100vh;
left: 0;
mask-image: gradient(linear, left 90%, left top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
-webkit-mask-image: -webkit-gradient(linear, left 90%, left top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
outline: 1px solid transparent;
padding: 1px;
position: fixed;
right: 0;
transform: perspective(60vh) rotateX(20deg) scale(2,2) translateZ(0);
transform-origin: bottom center;
transform-style: preserve-3d;
-webkit-transform-style: preserve-3d;
width: 100%;
will-change: transform;
}
.gridContainer.js-isAnimated::after {
animation: move-up 50s infinite;
animation-timing-function: ease-out;
animation-fill-mode: backwards;
}
@keyframes move-up {
0% { background-position: 0 0; }
100% { background-position: 0 -50%; }
}
.heroImage {
clear: both;
display: block;
margin: auto;
max-width: 100%;
}
@ -547,9 +608,9 @@ samp {
}
.licenseLink {
display: inline-block;
border: 1px solid transparent;
border-bottom: 1px solid transparent;
display: inline-block;
vertical-align: baseline;
}
@ -591,10 +652,10 @@ samp {
margin: 0 auto;
max-width: 15em;
padding: 0.5em;
-webkit-transition: 0.3s background-color, 0.3s color, 0.3s border-radius,
0.3s border-width;
transition: 0.3s background-color, 0.3s color, 0.3s border-radius,
0.3s border-width;
-webkit-transition: 0.3s background-color, 0.3s color, 0.3s border-radius,
0.3s border-width;
}
.mainBio-div-imgLink:hover {
@ -668,6 +729,7 @@ samp {
min-height: 100vh;
padding: 0;
position: relative;
z-index: 1;
}
.pageFooter {
@ -727,8 +789,8 @@ samp {
}
.pageMenu-caption {
left: 0;
bottom: 0.5em;
left: 0;
position: absolute;
right: 0;
}
@ -757,8 +819,8 @@ samp {
max-width: 340px;
text-align: center;
text-decoration: none;
-webkit-transition: 0.3s background-color, 0.3s border-color, 0.3s color;
transition: 0.3s background-color, 0.3s border-color, 0.3s color;
-webkit-transition: 0.3s background-color, 0.3s border-color, 0.3s color;
}
.pageMenu-item:hover {
@ -784,8 +846,8 @@ samp {
background: rgba(50, 50, 50, 0.8);
display: inline-block;
margin: 0 auto;
-webkit-transition: 0.3s background-color, 0.3s color;
transition: 0.3s background-color, 0.3s color;
-webkit-transition: 0.3s background-color, 0.3s color;
width: 100%;
}
@ -884,10 +946,17 @@ samp {
font-size: 1.75em;
}
.scrollToggle {
align-items: center;
display: flex;
flex-direction: column;
}
.searchBox-query {
background-color: #049c74;
background: #040308;
background: rgba(4, 3, 8, 0.9);
border-radius: 0.3rem;
color: #000;
color: #ddd;
width: 10em;
}
@ -898,18 +967,18 @@ samp {
.siteTitle-link {
border: 1px solid transparent;
color: #25baba;
border-radius: 1rem;
color: #25baba;
display: inline-block;
font-style: normal;
padding: 0.2em;
position: relative;
text-decoration: none;
text-shadow: 0px 2px 2px rgba(16, 16, 16, 0.6);
-webkit-transition: 0.3s border-color, 0.3s color, 0.3s text-shadow,
0.3s border-radius;
transition: 0.3s border-color, 0.3s color, 0.3s text-shadow,
0.3s border-radius;
-webkit-transition: 0.3s border-color, 0.3s color, 0.3s text-shadow,
0.3s border-radius;
}
.siteTitle-link:visited {
@ -965,77 +1034,121 @@ samp {
border: 1px solid #25baba;
}
/* toggleSwitch & overrides */
/*
.toggleSwitch:empty
{
margin-left: -999px;
.toggleBox {
align-items: center;
display: flex;
justify-content: end;
}
.toggleSwitch:empty ~ label .toggleSwitch-info,
.toggleSwitch:checked ~ label .toggleSwitch-info {
visibility: hidden;
.toggleSwitchV3 {
border: 1px solid #726f6a;
border-radius: 0.3em;
}
.toggleSwitch:empty ~ label
{
position: relative;
float: left;
line-height: 1.6em;
text-indent: 4em;
margin: 0.2em 0;
.toggleSwitchV3-checkbox {
display: none;
}
.toggleSwitchV3-checkbox::selection,
.toggleSwitchV3-checkbox::-moz-selection,
.toggleSwitchV3-checkbox:after::selection,
.toggleSwitchV3-checkbox:after::-moz-selection,
.toggleSwitchV3-checkbox:before::selection,
.toggleSwitchV3-checkbox:before::-moz-selection,
.toggleSwitchV3-checkbox *::selection,
.toggleSwitchV3-checkbox *::-moz-selection,
.toggleSwitchV3-checkbox *:after::selection,
.toggleSwitchV3-checkbox *:after::-moz-selection,
.toggleSwitchV3-checkbox *:before::selection,
.toggleSwitchV3-checkbox *:before::-moz-selection,
.toggleSwitchV3-checkbox + .toggleSwitchV3-status::selection,
.toggleSwitchV3-checkbox + .toggleSwitchV3-status::-moz-selection {
background: none;
}
.toggleSwitchV3-checkbox:checked + .toggleSwitchV3-status {
background: #1e6e58;
}
.toggleSwitchV3-checkbox:checked + .toggleSwitchV3-status:before {
bottom: -100%;
}
.toggleSwitchV3-checkbox:checked + .toggleSwitchV3-status:after {
bottom: 0;
}
.toggleSwitchV3-description {
color: #049c74;
font-weight: bold;
padding: .5rem;
text-decoration: none;
transition: 0.3s background-color, 0.3s color, 0.3s border-radius;
-webkit-transition: 0.3s background-color, 0.3s color, 0.3s border-radius;
}
.toggleSwitchV3-status {
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
background: transparent;
border-radius: 0 0 0.3em 0.3em;
cursor: pointer;
-webkit-user-select: none;
display: block;
font-family: sans-serif;
height: 2em;
outline: 0;
overflow: hidden;
position: relative;
transition: all 0.2s ease;
user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-user-select: none;
width: 100%;
}
.toggleSwitch:empty ~ label:before,
.toggleSwitch:empty ~ label:after
{
.toggleSwitchV3-status:after,
.toggleSwitchV3-status:before {
color: #049c74;
content: "";
display: inline-block;
font-weight: bold;
height: 100%;
line-height: 2em;
position: absolute;
display: block;
top: 0;
bottom: 0;
text-align: center;
transition: all 0.2s ease;
width: 100%;
}
.toggleSwitchV3-status:after {
border-top: 1px solid transparent;
bottom: 100%;
color: #040308;
content: attr(data-ts-on);
left: 0;
content: ' ';
width: 3.6em;
background-color: #c33;
border-radius: 0.3em;
box-shadow: inset 0 0.2em 0 rgba(0,0,0,0.3);
-webkit-transition: all 100ms ease-in;
transition: all 100ms ease-in;
}
.toggleSwitch:empty ~ label:before
{
content: "Disable Embeds";
.toggleSwitchV3-status:before {
border-top: 1px solid #726f6a;
bottom: 0;
content: attr(data-ts-off);
left: 0;
}
.toggleSwitch:empty ~ label:after
{
background-color: #fff;
border-radius: 0.15em;
bottom: 0.1em;
box-shadow: inset 0 -0.2em 0 rgba(0,0,0,0.2);
margin-left: 0.1em;
top: 0.1em;
width: 1.4em;
}
/* hover effects only on devices with fine pointers (AKA not phones) */
@media(hover: hover) and (pointer: fine) {
.toggleSwitchV3:hover {
border: 1px solid hsl(180, 67%, 44%);
}
.toggleSwitch:checked ~ label:before
{
content: "Enable Embeds";
background-color: #393;
.toggleSwitchV3:hover > .toggleSwitchV3-description {
background-color: #25baba;
color: #040308;
text-decoration: none;
}
}
.toggleSwitch:checked ~ label:after
{
margin-left: 2em;
}
*/
.topAnchor {
border: none;
height: 0;
@ -1052,6 +1165,9 @@ samp {
text-align: center;
width: 7rem;
}
.topLink:visited {
color: #049c74;
}
.twitter-tweet {
margin: 0;
@ -1206,8 +1322,8 @@ samp {
****************************************************************************/
.js .pubDate.isDone {
-webkit-transition: 0.3s visibility;
transition: 0.3s visibility;
-webkit-transition: 0.3s visibility;
visibility: visible;
}
@ -1219,6 +1335,12 @@ samp {
* Media Queries
****************************************************************************/
@media all and (min-width: 34em) {
.magic-commander-img {
float: right;
}
}
@media all and (min-width: 38em) {
.navMenu {
padding: 0;
@ -1239,12 +1361,6 @@ samp {
}
}
@media all and (min-width: 34em) {
.magic-commander-img {
float: right;
}
}
@media all and (min-width: 52em) {
body {
font-size: 1.1em;
@ -1271,6 +1387,8 @@ samp {
}
.asideRight {
max-height: 80%;
overflow-y: auto;
right: -15em;
width: 15em;
}

View File

@ -2,13 +2,13 @@
<!-- BOTTOM BEGIN -->
</div>
<div class="gridContainer" id="gridContainer"></div>
<script type="text/javascript" src="/scripts/1-docready.min.js"></script>
<script type="text/javascript" src="/scripts/2-es6-promise.auto.min.js"></script>
<script type="text/javascript" src="/scripts/3-lazy-progressive-enhancement.min.js"></script>
<script type="text/javascript" src="/scripts/4-js.cookie.min.js"></script>
<script type="text/javascript" src="/scripts/5-fontfaceobserver.min.js"></script>
<script type="text/javascript" src="/scripts/6-classlist.min.js"></script>
<script type="text/javascript" src="/scripts/7-dayjs.min.js"></script>
<script type="text/javascript" src="/scripts/6-dayjs.min.js"></script>
<script type="text/javascript" src="/scripts/scripts.js"></script>
</body>
</html>

View File

@ -18,13 +18,13 @@
<p>
<a rel="license"
class="licenseLink"
href="http://creativecommons.org/licenses/by-sa/4.0/">
href="/licenses/cc-by-sa/">
<img
alt="Creative Commons NC-BY-SA 4.0 License"
class="licenseImg"
src="https://i.creativecommons.org/l/by-sa/4.0/80x15.png" />
</a>
Except where otherwise noted, content on this site is &copy; 2014-2023
Except where otherwise noted, content on this site is &copy; 2014-2024
<a
xmlns:cc="http://creativecommons.org/ns#"
href="<%=site.author.uri ?? '/'%>"
@ -37,14 +37,10 @@
href="/licenses/cc-by-sa/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.
</p>
<div class="actionBox">
<a href="#top" class="topLink">Back to Top</a>
</div>
<p>
Background image by <a href="https://www.pexels.com/photo/programming-427722/">EMIL Ivanov / PEXELS</a>, used under the <a href="http://creativecommons.org/publicdomain/zero/1.0/">CC0 Public Domain License</a>.
</p>
<a href="#top" class="topLink">Back to Top</a>
</div>
</footer>