Initial commit

This commit is contained in:
Eric Woodward 2023-02-20 11:46:46 -05:00
commit 3e53235230
148 changed files with 10284 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
out/
IDEAS.md
TODO.md

2
.prettierignore Normal file
View File

@ -0,0 +1,2 @@
*.ejs
*.min.js

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Douglas Matoso
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

30
app.js Normal file
View File

@ -0,0 +1,30 @@
#!/usr/bin/env node
"use strict";
require("dotenv").config();
const path = require("path"),
build = require("./lib/build"),
serve = require("./lib/serve"),
watch = require("./lib/watch"),
{ log, readJsonIfExists } = require("./lib/utils"),
{ version } = require("./package.json"),
localConfig =
readJsonIfExists(path.resolve(process.cwd(), "site.config.json5")) ||
{},
siteOpts = require("./lib/loadConfig")(localConfig, "MULE"),
config = {
...siteOpts,
logFunction: log,
},
yargs = require("yargs")
.version(version)
.alias("v", "version")
.usage("Usage: $0 start|stop")
.demandCommand(1),
{ argv } = yargs;
if (argv._[0] === "build") return build(config);
else if (argv._[0] === "serve") return serve(config);
else if (argv._[0] === "watch") return watch(config);

387
lib/build.js Normal file
View File

@ -0,0 +1,387 @@
const { exists } = require("fs-extra/lib/fs");
module.exports = async (config) => {
const { promises: fs } = require("fs"),
fse = require("fs-extra"),
path = require("path"),
ejs = require("ejs"),
frontMatter = require("front-matter"),
glob = require("glob"),
hljs = require("highlight.js"),
md = require("markdown-it")({
highlight: (str, lang) => {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(str, { language: lang }).value;
} catch (__) {}
}
return ""; // use external default escaping
},
html: true,
linkify: true,
typographer: true,
xhtmlOut: true,
})
.use(require("markdown-it-anchor").default)
.use(require("markdown-it-table-of-contents"), {
containerHeaderHtml:
'<div class="toc-container-header">Contents</div>',
includeLevel: [2, 3, 4],
})
.use(require("markdown-it-footnote"))
.use(require("markdown-it-multimd-table"), {
multiline: true,
rowspan: true,
headerless: true,
multibody: true,
aotolabel: true,
}),
emoji = require("markdown-it-emoji"),
// { readJsonIfExists } = require("./utils"),
{ build, isRebuild, logFunction: log = () => {} } = config || {},
{ outputPath, journalsPerPage = 5, srcPath } = build,
{ site } = config,
copyAssets = async (directory) => {
const assets = await fs.readdir(directory);
assets.forEach(async (asset) => {
// we no longer merge scripts and styles, thanks to http/2's parallel file handling
if (asset === "_root") {
fse.copy(path.join(srcPath, "assets", asset), outputPath);
} else {
fse.copy(
path.join(srcPath, "assets", asset),
path.join(outputPath, asset)
);
}
});
},
getReadTime = (text) => {
const WPM = 275,
fixedString = text.replace(/[^\w\s]+/g, ""),
count = fixedString.split(/\s+/).length;
if (count < WPM) return "less than 1 minute";
else return `${Math.ceil(count / WPM)} minutes`;
},
tagSorter = (a, b) => a.toLowerCase().localeCompare(b.toLowerCase()),
parseFile = (file, pagePath, siteData, isSupport) => {
const { dir, ext, name } = path.parse(file) || {},
hasExt = name.indexOf(".") > -1,
destPath = path.join(outputPath, dir),
filePath = path.join(pagePath, file),
// read page file
data = fse.readFileSync(filePath, "utf-8"),
// render page
{ attributes, body } = frontMatter(data),
{ content_type: contentType, tags: originalTags = [] } =
attributes,
// TODO: Look for tags in posts as well, link to them, and add them to tag pages
tags =
typeof originalTags === "string"
? originalTags.split(/\W+/)
: [].concat(originalTags),
innerTags = (
contentType === "journal"
? body.match(/\b#(\w+)/g) || []
: []
).map((val) => val.replace("#", "")),
allTags = [...tags, ...innerTags].sort(tagSorter),
updatedBody =
contentType === "journal"
? allTags.reduce(
(acc, tag) =>
acc.replace(
`#${tag}`,
`
<a href="/journal/tags/${tag}/index.html">
#<span class="p-category category">${tag}</span>
</a>`
),
body
)
: body;
return {
...config,
page: {
name,
...attributes,
body: updatedBody,
destPath,
filePath,
path: path.join(dir, hasExt ? name : `${name}.html`),
tags: [...tags, ...innerTags].sort(tagSorter),
ext,
},
site: {
...site,
pages: isSupport ? siteData : [],
},
};
},
parseContent = (page, siteData) => {
const {
body,
content_type: contentType,
filePath,
// tags,
} = page || {},
{ ext } = path.parse(filePath) || {},
{ pages, tags } = siteData || {};
let content = body,
readTime;
if (ext === ".md") {
if (contentType === "journal" && typeof body === "string") {
readTime = getReadTime(body);
}
content = md.render(body);
} else if (ext === ".ejs") {
content = ejs.render(
body,
{ page, site: { ...site, pages, tags } },
{ filename: filePath }
);
}
return { ...page, content, readTime };
},
renderFile = async (page, isSupport) => {
const {
content,
destPath,
layout,
path: pagePath,
pages,
siteTags,
tags,
} = page || {};
try {
const layoutFileName = `${srcPath}/layouts/${
layout || "default"
}.ejs`,
layoutData = await fs.readFile(layoutFileName, "utf-8"),
completePage = isSupport
? content
: ejs.render(layoutData, {
content,
page,
site: {
...site,
pages,
tags:
page.content_type === "journal"
? siteTags
: tags,
},
filename: layoutFileName,
});
if (!completePage) {
console.log("failed!", pagePath, content);
return;
}
// create destination directory
fse.mkdirsSync(destPath);
// save the html file
fse.writeFileSync(
path.join(outputPath, pagePath),
completePage
);
} catch (e) {
console.log("failed!", pagePath);
console.log("paths", destPath, outputPath);
console.error(e);
return;
}
};
md.use(emoji);
log(`${isRebuild ? "Reb" : "B"}uilding...`);
// clear destination folder
fse.emptyDirSync(outputPath);
// copy assets folder
await copyAssets(path.join(srcPath, "assets"));
const files = ["pages", "sitePosts"].reduce((acc, pageDir) => {
return [
...acc,
...glob
.sync("**/*.@(md|ejs|html)", {
cwd: path.join(srcPath, pageDir),
})
.map((file) =>
parseFile(file, path.join(srcPath, pageDir))
),
];
}, []),
sortByPubDate = (a, b) => {
if (a.date_pub && b.date_pub) {
let a_dt = new Date(a.date_pub).getTime(),
b_dt = new Date(b.date_pub).getTime();
if (a_dt < b_dt) {
return 1;
}
if (b_dt < a_dt) {
return -1;
}
return 0;
}
if (a.date_pub) return -1;
if (b.date_pub) return 1;
return 0;
},
pages = files
.map(({ page }) => ({ ...page }))
.filter(({ is_draft }) => !is_draft)
.sort(sortByPubDate),
tagCloud = pages.reduce((acc, curr) => {
const { tags } = curr;
tags.forEach((tag) => {
if (acc[tag]) acc[tag]++;
else acc[tag] = 1;
});
return acc;
}, {}),
tags = Object.keys(tagCloud).sort(tagSorter),
yearCloud = pages
.filter(({ content_type = "" }) => content_type === "journal")
.reduce((acc, curr) => {
const { date_pub } = curr;
if (date_pub) {
const year = new Date(date_pub).getFullYear();
if (acc[year]) acc[year]++;
else acc[year] = 1;
}
return acc;
}, {}),
years = Object.keys(yearCloud).sort().reverse(),
pagesWithContent = pages.map((page) =>
parseContent(page, { pages, tags })
);
// add data for the whole site to each page as it's rendered
pagesWithContent.forEach((page) => {
renderFile({ ...page, pages: pagesWithContent, siteTags: tags });
});
/* Journal Stuff - Tags & Years */
// make page(s) for each tag
tags.forEach((tag) => {
// check counts
let postCount = tagCloud[tag],
pageCount = Math.ceil(postCount / journalsPerPage);
for (let i = 1; i <= pageCount; i++) {
const firstEntryIndex = journalsPerPage * (i - 1),
lastEntryIndex = journalsPerPage * i;
renderFile({
content: tag,
destPath: path.join(outputPath, "journal", "tags", tag),
entriesToList: pagesWithContent
.filter(
(p) =>
p && Array.isArray(p.tags) && p.tags.includes(tag)
)
.slice(firstEntryIndex, lastEntryIndex),
layout: "tag",
path: `journal/tags/${tag}/${
i === 1 ? "index.html" : `page${i}.html`
}`,
site: { ...site, pages: pagesWithContent, tags },
pageCount,
pageNum: i,
pages: pagesWithContent,
tag,
tags,
title: `Journal Entries Tagged with #${tag}`,
});
}
});
// make page(s) for each year
years.forEach((year) => {
// check counts
let postCount = yearCloud[year],
pageCount = Math.ceil(postCount / journalsPerPage);
for (let i = 1; i <= pageCount; i++) {
const firstEntryIndex = journalsPerPage * (i - 1),
lastEntryIndex = journalsPerPage * i;
// TODO: rethink the data passed in here - you're paging solution works (kinda), take it over the finish line!
renderFile({
content: year,
destPath: path.join(outputPath, "journal", year),
entriesToList: pagesWithContent
.filter(({ content_type = "", date_pub = "" }) => {
if (!date_pub || content_type !== "journal")
return false;
const p_dt = new Date(date_pub).getTime(),
y1_dt = new Date(
`${year}-01-01T00:00:00-0500`
).getTime(),
y2_dt = new Date(
`${year}-12-31T23:59:59-0500`
).getTime();
return p_dt >= y1_dt && p_dt <= y2_dt;
})
.slice(firstEntryIndex, lastEntryIndex),
layout: "journal-year",
path: `journal/${year}/${
i === 1 ? "index.html" : `page${i}.html`
}`,
site: { ...site, pages: pagesWithContent, tags },
pageCount,
pageNum: i,
pages: pagesWithContent,
tags,
title: `Journal Entries from ${year}`,
year,
});
}
});
/* Support pages - anything too weird / specific for markdown rendering */
// collect support pages
const support = ["support"].reduce((acc, pageDir) => {
return [
...acc,
...glob
.sync("**/*.@(md|ejs|html)", {
cwd: path.join(srcPath, pageDir),
})
.map((file) =>
parseFile(
file,
path.join(srcPath, pageDir),
pagesWithContent,
true
)
),
];
}, []);
// write each one out
support.forEach((fileData) => {
const { page } = fileData;
if (page?.ext === ".ejs") {
const pageAndContent = parseContent(page, {
pages: pagesWithContent,
tags,
});
return renderFile({ ...fileData, ...pageAndContent, tags }, true);
}
return renderFile(fileData, true);
});
};

19
lib/defaults.json5 Normal file
View File

@ -0,0 +1,19 @@
{
/*
*/
/** 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,
},
}

43
lib/loadConfig.js Normal file
View File

@ -0,0 +1,43 @@
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;
}, {}),
};
};

26
lib/serve.js Normal file
View File

@ -0,0 +1,26 @@
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}`);
});
};

57
lib/utils.js Normal file
View File

@ -0,0 +1,57 @@
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;
}
},
};
})();

58
lib/watch.js Normal file
View File

@ -0,0 +1,58 @@
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}`);
});
};

1728
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

38
package.json Executable file
View File

@ -0,0 +1,38 @@
{
"name": "planar-vagabond",
"version": "0.9.1",
"description": "",
"main": "index.js",
"scripts": {
"build": "node app.js build",
"build:prod": "cross-env NODE_ENV=production node ./lib/build",
"serve": "node app.js serve",
"watch": "node app.js watch"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"chalk": "^4.1.2",
"chokidar": "^3.4.3",
"dotenv": "^16.0.1",
"highlight.js": "^11.3.1",
"json5": "^2.1.3",
"markdown-it": "^13.0.1",
"markdown-it-anchor": "^8.6.7",
"markdown-it-emoji": "^2.0.0",
"markdown-it-footnote": "^3.0.3",
"markdown-it-multimd-table": "^4.2.0",
"markdown-it-table-of-contents": "^0.6.0",
"network-address": "^1.1.2",
"serve-handler": "^6.1.3"
},
"devDependencies": {
"ejs": "^3.1.5",
"front-matter": "^4.0.2",
"fs-extra": "^10.0.0",
"glob": "^8.0.3",
"serve": "^14.1.2",
"yargs": "^17.3.1"
}
}

25
site.config.json5 Normal file
View File

@ -0,0 +1,25 @@
{
site: {
title: "The Planar Vagabond's Guide to the Multiverse",
author: {
name: "Eric Woodward",
email: "redacted@planarvagabond.com", // not used
photo: "/images/eric-8bit.gif",
site: "https://itsericwoodward.com",
},
base_uri: "",
robots: "index,follow",
language: "en-us",
copyright: "Copyright 2023 Eric Woodward, licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.",
basePath: "",
uri: "https://www.planarvagabond.com",
},
build: {
srcPath: "src",
outputPath: "out",
},
serve: {
port: 4997,
},
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/images/favicons/ms-icon-70x70.png"/><square150x150logo src="/images/favicons/ms-icon-150x150.png"/><square310x310logo src="/images/favicons/ms-icon-310x310.png"/><TileColor>#0d1852</TileColor></tile></msapplication></browserconfig>

Binary file not shown.

View File

@ -0,0 +1,348 @@
/*!
* jQLite JavaScript Library v1.1.1 (http://code.google.com/p/jqlite/)
* Copyright (c) 2010 Brett Fattori (bfattori@gmail.com)
* Licensed under the MIT license
* http://www.opensource.org/licenses/mit-license.php
*
* Many thanks to the jQuery team's efforts. Some code is
* Copyright (c) 2010, John Resig. See
* http://jquery.org/license
*
* @author Brett Fattori (bfattori@gmail.com)
* @author $Author: bfattori $
* @version $Revision: 145 $
*
* Created: 03/29/2010
* Modified: $Date: 2010-06-21 11:08:14 -0400 (Mon, 21 Jun 2010) $
*/
(function(){function B(){return+new Date}var D=function(a,b){if(a===""&&b)return b;var d=a.split(" "),c=d.shift(),e;if(c.charAt(0)=="#"){var g=i.getElementById(c.substring(1));e=g?[g]:[]}else{e=c.charAt(0)!=="."?c.split(".")[0]:"*";var h=c.split("."),j=null;if(e.indexOf("[")!=-1){j=e;e=e.substr(0,e.indexOf("["))}g=function(o){var n=arguments.callee,k;if(!(k=!n.needClass)){k=n.classes;if(o.className.length==0)k=false;else{for(var r=o.className.split(" "),l=k.length,p=0;p<k.length;p++)f.inArray(k[p],
r)!=-1&&l--;k=l==0}}if(k=k){if(!(k=!n.needAttribute)){n=n.attributes;k=true;for(r=0;r<n.length;r++){l=n[r].split("=");p=l[0].indexOf("!")!=-1||l[0].indexOf("*")!=-1?l[0].charAt(l[0].length-1)+"=":"=";if(p!="=")l[0]=l[0].substring(0,l[0].length-1);switch(p){case "=":k&=o.getAttribute(l[0])===l[1];break;case "!=":k&=o.getAttribute(l[0])!==l[1];break;case "*=":k&=o.getAttribute(l[0]).indexOf(l[1])!=-1;break;default:k=false}}k=k}k=k}if(k)return o};for(var u=[],s=0;s<b.length;s++)for(var C=b[s].getElementsByTagName(e),
v=0;v<C.length;v++)u.push(C[v]);h&&h.shift();e=[];g.classes=h;if(j!=null){var w=j.indexOf("[");s=j.lastIndexOf("]");w=j.substring(w+1,s).split("][")}g.attributes=j!=null?w:null;g.needClass=c.indexOf(".")!=-1&&h.length>0;g.needAttribute=j!=null;for(c=0;c<u.length;c++)g(u[c])&&e.push(u[c])}return D(d.join(" "),e)},Q=function(a,b){b=b||i;if(a.nodeType&&a.nodeType===E){a=i.body;if(a===null)return[i]}if(a.nodeType&&a.nodeType===m)return[a];if(a.jquery&&typeof a.jquery==="string")return a.toArray();if(b)b=
F(b);if(f.isArray(a))return a;else if(typeof a==="string"){for(var d=[],c=0;c<b.length;c++){var e=[b[c]];if(!f.forceSimpleSelectorEngine&&e[0].querySelectorAll){e=e[0].querySelectorAll(a);for(var g=0;g<e.length;g++)d.push(e.item(g))}else d=d.concat(D(a,e))}return d}else return null},G=false;setTimeout(function(){var a=i.body;if(a){var b=i.createElement("script"),d="i"+(new Date).getTime();b.type="text/javascript";try{b.appendChild(i.createTextNode("window."+d+"=1;"))}catch(c){}a.insertBefore(b,a.firstChild);
var e=true;if(window[d])delete window[d];else e=false;a.removeChild(b);G=e}else setTimeout(arguments.callee,33)},33);var H=function(a){var b=i.createElement("div");b.innerHTML=a;return{scripts:b.getElementsByTagName("script"),data:a}},I=function(a){a=a.replace(/-/g," ");a=a;var b=true;b=b||false;a=!a?"":a.toString().replace(/^\s*|\s*$/g,"");var d="";if(a.length<=0)a="";else{var c=false;d+=b?a.charAt(0):a.charAt(0).toUpperCase();for(b=1;b<a.length;b++){d+=c?a.charAt(b).toUpperCase():a.charAt(b).toLowerCase();
var e=a.charCodeAt(b);c=e==32||e==45||e==46;if(e==99||e==67)if(a.charCodeAt(b-1)==77||a.charCodeAt(b-1)==109)c=true}a=d}return a.replace(/ /g,"")},J={click:"MouseEvents",dblclick:"MouseEvents",mousedown:"MouseEvents",mouseup:"MouseEvents",mouseover:"MouseEvents",mousemove:"MouseEvents",mouseout:"MouseEvents",contextmenu:"MouseEvents",keypress:"KeyEvents",keydown:"KeyEvents",keyup:"KeyEvents",load:"HTMLEvents",unload:"HTMLEvents",abort:"HTMLEvents",error:"HTMLEvents",resize:"HTMLEvents",scroll:"HTMLEvents",
select:"HTMLEvents",change:"HTMLEvents",submit:"HTMLEvents",reset:"HTMLEvents",focus:"HTMLEvents",blur:"HTMLEvents",touchstart:"MouseEvents",touchend:"MouseEvents",touchmove:"MouseEvents"},K=function(a,b,d){if(f.isFunction(d)){if(typeof b==="string")b=b.toLowerCase();var c=J[b];if(b.indexOf("on")==0)b=b.substring(2);if(c){c=function(e){var g=arguments.callee,h=e.data||[];h.unshift(e);g=g.fn.apply(a,h);if(typeof g!="undefined"&&g===false){if(e.preventDefault&&e.stopPropagation){e.preventDefault();
e.stopPropagation()}else{e.returnValue=false;e.cancelBubble=true}return false}return true};c.fn=d;a.addEventListener?a.addEventListener(b,c,false):a.attachEvent("on"+b,c)}else{if(!a._handlers)a._handlers={};c=a._handlers[b]||[];c.push(d);a._handlers[b]=c}}},f=function(a,b){return(new x).init(a,b)},i=window.document,y=Object.prototype.hasOwnProperty,z=Object.prototype.toString,L=Array.prototype.push,R=Array.prototype.slice,m=1,E=9,A=[],M=false,N=false,q;f.forceSimpleSelectorEngine=false;f.each=function(a,
b){var d,c=0,e=a.length;if(e===undefined||f.isFunction(a))for(d in a){if(b.call(a[d],d,a[d])===false)break}else for(d=a[0];c<e&&b.call(d,c,d)!==false;d=a[++c]);return a};f.noop=function(){};f.isFunction=function(a){return z.call(a)==="[object Function]"};f.isArray=function(a){return z.call(a)==="[object Array]"};f.isPlainObject=function(a){if(!a||z.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!y.call(a,"constructor")&&!y.call(a.constructor.prototype,"isPrototypeOf"))return false;
var b;for(b in a);return b===undefined||y.call(a,b)};f.merge=function(a,b){var d=a.length,c=0;if(typeof b.length==="number")for(var e=b.length;c<e;c++)a[d++]=b[c];else for(;b[c]!==undefined;)a[d++]=b[c++];a.length=d;return a};f.param=function(a){var b="";a&&f.each(a,function(d,c){b+=(b.length!=0?"&":"")+c+"="+encodeURIComponent(d)});return b};f.evalScripts=function(a){for(var b=i.getElementsByTagName("head")[0]||i.documentElement,d=0;d<a.length;d++){var c=i.createElement("script");c.type="text/javascript";
if(G)c.appendChild(i.createTextNode(a[d].text));else c.text=a[d].text;b.insertBefore(c,b.firstChild);b.removeChild(c)}};f.ready=function(){for(M=true;A.length>0;)A.shift()()};var t="jQuery"+B(),S=0,O={};f.noData={embed:true,object:true,applet:true};f.cache={};f.data=function(a,b,d){if(!(a.nodeName&&jQuery.noData[a.nodeName.toLowerCase()])){a=a==window?O:a;var c=a[t];c||(c=a[t]=++S);if(b&&!jQuery.cache[c])jQuery.cache[c]={};if(d!==undefined)jQuery.cache[c][b]=d;return b?jQuery.cache[c][b]:c}};f.removeData=
function(a,b){a=a==window?O:a;var d=a[t];if(b){if(jQuery.cache[d]){delete jQuery.cache[d][b];b="";for(b in jQuery.cache[d])break;b||jQuery.removeData(a)}}else{try{delete a[t]}catch(c){a.removeAttribute&&a.removeAttribute(t)}delete jQuery.cache[d]}};f.ajax={status:-1,statusText:"",responseText:null,responseXML:null,send:function(a,b,d){if(f.isFunction(b)){d=b;b={}}if(a){var c=true,e=null,g=null;if(typeof b.async!=="undefined"){c=b.async;delete b.async}if(typeof b.username!=="undefined"){e=b.username;
delete b.username}if(typeof b.password!=="undefined"){g=b.password;delete b.password}b=f.param(b);if(b.length!=0)a+=(a.indexOf("?")==-1?"?":"&")+b;b=new XMLHttpRequest;b.open("GET",a,c,e,g);b.send();if(c){a=function(h){var j=arguments.callee;h.status==200?f.ajax.complete(h,j.cb):f.ajax.error(h,j.cb)};a.cb=d;d=function(){var h=arguments.callee;h.req.readyState!=4?setTimeout(h,250):h.xcb(h.req)};d.req=b;d.xcb=a;setTimeout(d,250)}}},complete:function(a,b){f.ajax.status=a.status;f.ajax.responseText=a.responseText;
f.ajax.responseXML=a.responseXML;f.isFunction(b)&&b(a.responseText,a.status)},error:function(a,b){f.ajax.status=a.status;f.ajax.statusText=a.statusText;f.isFunction(b)&&b(a.status,a.statusText)}};f.makeArray=function(a,b){var d=b||[];if(a!=null)a.length==null||typeof a==="string"||jQuery.isFunction(a)||typeof a!=="function"&&a.setInterval?L.call(d,a):f.merge(d,a);return d};f.inArray=function(a,b){for(var d=0;d<b.length;d++)if(b[d]===a)return d;return-1};f.trim=function(a){return a!=null?a.toString().replace(/^\s*|\s*$/g,
""):""};var x=function(){};x.prototype={selector:"",context:null,length:0,jquery:"jqlite-1.1.1",init:function(a,b){if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1}else if(typeof a==="function")this.ready(a);else{var d=[];if(a.jquery&&typeof a.jquery==="string")d=a.toArray();else if(f.isArray(a))d=a;else if(typeof a==="string"&&f.trim(a).indexOf("<")==0&&f.trim(a).indexOf(">")!=-1){d=f.trim(a).toLowerCase();d=d.indexOf("<option")==0?"SELECT":d.indexOf("<li")==0?"UL":d.indexOf("<tr")==
0?"TBODY":d.indexOf("<td")==0?"TR":"DIV";d=i.createElement(d);d.innerHTML=a;d=[d.removeChild(d.firstChild)]}else{if(a.indexOf(",")!=-1){d=a.split(",");for(var c=0;c<d.length;c++)d[c]=f.trim(d[c])}else d=[a];c=[];for(var e=0;e<d.length;e++)c=c.concat(Q(d[e],b));d=c}L.apply(this,d)}return this},each:function(a){return f.each(this,a)},size:function(){return this.length},toArray:function(){return R.call(this,0)},ready:function(a){if(M)a();else{A.push(a);return this}},data:function(a,b){if(typeof a===
"undefined"&&this.length)return jQuery.data(this[0]);else if(typeof a==="object")return this.each(function(){jQuery.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===undefined){if(data===undefined&&this.length)data=jQuery.data(this[0],a);return data===undefined&&d[1]?this.data(d[0]):data}else return this.each(function(){jQuery.data(this,a,b)})},removeData:function(a){return this.each(function(){jQuery.removeData(this,a)})},addClass:function(a){return this.each(function(){if(this.className.length!=
0){var b=this.className.split(" ");if(f.inArray(a,b)==-1){b.push(a);this.className=b.join(" ")}}else this.className=a})},removeClass:function(a){return this.each(function(){if(this.className.length!=0){var b=this.className.split(" "),d=f.inArray(a,b);if(d!=-1){b.splice(d,1);this.className=b.join(" ")}}})},hasClass:function(a){if(this[0].className.length==0)return false;return f.inArray(a,this[0].className.split(" "))!=-1},isElementName:function(a){return this[0].nodeName.toLowerCase()===a.toLowerCase()},
toggleClass:function(a){return this.each(function(){if(this.className.length==0)this.className=a;else{var b=this.className.split(" "),d=f.inArray(a,b);d!=-1?b.splice(d,1):b.push(a);this.className=b.join(" ")}})},hide:function(a){return this.each(function(){if(this.style&&this.style.display!=null)if(this.style.display.toString()!="none"){this._oldDisplay=this.style.display.toString()||(this.nodeName!="span"?"block":"inline");this.style.display="none"}f.isFunction(a)&&a(this)})},show:function(a){return this.each(function(){this.style.display=
(this._oldDisplay&&this._oldDisplay!=""?this._oldDisplay:null)||(this.nodeName!="span"?"block":"inline");f.isFunction(a)&&a(this)})},css:function(a,b){if(typeof a==="string"&&b==null)return this[0].style[I(a)];else{a=typeof a==="string"?P(a,b):a;return this.each(function(){var d=this;typeof d.style!="undefined"&&f.each(a,function(c,e){e=typeof e==="number"?e+"px":e;var g=I(c);d.style[g]||(g=c);d.style[g]=e})})}},load:function(a,b,d){if(f.isFunction(b)){d=b;b={}}return this.each(function(){var c=function(e,
g){var h=arguments.callee;if(e){var j=H(e);h.elem.innerHTML=j.data;f.evalScripts(j.scripts)}f.isFunction(h.cback)&&h.cback(e,g)};c.cback=d;c.elem=this;f.ajax.send(a,b,c)})},html:function(a){return a?this.each(function(){var b=H(a);this.innerHTML=b.data;f.evalScripts(b.scripts)}):this[0].innerHTML},attr:function(a,b){return typeof a==="string"&&b==null?this[0]?this[0].getAttribute(a):"":this.each(function(){a=typeof a==="string"?P(a,b):a;for(var d in a)this.setAttribute(d,a[d])})},eq:function(a){var b=
this.toArray();this.context=this[0]=a<0?b[b.length+a]:b[a];this.length=1;return this},first:function(){this.context=this[0]=this.toArray()[0];this.length=1;return this},last:function(){var a=this.toArray();this.context=this[0]=a[a.length-1];this.length=1;return this},index:function(a){var b=-1;if(this.length!=0){var d=this[0];if(a){var c=f(a)[0];this.each(function(g){if(this===c){b=g;return false}})}else{a=this.parent()[0].firstChild;for(var e=[];a!=null;){a.nodeType===m&&e.push(a);a=a.nextSibling}f.each(a,
function(g){if(this===d){b=g;return false}})}}return b},next:function(a){var b=[];if(a){var d=f(a);this.each(function(){for(var c=this.nextSibling;c!=null&&c.nodeType!==m;)c=c.nextSibling;if(c!=null){var e=false;d.each(function(){if(this==c){e=true;return false}});e&&b.push(c)}})}else this.each(function(){for(var c=this.nextSibling;c!=null&&c.nodeType!==m;)c=c.nextSibling;c!=null&&b.push(c)});return f(b)},prev:function(a){var b=[];if(a){var d=f(a);this.each(function(){for(var c=this.previousSibling;c!=
null&&c.nodeType!==m;)c=c.previousSibling;if(c!=null){var e=false;d.each(function(){if(this==c){e=true;return false}});e&&b.push(c)}})}else this.each(function(){for(var c=this.previousSibling;c!=null&&c.nodeType!==m;)c=c.previousSibling;c!=null&&b.push(c)});return f(b)},parent:function(a){var b=[];if(a){var d=f(a);this.each(function(){var c=this.parentNode,e=false;d.each(function(){if(this==c){e=true;return false}});e&&b.push(c)})}else this.each(function(){b.push(this.parentNode)});return f(b)},parents:function(a){var b=
[];if(a){var d=f(a);this.each(function(){for(var c=this;c!=i.body;){d.each(function(){this==c&&b.push(c)});c=c.parentNode}})}else this.each(function(){for(var c=this;c!=i.body;){c=c.parentNode;b.push(c)}});return f(b)},children:function(a){var b=[];if(a){var d=f(a);this.each(function(){for(var c=this.firstChild;c!=null;){c.nodeType==m&&d.each(function(){this===c&&b.push(c)});c=c.nextSibling}})}else this.each(function(){for(var c=this.firstChild;c!=null;){c.nodeType==m&&b.push(c);c=c.nextSibling}});
return f(b)},append:function(a){a=F(a);return this.each(function(){for(var b=0;b<a.length;b++)this.appendChild(a[b])})},remove:function(a){return this.each(function(){a?$(a,this).remove():this.parentNode.removeChild(this)})},empty:function(){return this.each(function(){this.innerHTML=""})},val:function(a){if(a==null){var b=null;if(this&&this.length!=0&&typeof this[0].value!="undefined")b=this[0].value;return b}else return this.each(function(){if(typeof this.value!="undefined")this.value=a})},bind:function(a,
b){return this.each(function(){K(this,a,b)})},trigger:function(a,b){return this.each(function(){var d;var c;c=a;if(typeof c==="string")c=c.toLowerCase();var e=null,g=J[c]||"Event";if(i.createEvent){e=i.createEvent(g);e._eventClass=g;c&&e.initEvent(c,true,true)}if(i.createEventObject){e=i.createEventObject();if(c){e.type=c;e._eventClass=g}}c=e;if(c._eventClass!=="Event"){c.data=b;d=this.dispatchEvent(c)}else if(e=(this._handlers||{})[a])for(g=0;g<e.length;g++){var h=f.isArray(b)?b:[];h.unshift(c);
h=e[g].apply(this,h);if(!(typeof h=="undefined"?true:h))break}return d})},submit:function(a){return this.each(function(){if(f.isFunction(a))K(this,"onsubmit",a);else this.submit&&this.submit()})}};if(i.addEventListener)q=function(){i.removeEventListener("DOMContentLoaded",q,false);f.ready()};else if(i.attachEvent)q=function(){if(i.readyState==="complete"){i.detachEvent("onreadystatechange",q);f.ready()}};if(!N){N=true;if(i.readyState==="complete")return f.ready();if(i.addEventListener){i.addEventListener("DOMContentLoaded",
q,false);window.addEventListener("load",f.ready,false)}else if(i.attachEvent){i.attachEvent("onreadystatechange",q);window.attachEvent("onload",f.ready)}}var P=function(a,b){var d={};d[a]=b;return d},F=function(a){if(a.nodeType&&(a.nodeType===m||a.nodeType===E))a=[a];else if(typeof a==="string")a=f(a).toArray();else if(a.jquery&&typeof a.jquery==="string")a=a.toArray();return a};if(typeof window.jQuery=="undefined"){window.jQuery=f;window.jQuery.fn=x.prototype;window.$=window.jQuery;window.now=B}jQuery.extend=
jQuery.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,c=false,e,g,h,j;if(typeof a==="boolean"){c=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!jQuery.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(g in e){h=a[g];j=e[g];if(a!==j)if(c&&j&&(jQuery.isPlainObject(j)||jQuery.isArray(j))){h=h&&(jQuery.isPlainObject(h)||jQuery.isArray(h))?h:jQuery.isArray(j)?[]:{};a[g]=jQuery.extend(c,h,j)}else if(j!==undefined)a[g]=j}return a};jQuery.each("click,dblclick,mouseover,mouseout,mousedown,mouseup,keydown,keypress,keyup,focus,blur,change,select,error,load,unload,scroll,resize,touchstart,touchend,touchmove".split(","),
function(a,b){jQuery.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)}})})();
(function() {
this.Dosbox = (function() {
function Dosbox(options) {
this.onload = options.onload;
this.onrun = options.onrun;
this.ui = new Dosbox.UI(options);
this.module = new Dosbox.Module({
canvas: this.ui.canvas
});
this.ui.onStart((function(_this) {
return function() {
_this.ui.showLoader();
return _this.downloadScript();
};
})(this));
}
Dosbox.prototype.run = function(archiveUrl, executable) {
return new Dosbox.Mount(this.module, archiveUrl, {
success: (function(_this) {
return function() {
var func, hide;
_this.ui.updateMessage("Launching " + executable);
hide = function() {
return _this.ui.hideLoader();
};
func = function() {
return _this._dosbox_main(_this, executable);
};
setTimeout(func, 1000);
return setTimeout(hide, 3000);
};
})(this),
progress: (function(_this) {
return function(total, current) {
return _this.ui.updateMessage("Mount " + executable + " (" + (current * 100 / total | 0) + "%)");
};
})(this)
});
};
Dosbox.prototype.requestFullScreen = function() {
if (this.module.requestFullScreen) {
return this.module.requestFullScreen(true, false);
}
};
Dosbox.prototype.downloadScript = function() {
this.module.setStatus('Downloading js-dos');
this.ui.updateMessage('Downloading js-dos');
return new Dosbox.Xhr('https://js-dos.com/cdn/js-dos-v3.js', {
success: (function(_this) {
return function(script) {
var func;
_this.ui.updateMessage('Initializing dosbox');
func = function() {
return _this._jsdos_init(_this.module, script, _this.onload);
};
return setTimeout(func, 1000);
};
})(this),
progress: (function(_this) {
return function(total, current) {
return _this.ui.updateMessage("Downloading js-dos (" + (current * 100 / total | 0) + "%)");
};
})(this)
});
};
Dosbox.prototype._jsdos_init = function(module, script, onload) {
var Module;
Module = module;
eval(script);
if (onload) {
return onload(this);
}
};
Dosbox.prototype._dosbox_main = function(dosbox, executable) {
var exception, func;
try {
if (dosbox.onrun) {
func = function() {
return dosbox.onrun(dosbox, executable);
};
setTimeout(func, 1000);
}
return dosbox.module.ccall('dosbox_main', 'int', ['string'], [executable]);
} catch (error) {
exception = error;
if (exception === 'SimulateInfiniteLoop') {
} else {
return typeof console !== "undefined" && console !== null ? typeof console.error === "function" ? console.error(exception) : void 0 : void 0;
}
}
};
return Dosbox;
})();
}).call(this);
(function() {
Dosbox.Module = (function() {
function Module(options) {
this.elCanvas = options.canvas;
this.canvas = this.elCanvas[0];
}
Module.prototype.preRun = [];
Module.prototype.postRun = [];
Module.prototype.totalDependencies = 0;
Module.prototype.print = function(text) {
text = Array.prototype.slice.call(arguments).join(' ');
return typeof console !== "undefined" && console !== null ? typeof console.log === "function" ? console.log(text) : void 0 : void 0;
};
Module.prototype.printErr = function(text) {
text = Array.prototype.slice.call(arguments).join(' ');
return typeof console !== "undefined" && console !== null ? typeof console.error === "function" ? console.error(text) : void 0 : void 0;
};
Module.prototype.setStatus = function(text) {
return typeof console !== "undefined" && console !== null ? typeof console.log === "function" ? console.log(text) : void 0 : void 0;
};
Module.prototype.monitorRunDependencies = function(left) {
var status;
this.totalDependencies = Math.max(this.totalDependencies, left);
status = left ? "Preparing... (" + (this.totalDependencies - left) + "/" + this.totalDependencies + ")" : 'All downloads complete.';
return this.setStatus(status);
};
return Module;
})();
}).call(this);
(function() {
Dosbox.Mount = (function() {
function Mount(module, url, options) {
this.module = module;
new Dosbox.Xhr(url, {
success: (function(_this) {
return function(data) {
var bytes;
bytes = _this._toArray(data);
if (_this._mountZip(bytes)) {
return options.success();
} else {
return typeof console !== "undefined" && console !== null ? typeof console.error === "function" ? console.error('Unable to mount', url) : void 0 : void 0;
}
};
})(this),
progress: options.progress
});
}
Mount.prototype._mountZip = function(bytes) {
var buffer, extracted;
buffer = this.module._malloc(bytes.length);
this.module.HEAPU8.set(bytes, buffer);
extracted = this.module.ccall('extract_zip', 'int', ['number', 'number'], [buffer, bytes.length]);
this.module._free(buffer);
return extracted === 0;
};
Mount.prototype._toArray = function(data) {
var arr, i, len;
if (typeof data === 'string') {
arr = new Array(data.length);
i = 0;
len = data.length;
while (i < len) {
arr[i] = data.charCodeAt(i);
++i;
}
return arr;
}
return data;
};
return Mount;
})();
}).call(this);
(function() {
Dosbox.UI = (function() {
function UI(options) {
this.appendCss();
this.div = $('#' + (options.id || 'dosbox'));
this.wrapper = $('<div class="dosbox-container">');
this.canvas = $('<canvas class="dosbox-canvas" oncontextmenu="event.preventDefault()">');
this.overlay = $('<div class="dosbox-overlay">');
this.loaderMessage = $('<div class="dosbox-loader-message">');
this.loader = $('<div class="dosbox-loader">').append($('<div class="st-loader">').append($('<span class="equal">'))).append(this.loaderMessage);
this.start = $('<div class="dosbox-start">Click to start');
this.div.append(this.wrapper);
this.wrapper.append(this.canvas);
this.wrapper.append(this.loader);
this.wrapper.append(this.overlay);
this.overlay.append($('<div class="dosbox-powered">Powered by &nbsp;').append($('<a href="http://js-dos.com">js-dos.com')));
this.overlay.append(this.start);
}
UI.prototype.onStart = function(fun) {
return this.start.click((function(_this) {
return function() {
fun();
return _this.overlay.hide();
};
})(this));
};
UI.prototype.appendCss = function() {
var head, style;
head = document.head || document.getElementsByTagName('head')[0];
style = document.createElement('style');
style.type = 'text/css';
if (style.styleSheet) {
style.styleSheet.cssText = this.css;
} else {
style.appendChild(document.createTextNode(this.css));
}
return head.appendChild(style);
};
UI.prototype.showLoader = function() {
this.loader.show();
return this.loaderMessage.html('');
};
UI.prototype.updateMessage = function(message) {
return this.loaderMessage.html(message);
};
UI.prototype.hideLoader = function() {
return this.loader.hide();
};
UI.prototype.css = '.dosbox-container { position: relative; min-width: 320px; min-height: 200px; } .dosbox-canvas { } .dosbox-overlay, .dosbox-loader { position: absolute; left: 0; right: 0; top: 0; bottom: 0; background-color: #333; } .dosbox-start { text-align: center; position: absolute; left: 0; right: 0; bottom: 50%; color: #f80; font-size: 1.5em; text-decoration: underline; cursor: pointer; } .dosbox-overlay a { color: #f80; } .dosbox-loader { display: none; } .dosbox-powered { position: absolute; right: 1em; bottom: 1em; font-size: 0.8em; color: #9C9C9C; } .dosbox-loader-message { text-align: center; position: absolute; left: 0; right: 0; bottom: 50%; margin: 0 0 -3em 0; box-sizing: border-box; color: #f80; font-size: 1.5em; } @-moz-keyframes loading { 0% { left: 0; } 50% { left: 8.33333em; } 100% { left: 0; } } @-webkit-keyframes loading { 0% { left: 0; } 50% { left: 8.33333em; } 100% { left: 0; } } @keyframes loading { 0% { left: 0; } 50% { left: 8.33333em; } 100% { left: 0; } } .st-loader { width: 10em; height: 2.5em; position: absolute; top: 50%; left: 50%; margin: -1.25em 0 0 -5em; box-sizing: border-box; } .st-loader:before, .st-loader:after { content: ""; display: block; position: absolute; top: 0; bottom: 0; width: 1.25em; box-sizing: border-box; border: 0.25em solid #f80; } .st-loader:before { left: -0.76923em; border-right: 0; } .st-loader:after { right: -0.76923em; border-left: 0; } .st-loader .equal { display: block; position: absolute; top: 50%; margin-top: -0.5em; left: 4.16667em; height: 1em; width: 1.66667em; border: 0.25em solid #f80; box-sizing: border-box; border-width: 0.25em 0; -moz-animation: loading 1.5s infinite ease-in-out; -webkit-animation: loading 1.5s infinite ease-in-out; animation: loading 1.5s infinite ease-in-out; }';
return UI;
})();
}).call(this);
(function() {
Dosbox.Xhr = (function() {
function Xhr(url, options) {
var e;
this.success = options.success;
this.progress = options.progress;
if (window.ActiveXObject) {
try {
this.xhr = new ActiveXObject('Microsoft.XMLHTTP');
} catch (error) {
e = error;
this.xhr = null;
}
} else {
this.xhr = new XMLHttpRequest();
}
this.xhr.open('GET', url, true);
this.xhr.overrideMimeType('text/plain; charset=x-user-defined');
this.xhr.addEventListener('progress', (function(_this) {
return function(evt) {
if (_this.progress) {
return _this.progress(evt.total, evt.loaded);
}
};
})(this));
this.xhr.onreadystatechange = (function(_this) {
return function() {
return _this._onReadyStateChange();
};
})(this);
this.xhr.send();
}
Xhr.prototype._onReadyStateChange = function() {
if (this.xhr.readyState === 4 && this.success) {
return this.success(this.xhr.responseText);
}
};
return Xhr;
})();
}).call(this);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,41 @@
{
"name": "Its Eric Woodward! (dotcom)",
"icons": [
{
"src": "\/images\/favicons\/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "\/images\/favicons\/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "\/images\/favicons\/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "\/images\/favicons\/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "\/images\/favicons\/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "\/images\/favicons\/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 KiB

9
src/assets/scripts/1-docready.min.js vendored Normal file
View File

@ -0,0 +1,9 @@
// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT
/*
docready.js
https://github.com/jfriend00/docReady
The MIT License (MIT)
Copyright (c) 2014 John Friend
*/
!(function(t,e){"use strict";function n(){if(!a){a=!0;for(var t=0;t<o.length;t+=1){o[t].fn.call(window,o[t].ctx)}o=[]}}function d(){"complete"===document.readyState&&n()}(t=t||"docReady"),(e=e||window);var o=[],a=!1,c=!1;e[t]=function(t,e){return a?void setTimeout(function(){t(e)},1):(o.push({fn:t,ctx:e}),void("complete"===document.readyState||(!document.attachEvent&&"interactive"===document.readyState)?setTimeout(n,1):c||(document.addEventListener?(document.addEventListener("DOMContentLoaded",n,!1),window.addEventListener("load",n,!1)):(document.attachEvent("onreadystatechange",d),window.attachEvent("onload",n)),(c=!0))))}})("docReady",window);
// @license-end

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,9 @@
// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT
/*
lazy-progressive-enhancement.js v1.0.0
https://github.com/tvler/lazy-progressive-enhancement
The MIT License (MIT)
Copyright (c) 2016 Tyler Deitz
*/
function loadMedia(t,e,n){"use strict";function r(t){if(t==null){t=document.querySelectorAll("noscript")}else if(t instanceof Element){t=[t]}else if(typeof t==="string"){t=document.querySelectorAll(t)}return t}function i(t,e){return window.setInterval(function(){var n=t.getBoundingClientRect(),r=150;if(n.bottom>= -r&&n.top-window.innerHeight<r&&n.right>= -r&&n.left-window.innerWidth<r){window.clearInterval(t.getAttribute("data-intervalid"));t.srcset=t.getAttribute("data-srcset");t.src=t.getAttribute("data-src");e&&e()}},100)}function a(t){var a,A,l="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";for(var d=0;d<(t=r(t)).length;d+=1){A=new DOMParser().parseFromString((a=t[d]).textContent,"text/html").body.firstElementChild;if(n){A.setAttribute("data-src",A.getAttribute("src"));A.setAttribute("data-srcset",A.getAttribute("srcset")||"");A.src=A.srcset=l;a.parentElement.replaceChild(A,a);A.setAttribute("data-intervalid",i(A,e&&e.bind(A)))}else{A.onload=e;a.parentElement.replaceChild(A,a)}}}var A=function(){a(t)};if(document.readyState!=="loading"){A()}else{document.addEventListener("DOMContentLoaded",A)}}
// @license-end

9
src/assets/scripts/4-js.cookie.min.js vendored Normal file
View File

@ -0,0 +1,9 @@
// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT
/*
js-cookie v3.0.1
https://github.com/js-cookie/js-cookie
Copyright (c) 2018 Copyright 2018 Klaus Hartl, Fagner Brack, GitHub Contributors
Released under the MIT license
*/
!(function(e,t){"object"==typeof exports&&"undefined"!=typeof module?(module.exports=t()):"function"==typeof define&&define.amd?define(t):((e=e||self),(function(){var n=e.Cookies,o=(e.Cookies=t());o.noConflict=function(){return(e.Cookies=n),o}})())})(this,function(){"use strict";function e(e){for(var t=1;t<arguments.length;t+=1){var n=arguments[t];for(var o in n){e[o]=n[o]}}return e}return(function t(n,o){function r(t,r,i){if("undefined"!=typeof document){"number"==typeof(i=e({},o,i)).expires&&(i.expires=new Date(Date.now()+864e5*i.expires)),i.expires&&(i.expires=i.expires.toUTCString()),(t=encodeURIComponent(t).replace(/%(2[346B]|5E|60|7C)/g,decodeURIComponent).replace(/[()]/g,escape));var c="";for(var u in i){i[u]&&((c+="; "+u),!0!==i[u]&&(c+="="+i[u].split(";")[0]))}return(document.cookie=t+"="+n.write(r,t)+c)}}return Object.create({set:r,get:function(e){if("undefined"!=typeof document&&(!arguments.length||e)){for(var t=document.cookie?document.cookie.split("; "):[],o={},r=0;r<t.length;r+=1){var i=t[r].split("="),c=i.slice(1).join("=");try{var u=decodeURIComponent(i[0]);if(((o[u]=n.read(c,u)),e===u)){break}}catch(e){}}return e?o[e]:o}},remove:function(t,n){r(t,"",e({},n,{expires:-1}))},withAttributes:function(n){return t(this.converter,e({},this.attributes,n))},withConverter:function(n){return t(e({},this.converter,n),this.attributes)}},{attributes:{value:Object.freeze(o)},converter:{value:Object.freeze(n)}})})({read:function(e){return('"'===e[0]&&(e=e.slice(1,-1)),e.replace(/(%[\dA-F]{2})+/gi,decodeURIComponent))},write:function(e){return encodeURIComponent(e).replace(/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,decodeURIComponent)}},{path:"/"})});
// @license-end

View File

@ -0,0 +1,10 @@
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
/*
Font Face Observer v2.3.0
https://github.com/bramstein/fontfaceobserver
The BSD 3-Clause License
Copyright Bram Stein. All rights reserved.
*/
(function(){"use strict";var f,g=[];function l(a){g.push(a);1==g.length&&f()}function m(){for(;g.length;){g[0](),g.shift()}}f=function(){setTimeout(m)};function n(a){this.a=p;this.b=void 0;this.f=[];var b=this;try{a(function(a){q(b,a)},function(a){r(b,a)})}catch(c){r(b,c)}}var p=2;function t(a){return new n(function(b,c){c(a)})}function u(a){return new n(function(b){b(a)})}function q(a,b){if(a.a==p){if(b==a){throw new TypeError()}var c=!1;try{var d=b&&b.then;if(null!=b&&"object"==typeof b&&"function"==typeof d){d.call(b,function(b){c||q(a,b);c=!0},function(b){c||r(a,b);c=!0});return}}catch(e){c||r(a,e);return}a.a=0;a.b=b;v(a)}}function r(a,b){if(a.a==p){if(b==a){throw new TypeError()}a.a=1;a.b=b;v(a)}}function v(a){l(function(){if(a.a!=p){for(;a.f.length;){var b=a.f.shift(),c=b[0],d=b[1],e=b[2],b=b[3];try{0==a.a?"function"==typeof c?e(c.call(void 0,a.b)):e(a.b):1==a.a&&("function"==typeof d?e(d.call(void 0,a.b)):b(a.b))}catch(h){b(h)}}}})}n.prototype.g=function(a){return this.c(void 0,a)};n.prototype.c=function(a,b){var c=this;return new n(function(d,e){c.f.push([a,b,d,e]);v(c)})};function w(a){return new n(function(b,c){function d(c){return function(d){h[c]=d;e+=1;e==a.length&&b(h)}}var e=0,h=[];0==a.length&&b(h);for(var k=0;k<a.length;k+=1){u(a[k]).c(d(k),c)}})}function x(a){return new n(function(b,c){for(var d=0;d<a.length;d+=1){u(a[d]).c(b,c)}})}window.Promise||((window.Promise=n),(window.Promise.resolve=u),(window.Promise.reject=t),(window.Promise.race=x),(window.Promise.all=w),(window.Promise.prototype.then=n.prototype.c),(window.Promise.prototype["catch"]=n.prototype.g))})();
(function(){function p(a,c){document.addEventListener?a.addEventListener("scroll",c,!1):a.attachEvent("scroll",c)}function u(a){document.body?a():document.addEventListener?document.addEventListener("DOMContentLoaded",function b(){document.removeEventListener("DOMContentLoaded",b);a()}):document.attachEvent("onreadystatechange",function g(){if("interactive"==document.readyState||"complete"==document.readyState){document.detachEvent("onreadystatechange",g),a()}})}function w(a){this.g=document.createElement("div");this.g.setAttribute("aria-hidden","true");this.g.appendChild(document.createTextNode(a));this.h=document.createElement("span");this.i=document.createElement("span");this.m=document.createElement("span");this.j=document.createElement("span");this.l=-1;this.h.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";this.i.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";this.j.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";this.m.style.cssText="display:inline-block;width:200%;height:200%;font-size:16px;max-width:none;";this.h.appendChild(this.m);this.i.appendChild(this.j);this.g.appendChild(this.h);this.g.appendChild(this.i)}function x(a,c){a.g.style.cssText="max-width:none;min-width:20px;min-height:20px;display:inline-block;overflow:hidden;position:absolute;width:auto;margin:0;padding:0;top:-999px;white-space:nowrap;font-synthesis:none;font:"+c+";"}function B(a){var c=a.g.offsetWidth,b=c+100;a.j.style.width=b+"px";a.i.scrollLeft=b;a.h.scrollLeft=a.h.scrollWidth+100;return a.l!==c?((a.l=c),!0):!1}function C(a,c){function b(){var e=g;B(e)&&null!==e.g.parentNode&&c(e.l)}var g=a;p(a.h,b);p(a.i,b);B(a)}function D(a,c,b){c=c||{};b=b||window;this.family=a;this.style=c.style||"normal";this.weight=c.weight||"normal";this.stretch=c.stretch||"normal";this.context=b}var E=null,F=null,G=null,H=null;function I(a){null===F&&(M(a)&&/Apple/.test(window.navigator.vendor)?((a=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))(?:\.([0-9]+))/.exec(window.navigator.userAgent)),(F=!!a&&603>parseInt(a[1],10))):(F=!1));return F}function M(a){null===H&&(H=!!a.document.fonts);return H}function N(a,c){var b=a.style,g=a.weight;if(null===G){var e=document.createElement("div");try{e.style.font="condensed 100px sans-serif"}catch(q){}G=""!==e.style.font}return[b,g,G?a.stretch:"","100px",c].join(" ")}D.prototype.load=function(a,c){var b=this,g=a||"BESbswy",e=0,q=c||3e3,J=new Date().getTime();return new Promise(function(K,L){if(M(b.context)&&!I(b.context)){var O=new Promise(function(r,t){function h(){new Date().getTime()-J>=q?t(Error(""+q+"ms timeout exceeded")):b.context.document.fonts.load(N(b,'"'+b.family+'"'),g).then(function(n){1<=n.length?r():setTimeout(h,25)},t)}h()}),P=new Promise(function(r,t){e=setTimeout(function(){t(Error(""+q+"ms timeout exceeded"))},q)});Promise.race([P,O]).then(function(){clearTimeout(e);K(b)},L)}else{u(function(){function r(){var d;if((d=(-1!=k&&-1!=l)||(-1!=k&&-1!=m)||(-1!=l&&-1!=m))){(d=k!=l&&k!=m&&l!=m)||(null===E&&((d=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(window.navigator.userAgent)),(E=!!d&&(536>parseInt(d[1],10)||(536===parseInt(d[1],10)&&11>=parseInt(d[2],10))))),(d=E&&((k==y&&l==y&&m==y)||(k==z&&l==z&&m==z)||(k==A&&l==A&&m==A)))),(d=!d)}d&&(null!==f.parentNode&&f.parentNode.removeChild(f),clearTimeout(e),K(b))}function t(){if(new Date().getTime()-J>=q){null!==f.parentNode&&f.parentNode.removeChild(f),L(Error(""+q+"ms timeout exceeded"))}else{var d=b.context.document.hidden;if(!0===d||void 0===d){(k=h.g.offsetWidth),(l=n.g.offsetWidth),(m=v.g.offsetWidth),r()}e=setTimeout(t,50)}}var h=new w(g),n=new w(g),v=new w(g),k=-1,l=-1,m=-1,y=-1,z=-1,A=-1,f=document.createElement("div");f.dir="ltr";x(h,N(b,"sans-serif"));x(n,N(b,"serif"));x(v,N(b,"monospace"));f.appendChild(h.g);f.appendChild(n.g);f.appendChild(v.g);b.context.document.body.appendChild(f);y=h.g.offsetWidth;z=n.g.offsetWidth;A=v.g.offsetWidth;t();C(h,function(d){k=d;r()});x(h,N(b,'"'+b.family+'",sans-serif'));C(n,function(d){l=d;r()});x(n,N(b,'"'+b.family+'",serif'));C(v,function(d){m=d;r()});x(v,N(b,'"'+b.family+'",monospace'))})}})};"object"===typeof module?(module.exports=D):((window.FontFaceObserver=D),(window.FontFaceObserver.prototype.load=D.prototype.load))})();
// @license-end

9
src/assets/scripts/6-classlist.min.js vendored Normal file
View File

@ -0,0 +1,9 @@
// @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

10
src/assets/scripts/7-dayjs.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,80 @@
// @license magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt CC0-1.0
/****************************************************************************
* It's Eric Woodward's Site
*
* Copyright 2014-2022 Eric Woodward
* Source released under CC0 Public Domain License v1.0
* http://creativecommons.org/publicdomain/zero/1.0/
****************************************************************************/
(function () {
"use strict";
// Checking if browser "cuts the mustard" - https://gomakethings.com/ditching-jquery/
if (!(!!document.querySelector && !!window.addEventListener)) return;
if (window.dayjs) dayjs.extend(window.dayjs_plugin_relativeTime);
// Indicate JS is loaded
document.documentElement.className =
document.documentElement.className.replace("no-js", "js");
// Enable cached fonts ASAP
if (window.Cookies && !!Cookies.get("fonts_loaded")) {
document.documentElement.className += " js-hasFontsLoaded";
}
docReady(function () {
setTimeout(function () {
if (!window.Cookies || !window.FontFaceObserver) return;
// Handle Fonts
const font_ls = new FontFaceObserver("liberation_serif"),
font_msc = new FontFaceObserver("marcellus_sc");
Promise.all([font_ls.load(), font_msc.load()]).then(function () {
if (
document.documentElement.className.indexOf(
"js-hasFontsLoaded"
) == -1
) {
document.documentElement.className += " js-hasFontsLoaded";
}
Cookies.set("fonts_loaded", true);
});
// Lazy-Load Media
if (typeof loadMedia === "function") {
loadMedia(".js-lazyLoader", null, true);
}
// Add relative dates via dayjs
if (window.dayjs) {
const times = document.getElementsByTagName("time");
[].forEach.call(times, function (the_time) {
let pub_time = the_time.getAttribute("datetime");
if (the_time.className.indexOf("js-noRelativeTime") === -1)
the_time.innerHTML +=
' <span class="js-momentTime">(' +
dayjs(pub_time).from(dayjs()) +
")</span>";
the_time.classList.add("isDone");
});
}
if (document.documentElement.className.indexOf("is404") > -1) {
document.getElementById("searchQuery").value =
window.location.pathname
.replace(/\\.html?$/, "")
.replace(/\//g, " ");
}
document
.getElementById("searchForm")
.addEventListener("submit", function (e) {
document.getElementById("searchQuery").value +=
" site:" + window.location.hostname;
});
}, 1);
});
})();
// @license-end

View File

@ -0,0 +1,43 @@
@font-face {
font-family: 'exo_2';
src: url('/files/fonts/Exo2-Regular-webfont.eot');
src: url('/files/fonts/Exo2-Regular-webfont.eot?#iefix') format('embedded-opentype'),
url('/files/fonts/Exo2-Regular-webfont.woff') format('woff'),
url('/files/fonts/Exo2-Regular-webfont.ttf') format('truetype'),
url('/files/fonts/Exo2-Regular-webfont.svg#exo_2') format('svg');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'exo_2';
src: url('/files/fonts/Exo2-Italic-webfont.eot');
src: url('/files/fonts/Exo2-Italic-webfont.eot?#iefix') format('embedded-opentype'),
url('/files/fonts/Exo2-Italic-webfont.woff') format('woff'),
url('/files/fonts/Exo2-Italic-webfont.ttf') format('truetype'),
url('/files/fonts/Exo2-Italic-webfont.svg#exo_2') format('svg');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'exo_2';
src: url('Exo2-Bold-webfont.eot');
src: url('Exo2-Bold-webfont.eot?#iefix') format('embedded-opentype'),
url('Exo2-Bold-webfont.woff') format('woff'),
url('Exo2-Bold-webfont.ttf') format('truetype'),
url('Exo2-Bold-webfont.svg#exo_2') format('svg');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'exo_2';
src: url('Exo2-BoldItalic-webfont.eot');
src: url('Exo2-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'),
url('Exo2-BoldItalic-webfont.woff') format('woff'),
url('Exo2-BoldItalic-webfont.ttf') format('truetype'),
url('Exo2-BoldItalic-webfont.svg#exo_2') format('svg');
font-weight: 600;
font-style: italic;
}

View File

@ -0,0 +1,297 @@
/****************************************************************************
* It's Eric Woodward (dotcom)
*
* Copyright 2015-2018 Eric Woodward
* Source released under CC0 Public Domain License v1.0
* http://creativecommons.org/publicdomain/zero/1.0/
****************************************************************************/
/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:0.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace, monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace, monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}
/****************************************************************************
* Default settings
****************************************************************************/
/* set options */
@charset "UTF-8";
@-ms-viewport {
width: device-width;
}
@viewport {
width: device-width;
}
/* apply a natural box layout model to all elements */
*, *:before, *:after {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
abbr[title] {
border-bottom: none;
}
html {
font-size: 16px;
}
/* Fix 300ms Delay */
a, button {
-ms-touch-action: manipulation; /* IE10 */
touch-action: manipulation; /* IE11+ */
}
/****************************************************************************
* Grid settings (http://www.responsivegridsystem.com/)
****************************************************************************/
.section {
clear: both;
margin: 0px;
padding: 0px;
}
.group:before,
.group:after {
content:"";
display:table;
}
.group:after {
clear:both;
}
.group {
zoom:1; /* For IE 6/7 (trigger hasLayout) */
}
.col {
display: block;
float:left;
margin: 1% 0 1% 0%;
}
.col:first-child { margin-left: 0; } /* all browsers except IE6 and lower */
@media only screen and (min-width: 30em) {
.col {
margin: 1% 0 1% 1.6%;
}
}
.span_6_of_6 {
width: 100%;
}
.span_5_of_6 {
width: 100%;
}
.span_4_of_6 {
width: 100%;
}
.span_3_of_6 {
width: 100%;
}
.span_2_of_6 {
width: 100%;
}
.span_1_of_6 {
width: 100%;
}
@media only screen and (min-width: 30em) {
.span_6_of_6 {
width: 100%;
}
.span_5_of_6 {
width: 83.06%;
}
.span_4_of_6 {
width: 66.13%;
}
.span_3_of_6 {
width: 49.2%;
}
.span_2_of_6 {
width: 32.26%;
}
.span_1_of_6 {
width: 15.33%;
}
}
.span_5_of_5 {
width: 100%;
}
.span_4_of_5 {
width: 100%;
}
.span_3_of_5 {
width: 100%;
}
.span_2_of_5 {
width: 100%;
}
.span_1_of_5 {
width: 100%;
}
@media only screen and (min-width: 30em) {
.span_5_of_5 {
width: 100%;
}
.span_4_of_5 {
width: 79.68%;
}
.span_3_of_5 {
width: 59.36%;
}
.span_2_of_5 {
width: 39.04%;
}
.span_1_of_5 {
width: 18.72%;
}
}
.span_4_of_4 {
width: 100%;
}
.span_3_of_4 {
width: 100%;
}
.span_2_of_4 {
width: 100%;
}
.span_1_of_4 {
width: 100%;
}
@media only screen and (min-width: 30em) {
.span_4_of_4 {
width: 100%;
}
.span_3_of_4 {
width: 74.6%;
}
.span_2_of_4 {
width: 49.2%;
}
.span_1_of_4 {
width: 23.8%;
}
}
/****************************************************************************
* Helper classes
****************************************************************************/
.clearfix:before,
.clearfix:after {
content: " ";
display: table;
}
.clearfix:after {
clear: both;
}
.clearfix {
*zoom: 1;
}
.pullRight{
float: right;
}
code, kbd, pre, samp {
font-size: .8em;
}
/****************************************************************************
* Print styles
****************************************************************************/
@media print {
* {
text-shadow: none !important;
color: #000 !important;
background: transparent !important;
box-shadow: none !important;
}
a,
a:visited {
text-decoration: underline;
}
a[href]:after {
content: " (" attr(href) ")";
}
abbr[title]:after {
content: " (" attr(title) ")";
}
a[href^="javascript:"]:after,
a[href^="#"]:after {
content: "";
}
pre,
blockquote {
border: 1px solid #999;
page-break-inside: avoid;
}
thead {
display: table-header-group;
}
tr,
img {
page-break-inside: avoid;
}
img {
max-width: 100% !important;
}
p,
h2,
h3 {
orphans: 3;
widows: 3;
}
h2,
h3 {
page-break-after: avoid;
}
select {
background: #FFFDD7 !important;
}
.navbar {
display: none;
}
.table td,
.table th {
background-color: #FFFDD7 !important;
}
.btn > .caret,
.dropup > .btn > .caret {
border-top-color: #000 !important;
}
.label {
border: 1px solid #000;
}
.table {
border-collapse: collapse !important;
}
.table-bordered th,
.table-bordered td {
border: 1px solid #ddd !important;
}
}

1483
src/assets/styles/styles.css Normal file

File diff suppressed because it is too large Load Diff

44
src/layouts/default.ejs Normal file
View File

@ -0,0 +1,44 @@
<!-- DEFAULT BEGIN -->
<%- include('partials/top') %>
<body>
<a name="top" id="topAnchor" class="topAnchor"></a>
<div class="page">
<header id="header" class="pageHeader pageSection">
<%- include('partials/siteTitle') %>
<%- include('partials/navmain') %>
</header>
<main id="content" class="pageMain pageSection">
<div class="pageMain-inner">
<%- include('partials/embed_switch') %>
<% if (page.content_type && page.content_type !== 'feature') { -%>
<%- (include('partials/content_types/' + page.content_type) || '').trim() %>
<% } else { -%>
<%- include('partials/pageTitle') %>
<%- content %>
<% } -%>
</div>
</main>
<%- include('partials/menusub') %>
<%- include('partials/footer') %>
</div>
<%- include('partials/bottom') %>
<!-- DEFAULT END -->

103
src/layouts/functions.ejs Normal file
View File

@ -0,0 +1,103 @@
<%
htmlize = (text) => {
return (text || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
};
prettyDate = (date) => {
// January 20, 2017 at 20:26
const
month_names = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
];
let
d = new Date(date),
has_time = d && !(d.getHours() === 0 && d.getMinutes() === 0 && d.getSeconds() === 0),
dArr = [];
if (d) {
dArr = [
month_names[d.getMonth()],
' ',
(d.getDate() < 10 ? '0' : ''),
d.getDate(),
', ',
d.getFullYear()
];
if (has_time) {
dArr = dArr
.concat([
' at ',
(d.getHours() < 10 ? '0' : ''),
d.getHours(),
':',
(d.getMinutes() < 10 ? '0' : ''),
d.getMinutes()
]);
}
}
return dArr.join('');
};
shortDate = (date) => {
// 2021-06-20 20:26
const
d = new Date(date),
has_time = d && !(d.getHours() === 0 && d.getMinutes() === 0 && d.getSeconds() === 0);
let dArr = [];
if (d) {
dArr = [
d.getFullYear(),
'-',
(d.getMonth() < 9 ? '0' : ''),
(d.getMonth() + 1),
'-',
(d.getDate() < 10 ? '0' : ''),
d.getDate()
];
if (has_time) {
dArr = dArr
.concat([
' @ ',
(d.getHours() < 10 ? '0' : ''),
d.getHours(),
':',
(d.getMinutes() < 10 ? '0' : ''),
d.getMinutes()
]);
}
return dArr.join('');
}
};
snakeCase = (val) => {
return val.trim().toLowerCase().replace(/\W/g, '_');
};
sortByPath = (p1, p2) => {
if (p1.path < p2.path) return -1;
if (p1.path > p2.path) return 1;
return 0;
};
reverseSortByDate = (p1, p2) => {
if (p1.date_pub && p2.date_pub) {
let
p1_dt = (new Date(p1.date_pub)).getTime(),
p2_dt = (new Date(p2.date_pub)).getTime();
if (p1_dt < p2_dt) {
return 1;
}
if (p2_dt < p1_dt) {
return -1;
}
return 0;
}
};
-%>

View File

@ -0,0 +1,170 @@
<%
const titleLink = (site?.base_uri) ? site.base_uri : '/';
const { entriesToList = [], pageCount, pageNum, year = '' } = page;
-%>
<%- include('partials/top') %>
<body class='postMainIndex'>
<a name="top" id="topAnchor" class="topAnchor"></a>
<div class="page">
<header id="header" class="pageHeader pageSection">
<div class="pageHeader-titleBlock clearfix">
<h1 class="siteTitle"><a href="<%= titleLink %>" class="siteTitle-link"><%= site.title %></a></h1>
</div>
<%- include('partials/navmain') %>
</header>
<main id="content" class="pageMain pageSection">
<div class="pageMain-inner">
<%- include('partials/embed_switch') %>
<%- include('functions') -%>
<% if (page?.title && (page.render_opts || '').indexOf('no_title') == -1) { -%>
<h2>
Journal Entries By Year:
<a
class="boxLink isCurrent"
href="<%= page.path %>"
id="<%= snakeCase(page.title) %>"
name="<%= snakeCase(page.title) %>"
> <%= year %></a>
</h2>
<% if (pageCount > 1) { -%>
<p>
(Page <%= pageNum %> of <%= pageCount %>)
</p>
<% } -%>
<p>
Assorted journal entries from <%= year %>.
</p>
<hr class="feedLine">
<p>
<% } -%>
<% if (entriesToList && year) { -%>
<% entriesToList.forEach((entry) => { -%>
<% if (entry?.content_type === 'journal') { -%>
<%
const {
author = {},
content = '',
date_pub = '',
description = '',
path = '',
readTime = '',
tags = [],
title = '',
tldr = ''
} = entry;
-%>
<article class="update journal h-entry js-update">
<div class="p-author author h-card vcard authorHidden">
<% if (author?.site && author?.name) { -%>
<% if (author?.photo) { -%>
<a href="<%= author.site %>"><img class="u-photo" src="<%= author.photo %>"></a>
<% } -%>
<a class="p-name fn u-url url" rel="author" href="<%= author.site %>"><%= author.name %></a>
<% } else if (site?.author?.site && site?.author?.name) { -%>
<% if (site?.author?.photo) { -%>
<a href="<%= site.author.site %>"><img class="u-photo" src="<%= site.author.photo %>"></a>
<% } -%>
<a class="p-name fn u-url url" rel="author" href="<%= site.author.site %>"><%= site.author.name %></a>
<% } -%>
</div>
<h3 id="<%= snakeCase(title) %>" >
<a class="p-name u-url" href="/<%= path %>"><%= title %></a>
</h3>
<% if (tldr || description) { -%>
<p><i>TL;DR &mdash; <%- tldr || description %></i></p>
<% } -%>
<% if (readTime) { -%>
<p class="journal readTime">
<span class="icon glasses-icon">&#x1f453;</span> <%= readTime %>
</p>
<% } -%>
<% if (content) { -%>
<div class="e-content">
<%- content %>
</div>
<% } -%>
<footer class="update-footer clearfix">
<a rel="bookmark" href="/<%= path %>" class="u-url update-footer-link update-footer-time">
<time datetime="<%= date_pub %>"
class="dt-published published js-pubDate pubDate"
><%= prettyDate(date_pub) %></time>
</a>
<!--
<span class="update-citation">[iew.us q/1g0k2]
<a class="u-shortlink" type="text/html" rel="shortlink" href="https://iew.us/q/1g0k2">&#128279;</a>
</span>
-->
<% if (Array.isArray(tags) && tags.length) { -%>
<div class="update-tags">
<span class="icon folder-icon">&#x1f4c1;</span>
<% tags.forEach((tag) => { -%>
<a class="boxLink" href="/journal/tags/<%= tag %>/index.html">
#<span class="p-category category"><%= tag %></span>
</a>
<% }) -%>
</div>
<% } -%>
</footer>
</article>
<p class="backLink">
<a class="boxLink" href="#top">Top</a>
</p>
<hr class="feedLine">
<% } -%>
<% }); -%>
<% } -%>
<%- include('partials/pageMenu') %>
</div>
</main>
<%- include('partials/journal/menusub') %>
<%- include('partials/bio') %>
<%- include('partials/footer') %>
</div>
<%- include('partials/bottom') %>

View File

@ -0,0 +1,76 @@
<!-- BIO BEGIN -->
<%
var
show_bio = !(page.name === 'about' && page.path.indexOf('about') === 0),
photo_class = (page.name === 'index' && page.path === '' || page.path.indexOf('index') === 0 ? ' showPhoto' : '');
if (show_bio) {
%>
<aside id="bio" class="vcard h-card p-author author asideContent asideLeft asideBio asideMenu">
<div class="asideBio-div <%=photo_class%>">
<a href="/"
class="u-url u-uid url icon-container asideBio-div-link asideBio-div-imgLink"
rel="author"><img
class="photo u-photo asideBio-div-img"
alt="It's Eric Woodward's avatar"
src="<%=site?.author?.photo%>"/></a>
<a class="p-name fn u-url u-uid url asideBio-div-link asideBio-div-textLink"
href="/"><%=site.author.name%></a>
<a class="u-url url asideBio-div-textLink" href="/">
<!-- This is here to force the hand of your MF2 parser --></a>
</div>
<p>My name is <a class="p-name fn u-url u-uid url asideBio-div-link"
href="/"><%=site.author.name%></a> and this is my website.</p>
<p class="p-note p-role role">
I am a <span class="p-category category">geek</span>,
<span class="p-category category">coder</span>,
<span class="p-category category">gamer</span>,
<span class="p-category category">tinkerer</span>,
<span class="p-category category">husband</span>,
<span class="p-category category">father</span>,
<span class="p-category category">server admin</span>,
<span class="p-category category">web developer</span>,
and <span class="p-category category">American</span>
<span class="p-category category">cyborg</span>,
though not necessarily in that order.
</p>
<ul class="asideMenu-list socialList">
<li class="asideMenu-item socialList-item">
<a rel="me auth" class="asideMenu-link u-url url" title="It's Eric Woodward's Gitea Instance" href="https://git.itsericwoodward.com">My Git Repos</a>
</li>
<li class="asideMenu-item socialList-item">
<a rel="me" class="asideMenu-link u-url url" title="itsericwoodward on LinkedIn" href="https://www.linkedin.com/in/itsericwoodward">LinkedIn</a>
</li>
<li class="asideMenu-item socialList-item">
<a rel="me auth" class="asideMenu-link u-url url" title="ItsEricWoodward on GitHub" href="https://github.com/ItsEricWoodward">GitHub</a>
</li>
<li class="asideMenu-item socialList-item">
<a rel="me" class="asideMenu-link u-url url" title="EricPlaysGames on BoardGameGeek"
href="https://boardgamegeek.com/user/ericplaysgames">BoardGameGeek</a>
</li>
<li class="asideMenu-item socialList-item">
<a rel="me" class="asideMenu-link u-url url u-email" title="&#69;&#109;&#97;&#105;&#108;" href="&#109;&#x61;&#x69;&#108;&#116;&#x6f;&#58;&#x65;&#x72;&#x69;&#x63;&#64;&#x69;&#116;&#x73;&#x65;&#114;&#x69;&#x63;&#119;&#111;&#111;&#x64;&#x77;&#97;&#x72;&#100;&#x2e;&#99;&#111;&#109;">&#69;&#109;&#97;&#105;&#108;</a>
</li>
<!--
<li class="asideMenu-item socialList-item">
<a rel="pgpkey" class="asideMenu-link u-url url" type="application/pgp-keys" title="PGP Public Key" href="/files/public.aexpk">
PGP Key
</a>
</li>
-->
<li class="asideMenu-item socialList-item">
<a class="asideMenu-link" href="http://h2vx.com/vcf/https%3A//itsericwoodward.com">
Add to Address Book
</a>
</li>
</ul>
</aside>
<% } %>
<!-- BIO END-->

View File

@ -0,0 +1,16 @@
<!-- BOTTOM BEGIN -->
</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/scripts.js"></script>
</body>
</html>
<!-- BOTTOM END -->

View File

@ -0,0 +1,15 @@
<%- include('../../functions') -%>
<% if (page.title && (page.render_opts || '').indexOf('no_title') == -1) { -%>
<h2 class="pageTitle">
<a
class="pageTitle-link"
href="/<%= page.path %>"
id="<%= snakeCase(page.title) %>"
name="<%= snakeCase(page.title) %>"
><%= page.title %></a>
</h2>
<% } -%>
<%- content %>

View File

@ -0,0 +1,108 @@
<%- include('../../functions') -%>
<%
var
journals = site.pages.filter(thePage => thePage.content_type === 'journal'),
curr = journals.filter(journal => journal.filePath === page.filePath),
currIndex = journals.map(journal => journal.filePath).indexOf(page.filePath),
prev = currIndex < (journals.length - 1) ? journals[currIndex + 1] : null,
next = currIndex > 0 ? journals[currIndex - 1] : null;
%>
<% if (page.title) { -%>
<h2>
<a class="p-name u-url" href="/<%= page.path %>"><%= page.title %></a>
</h2>
<% } -%>
<% if (page.readTime) { -%>
<p class="journal readTime">
<span class="icon glasses-icon">&#x1f453;</span> <%= page.readTime %>
</p>
<% } -%>
<% if (page.tldr || page.description) { -%>
<p><i>TL;DR &mdash; <%- page.tldr || page.description %></i></p>
<% } -%>
<div class="e-content">
<%- content %>
</div>
<footer class="update-footer clearfix">
<% if (Array.isArray(page?.tags) && page.tags.length) { -%>
<div class="update-tags">
<span class="icon folder-icon">&#x1f4c1;</span>
<% page.tags.forEach((tag, index) => { -%>
<%= index > 0 ? ' / ' : '' %>
<a href="/journal/tags/<%= tag %>/index.html">
#<span class="p-category category"><%= tag %></span>
</a>
<% }) -%>
</div>
<% } -%>
<!--
<span class="update-citation">[iew.us q/1g0k2]
<a class="u-shortlink" type="text/html" rel="shortlink" href="https://iew.us/q/1g0k2">&#128279;</a>
</span>
-->
<a rel="bookmark" href="/<%= page.path %>" class="u-url update-footer-link update-footer-time">
<time datetime="<%= page.date_pub %>"
class="dt-published published js-pubDate pubDate"
><%= prettyDate(page.date_pub) %></time>
</a>
<div class="update-nav clearfix">
<% if (prev) { -%>
<div class="update-nav-linkWrapper">
&lt; Previous Entry
<a class="update-nav-link update-nav-prev" href='/<%= prev.path %>'>
<%=prev.title%>
<br />
<span class="update-nav-pubDate">
(<time
datetime="<%= prev.date_pub %>"
class="dt-published published js-pubDate pubDate js-noRelativeTime"
><%= shortDate(prev.date_pub) %></time>)
</span>
</a>
</div>
<% } -%>
<% if (next) { -%>
<div class="update-nav-linkWrapper update-nav-nextWrapper">
Next Entry &gt;
<a class="update-nav-link update-nav-next" href='/<%= next.path %>'>
<%=next.title%>
<br />
<span class="update-nav-pubDate">
(<time
datetime="<%= next.date_pub %>"
class="dt-published published js-pubDate pubDate js-noRelativeTime"
><%= shortDate(next.date_pub) %></time>)
</span>
</a>
</div>
<% } -%>
</div>
</footer>

View File

@ -0,0 +1,99 @@
<%- include('../../functions') -%>
<%
var
card_url = "http://gatherer.wizards.com/Pages/Search/Default.aspx?name=+[%22CARD-NAME%22]",
detail_url = "http://gatherer.wizards.com/Pages/Card/Details.aspx?multiverseid=",
image_url = "/images/magic/commanders/",
deck = page.deck || {},
info = deck.info || {},
cards = deck.cards || [],
lands = deck.lands || [],
changes = deck.changes || [],
starting_lands = info.starting_lands || [],
commander = info.commander && info.commander_id ?
"<li><a href='" + detail_url + info.commander_id + "' target='_blank'>" + info.commander +
"</a> <em>(Commander)</em></li>" :
"",
commander_img = info.commander && info.commander_img ?
"<img src='" + image_url + info.commander_img + "' class='magic-commander-img' alt='" + info.commander + " card' />":
""
;
-%>
<h2>
<a href="/<%= page.path %>" name="<%= snakeCase(page.title) %>" id="<%= snakeCase(page.title) %>">
<%= page.title %>
</a>
</h2>
<p>
Below is my <strong><%= info.name %></strong> deck for <a href="https://magic.wizards.com/">Magic: the Gathering</a>
(<%= info.format %> format).
It was last updated on <%= info.date_upd %>.
</p>
<p>
Each card listed below is linked to its entry on <a href="http://gatherer.wizards.com/">The Gatherer</a>
(the official <abbr title="Magic: the Gatheirng">MtG</abbr> card database) and should open in a new window or tab in
your browser.
</p>
<h3> Decklist </h3>
<%- commander_img %>
<ul>
<%- commander %>
<% cards.forEach(function(card) { %>
<li><a href="<%- card_url.replace('CARD-NAME', card) %>" target="_blank"><%= card %></a></li>
<% }) %>
<% lands.forEach(function(land) { %>
<li><a href="<%- card_url.replace('CARD-NAME', land.type) %>" target="_blank"><%= land.type %></a> (x<%=land.count%>)</li>
<% }) %>
</ul>
<% if (starting_lands && starting_lands.length > 0) { %>
<h4>Starting Lands</h4>
<p>
In order to speed our games along, my gaming group allows everyone to start with 3 basic lands.
The lands listed below are included in the counts above.</p>
<ul>
<% starting_lands.forEach(function(land) { %>
<% if (typeof land === 'string') { %>
<li><a href="<%- card_url.replace('CARD-NAME', land) %>" target="_blank"><%= land %></a></li>
<% } else {%>
<li>
<a href="<%- card_url.replace('CARD-NAME', land.type) %>" target="_blank"><%= land.type %></a> (x<%=land.count%>)
</li>
<% } %>
<% }) %>
</ul>
<% } %>
<% if (changes && changes.length > 0) { %>
<h4>Changes from Previous Versions</h4>
<ul>
<% changes.forEach(function(change) { %>
<li>
Implemented <%= change.date_upd %>:
<ul>
<% if (change.adds) { %>
<% change.adds.forEach(function(add) { %>
<li><a href="<%- card_url.replace('CARD-NAME', add) %>" target="_blank"><%= add %></a> (added)</li>
<% }) %>
<% } %>
<% if (change.dels) { %>
<% change.dels.forEach(function(del) { %>
<li><a href="<%- card_url.replace('CARD-NAME', del) %>" target="_blank"><%= del %></a> (removed)</li>
<% }) %>
<% } %>
</ul>
</li>
<% }) %>
</ul>
<% } %>
<%- content %>

View File

@ -0,0 +1,15 @@
<!-- EMBED-SWITCH BEGIN -->
<%
if (page.path && page.path.indexOf('/updates') === 0) {
-%>
<div class='embedSwitch'>
<form action='' method=''>
<input type='checkbox' id='toggle_embeds' name='toggle_embeds' class='toggleSwitch js-toggleEmbeds' />
<label for='toggle_embeds'><span class='toggleSwitch-info'>Enable Embedded Media<span></label>
</form>
</div>
<% } -%>
<!-- EMBED-SWITCH END -->

View File

@ -0,0 +1,51 @@
<!-- FOOTER BEGIN -->
<footer id="footer" class="pageFooter pageSection clearfix">
<div class="pageFooter-inner">
<p class="pageFooter-dates">
<% if (page.date_pub && !isNaN(new Date(page.date_pub).getTime())) { -%>
Page first published:
<time datetime="<%= new Date(page.date_pub).toISOString() %>"><%= shortDate(new Date(page.date_pub).toISOString()) %></time>
<br />
<% } -%>
<% if (page.date_upd && !isNaN(new Date(page.date_upd).getTime())) { -%>
Page last updated:
<time datetime="<%= new Date(page.date_upd).toISOString() %>"><%= shortDate(new Date(page.date_upd).toISOString()) %></time>
<br />
<% } -%>
</p>
<p>
<a rel="license"
class="licenseLink"
href="http://creativecommons.org/licenses/by-sa/4.0/">
<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; 2023
<a
xmlns:cc="http://creativecommons.org/ns#"
href="<%=site.author.uri%>"
property="cc:attributionName"
rel="cc:attributionURL">
<%=site.author.name%></a>,
and is licensed under a
<a
rel="license"
href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.
</p>
<p>
Background image by <a href="https://pixabay.com/illustrations/ai-art-ai-generated-outdoor-7717011/">AlanFrijns / Pixabay</a>, used under the <a href="https://pixabay.com/service/terms/">Pixabay License</a>.
</p>
<a href="#top" class="topLink">Back to Top</a>
</div>
</footer>
<!-- FOOTER END -->

View File

@ -0,0 +1,21 @@
<!-- MENUSUB BEGIN -->
<% if (page.section && page.subsection && page.section === 'games') { %>
<% if (page.subsection === 'anachronism') { %>
<%- include('anachronism/menusub') %>
<% } else if (page.subsection === 'magic-cards' && page.name !== 'index') { %>
<%- include('magic-cards/menusub') %>
<% } else if (page.subsection === 'magic-decks' && page.name !== 'index') { %>
<%- include('magic-decks/menusub') %>
<% } else if (page.subsection === 'thur' && page.name !== 'index') { %>
<%- include('thur/menusub') %>
<% } %>
<% } else if (page.section && page.subsection && page.section === 'web') { %>
<% if (page.subsection === 'linklists' && page.name !== 'index') { %>
<%- include('linklists/menusub') %>
<% } %>
<% } else if (page.content_type === 'journal') { %>
<%- include('journal/menusub') %>
<% } %>
<!-- MENUSUB END -->

View File

@ -0,0 +1,100 @@
<!-- NAVMAIN BEGIN -->
<div class="menubar clearfix">
<input id="menu__toggle" type="checkbox" />
<label class="menu__btn" for="menu__toggle">
<span></span>
</label>
<nav class="navMenu" role="navigation">
<ul>
<li class="hasSubMenu"><a href="/astral/index.html" aria-haspopup="true">Astral Plane</a>
<ul class="dropdown" aria-label="submenu">
<li><a href="/astral/adventuring.html">Adventuring</a></li>
<li><a href="/astral/factions.html">Factions</a></li>
<li><a href="/monsters/index.html">Monsters</a></li>
<li><a href="/astral/timeline.html">Timeline</a></li>
<li><a href="/astral/vessels.html">Vessels</a></li>
<!-- <li><a href="/astral/zenopus-cove.html">Zenopus Cove</a></li> -->
</ul>
</li>
<li class="hasSubMenu"><a href="/planes/index.html" aria-haspopup="true">Other Planes</a>
<ul class="dropdown" aria-label="submenu">
<li><a href="/planes/elemental.html">Elemental Planes</a></li>
<li><a href="/planes/material.html">Material Planes</a></li>
<li><a href="/planes/outer.html">Outer Planes</a></li>
<!--<li><a href="/planes/infernus.html">Infernus</a></li>-->
</ul>
</li>
<li class="hasSubMenu"><a href="/classes/index.html" aria-haspopup="true">Classes</a>
<ul class="dropdown" aria-label="submenu">
<li><a href="/classes/automaton.html">Automaton</a></li>
<li><a href="/classes/corsair.html">Corsair</a></li>
<li><a href="/classes/astral-corsair.html">Corsair, Astral</a></li>
<li><a href="/classes/dracokin.html">Dracokin</a></li>
<li><a href="/classes/felinar.html">Felinar</a></li>
<li><a href="/classes/firfolk.html">Firfolk</a></li>
<li><a href="/classes/mimikin.html">Mimikin</a></li>
<li><a href="/classes/tortokin.html">Tortokin</a></li>
<li><a href="/classes/warlock.html">Warlock</a></li>
</ul>
</li>
<li class="hasSubMenu"><a href="/" aria-haspopup="true">Races</a>
<ul class="dropdown" aria-label="submenu">
<li><a href="/races/automaton.html">Automaton</a></li>
<li><a href="/races/dracokin.html">Dracokin</a></li>
<li><a href="/races/felinar.html">Felinar</a></li>
<li><a href="/races/firfolk.html">Firfolk</a></li>
<li><a href="/races/mimikin.html">Mimikin</a></li>
<li><a href="/races/tortokin.html">Tortokin</a></li>
</ul>
</li>
<li class="hasSubMenu"><a href="/" aria-haspopup="true">Miscellanea</a>
<ul class="dropdown" aria-label="submenu">
<!--<li><a href="/classes/automaton.html">The Dragon Gods</a></li>-->
<li><a href="/magic-items/index.html">Magic Items</a></li>
<li><a href="/monsters/index.html">Monsters</a></li>
<li><a href="/weapons/index.html">Weapons</a></li>
</ul>
</li>
<li class="hasSubMenu"><a href="/rules/index.html" aria-haspopup="true">House Rules</a>
<ul class="dropdown" aria-label="submenu">
<li><a href="/rules/critical-hits.html">Critical Hits</a></li>
<li><a href="/rules/fumbles.html">Fumbles</a></li>
<li><a href="/rules/ose.html">Official OSE Rules</a></li>
<li><a href="/rules/custom.html">Custom Rules</a></li>
</ul>
</li>
<li class="hasSubMenu"><a href="/campaign/index.html" aria-haspopup="true">Campaign Info</a>
<ul class="dropdown" aria-label="submenu">
<li><a href="/npcs/index.html">NPCs</a></li>
<li><a href="/campaign/ravager.html">The Ravager</a></li>
<li><a href="/campaign/shazz-journals.html">Shazzograx's Journals</a></li>
<li><a href="/campaign/timeline.html">Timeline</a></li>
</ul>
</li>
</ul>
<!--
<ul class="navMenu-list">
<li class="navMenu-hasSubnav">
<a href="/"><span class="navMenu-subTitle-short">Game</span><span class="navMenu-subTitle-long">The Game</span></a>
<ul class="navMenu-list navMenu-lev2 navMenu-game">
<li>
<a href="/clash.html" class="navMenu-list-clash">Clash of the Gods</a>
</li>
<li>
<a href="/cthulhu.html">Cthulhu Rises</a>
</li>
<li>
<a href="/nemesis.html">Nemesis</a>
</li>
</ul>
</li>
</ul>
-->
</nav>
</div>
<!-- NAVMAIN END -->

View File

@ -0,0 +1,17 @@
<% const { pageCount, pageNum } = page; -%>
<% if (pageNum && pageCount > 1) { -%>
<div class="pageMenu">
Pages
<br />
<% for (let i=1; i <= pageCount; i++) { -%>
<% if (i === (pageNum + 1)) { -%>
<a class="boxLink" href="page<%= i -%>.html" rel="next"><%= i -%></a>
<% } else if (i === (pageNum - 1)) { -%>
<a class="boxLink" href="<%= i === 1 ? 'index' : `page${i}`-%>.html" rel="prev"><%= i -%></a>
<% } else { -%>
<a class="boxLink <%= i === pageNum ? 'isCurrent' : '' -%>" href="page<%= i -%>.html"><%= i -%></a>
<% } -%>
<% } -%>
</div>
<% } -%>

View File

@ -0,0 +1,13 @@
<%- include('../functions') -%>
<% if (page.title && (page.render_opts || '').indexOf('no_title') == -1) { -%>
<h2 class="pageTitle">
<a
class="pageTitle-link"
href="/<%= page.path %>"
id="<%= snakeCase(page.title) %>"
name="<%= snakeCase(page.title) %>"
><%= page.title %></a>
</h2>
<% } -%>

View File

@ -0,0 +1,206 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2072.696 1656.289" enable-background="new 0 0 2072.696 1656.289" xml:space="preserve"
class="siteTitle-shipIcon">
<path d="M2035.781,1079.881c-4.52-4.731-12.924-2.252-15.811,3.205c-152.484,31.255-235.471,51.946-254.125,51.469
c-67.21,12.454-130.797,25.805-144.083,26.422c-12.488-0.715-197.927,46.143-205.846,36.912
c8.358-13.499,33.783-74.338,39.12-101.109c1.238-6.992,18.511-41.617-2.691-128.695c-6.561-29.848-14.351-52.552-33.345-86.311
c-1.98-3.779-5.956-7.135-5.275-11.76c20.512-24.578,42.656-49.383,52.935-80.34c18.802-57.305,4.442-91.487-22.235-152.245
c-26.358-65.701-43.632-106.546-72.404-149.464c-4.653-4.699,0.111-8.45-21.162,5.23c-13.194,6.189-44.817,15.879-76.697,17.791
c-13.586,0.887-9.52-1.651-8.707-71.83c0.106-4.837-1.436-10.203,1.481-14.526c27.313-36.27,32.028-27.212,29.068-38.137
c-11.019-32.151-18.91-65.255-29.143-97.647c-4.239-10.735-0.548-47.699,0.665-54.356c84.65-5.954,123.762,18.616,138.596,18.759
c-7.6-12.501-38.406-29.446-59.45-35.643c-0.076-1.36-0.121-2.721-0.136-4.066c8.237,1.373,37.143-1.839,50.623-13.71
c-81.105-0.749-77.154-25.756-110.239-28.493c-5.593-0.423-11.034-1.814-16.189-3.945c0.196-4.368,0.227-8.752-0.03-13.12
c-3.235-0.62-6.439-1.27-9.644-1.874c-4.271,11.092-7.145,119.16-8.435,135.694c-0.272,7.15-4.172,13.332-6.651,19.847
c-29.682,72.953-38.384,81.063-33.103,85.691c10.929,10.203,19.409,22.628,28.826,34.192c1.753,2.509,4.52,4.913,3.945,8.314
c-0.124,5.355-0.917,62.811-4.52,70.999c-14.819,1.557-82.029-15.333-110.587-28.765c8.994,26.105,18.154,52.316,23.671,79.448
c0,0-129.464,1.36-129.466,1.36c-2.857-0.197-7.029,0.197-8.057-3.205c-26.768-63.94-41.732-101.56-35.084-162.237
c-40.479-10.335-83.552-15.416-146.124-32.862c-2.6-5.396,3.235-10.218,5.472-14.844c4.006-4.701,6.923-10.188,10.657-15.101
c3.945-5.563,12.833-5.895,15.237-12.803c0.786-6.077-7.361-6.742-11.382-8.858c-6.731-12.719-43.59-113.938-42.989-151.233
c0.648-22.911-1.487-58.49,5.2-57.833c68.864-2.754,104.364,8.353,140.773-4.625c2.101-0.423,3.975-1.451,5.018-3.416
c-11.367-2.887-23.097-3.9-34.691-5.442c-9.024-1.466-17.398-5.26-26.165-7.664c-4.081-1.421-9.326-2.373-10.793-7.119
c4.032-0.117,39.488-5.656,53.767-11.322c2.207-0.484,2.993-2.6,3.855-4.414c-10.898-2.54-22.22-1.542-33.119-4.021
c-50.361-11.916-78.75-9.112-98.963-11.443c-0.257-3.129-0.484-6.243-0.59-9.372c-2.373-2.887-6.046-3.114-9.493-3.219
c-0.952,1.013-1.889,2.041-2.827,3.053c0.124,3.505-1.18,113.799-1.194,114.804c-1.76,26.85-22.254,81.817-26.846,98.509
c-0.276,1-16.965,60.925-29.158,69.774c0,1.874,0.015,3.749,0.045,5.638c4.807,6.908,12.229,12.365,14.435,20.754
c-13.876-4.247-28.07-7.376-41.78-12.214c-24.458-6.352-85.688-26.099-86.689-26.392c-25.512-6.851-28.737-13.3-43.896-8.646
c5.491,15.495,21.975,32.543,38.5,93.959c23.586,72.495,17.9,195.96-6.077,261.336c-0.333,0.257-1.013,0.786-1.36,1.058
c-12.818-8.268-23.596-19.167-35.386-28.765C442.587,503.866,463.888,518.68,456.794,503.458
c-1.965-5.396-6.469-10.717-3.915-16.703c19.907,0.635,39.497-4.837,59.39-5.245c19.318,0.106,38.681,0.544,57.893-2.192
c-8.006-12.166-35.439-8.437-56.019-19.152c5.398-3.147,42.987-0.429,51-22.22c-17.452,6.787-42.72,4.002-49.504,1.466
c-26.763-7.864-2.973-6.244-59.465-1.769c-0.816-0.287-2.434-0.862-3.235-1.134c0.272-4.202-0.212-8.435-3.099-11.684
c-2.162,0.03-4.308,0.091-6.454,0.136c-1.481,2.509-3.084,5.139-2.721,8.208c0.06,20.089-0.635,40.178-0.378,60.266
c-5.94,4.172-7.074,11.73-8.616,18.29c-1.166,5.07-21.911,81.204-23.807,85.374c-6.082,4.199-32.118-45.111-57.334-79.221
c-81.736-96.943-71.34-89.761-78.526-90.815c-4.45,17.015,18.127,52.846,27.556,77.272c27.432,59.336,85.43,226.038,80.083,244.346
c-3.914,13.402-63.864,286.087-71.15,289.406c-5.215,0.378-10.082-2.026-15.222-2.373c-26.997-1.784-53.132-10.158-80.234-10.959
c-12.844,0.218-11.98-15.887-17.323-43.563c8.918-3.492,19.62,1.708,27.768-4.187c20.292-14.561,2.481-29.956-19.56-41.901
c8.923-17.536,31.972-30.548,39.936-63.849c6.092-28.932,1.617-59.254-9.205-86.507c-7.995-16.44-26.412-40.386-57.122-63.002
c-28.682-26.601-16.785-24.255-16.038-36.036c3.08-15.238-19.911-22.045-28.493-10.989c-7.661,11.555-1.099,15.938,2.434,31.032
c12.114,73.498,8.195,85.673,11.972,119.142c1.573,23.652,6.817,55.351,4.127,59.314c-5.306,8.676-10.551,17.731-12.682,27.768
c-1.496,14.36-2.252,29.355,2.721,43.17c5.031,14.163,23.082,28.802,23.838,37.593c-8.903,0.967-17.927,0.605-26.74-0.922
c-20.681-3.708-49.47-6.13-45.529-15.932c0.384-1.095,24.871-58.413,28.448-65.451c1.682-5.278-13.653-5.038-27.223-19.922
c-4.157-4.474-7.059-9.886-10.808-14.677c-13.725,11.291-31.184,15.902-48.189,19.575c-0.984,30.387,4.472,64.317,9.946,73.085
c5.457,6.59,16.476,4.474,21.253,11.896c20.364,30.471,60.629,31.127,88.85,36.263c0,0,12.512,134.9,58.256,238.254
c14.376,28.377,24.471,27.489,30.473,59.223c5.419,41.445,8.091,128.171,10.838,148.376c1.058,10.052-0.907,20.104-0.65,30.201
c-3.488,74.379-5.699,72.034-28.115,87.626c-16.023,9.659-31.501,20.24-47.539,29.823c-2.963,4.051-1.451,10.082-2.645,14.919
c0.559,0.65,1.648,1.935,2.192,2.585c0,0,153.88,1.659,254.02-3.114c493.085-23.502,660.641,15.934,844.205-20.372
c107.289-21.22,156.188-43.946,212.066-129.303c25.834-43.293,16.983-56.266,20.905-77.347
c120.948-50.889,92.107-127.051,123.556-143.388c11.624-6.092,25.349-9.478,34.282-19.711c11.761-13.51,3.206-16.542-2.948-28.916
c-12.414-21.724-20.883-15.387-34.963-12.138c-5.714-0.589-6.167-7.512-5.351-11.941c0,0,300.539-69.618,394.097-87.112
c5.215-1.028,10.747,2.66,15.705-0.151C2038.714,1091.293,2037.973,1084.219,2035.781,1079.881z M1343.089,215.761l-0.121-1.421
C1345.31,211.226,1345.915,218.33,1343.089,215.761z M1269.732,406.506c-2.358-2.721-4.066-9.236,0.302-10.672
c2.449-0.212,4.913-0.076,7.392-0.03C1276.201,400.203,1272.952,403.468,1269.732,406.506z M1264.955,325.501l2.237-1.119
c2.872,9.931,4.474,20.225,7.981,29.99c3.598,10.218,7.225,20.421,10.46,30.76c-5.744,2.101-12.002,1.36-17.973,2.026
c-0.62-2.267-1.255-4.535-1.255-6.893C1266.119,362.005,1263.852,343.76,1264.955,325.501z M68.592,959.605
c-4.807,0.196-9.961,0.453-14.299-2.01c-2.539-15.494-5.865-31.048-6.727-46.798c0.212-2.252-0.967-5.986,2.116-6.726
c5.804,0.816,11.322,3.023,17.156,3.718c2.026,6.878,1.723,14.043,2.434,21.102C68.169,939.093,69.862,949.372,68.592,959.605z
M77.586,959.076c1.209-16.053,4.308-31.909,6.636-47.826c7.135,1.481,15.796,1.088,21.056,6.908
c-4.656,8.873-4.807,19.242-8.782,28.372c-1.451,5.608-4.716,10.551-5.744,16.295C86.292,963.082,80.155,963.611,77.586,959.076z
M400.654,969.959c-3.099,12.833-5.336,25.954-10.158,38.303c-4.822,13.483-9.689,26.966-16.053,39.815l-1.27,0.922
c-7.331-0.877-14.436-3.25-21.782-4.187c-7.512-1.935-15.675-1.481-22.749-4.913c-2.237-1.632-1.179-4.399-0.831-6.606
c7.195-31.199,13.982-62.443,21.041-93.672c5.547-25.682,10.294-51.545,16.31-77.135c2.796-14.723,5.215-29.521,8.419-44.168
c2.419-16.37,6.107-32.65,11.458-48.295c2.902,2.917,3.114,7.15,3.688,10.989c0.695,6.606,2.796,12.954,3.325,19.59
c2.116,21.102,5.427,42.082,6.742,63.259c2.283,19.091,3.084,38.303,4.535,57.47c0.106,4.081-0.348,8.162-0.151,12.244
C404.191,945.744,401.893,957.882,400.654,969.959z M426.32,1046.989c-2.963,1.451-3.053,5.321-5.608,6.953
c-5.26,0.59-10.626-0.302-15.358-2.676c6.485-5.17,13.045-10.279,19.771-15.131C427.469,1039.325,426.532,1043.346,426.32,1046.989z
M433.878,606.094c-3.537,0.363-7.24,0.62-10.566-0.862c-2.146-3.053-0.257-7.165-0.121-10.581
c3.522-15.554,5.835-31.305,8.797-46.964c0.877,0,2.63-0.015,3.492-0.03C437.899,567.125,435.344,586.67,433.878,606.094z
M687.505,735.696c6.757,2.494,9.417,10.067,13.634,15.358c5.593,6.968,10.883,14.179,15.69,21.736
c9.75,11.896,16.869,25.757,27.223,37.185c1.95,2.116,3.129,4.761,3.552,7.618c-5.29,0.166-10.46-0.862-15.539-2.177
c-20.104-2.116-40.253-4.081-60.115-8.026c-24.578-3.446-49.081-7.24-73.629-10.747c-10.536-2.177-21.389-2.6-31.743-5.593
c-0.589-1.663-2.63-3.96-0.423-5.321c14.163-13.695,30.05-26.241,48.93-32.65C637.85,743.103,662.156,733.444,687.505,735.696z
M748.194,899.051c4.308,0.015,8.782-0.846,12.939,0.771c-0.499,3.855-1.935,7.437-5.623,9.221
c-2.267,6.001-5.804,11.609-5.835,18.23c-3.9,10.022-9.372,19.469-12.153,29.929c-3.295,3.809-3.975,8.994-6.167,13.408
c-5.427,11.957-9.81,24.336-15.494,36.172c-7.225,17.882-17.202,34.555-23.535,52.814c-1.844-0.801-3.885-1.421-4.988-3.205
c-14.859-17.867-29.899-35.597-43.911-54.144c-6.847-10.294-15.856-19.031-22.553-29.37c-9.825-28.72-24.639-55.278-38.56-82.139
c-1.633-3.023-4.021-5.699-4.474-9.206c15.872,0.318,31.97,3.87,47.796,0.559c12.667-3.477,25.092-8.026,36.52-14.556
c12.455,12.803,28.055,22.568,45.317,27.405C720.82,897.918,734.5,899.641,748.194,899.051z M458.698,535.05
c7.452,18.456,11.488,38.031,17.171,57.062c0.438,2.026,1.844,5.532-0.892,6.56c-4.58,1.859-9.689,2.524-14.526,1.345
c-0.62-21.948-3.975-43.73-5.563-65.617C455.841,534.566,457.746,534.884,458.698,535.05z M506.781,1074.152
c-3.628,0.952-7.119-1.315-10.657-1.859c-12.788-2.479-25.47-5.638-37.956-9.357c-4.081-1.769-9.78-4.943-8.692-10.203
c0.499-11.05,0.045-22.16,1.194-33.149c1.859-3.341,5.457-4.958,8.616-6.787c9.75-5.578,20.089-10.037,30.473-14.254
c2.283-1.451,3.96-1.058,5.064,1.149c6.122,21.328,8.389,43.458,11.851,65.315C506.691,1068.015,508.202,1071.295,506.781,1074.152z
M502.866,751.31c-5.865-6.772-8.918-15.267-13.771-22.689c-2.811-5.986-7.905-10.914-9.311-17.504
c-2.101-17.066-6.273-33.799-8.268-50.88c0.71-1.013,1.421-2.01,2.146-2.993c-1.315-6.742-0.574-13.65-1.058-20.452
c6.122-9.19,14.708-16.401,21.026-25.394c-5.744-22.22-15.418-43.291-20.678-65.678c7.845,3.93,13.695,10.838,20.89,15.796
c7.361,6.938,15.645,12.773,23.082,19.635c14.36,11.322,27.677,23.928,42.158,35.068c16.068,14.088,32.771,27.495,48.476,41.976
c2.388,2.388,7.014,3.779,7.044,7.694c-1.406,7.497-5.88,13.816-8.616,20.814c-0.907,1.527-1.799,3.053-2.706,4.58
c-5.457,11.427-12.697,21.888-19.257,32.695c-4.882,5.29-8.374,11.654-13.272,16.93c-10.883,13.649-20.557,28.795-34.917,39.15
c-2.978,2.902-6.863,7.513-11.261,4.021C513.976,775.889,510.984,761.589,502.866,751.31z M531.858,1080.425
c-3.159-11.11-5.668-22.417-7.256-33.844c-3.537-14.587-5.729-29.445-8.918-44.108c-0.363-3.189-2.237-6.727-0.106-9.629
c5.079-0.181,10.46,0.408,15.055,2.706c6.938,23.475,9.976,47.841,14.677,71.769c0.393,5.064,1.708,10.022,2.237,15.085
C542.243,1082.345,537.088,1081.12,531.858,1080.425z M564.085,1073.396c-5.623-14.859-7.83-30.715-13.015-45.695
c-2.524-10.445-4.444-21.177-8.903-31.017c0.816-0.317,2.449-0.922,3.265-1.24c9.901,6.333,19.514,13.12,29.778,18.88
c11.881,8.389,24.941,15.418,34.948,26.211c-6.167,7.361-12.939,14.209-20.557,20.059c-7.271,5.638-13.619,12.41-21.313,17.504
C566.715,1076.676,564.705,1075.527,564.085,1073.396z M647.524,1160.538c-0.227,2.751-4.111,4.293-6.137,2.434
c-20.376-14.708-38.999-31.985-54.25-51.983c-5.064-6.575-6.893-14.904-11.7-21.615c-0.076-0.816-0.227-2.418-0.302-3.235
c5.956-4.202,10.46-10.082,16.038-14.768c9.039-7.256,16.567-16.174,25.651-23.369c8.389-1.844,17.081-1.345,25.44-3.28
c-0.408-11.699-6.59-22.22-7.77-33.708c4.096,2.57,6.606,6.802,9.795,10.324c14.284,16.612,27.798,33.814,41.266,51.076
c-0.151,0.771-0.438,2.298-0.574,3.068C672.752,1103.945,661.067,1132.68,647.524,1160.538z M698.01,1148.929
c-3.114,9.795-5.29,19.892-8.676,29.612l-1.633,0.831c-5.759-0.998-11.261-3.038-16.688-5.154c0.484-2.464,0.892-4.928,1.844-7.24
c9.251-23.097,15.615-47.176,24.654-70.349c0.937-1.859,0.846-5.079,3.446-5.442c3.9,3.295,6.757,7.649,9.931,11.639
C706.687,1118.214,703.255,1133.844,698.01,1148.929z M713.579,1089.192c-3.008-3.477-5.85-7.059-8.918-10.445
c2.751-9.069,6.077-17.957,8.979-26.966c8.51-23.021,14.466-46.904,23.324-69.82c1.874-8.661,5.623-16.703,8.299-25.107
c4.081-9.734,6.167-20.164,10.475-29.838c1.693,0.423,3.235,1.164,4.625,2.207c0.378,2.464-0.741,4.867-1.36,7.225
c-4.807,13.302-7.119,27.329-11.548,40.737c-10.128,36.731-20.573,73.432-31.955,109.755
C715.015,1087.514,714.063,1088.633,713.579,1089.192z M718.583,1181.383c3.945-14.194,6.273-28.795,10.415-42.928
c0.726-3.386,0.62-6.969,2.449-10.007c3.295,3.205,7.225,6.228,8.888,10.687c-1.799,16.053-6.953,31.592-9.432,47.539
C726.745,1186.069,717.222,1188.351,718.583,1181.383z M769.991,1201.426c-7.664,0.62-11.488-6.878-14.269-12.682
c-5.215-3.114-13.151,1.481-17.141-3.9c1.859-6.893,2.494-14.012,3.93-20.981c0.982-5.26,3.099-10.248,3.613-15.584
c4.066,1.33,5.85,5.472,8.707,8.329c5.321,5.276,10.007,11.155,14.904,16.839c1.678,1.723,1.345,4.293,1.451,6.53
C770.944,1187.127,770.581,1194.276,769.991,1201.426z M773.513,1033.355c-0.559,40.797-0.65,81.61-2.207,122.392
c-4.096-0.227-5.91-4.535-8.601-6.984c-5.049-4.852-7.951-11.609-13.634-15.796c-1.33-6.968,1.149-13.755,2.585-20.482
c2.131-8.722,2.842-17.716,4.867-26.452c3.87-14.798,6.53-29.854,9.916-44.758c1.935-8.238,3.658-16.537,4.474-24.956l1.995-1.738
C774.707,1020.703,773.407,1027.082,773.513,1033.355z M772.077,980.087c-4.792,29.929-11.624,59.465-17.292,89.243
c-4.127,17.852-7.286,35.915-11.367,53.782c-3.688-1.89-6.424-5.003-8.979-8.163c3.174-13.392,5.442-27.027,9.719-40.117
c4.368-19.953,9.931-39.618,14.632-59.495c3.265-17.882,8.223-35.401,12.274-53.117c0.393-2.434,1.693-4.52,3.099-6.454
C775.539,963.958,772.939,971.985,772.077,980.087z M763.733,904.493c1.043-2.267,1.829-5.547,4.958-5.638
c-0.816,3.643,0.68,9.659-3.764,11.261C762.01,909.632,762.962,906.292,763.733,904.493z M776.944,820.314
c-2.086,2.086-4.414-0.65-5.789-2.101c-3.28-4.172-4.535-9.508-7.361-13.952c-7.633-12.259-13.967-25.243-20.845-37.925
c-2.464-4.973-6.651-9.84-6.046-15.72c13.589,0.892,27.057-3.22,40.646-1.391C778.622,772.896,777.594,796.627,776.944,820.314z
M784.517,346.315c-3.537,0.151-4.988-2.993-6.031-5.774c-1.481-4.671-5.789-8.238-5.064-13.498
c3.885,1.421,9.961-0.484,12.289,3.507C785.152,335.794,784.245,341.009,784.517,346.315z M796.671,233.476
c-1.98,14.571-2.207,29.279-3.658,43.881c-0.363,10.657-2.721,21.117-2.827,31.804c-4.973,0.741-9.931-0.378-14.753-1.406
c-3.734-1.512-2.252-6.167-1.768-9.16c5.079-21.948,11.836-43.458,17.504-65.27c0.907-3.038,2.434-5.835,3.93-8.616
C796.731,227.354,797.154,230.438,796.671,233.476z M1157.619,570.496c2.358,9.069,6.439,17.776,7.24,27.193
c3.779,20.588,2.6,41.735,0.151,62.413c-2.282,10.611-3.144,21.751-7.815,31.698c-2.6,6.711-4.55,13.68-7.452,20.285
c-7.437,15.055-12.395,31.274-20.678,45.906c-4.127,8.888-8.692,17.58-13.468,26.135c-11.533,18.078-21.661,37.018-31.622,55.974
c-4.701-2.403-7.543-6.908-10.868-10.793c-14.602-17.746-28.69-35.9-43.125-53.782c-2.781-2.781-0.801-6.621,1.255-9.115
c0.453-2.842,1.164-5.623,2.464-8.178c2.872-5.774,4.187-12.123,6.016-18.245c4.278-11.76,5.185-24.367,8.057-36.489
c1.33-8.933,1.028-18.003,1.678-26.997c-1.164-19.771-0.983-39.921-6.454-59.148c-1.814-6.545-2.963-13.408-6.349-19.378
c-2.932-9.024-10.566-16.385-11.035-26.18C1069.615,571.161,1113.602,569.574,1157.619,570.496z M1039.127,1014.551
c9.039-30.428,19.635-60.327,28.992-90.634c4.898-2.796,10.717-1.209,15.161,1.723c-4.127,11.019-7.18,22.401-11.624,33.3
c-7.074,24.382-15.841,48.234-22.9,72.616c0.121,2.993-3.839,3.537-5.487,1.663c-2.449-1.814-4.837-3.718-6.968-5.865
C1035.604,1022.879,1037.932,1018.723,1039.127,1014.551z M1026.157,1015.337c-0.786,0.136-2.358,0.408-3.144,0.544
c-15.509-12.848-30.473-26.316-45.906-39.255c-4.323-3.205-8.268-6.863-12.062-10.657c-20.24-17.111-40.329-34.449-59.934-52.285
c-3.053-2.237,1.33-4.761,3.446-4.928c5.23-1.617,9.614-4.943,14.209-7.785c2.101-0.831,4.051,0.62,5.563,1.935
c8.676,7.437,18.411,14.345,29.808,16.703c15.252,3.265,31.154,0.695,45.695-4.414c8.555-4.58,16.582-10.158,24.246-16.098
c2.146-1.391,4.202,0.892,5.789,2.131c6.953,6.757,14.224,13.74,23.444,17.247c-2.57,14.073-9.009,27.057-12.546,40.873
C1039.066,978.182,1031.886,996.518,1026.157,1015.337z M890.146,779.184c1.27-1.315,2.963-2.071,4.52-3.008
c3.462-5.215,8.828-8.571,13.559-12.501c3.628-2.948,6.621-6.696,10.702-9.069c2.192-1.104,4.232,0.982,6.243,1.678
c28.191,14.662,55.036,31.728,82.033,48.446c1.3-0.287,2.63-0.59,3.975-0.877c1.602-6.001,6.711-10.097,9.206-15.69
c1.95,0.151,3.945,0.771,4.913,2.66c17.247,21.359,34.781,42.46,51.136,64.529c-6.923,2.812-14.39,0.106-21.479-0.408
c-27.571-4.112-55.505-4.777-83.091-8.873c-23.218-2.222-46.299-5.699-69.411-8.722c-12.183-0.786-24.17-3.25-36.308-4.414
c-7.195-1.285-14.617-1.285-21.615-3.507c1.27-4.55,5.729-7.044,8.283-10.808C865.628,805.818,877.086,791.745,890.146,779.184z
M838.526,333.21c-4.58,6.152-7.361,13.393-11.896,19.605c-2.041,2.419-1.617,7.845-5.819,7.452
c-2.524,0.378-4.096-2.041-3.96-4.278c-0.408-8.661-0.151-17.353,0.302-26.014c5.215-0.287,10.445-0.484,15.675-0.726
C835.563,328.69,836.803,331.728,838.526,333.21z M816.003,265.431c1.587-9.81,0.181-19.817,1.814-29.642
c2.842,2.222,2.827,6.077,3.99,9.19c3.552,10.218,8.51,20.059,10.309,30.821c2.494,10.536,3.462,21.343,5.517,31.97
c0.045,2.63-2.796,3.356-4.882,3.008c-4.081-0.287-8.147-0.907-12.244-0.68c-1.496-1.285-2.948-2.57-4.383-3.87
C816.805,292.654,815.021,279.005,816.003,265.431z M815.384,820.057c0.061-7.543-0.227-15.146,0.847-22.628
c3.371-14.496,5.85-29.34,6.379-44.229c1.768-1.028,3.401-2.554,5.502-2.917c14.027,1.466,27.813,4.55,41.523,7.8
c0.529,2.434,0.998,5.185-0.877,7.24c-10.657,14.904-22.553,28.871-33.905,43.276c-4.807,5.336-9.946,10.369-14.904,15.554
C817.198,826.239,814.976,822.566,815.384,820.057z M826.887,1243.69c-6.303-1.149-12.062-4.127-17.61-7.195
c-0.816-2.691-1.512-5.427-1.406-8.238c0.151-31.743,1.421-63.456,2.026-95.199c0.015-5.684-0.151-11.443,1.602-16.93
c3.794,12.319,4.187,25.485,6.182,38.228c4.051,29.219,5.956,58.83,12.319,87.656C829.23,1242.435,827.673,1243.266,826.887,1243.69
z M838.541,1243.599c-2.66-15.66-5.532-31.305-7.21-47.116c-2.343-9.115-1.436-18.562-2.721-27.783
c-1.406-21.117-4.837-42.022-6.878-63.063c-1.345-13.695-4.55-27.103-5.94-40.782c-1.935-16.446-5.759-32.922-3.205-49.534
c4.263,7.528,3.749,16.582,6.122,24.744c1.602,11.942,5.487,23.475,5.865,35.567c0.892,14.375,4.958,28.251,7.165,42.445
c1.889,12.365,4.973,24.487,6.666,36.882c3.885,17.232,4.595,34.948,8.132,52.24c1.693,12.894,5.986,25.334,6.953,38.349
C848.487,1246.607,842.622,1247.363,838.541,1243.599z M882.059,1246.879c-8.646-39.83-16.521-79.841-23.717-119.943
c-2.857-10.007-3.598-20.421-6.243-30.458c-5.457-26.619-11.427-53.147-16.385-79.856c-3.975-16.839-6.182-34.086-9.931-50.91
c0.71,0.106,2.131,0.317,2.842,0.423c6.152,20.195,10.626,40.858,16.219,61.219c2.902,11.05,4.247,22.432,7.331,33.451
c4.913,16.914,9.583,33.905,14.647,50.774c6.968,28.342,13.211,56.865,20.361,85.162c2.358,6.213,1.784,13.03,3.885,19.318
c2.494,10.959,5.714,21.751,7.921,32.756C893.26,1249.328,887.486,1248.754,882.059,1246.879z M945.968,1250.643
c-3.779-0.529-9.946,1.89-11.412-3.038c-4.716-11.533-8.117-23.535-12.002-35.356c-5.532-15.796-10.687-31.698-16.128-47.509
c-7.603-20.104-16.159-39.89-22.205-60.554c-4.429-11.579-7.225-23.671-11.624-35.25c-5.895-21.222-13.831-41.795-21.056-62.579
c-5.321-17.307-11.639-34.298-17.685-51.363c-5.971-13.77-10.898-27.934-15.524-42.203c-1.134-4.429-3.099-8.707-3.22-13.332
c11.155,4.006,22.296,8.314,34.086,10.128c10.052,2.63,20.497,1.965,30.745,3.114c5.714,0.831,12.622-1.391,17.126,3.28
c39.104,33.542,77.846,67.446,117.026,100.897c3.552,3.265,9.553,6.772,7.195,12.531c-19.968,63.032-42.49,125.203-62.503,188.206
C954.312,1228.589,951.818,1240.258,945.968,1250.643z M966.465,1235.799c0.438-9.553,5.155-18.184,7.89-27.193
c4.066-13.77,9.19-27.193,14.269-40.601c3.628-13.211,8.238-26.105,12.062-39.24c8.495-22.296,16.113-44.924,22.477-67.915
c3.371-8.858,6.001-18.048,10.415-26.468c3.87,2.827,7.77,5.82,10.747,9.659c-0.544,7.255-5.23,13.483-6.046,20.754
c-3.582,10.128-5.668,20.693-9.704,30.67c-5.155,17.912-13.665,34.645-18.698,52.588c-4.293,13.211-8.858,26.301-13.362,39.452
c-6.772,18.094-10.838,37.291-20.497,54.22C972.391,1244.899,966.934,1239.548,966.465,1235.799z M1125.724,1216.92
c-9.538,13.12-23.853,22.175-39.24,26.846c-10.778,2.298-21.797,4.187-32.846,3.416c-5.457-0.484-10.914,0.348-16.355,0.212
c3.794-4.489,9.765-5.578,14.949-7.694c12.153-5.018,24.926-8.404,36.837-13.997c12.274-4.399,24.578-8.692,36.671-13.559
C1129.216,1212.37,1126.616,1215.574,1125.724,1216.92z M1144.801,1133.194c-10.762,21.464-28.402,38.485-47.222,52.965
c-20.603,17.277-43.82,30.957-65.829,46.284c-8.888,6.515-18.91,11.186-27.904,17.504c-3.341,2.494-7.437,3.189-10.611,0.015
c1.103-14.042,8.404-26.468,11.76-39.966c5.154-16.083,9.553-32.423,15.221-48.355c1.98-5.85,3.628-11.82,4.822-17.867
c0.499-0.574,1.496-1.693,2.01-2.267c2.056-9.825,5.638-19.242,8.948-28.705c2.645-8.873,5.517-17.701,8.692-26.407
c0.665-1.376,0.302-2.887,0.076-4.293c4.429-4.369,3.9-11.05,6.591-16.295c1.663-3.326,2.615-6.938,3.431-10.536
c3.144-0.665,5.608,1.436,7.845,3.341c26.256,22.734,52.981,44.909,78.844,68.081
C1143.198,1128.266,1146.66,1130.473,1144.801,1133.194z M1148.852,1122.885c-8.299-4.293-14.375-11.609-21.676-17.262
c-21.177-17.398-41.614-35.658-62.428-53.479c-2.963-2.373-7.709-5.26-5.744-9.735c10.974-31.244,20.119-63.063,29.959-94.67
c3.87-6.636,4.399-14.783,9.281-20.89c2.086-2.721,3.28-5.971,4.096-9.266c1.24-6.076,4.61-11.427,5.82-17.504
c2.177-1.844,4.006,0.892,5.2,2.449c12.168,17.776,22.87,36.64,31.093,56.578c6.953,16.098,12.803,32.756,15.66,50.109
c3.25,17.957,6.001,36.278,3.446,54.507c-2.298,19.016-5.306,38.137-12.622,55.958
C1150.408,1120.482,1149.381,1122.084,1148.852,1122.885z M1173.717,1201.063c-6.454,0.136-13.12,1.874-19.454,0.287l0.03-1.663
c4.716-2.978,10.203-4.52,15.554-5.94C1173.687,1193.747,1174.11,1198.161,1173.717,1201.063z M1233.983,862.955
c-26.438,1.33-52.618-3.628-79.01-4.157c-12.637-1.073-25.319-1.753-37.94-2.902c-4.293-0.605-10.475,0.816-12.44-4.217
c8.329-5.578,18.396-7.014,27.873-9.614c26.966-7.361,55.157-6.968,82.834-9.311c6.424,1.028,14.919-2.645,19.877,2.464
C1235.57,844.438,1235.858,853.855,1233.983,862.955z M1244.081,409.453c-0.801,0.076-2.403,0.257-3.205,0.348
c-2.902-3.507-6.953-6.893-6.575-11.896c3.265,0.212,6.56,0.469,9.855,0.741C1244.458,402.228,1244.746,405.871,1244.081,409.453z
M1246.348,388.775c-7.422,1.3-15.055,1.391-22.432-0.076c1.92-12.425,7.558-23.883,11.941-35.582
c3.25-9.719,5.986-19.756,11.125-28.69l1.542-1.119c0.862,0.06,2.585,0.181,3.446,0.242
C1249.054,345.197,1248.509,367.039,1246.348,388.775z M1268.674,840.07c0.665-3.144,4.671-2.207,7.014-2.283
c13.619,1.663,27.39,1.905,40.964,4.006c18.97,3.598,38.001,7.996,55.656,15.992c5.653,2.524,11.715,4.353,16.839,7.921
c0.318,0.756,0.967,2.282,1.3,3.038c-21.615,0.756-43.216-0.922-64.801-1.693c-19.771-1.436-39.724-0.091-59.359-2.993
C1265.953,856.017,1266.558,847.839,1268.674,840.07z M1377.159,1197.753c-12.606-1.512-25.304,0.877-37.956,0.151
c-19.121-1.058-38.258-1.602-57.394-1.209c-2.282-1.028-1.648-4.232-2.086-6.228c-0.499-7.588,2.086-14.98,1.194-22.583
c-0.453-3.068,3.265-3.719,5.517-3.96c8.949-0.65,18.063-1.27,26.936,0.514c18.048,2.192,36.807,4.867,52.724,14.269
c9.19,6.243,18.003,13.075,25.742,21.086c1.013,1.345,1.889,2.796,2.63,4.368C1387.619,1205.356,1383.81,1197.738,1377.159,1197.753
z M1555.6,1231.642c-8.238,3.265-16.219,7.225-24.608,10.067c-13.634,3.068-26.921,7.573-40.722,9.825
c-7.921,1.451-15.902,5.154-24.049,3.129c-7.377-2.086-15.705-1.134-22.25-5.668c-5.306-3.9-11.639-6.303-16.159-11.201
c-3.492-3.824-8.722-6.228-10.868-11.125c13.604-5.336,27.828-9.145,42.203-11.76c41.266-9.145,82.789-17.111,124.236-25.349
c1.693-0.227,4.489-1.104,5.306,1.119c3.34,6.681,4.58,14.179,5.986,21.479C1582.824,1220.714,1568.706,1225.4,1555.6,1231.642z" />
</svg>

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,13 @@
<% var titleLink = (site && site.base_uri) ? site.base_uri : '/'; -%>
<div class="pageHeader-titleBlock clearfix">
<h1 class="siteTitle">
<a href="<%= titleLink %>" class="siteTitle-link">
<%- include('shipIcon') %>
<span class="siteTitle-text">
<%= site.title %>
</span>
</a>
</h1>
</div>

View File

@ -0,0 +1,11 @@
<!-- TOOLBAR BEGIN -->
<div class="toolbar clearfix">
<a href="/" class="logoBtn">
<img src="/images/mw-logo-wide-300x70.png" class="toolbar-logo" />
</a>
<a href="#pageMenu" class="linkButton linkButton-pageMenu js-linkButton-pageMenu">Menu</a>
</div>
<!-- TOOLBAR END -->

View File

@ -0,0 +1,88 @@
<!-- TOP BEGIN -->
<%
const
getPageField = (field_name) => {
return page[field_name] || site[field_name] || '';
},
getUrl = () => site.base_uri + (site.base_uri.endsWith('/') ? '' : '/') + page.path;
-%>
<!doctype html>
<!--[if lt IE 7]> <html class="no-js ie lt-ie10 lt-ie9 lt-ie8 lt-ie7" lang="en" xmlns:fb="http://ogp.me/ns/fb#"> <![endif]-->
<!--[if IE 7]> <html class="no-js ie lt-ie10 lt-ie9 lt-ie8" lang="en" xmlns:fb="http://ogp.me/ns/fb#"> <![endif]-->
<!--[if IE 8]> <html class="no-js ie lt-ie10 lt-ie9" lang="en" xmlns:fb="http://ogp.me/ns/fb#"> <![endif]-->
<!--[if IE 9]> <html class="no-js ie lt-ie10" lang="en" xmlns:fb="http://ogp.me/ns/fb#"> <![endif]-->
<!--[if gt IE 9]> <html class="no-js ie" lang="en" xmlns:fb="http://ogp.me/ns/fb#"> <![endif]-->
<!--[if !IE]>--> <html class="no-js" lang="en" xmlns:fb="http://ogp.me/ns/fb#"> <!--<![endif]-->
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<base href="<%= getUrl() %>" />
<title><%= page.title ? page.title + ' | ' : ''%><%= page.sub_title ? page.sub_title + ' | ' : ''%><%= site.title %></title>
<link rel="pgpkey" type="application/pgp-keys" title="PGP Public Key" href="/files/public.aexpk" />
<!-- Courtesy of https://www.favicon-generator.org/ -->
<link rel="apple-touch-icon" sizes="57x57" href="/images/favicons/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/favicons/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/favicons/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/favicons/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/favicons/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/favicons/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/favicons/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/favicons/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/favicons/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/images/favicons/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicons/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<meta name="msapplication-TileColor" content="#9aa8bc">
<meta name="msapplication-TileImage" content="/images/favicons/ms-icon-144x144.png">
<meta name="theme-color" content="#9aa8bc">
<meta name="description" content="<%= getPageField('description') %>">
<meta name="author" content="<%= (page.author || site.author).name %>">
<meta name="generator" content="Gulp"/>
<meta name="keywords" content="<%= getPageField('keywords') %>">
<meta name="robots" content="<%= getPageField('robots') %>">
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:url" content="<%= site.base_uri %><%= page.path %>" />
<meta property="og:site_name" content="<%= site.title %>" />
<meta property="og:title" content="<%= getPageField('title') %>" />
<% if (page.image) { %>
<meta property="og:image" content="<%= page.image %>"/>
<% } %>
<% if (page.description) { %>
<meta property="og:description" content="<%= page.description %>" />
<% } %>
<!-- Twitter Card -->
<% if (page.image) { %>
<meta name="twitter:card" content="summary_large_image" />
<% } else { %>
<meta name="twitter:card" content="summary" />
<% } %>
<meta name="twitter:url" content="<%= site.base_uri %><%= page.path %>" />
<meta name="twitter:title" content="<%= getPageField('title') %>" />
<meta name="twitter:description" content="<%= getPageField('description') %>" />
<meta name="twitter:image:src" content="<%= getPageField('image') %>" />
<% if (page.author && page.author.twitter) { %>
<meta name="twitter:creator" content="<%= page.author.twitter %>" />
<% } else if (site.author && site.author.twitter) { %>
<meta name="twitter:creator" content="<%= site.author.twitter %>" />
<% } %>
<link rel="start" href="<%= site.base_uri %>/"/>
<link rel="contents" href="/sitemap.xml" title="Sitemap"/>
<link rel="alternate" type="application/rss+xml" title="Recent Changes" href="/feed"/>
<link rel="canonical" href="<%= site.base_uri %><%= page.path %>"/>
<link rel="stylesheet" href="/styles/imports.css" type="text/css" />
<link rel="stylesheet" href="/styles/fonts.css" type="text/css" />
<link rel="stylesheet" href="/styles/styles.css" type="text/css" />
</head>
<!-- TOP END -->

View File

161
src/layouts/tag.ejs Normal file
View File

@ -0,0 +1,161 @@
<%
var cwd = (process && process.cwd) ? process.cwd() : '';
var titleLink = (site && site.base_uri) ? site.base_uri : '/';
const { entriesToList, pageCount, pageNum, tag } = page;
-%>
<%- include('partials/top') %>
<!-- BEGIN TAG PAGE -->
<body class='postMainIndex'>
<a name="top" id="topAnchor" class="topAnchor"></a>
<div class="page">
<header id="header" class="pageHeader pageSection">
<div class="pageHeader-titleBlock clearfix">
<h1 class="siteTitle"><a href="<%= titleLink %>" class="siteTitle-link"><%= site.title %></a></h1>
</div>
<%- include('partials/navmain') %>
</header>
<main id="content" class="pageMain pageSection">
<div class="pageMain-inner">
<%- include('partials/embed_switch') -%>
<%- include('functions') -%>
<% if (page.title && (page.render_opts || '').indexOf('no_title') == -1) { -%>
<h2>
Journal Entries By Tag: <a
class="boxLink isCurrent"
href="<%= page.path %>"
id="<%= snakeCase(page.title) %>"
name="<%= snakeCase(page.title) %>"
>#<%= tag -%></a>
</h2>
<% if (pageCount > 1) { -%>
<p>
(Page <%= pageNum %> of <%= pageCount %>)
</p>
<% } -%>
<p>
Assorted journal entries with the tag <em>#<%= tag %></em>.
</p>
<hr class="feedLine">
<p>
<% } -%>
<% if (content && Array.isArray(entriesToList)) { -%>
<% entriesToList.forEach((page) => { -%>
<article class="update journal h-entry js-update">
<div class="p-author author h-card vcard authorHidden">
<% if (page.author && page.author.site && page.author.name) { -%>
<% if (page.author.photo) { -%>
<a href="<%= page.author.site %>"><img class="u-photo" src="<%= page.author.photo %>"></a>
<% } -%>
<a class="p-name fn u-url url" rel="author" href="<%= page.author.site %>"><%= page.author.name %></a>
<% } else if (site.author && site.author.site && site.author.name) { -%>
<% if (site.author.photo) { -%>
<a href="<%= site.author.site %>"><img class="u-photo" src="<%= site.author.photo %>"></a>
<% } -%>
<a class="p-name fn u-url url" rel="author" href="<%= site.author.site %>"><%= site.author.name %></a>
<% } -%>
</div>
<h3 id="<%= snakeCase(page.title) %>" >
<a class="p-name u-url" href="/<%= page.path %>"><%= page.title %></a>
</h3>
<% if (page.tldr || page.description) { -%>
<p><i>TL;DR &mdash; <%- page.tldr || page.description %></i></p>
<% } -%>
<% if (page.readTime) { -%>
<p class="journal readTime">
<span class="icon glasses-icon">&#x1f453;</span> <%= page.readTime %>
</p>
<% } -%>
<% if (page.content) { -%>
<div class="e-content">
<%- page.content %>
</div>
<% } -%>
<footer class="update-footer clearfix">
<% if (Array.isArray(page?.tags) && page.tags.length) { -%>
<div class="update-tags">
<span class="icon folder-icon">&#x1f4c1;</span>
<% page.tags.forEach((tag, index) => { -%>
<%= index > 0 ? ' / ' : '' %>
<a href="/journal/tags/<%= tag %>/index.html">
#<span class="p-category category"><%= tag %></span>
</a>
<% }) -%>
</div>
<% } -%>
<!--
<span class="update-citation">[iew.us q/1g0k2]
<a class="u-shortlink" type="text/html" rel="shortlink" href="https://iew.us/q/1g0k2">&#128279;</a>
</span>
-->
<a rel="bookmark" href="/<%= page.path %>" class="u-url update-footer-link update-footer-time">
<time datetime="<%= page.date_pub %>"
class="dt-published published js-pubDate pubDate"
><%= prettyDate(page.date_pub) %></time>
</a>
</footer>
</article>
<p class="backLink">
<a class="backLink-link" href="#top">Top</a>
</p>
<hr class="feedLine">
<% }) -%>
<% } -%>
<%- include('partials/pageMenu') %>
<!-- END TAG PAGE -->
</div>
</main>
<%- include('partials/journal/menusub') %>
<%- include('partials/bio') %>
<%- include('partials/footer') %>
</div>
<%- include('partials/bottom') %>

View File

@ -0,0 +1,205 @@
---
title: Astral Adventuring
description: Notes and rules for adventuring in the astral plane.
date_pub: 2023-02-19T15:10:00-05:00
section: astral
content_type: feature
short_code: aa1
---
[[toc]]
### Sequence of Play Per Day
1. Decide course: The players decide on their course of travel for the day.
2. Losing direction: The referee determines whether the party gets lost.
3. Wandering monsters: The referee makes checks as applicable.
4. Description: The referee describes the regions passed through and any sites of interest that the party comes across, asking players for their actions, as required. If monsters are encountered, follow the procedure described in Encounters.
5. End of day: The referee updates time records, with special attention spell durations.
### Astral Currents
Invisible flows of psychic energy that permeate and swirl around the astral plane.
**Creatures** - Can move up to 10x faster when moving with the current.
**AstralJammers** - Ships designed to ride on these currents.
### Calendar
As of last session, it is the 3rd phase of the 6th Aerday of Urtson, in year 5023 of the Common Astral Calendar.
- Each day on the astral plane is broken up into 3 phases of 24 hours (to keep the astral calendars in sync with the material planes).
- There are 6 days in a week, 5 weeks in a month, 3 months in a season, and 4 season in a year.
- Each day is named after one of the elements:
- Urtday (Earth)
- Aerday (Air)
- Fyday (Fire)
- Warday (water)
- Kayday (Chaos)
- Lawday (Law)
- Each month is named after an alignment (Law / Kay / Nu)
- Each season is named after a natural element:
- Urtson (Earth)
- Aerson (Air)
- Fyson (Fire)
- Warson (Water)
### Distance and Measurement
**Ranges**: Are measured in yards, instead of feet, which means they are tripled.
**Movement rates**: Are still measured in feet.
**Areas**: Of spell effects, breath weapons, etc. are also still measured in feet.
### Evasion and Pursuit
#### Evasion
The chance of evasion is determined by the difference between the two sides movement rates, listed in the table below.
**Success**: If the evasion roll succeeds, the pursuers cannot attempt to catch up with the fleeing side until the next day, and then only if a random encounter roll indicates an encounter.
**Failure**: If the evasion roll fails, a pursuit occurs.
#### Pursuit
**Time**: Is measured in rounds.
**Initial distance**: The two sides begin a pursuit at normal encounter distance.
**Closing in**: The distance between the two sides decreases by the difference between their two movement rates each round (a minimum of 30 per round).
#### Astralborne Evasion
**By ship** - Use the same table, but replace feet with yards in the measurements speed.
<div class="dividedTableWrapper">
| Fleeing Sides Movement Rate | Chance of Evasion |
| -------------------------------------- | ----------------- |
| Faster than pursuer | 80% |
| 030 per round slower than pursuer | 50% |
| 3160 per round slower than pursuer | 40% |
| 6190 per round slower than pursuer | 35% |
| 91120 per round slower than pursuer | 25% |
| 121+ per round slower than pursuer | 10% |
[Astralborne Evasion Chance]
</div>
### Fragments
Pieces of other planes (usually floating) in the astral plane. The shape is typically determined by the plane of origin:
- **Fire** - Fragments from the elemental plane of fire are usually globes of elemental fire (used in Dwarven fire engines and some Drahki weapons).
- **Water** - Fragments from the elemental plane of water are usually globes of water.
- **Earth** - Fragments from the elemental plane of earth are usually irregularlly-shaped chunks of rock and dirt.
- Large fragments of earth are called **islands**, and often used to support a [stronghold](https://oldschoolessentials.necroticgnome.com/srd/index.php/Structures) or [outpost](#outposts) in the astral void.
- **Air** - Fragments from the elemental plane of air are usually globes of air (used to supply some ships travelling to airless planes)
- **Chaos** - Fragments from the elemental plane of chaos are usually jet black globes, and tend to function as spheres of annhilation.
- **Law** - Fragments from the elemental plane of law are usually globes of crackling, radiant energy.
- It's believed that these arise spontaneously when a fragment from the plane of chaos appears, and are equal in size to the chaos ones.
- When a fragment of law and chaos are combined, the resulting release of energy can be seen for thousands of miles around.
### Gravity
**Subjective gravity**: Capable creatures (INT 5 or greater) can change "down" with a thought (CHA check if under duress).
**Falling / Flying**: Speed is INT x 30' / round (INT x 10' / encounter), specific maneuvers require a CHA check.
**Free floating**: Objects float in space unless able to move on their own (as above) or acted upon by an external force.
**Unintelligent creatures**: Fall "down" at 100' / round.
### Losing Direction
**With a navigator aboard**: The chance of getting lost is 1-in-6.
**Without a navigator aboard**: The chance of getting lost is 100% in the open astral void and 2-in-6 within sight of an astral body.
**Effects**: See [Losing Direction](https://oldschoolessentials.necroticgnome.com/srd/index.php/Hazards_and_Challenges).
### Outposts
A [stronghold](https://oldschoolessentials.necroticgnome.com/srd/index.php/Structures), shop, or town in the astral plane, usually established on a large (island)[#fragment].
**Docks**: Those interested in trade will have one (or more) docks for passing ships.
**Ports**: Outposts with permanent [portals](#portals) to other planes.
### Psychic Clouds
Thin streams of psychic "vapor", hundreds to thousands of miles across, which appear as clouds when viewed from distance.
**Psychic Storms** - Enormous storms of psychic energy which can disable a ship as well as its crew. Often found crawling with astral zombies. Best to avoid.
### Portals
**Use**: Step through to arrive on the destination plane.
**Destination**: Can lead to any other plane (Inner, Outer, or Material).
**Color**: Each different destination has its own color, and a portal leading to a character's home plane will always look silver to them.
**Directionality**: Most portals (99%) are one-way, and only visible (when open) from the entry side, but become two-way for 1d4 rounds after a character steps through.
**Duration**: Most portals (99%) are temporary, lasting 1d20 hours.
**Interactivity**: Can be opened, closed, or made translucent (allowing characters to see the other end for one turn) with a successful CHA check within 60' of the portal.
**Pools**: Temporary portals which lead from the astral to other planes.
**Ports**: Permanent portals to other planes, often established on a (island)[#fragment] and attached to an (outpost)[#outpost].
### Surprise
Wandering astral monsters are not usually surprised by travelling vessels. Special circumstances (e.g. thick astral clouds, psychic storm) may alter this.
### Temperature
The ambient temperatue in the astral plane tends towards the tepid side for most humans and demihumanas, being roughly equaivalent to a war spring day in a temperate climate.
### Time
**Quickened**: 3 rounds pass in the astral plane for each round that passes in a Material Plane.
- Spells cast in the Astral plane appear to last 3 times as long as listed
**Effects Stopped**: The effects of time don't happen while on the astral plane. This means that characters in the Astral Plane:
- do not age,
- do not need to breathe,
- do not need to eat or drink (which also means that they can't expend a ration to heal), and
- do not need to sleep (which also means that they can't heal from sleep), although they do still need to rest for 8 hours each day
### Travel
**By Ship** - Travel between most ports under the control of the Astral Trade Union takes 1d6+2 days.
**Without a Ship** - The vast distances between landmarks means one could fall for weeks, months, or even years before coming across any other creatures.
**Docking**: It takes 1 turn for a ship to dock, and 1 turn for it to disembark.
### Visibility
**Astral Bodies**: An astral body (such as a fragment, island, or even a dead god) may be spotted at a distance of hundreds of miles (based on its size).
**Ships**: May be sighted at great distances by the contrails they leave behind in the psychic clouds that permeate the plane. Identification is usually limited to tactical range (approxmiately 1 mile) in the open astral plane, or as little as 100 yards in a psychic storm.
- maybe differentiate between tactical and travel speed?
- travel speed = within 12 hours, can see the contrails (of other ships moving at travel speed)
- sighting ships by travel time
- what is the range of slowdown?
### Wandering Monsters
**Frequency**: A check is typically rolled once every other day, but the referee may choose to make more checks (e.g. when near a port or outpost): up to 1 or 2 a day.
**Chance**: The chance of encountering a wandering monster is 1-in-6 in the deep astral, 2-in-6 near other ports or outposts.
**Distance**: Wandering monsters are encountered 4d6 × 10 yards away. If either side is surprised (see Encounters), this is reduced to 1d4 × 10 yards.
**Location**: Astral encounters may occur either in the open astral plane or on land (such as a fragment, island, or dock), if the party lands or docks at some point during the day.

View File

@ -0,0 +1,95 @@
---
title: Astral Factions
description: TBD
date_pub: 2023-02-15T00:26:00-05:00
section: astral
content_type: feature
short_code: af1
---
### Astral Trade Union
- Conglomerate of harbormasters, ship captains, and mining companies who control nearly all of the trade between certain ports.
- Control all of the trading between nearly 100 ports, including ones leading to dozens of material planes, multiple to each inner plane, and even a handful to various outer planes.
- Have excellent maps of all of the astral currents that run between these ports.
### Drahki Federation
- Controlled by a race of dracokin from the world of Drahkenos, the so-called plane of dragons.
- Fly great ships made to look like their bigger cousins (called "dragonships")
- Generally peaceful traders
### Ghyffan Armada
- Run by a militaristic race of musket-wielding hippokin from the material plane of Ghyffu.
- Report to the Ghyffan Sovereignty, the plane-wide government of Ghyffu.
- Strongly believe in the supremacy of law and civilization.
- Ships range from light cargo carriers to massive warships, but everything in the armada is armed.
### Society of Wanderers
- Disparate, quasi-religious organization, with membership scattered about the planes.
- Refer to each other as "friends".
- Are obligated to help each other out (but can also count on other "friends" to help them when needed).
- Typically travel alone or in small groups.
- Don't usually have their own ships, so they often have to book passage (or stowe-away).
### Stral Empire
- Empire of ports ruled by a highly militaristic and decadant race of immortals, with sharp eyes, pointed ears, and no noses.
- Huge warships patrol the borders of the empire, which run near some Astral Trade Union-aligned ports.
- Also control the Forge, a massive smithing factory on the Plane of Fire.
- Served by a race of automatons called Forgelings, some of whom have escaped their masters and fled to ports aligned with one of the other factions.
### Pirates of Ataxia
- Led by (at least) 6 Pirate Lords (although some believe there are more).
- Responsible for nearly all of the piracy within this part of the astral plane.
- Come from all races and cultures.
### Relations
#### Astral Trade Union
- Are on good (trading) terms with both the _Drahki Federation_ and the _Ghyffan Armada_ (with some members of each also holding Union membership).
- Think most of the _Society of Wanderers_ are hopeless layabouts, inadequately interested in commerce (although few ship captains will refuse their payment for passage).
- Are on tenuous terms with the _Stral Empire_ (they've had some trade, but also some border skirmishes).
- Actively seek the destruction of the _Pirates of Ataxia_ (even though some Union members occasionally double as pirates).
#### Drahki Federation
- Are on good (trading) terms with _Astral Trade Union_ (with some Drahki captains also being Union members).
- Are on decent (trading) terms with the _Ghyffan Armada_ (there have been a few isolated incidents of fighting between the groups, but they generally continue to trade).
- Don't understand the _Society of Wanderers_, but generally allow them to book passage on their ships.
- Are on deteriorating terms with the _Stral Empire_ (they've had an increasing number of border skirmishes).
- Try to avoid engaging with ships from the _Pirates of Ataxia_, who generally return the favor (although there are some Drahki pirates who try to only attack ships from the other factions).
#### Ghyffan Armada
- Are on good (trading) terms with _Astral Trade Union_ (with some Ghyffan captains also being Union members).
- Are on decent (trading) terms with the _Drahki Federation_ (there have been a few isolated incidents of fighting between the groups, but they generally continue to trade).
- Don't understand (or like) the _Society of Wanderers_, and don't allow them on their ships.
- Are on deteriorating terms with the _Stral Empire_ (they've had an increasing number of border skirmishes).
- Actively seek the destruction of the _Pirates of Ataxia_ (although some Ghyffan ships have been known to turn pirate, abandoning the Armada and working for the Pirate Lords).
#### Society of Wanderers
- Don't care for the leadership of the _Astral Trade Union_, but try to stay friendly with the ship captains.
- Don't understand most of the _Drahki Federation_, but are generally happy to book passage on their ships.
- Don't get along with the _Ghyffan Armada_, and generally avoid them.
- Don't understand (or like) the _Stral Empire_, although a few Travellers have tried to engage with Stral crews (which hasn't ended well)
- Have many members who also work as _Pirates of Ataxia_, and don't generally preclude pirates from their ranks (although the pirate captains aren't always thrilled to have Wanderers on their ships).
#### Stral Empire
- Are on tenuous terms with the _Astral Trade Union_ (they've had some trade, but also some border skirmishes).
- Are on deteriorating terms with both the _Drahki Federation_ and the _Ghyffan Armada_ (they've had an increasing number of border skirmishes with each of them).
- Don't understand (or like) the _Society of Wanderers_, and don't allow them on their ships.
- Try to avoid engaging with ships from the _Pirates of Ataxia_, who generally return the favor (although they have had engagements in Stral territory).
#### Pirates of Ataxia
- Actively attack (and raid) the ships of the _Astral Trade Union_ and the _Ghyffan Armada_, occasionally even looting their ports.
- Try to avoid engaging with ships working for the _Drahki Federation_, who generally return the favor (although there are some Drahki pirates who try to only attack ships from the other factions).
- Have many members who also identify as members of the _Society of Wanderers_, usually pirates of lower ranks (the ship captains aren't fond of having members in their crew because of the high number of stowe-aways).
- Try to avoid engaging with ships of the _Stral Empire_, who generally return the favor (although they have had a few engagements in within Stral territory).

30
src/pages/astral/index.md Normal file
View File

@ -0,0 +1,30 @@
---
title: The Astral Plane
description: TBD
date_pub: 2023-02-15T00:26:00-05:00
section: astral
content_type: feature
short_code: a1
---
- Vast, impossibly large, possibly infinite in scale
- Looks like a boundless, open sky, the color of dark purple
- Filled with clouds of swirling, brightly-colored psychic vapor
- Distant arcs of light (and dark) cascade across the sky
- Everything appears to have a vague, silvery sheen to it
- Gives off a perpetual twilight
- Known as "the swirling void", or "boundless infinities"
- Air feels thicker than on most material planes, and has a vague scent of chocolate
- Subjective gravity (for intelligent creatures)
### More Info
- [Adventuring](./adventuring.html)
- [Factions](./factions.html)
- [Monsters](/monsters/index.html)
- [Timeline](./timeline.html)
- [Vessels](./vessels.html)
<!--
- [Zenpus Cove](./zenopus-cove.html)
->

View File

@ -0,0 +1,48 @@
---
title: Astral Timeline
description: A timeline of trade and war in the astral plane.
date_pub: 2023-02-19T20:47:00-05:00
section: astral
content_type: feature
short_code: at1
---
As of the start of the campaign, it is the
Today is the 3rd phase of the 6th Aerday of Urtson, in year 5023 of the Common Astral Calendar.
Below is a timeline of major events in (this section of) the astral plane:
- **CAC** - The Common Astral Calendar
- **BAC** - Before the (Common) Astral Calendar
<div class="dividedTableWrapper timelineTableWrapper">
| Date | Event |
| :-------------------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **???** | The gods arrive in and/or create the astral plane. |
| **circa 11,000 BAC**<br />_(ca. 16,000+ years ago)_ | The Stral arrive in the astral plane, establish the Eternal Dynasty. |
| **circa 5,000 BAC**<br />_(ca. 10,000 years ago)_ | The Damned Incursion occurs, ending with the fallen angel Shai'kel taking over [Infernus](/planes/infernus.html). |
| **circa 3,000 BAC**<br />_(ca. 8,000 years ago)_ | The Arcane Lords of Axion establish the first Mage Guilds. |
| **circa 2,000 BAC**<br />_(ca. 7,000 years ago)_ | The Eye Tyrants arrive in the astral plane, establish the Empire of Eyes. |
| **0 CAC**<br />_(5023 years ago)_ | The ATU is founded by a group of astral traders, the Purple Masters of Prasha.<br />The Arcane Lords of Axion and the Purple Masters establish the Common Astral Calendar, using this year to mark year 0. |
| **118 CAC**<br />_(4905 years ago)_ | The Axion Academy of Magicks is founded. |
| **circa 2,000 CAC**<br />_(ca. 3,000 years ago)_ | The Dwarves arrive in the astral plane, establish a number of independent strongholds. |
| **2287 CAC**<br />_(2736 years ago)_ | The Eye Tyrants are defeated at the battle of Jade Fields by the combined efforts of the stral and the dwarves.<br />Several other catastrophes befall the tyrants, leading to the great Civil War, which nearly wipes out the race. |
| **2289 CAC**<br />_(2734 years ago)_ | The Arcane Lords vanish. |
| **circa 3,000 CAC**<br />_(ca. 2,000 years ago)_ | The Drahki arrive in the astral plane, establish the Drahki Federation. |
| **3961 CAC**<br />_(1062 years ago)_ | The Stral slay the Dwarf king Thurborg Goldhelm and claim ownership of the Forge. |
| **4759 CAC**<br />_(264 years ago)_ | The Ghyffan Expeditionary Force arrives in the astral plane. |
</div>
<!--
Dates to add later:
| **4644 CAC**<br />_(379 years ago)_ | Zenopus arrives to the astral plane from Ayreon. |
| **4925 CAC**<br />_(98 years ago)_ | Zenopus establishes Sapphire Cove. |
-->

207
src/pages/astral/vessels.md Normal file
View File

@ -0,0 +1,207 @@
---
title: Astral Vessels
description: TBD
date_pub: 2023-02-15T00:26:00-05:00
section: astral
content_type: feature
short_code: av1
---
[[toc]]
### Astral Consoles
- Allow the ship to ride along the naturally occurring astral currents.
- Often shaped like a high-backed chair.
- Controlled by a **Pilot**.
- It takes one round to activate a console, and one round to deactivate a console.
- While active:
- The pilot can sense the astral currents that surround the ship.
- The pilot may move the ship in any direction at individual flight / "falling" speed (INT \* 30')
- can see currents and (subtly) move ship between them (although you can't turn the ship without using the sails)
- viewpoint is the front of the ship (as if standing on the bow), but can be turned 90 degrees left or right (so behind can be seen, but not well)
- The only movement capable via a standard astral console is forward (at
- It can take up to 1 turn to find a suitable current to ride at "traveling speed"
- Once a console has been installed on a vessel, it cannot be forcibly removed (without using the "release" word) until the integrity of the vessel itself has been compromised.
- The pilot can be any character class.
- Piloting a ship via a console requires concentration, and shouldn't be done for more than 8 hours in a row.
- Piloting beyond 8 hours runs the risk of incurring exhaustion.
- The pilot is not incapacitated while piloting the ship - instead, they can choose whether to focus on the ship's POV or their own, while still being peripherally aware of both.
- They have limited ability to move, -4 to all attacks, and can't cast spells, use psionics, or do anything else that would break their concentration.
- If the pilot is pulled or thrown from the console, their connection to the ship is immediately broken, and the ship instantly stops moving.
#### Multiple Consoles
- A ship can have multiple consoles attached to it, but only one may be active at a time.
- If two (or one and a different type of engine) are both active at the same time on the same vessel, perform a check as follows:
- For two consoles (or similar devices), make a contested CHA check between the two pilots, with the loser being locked-out of controlling the ship.
- For one console and an inert device (like a Fire Engine), the pilot should make a CHA check to override the device and take control of the vessel.
<!--
##### Usage with a Planar Compass
+ Using an astral console while wearing a planar compass provides the pilot with an enhanced ability to see the ship, its surrounding area, and even distant targets.
+ The compass can be focused on any ship or location within one day's travel of the wearer at their current speed.
-->
#### Other Types of Engines
- **Fire Engines** - A large thruster, built (and usually controlled) by Dwarves, and powered by elemental fire. Rumored to be capable of propelling even the largest vessels at traveling speeds.
- **Life Engines** - Some evil-aligned races use the life energy from slaves and captives to power their vessels.
- **Mind Engines** - Some psychic races use their psychic energy to power and control their vessels.
- **Orbipods (Tyrant Ships)** - Eye Tyrant ships are powered by one or more orbi (singular orbus), a mutant eye tyrant bred solely for this purpose.
#### Engine Speed
- **Console** = Pilot's INT x 30 yards / round
- **Orbipod** = 300 yards / round for a single one, each additional adds a 20% (60 yards / round) boost
- 2 orbipods = 360 yards / round
- 3 orbipods = 420 yards / round
### Roles
Party members can fulfill various roles aboard astral vessels.
- **Pilot** - the character who flies the ship, typically via an astral console.
- **Gunner** - a character who mans a ship-board weapon (ex: a ballista).
- **Boatswain** - a character who helps maintain (and potentially improve) the various components of the ship.
- **First Mate** - the charcter who leads the rest of the crew in their duties.
- **Navigator** - a character with experience reading astral maps and plotting courses by them.
- **Surgeon** - a character who heals the wounded members of the crew, be it through magic or medicine (or both).
#### Bonuses for Roles
During each round of pursuit or combat, PCs occupying these roles may attempt to improve the ship's tactical movement as follows:
- In lieu of an attack, each of the following roles may make the indicated check:
- **Pilot**: May make their choice of an INT or WIS check.
- **First Mate**: May make a CHA check.
- **Boatswain**: May make their choice of an INT or DEX check.
- **Bonus effects**: Choose one of the following for each successful check:
- +1 level (30' / round) to pursuit / evasion speed.
- +1 AC to the ship _AND_ all creatures on board until the end of the round.
- +1 to all attack rolls made by the ship _AND_ all creatures on board until end of the round.
- **Performing bonus checks**: _Must_ be declared before initiative is rolled, occurs at the start of the party's side of combat (just before the "fire missiles" combat phase while the party's side has initiative).
### Ship Statistics
<div class="dividedTableWrapper">
| Vessel | Cost (gp) | Cargo Capacity (coins) | Length | Beam | Crew | Weapons | Ram | Landing Types | AC | HP |
| --------------------- | :-------: | :--------------------: | :-------: | :-------: | :--: | :------: | :-: | :-------------: | :---------: | :-----: |
| Caravel | 10,000 | 100,000 | 60'-80' | 20'-30' | 10 | 1 | N | Water | 8&nbsp;[11] | 60-90 |
| Galleon | 20,000 | 300,000 | 100'-150' | 25'-30' | 20 | 3 | N | Water | 7 [12] | 120-180 |
| Warship, Sm | 6,600 | 100,000 | 60'-80' | 20'-30' | 10 | 1/2 | Y | Water | 8 [11] | 60-90 |
| Warship, Lg | 26,600 | 300,000 | 100'-150' | 25'-30' | 20 | 2/4 | Y | Water | 7 [12] | 150-210 |
| Ghyffan Corvette | 15,000 | 250,000 | 100'-150' | 25'-35' | 10 | 1/2 | N | Water | 7 [12] | 90-150 |
| Ghyffan Battlecruiser | 45,000 | 450,000 | 220'-280' | 20'-30' | 24 | 3/4 | Y | None | 7 [12] | 180-240 |
| Stral Destroyer | 40,000 | 350,000 | 150'-200' | 25'-35' | 10 | 5 lg | Y | None | 7 [12] | 150-210 |
| Stral Dreadnought | 50,000 | 600,000 | 220'-280' | 20'-30' | 24 | 3 lg | Y | None | 7 [12] | 180-240 |
| Dwarven Fortress | 100,000 | 3,000,000 | 200'-300' | 150'-250' | 10 | 10 lg | Y | Land | 7 [12] | 360-720 |
| Gnomish SteamJammer | 40,000 | 300,000 | 100'-150' | 20'-30' | 20 | 1 lg | N | Land, Water[^1] | 7 [12] | 90-120 |
| Drahki Dragonship, Sm | 60,000 | 150,000 | 80'-120' | 10'-15' | 10 | 1 lg[^2] | Y | Water | 7 [12] | 120-180 |
| Drahki Dragonship, Lg | 60,000 | 450,000 | 150'-200' | 15'-20' | 20 | 3 lg[^2] | Y | Water | 7 [12] | 180-240 |
[Ship Statistics]
</div>
[^1]: Base 50% chance of success on each landing
[^2]: Drahki ships always carry a Fire Cannon as one of their weapons
### Weapons
#### Ballistae
Fire large bolts (arrows) of wood and iron.
**Cargo Space**: A medium ballista plus twenty bolts requires 6,000 coins of cargo space, and a heavy ballista plus twenty bolts occupies 12,000 coins worth (both subtracted from the ships cargo allowance).
**Attack modifiers**: May be applied for environmental conditions, manoeuvrability, etc.
##### Medium Ballista
**Range**: 1,500 yards.
**Attack rolls and rate of fire**: Depend on the number of crew manning the ballista:
- 2 crew (minimum): Attacks with THAC0 15 [+4]. Fires every 3 rounds.
- 3 crew (maximum): Attacks with THAC0 14 [+5]. Fires every 2 rounds.
**Damage**: 3d6 hit points or 3d2 hull points.
##### Heavy Ballista
**Range**: 1,000 yards.
**Attack rolls and rate of fire**: Depend on the number of crew manning the ballista:
- 4 crew (minimum): Attacks with THAC0 15 [+4]. Fires every 4 rounds.
- 5 crew (maximum): Attacks with THAC0 14 [+5]. Fires every 3 rounds.
**Damage**: 3d10 hit points or 3d6 hull points.
#### Bombards (Cannons)
Fire magically-propelled large balls of iron.
**Cargo Space**: A bombard plus twenty cannonballs requires 10,000 coins of cargo space (subtracted from the ships cargo allowance).
**Range**: 1,000 yards.
**Attack modifiers**: May be applied for environmental conditions, manoeuvrability, etc.
**Attack rolls and rate of fire**: Depend on the number of crew manning the bombards:
- 3 crew (minimum): Attacks with THAC0 17 [+2]. Fires every 3 rounds.
- 4 crew (maximum): Attacks with THAC0 16 [+3]. Fires every 2 rounds.
**Damage**: 2d10 hit points or hull points.
#### Drahki Fire Cannons
Fire dragon's flame. Can be tuned to fire in a cloud, cone, or line. Requires a specialist gunner (one trained in the weapon's use) to use.
**Cargo Space**: A fire cannon plus twenty globes of elemental fire requires 10,000 coins of cargo space (subtracted from the ships cargo allowance).
**Range**: Depends on settings
- Cloud: 500 yards cubed.
- Cone: 2 yards diameter at start, widens to 500 yard wide at 1000 yards distance.
- Line: 2 yards in diameter, extends 1,500 yards.
**Attack rolls and rate of fire**: Depend on the number of crew manning the cannons:
- 2 crew (minimum): Attacks with THAC0 15 [+4]. Fires every 2 rounds.
- 3 crew (maximum): Attacks with THAC0 14 [+5]. Fires each round.
**Damage**: 1d6+5 x 10 hit points or 1d6+5 x 5 hull points
<!--
#### Eye Tyrant Blasters
Fire energy bolts that function like magic missiles. Requires 2 common Eye Tyrants to crew.
**Cargo Space**: TBD
**Range**: 1,500 yards.
**Attack rolls and rate of fire**: Attacks with THAC0 15 [+4]. Fires three bolts every round, roll for each bolt.
**Damage**: 1d6+1 hit points or hull points per bolt. Bolts that hit a ship only have a 2-in-6 chance of failing to do damage (scorching the surface).
-->
#### Rams
Can be used against ships or giant monsters. Small individuals cannot be targeted.
**Attack rolls**: Are made using a THAC0 of 19 [0] and occur at the same point in the combat sequence as missile fire.
**Attack modifiers**: May be applied for environmental conditions, manoeuvrability, etc.
**Large ships**: Deals 1d6+5 x 10 hull points damage against ships and 6d6 hit points damage against monsters.
**Small ships**: Deals 1d4+4 x 10 hull points damage against ships and 3d8 hit points damage against monsters.

View File

@ -0,0 +1,5 @@
## Zenopus Cove
Connects to: The Silver Shark, Portown, Ayreon
- Locals call it "Sapphire Cove", due to the portal's proximity to the Sapphire Sea on their homeworld.

View File

View File

@ -0,0 +1,39 @@
---
title: The Ravager
description:
date_pub: 2023-02-19T20:59:00-05:00
section: campaign
content_type: feature
short_code: ctr
---
According to the [Crystal Skull of Jund](/magic-items/crystal-skull-of-jund.html):
_**The Ravager** is an Eye Tyrant creation responsible for the destruction of 32 different planes. The Ravager is capable of honing in on a plane via any type of portal or color pool, passing through, and then destroying the plane from the other side._
### The Ravager's Lair
- An enormous asteroid, 10 miles in diameter.
- Has a one mile-diameter brass dome coming out of it.
- On side opposite dome there's a large gash which leads to a 200' diameter tunnel and a secret entrance (set of double doors.
#### The Black Tunnels
- Made of smooth basalt.
- Twists and turns made traveling slow going - took over an hour to traverse.
#### The Antechamber
- Natural-looking cavern with 10' diameter light globes and large rocks scattered throughout.
- Strange message carved on double doors leading further into the lair.
**Strange Message**
> You cannot have bypassed our defenses if you are of the Eye Tyrants. Therefore, if you are reading this message, you must be their enemies.... or should be.
>
> For reasons of our own, we helped the Eye Tyrants to create a powerful weapon. However, we also included a way to neutralize the project. We will not do this ourselves, for that is not the way of the Arcane Lords.
> The weapon we have created, the Ravager, depends on the energies of the Queens Eyes. The ten lesser eyes dwell each in their own domains within this structure. The greater eye sees all, but none can reach it.
>
> Go forth into the complex we created. Locate and destroy as many of the ten lesser eyes as you can. Only this can end the menace of the Ravager.
>
> We have admitted you. For the brave, this is enough, although you may find help if you know where to look. Go forward.

View File

@ -0,0 +1,235 @@
---
title: Sapphire Cove
description: A description of the "home port" for the party of adventurers.
date_pub: 2023-02-20T00:10:00-05:00
section: campaign
content_type: feature
short_code: csc
---
### NPCs
- **Lord Stengar Muziv** - Harbormaster
- Human noble
- Goal: political movement, more power
- Attitude: spinning lots of plates, flies off the handle at Fenwick when he screws up (and sometimes when he doesn't).
- **Zohar, Keeper of the Obelisk** - Cleric of Phoris
- Chatty and personable, but not always as devoted as he should be.
- **Amaryllis (Amary), the Alchemist** - Runs the Magic Shop
- And pretty and polite (but bored) female human
- Smarter than she looks
- Seems to be developing a relationship with Segu.
- Took the demonic scroll from Segu to destory it.
- Claims to be able to muster a "Wziard Army".
- **Fenwick C. Fizzlebell** - Harbormaster's Assistant / Secretary
- Fussy gnome dressed all in yellow
- Always appears to be in over his head
- Constantly apologizing for his failure, looks at the ground
- **Rocky** - Fenwick's living statue
- Exquisitely carved statue of human male
- Apparently mute
- Also serves as Fenwick's transportation and muscle
- **Unkhlar Thildison** - Dwarven blacksmith
- Thin and kinda shifty, with short copper hair and green eyes.
- Has a pet spider named Therva that occasionally hangs out on his shoulder.
<!--
- Actually an assassin
- He wears leather armor (disguised as a blacksmith's apron and gloves) and wields a poisoned short sword and dagger
- Has an animal companion, a giant spider named Therva
- Actual name is Festi
-->
### 1 - Unkhlar's Ironworks
- A fairly-modest blacksmith's workshop, small, dingy, with globes of flame stacked in the corner
- Run by Unkhlar Thildison
- Sells light globes and Dwarven unitools.
### 2 - The Armory
- Strong, 2-story, half-timbered building, that serves as the headquarters of the town guard when stationed at the cove.
- Bottom story is a large, divided cage which is used to detain thieves and scofflaws, usually holds prisoners temporarily before shipping them back to Portown.
- Always at least 1 guard in the armory, in addition to the 2 making the rounds, and the 2 guarding the gateway.
### 3 - Sapphire Shark
- "Help Wanted" sign in the window.
- Long bar runs along most of the back wall.
- People standing and sitting at perpendicular gravities to each other.
- Tables and chairs clustered in the center of each wall (and the ceiling), with people freely standing about.
- Everyone's drinks are served in small globes with straw-like protrusions.
- Bartender is an apparently exasperated (and sweaty) human.
#### Menu
Drinks
- Natural Spring Water - 2 sp
- Coffee - 1 sp
- Dragonberry juice - 3 sp
- Bloo Milk - 1 sp
Wine
- Bloodwine - 3 sp
- Evermead - 2 sp
- Fire Wine - 5 sp
- Glowfie - 4 sp
- Topaz - 3 sp
- Westgate Ruby - 2 sp
- Wizard's Choice - 5 sp
Liquor
- Cherry Fire - 1 sp
- Death Wish - 3 sp
- Elverquis - 2 sp
- Seawin - 1 sp
- Whiskey - 2 sp
Ale
- Bleak Stout - 1 sp
- Dragon's Breath - 3 sp
- Golden Sands Ale - 2 sp
- Halfling's Best - 3 sp
- Purple Noble - 2 sp
- Shadowdark Ale - 4 sp
Food
- Bloodhawk Breast from Krynn - 1 gp
- Sea Serpent Sushi Roll - 3 gp
- Mimic Tongue in Sauce - 2 gp
- Owlbear Steak & Eggs - 4 gp
- Tarrasque Ribeye - 5 gp
### 4 - Warehouse
- 2 storey, multiple (locked) entrances on the bottom floor (and a large locked trap door on the top)
### 5 - Amary's Emporium
- Run by Amaryllis the Alchemist
- Appears to be bigger on the inside.
- Lots of books, hints of "other services".
- She buys live animals and rare monster bits.
- Potions / Supplies
- healing - 100 gp
- 1gp for mystery potion
- 10gp for orbs of light
- Also carries "Sorcerer: The Summoning"
#### Emporium Stock
<!--
Random Potions
- https://litrpgreads.com/blog/100-fun-dnd-random-potion-effects-to-surprise-your-players
- https://www.dndspeak.com/2017/12/24/100-random-potion-effects/
Random Trinkets
- https://www.thievesguild.cc/tables/trinkets
-->
Potions
- Clairvoyance - 250 gp
- Control Giant - 350 gp
- Delusion - 250 gp
- Diminution - 100 gp
- Divination - 500 gp
- ESP - 100 gp
- Flying - 200 gp
- Gaseous Form - 150 gp
- Growth - 100 gp
- Healing (1d6+1) - 50 gp
- Healing, Greater (2d6+3) - 125 gp
- Healing, Supreme (4d6+7) - 300 gp
- Invisibility - 300 gp
- Invulnerability - 300 gp
- Levitation - 150 gp
- Poison - 100 gp
- Polymorph Self - 300 gp
- Raise Dead - 2500 gp
- Remove Curse - 200 gp
- Speak with Dead - 200 gp
- Random Potion - 2 gp
Equipment
- Acid (vial) - 1 gp
- Alchemist's Fire (flask) - 5 gp
- Antitoxin - 1 gp
- Bottle (glass) - 2 gp
- Flask or Tankard - 5 sp
- Elemental Fire Globe - 200 gp
- Elemental Water Globe - 200 gp
- Ink (1 ounce bottle) - 1 gp
- Ink, Gold (1 spell level) - 100 gp
- Jug or Pitcher - 5 sp
- Light Orb - 10 gp
- Oil (flask) - 2 gp
- Paper / parchement (2 heets) - 1 gp
- Vial - 1 gp
Scrolls
- Continual Light - 150 gp
- Haste - 500 gp
- Massmorph - 1200 gp
- Polymorph others - 1000 gp (x2)
- Detect Magic (A) - 25 gp
- Light (A) - 50 gp
Live Animals
- spider - 10gp
- bat - 20gp
- newt - 5gp
Random Potions (1 gp each)
- Clairaudience
- Clairvoyance
- Control Animal
- Control Dragon
- Control Giant
- Control Human
- Control Plant
- Control Undead
- Delusion
- Diminution
- ESP
- Fire Resistance
- Flying
- Gaseous Form
- Giant Strength
- Growth
- Healing (1d6+1)
- Healing, Greater (2d6+3)
- Healing, Supreme (4d6+7)
- Heroism
- Invisibility
- Invulnerability
- Levitation
- Longevity
- Polymorph Self
- Potion of Speed
- Treasure Finding
### 6 - The Obelisk of Law
- Maintained Zohar, Keeper of the Obelisk
- Open temple of Law
- A monolith of polished stone, built into the side of the wall just above the portal, with a praying circle around it.
- Primarily dedicated to Phoris, but other Law gods are listed as well, including Endrion (Dragon god of Law).
### 7 - Harbormaster's Office
- Office of Lord Stengar
- Fine wooden desk and chairs.
- Several knick-knacks seres as reminders of various deals he's put together.

View File

@ -0,0 +1,64 @@
---
title: Shazzogrox's Journals
description: Some notes gleaned from the journals of the Eye Tyrant Mage, Shazzogrox.
date_pub: 2023-02-19T22:43:00-05:00
section: campaign
content_type: feature
short_code: csj
---
The journals of the Eye Tyrant Mage, Shazzogrox, reveal a being with volatile, disturbed psyche, deep-seated trauma, and definite megalomaniacal tendencies.
- Eye Tyrants call themselves "Iy'tar", named for "Iy'taria", which is either a (mythical?) "home" plane or possibly a deity (Iy'tar means "firat source", but it's unclear what kind of source?)
> Like other great leaders, I was birthed from the vats of the clone farm of Greshtharia... Pity it was the final days of the Vakarian cycle, or I could have been the greatest leader the Iy'tar had ever known, but alas...
> During my youth, things were as they should be - Iy'tar were feared throughout the multiverse, and ruled a vast empire.
> As I grew, I sought to fulfill my duty to my kind, joining the exalted ranks of the great Iy'tarian navy.
> It was clear that I had potential, and I quickly rose through the ranks, becoming a lieutenant in just under 12 syklos.
> Then came the Tribulations - first, the upstart bipeds handed us our first defeat at the battle of Jade Fields, but then.... The Sazur incident.
It's at this point Segu decided to just glean useful facts from the journal, rather than get the entire narrative from a psychopathic Eye Tyrant Mage.
### The Sazur Incident
- A mad scientist named Sazur created the Ravager, but it escaped his control, killing him and destroying the plane of Batosh.
- Then it started wandering the currents.
- As the portal to Batosh was near the center of their astral empire, it soon found it's way to each of the major Iy'tarian planes, and destroyed them
- It was immune to many of the Tyrants' attacks, and it wiped out fleet of their ships
- Within a century, they were nearing extinction, with 95% of the race having been destroyed.
- They had lost the ability to breed with the destruction of their core worlds (and the cloning vats that existed on them).
- The remaining Iy'tar factionalized, blaming each other for the death of the species, and turned on each other
### Shazzogrox's Personal History
- Spent most of his early life (first 100 years or so) as a low-ranked soldier in the Iy'tarian navy.
- Spent the Tribulations on board various ships, always narrowly-avoiding being sent to the front.
- Was present (on ship) at the Final Council, where the 444 remaining Eye Tyrant Queens turned on each other.
- Spent next 200 years shuffled from ship to ship, watching the losses mount and morale fall.
- Tried to start a revolution by suggesting that the queen had failed them.
- The queen decided to make an example of him, and had her guards blind and torture him, exiling him to the Silent Wastes (at the edge of their former empire).
- Spent next 2300 years, nearly dead, drifting in the astral plane (which drove him completely mad).
- About 700 years ago he was found by someone Shazzogrox calls "The Silent Master", a wizard of apparently incredible power.
- Shazzogrox describes him as tall, always walking around in hooded robes (often with each hand tucked into the other sleeve), and with a mask over his lower face, so one could only ever see his eyes.
- The Silent Master healed him and began training him in the arcane arts.
- Shazzogrox took to it quickly (he says, although it took him over 300 years to get where he is now).
- Towards the end of his training, Shazzogrox learned the truth about the Ravager (that it was created with forbidden magicks) and decided he could control it, to use it against his enemies.
- Shazzogrox asked for (and was given) a specially-designed ship with an orbopod (orbus console) to control it, along with an assortment of magic items, and a variety of creatures from The Master's menagerie.
- He polymorphed one into an orbus and the rest into the crew.
- Shazzogrox then flew them to an old Tyrant research outpost (which he'd learned of during his own research into the Ravager), and that's where they found the watcher and Stonecrop.
### Sapphire Cove
Their next destination was Sapphire Cove...
> We left the dock with a bunch of whiny, two-eyed, stumbling stick-bones..
> The small one looks delicious, although I wouldn't want to have to get past his huge (and repulsive) stone statue.
> The wizard is as annoying as he is stupid, constantly prattling on about the most obvious things. He'd be quieter as a stone statue.
> I tire of the sell-sword's sneaking... I would like nothing more than to watch her turn to dust and scatter to the psychic winds.
> The silent cleric annoys me with his self-righteous glares... I wonder if I could use my thumbs to put out his eyes?
> The knight is so clumsy and stupid, I'm surprised he wasn't killed as a youngling... Had he been birthed as an Iy'tar, he would have been crushed right after he left the tube.
Other Details
- Shazzogrox is concerned that the other Tyrants know he's heading for the Ravager and want to keep him from getting there.
- He's not specific about his plans once he gets into the Ravager's Lair, just that he's discovered some truth about it.

View File

@ -0,0 +1,14 @@
---
title: Campaign Timeline
description: A timeline of events from the sessions.
date_pub: 2023-02-12001:52:00-05:00
section: campaign
content_type: feature
short_code: ct1
---
- First Arrived on Astral Plane: 2nd phase of the 3rd Lawday of Urtson-Law, in year 5023 of the Common Astral Calendar.
- Arrived at Island of Terror: 2nd phase of 4th Kayday of Urtson-Law.
- Stayed in port (Sapphire Cove): 2nd phase of 5th Warday of Urtson-Law.
- Left with Tobart to find Ravager: 3rd phase of 5th Warday of Urtson-Law.
- Arrived at the Ravager: 3rd phase of the 1st Aerday of Urtson-Nu.

View File

@ -0,0 +1,113 @@
---
title: The Astral Corsair
description: The Astral Corsair class for Old School Essentials.
date_pub: 2023-02-17T00:15:00-05:00
section: classes
content_type: feature
short_code: cac1
---
<div class='headlessTableWrapper'>
| | |
| ------------------- | ------------------- |
| **Requirements** | None |
| **Prime requisite** | DEX |
| **Hit Dice** | 1d4 |
| **Maximum Level** | 14 |
| **Armour** | Leather, no shields |
| **Weapons** | Any |
| **Languages** | Alignment, Common |
</div>
Astral corsairs are adventurers who live by their skills, sailing around the astral void. They have a range of specialised astral-sailing and adventuring skills unavailable to other characters. However, corsairs are not always to be trusted.
[[toc]]
### Boarding
Corsairs who are in the act of boarding another vessel don't suffer the usual boarding penalty to attack rolls and Armour Class.
### Combat
Because of their need for free movement in the open astral void, corsairs cannot wear armour heavier than leather and cannot use shields. They can use any weapon.
### Astral Corsair Skills
Corsairs can use the following skills, with the chance of success shown below:
- **Climb sheer surfaces (CS)**: A roll is required for each 100 to be climbed. If the roll fails, the corsair falls at the halfway point, suffering falling damage.
- **Move silently (MS)**: An astral corsair may attempt to sneak past enemies unnoticed.
- **Astral-faring (AF)**: An astral corsair always knows which way is down, how to get by in the open astral void, and how to make repairs when their vessel takes on damage.
- **Tightrope walking (TW)**: Corsairs can walk along tightropes, narrow beams, and ledges at up to half their normal movement rate. A roll is required every 60. Failure indicates that the corsair falls and suffers falling damage. Windy conditions may reduce the chance of success by up to 20%.
<div class="dividedTableWrapper">
| Level | CS | MS | SF | TW |
| :---: | :-: | :-: | :-: | :-: |
| 1 | 87 | 20 | 15 | 60 |
| 2 | 88 | 25 | 20 | 65 |
| 3 | 89 | 30 | 25 | 70 |
| 4 | 90 | 35 | 30 | 75 |
| 5 | 91 | 40 | 35 | 80 |
| 6 | 92 | 45 | 45 | 85 |
| 7 | 93 | 50 | 55 | 90 |
| 8 | 94 | 55 | 65 | 95 |
| 9 | 95 | 60 | 75 | 97 |
| 10 | 96 | 65 | 85 | 98 |
| 11 | 97 | 70 | 90 | 99 |
| 12 | 98 | 73 | 95 | 99 |
| 13 | 99 | 76 | 97 | 99 |
| 14 | 99 | 80 | 99 | 99 |
[Astral Corsair Skills Chance of Success]
</div>
### Rolling Skill Checks
All skills are rolled on d%, with a result of less than or equal to the listed percentage indicating success.
#### Player Knowledge
The referee should roll for hide in shadows and move silently on the players behalf, as the corsair does not immediately know if the attempt was successful. If a hide in shadows or move silently roll fails, the referee knows that the corsair has been noticed and should determine enemies actions appropriately.
### Lore
From 3rd level, a corsair has a 2-in-6 chance of knowing lore pertaining to monsters, magic items, or heroes of astral-plane-related folktale or legend. This ability may be used to identify the nature and powers of astral-related magic items.
### Sneak Attack
When attacking an unaware opponent from behind, a corsair receives a +4 bonus to hit and doubles any damage dealt.
### After Reaching 9th Level
A corsair can establish a crew, attracting 2d6 astral sailors of 1st level. These corsairs will serve the character with some reliability, and may be used to run one or more ships.
<div class="dividedTableWrapper levelTable">
| |||| Saving Throws |||||
| Level | XP | HD | THAC0 | D[^1] | W[^1] | P[^1] | B[^1] | S[^1] |
| :---: | :-----: | :--------: | :-----: | :---: | :---: | :---: | :---: | :---: |
| 1 | 0 | 1d4 | 19 [0] | 13 | 14 | 13 | 16 | 15 |
| 2 | 1,200 | 2d4 | 19 [0] | 13 | 14 | 13 | 16 | 15 |
| 3 | 2,400 | 3d4 | 19 [0] | 13 | 14 | 13 | 16 | 15 |
| 4 | 4,800 | 4d4 | 19 [0] | 13 | 14 | 13 | 16 | 15 |
| 5 | 9,600 | 5d4 | 17 [+2] | 12 | 13 | 11 | 14 | 13 |
| 6 | 20,000 | 6d4 | 17 [+2] | 12 | 13 | 11 | 14 | 13 |
| 7 | 40,000 | 7d4 | 17 [+2] | 12 | 13 | 11 | 14 | 13 |
| 8 | 80,000 | 8d4 | 17 [+2] | 12 | 13 | 11 | 14 | 13 |
| 9 | 160,000 | 9d4 | 14 [+5] | 10 | 11 | 9 | 12 | 10 |
| 10 | 280,000 | 9d4+2[^2] | 14 [+5] | 10 | 11 | 9 | 12 | 10 |
| 11 | 400,000 | 9d4+4[^2] | 14 [+5] | 10 | 11 | 9 | 12 | 10 |
| 12 | 520,000 | 9d4+6[^2] | 14 [+5] | 10 | 11 | 9 | 12 | 10 |
| 13 | 640,000 | 9d4+8[^2] | 12 [+7] | 8 | 9 | 7 | 10 | 8 |
| 14 | 760,000 | 9d4+10[^2] | 12 [+7] | 8 | 9 | 7 | 10 | 8 |
[Astral Corsair Level Progression]
[^1]: D: Death / poison; W: Wands; P: Paralysis / petrify; B: Breath attacks; S: Spells / rods / staves.
[^2]: [Modifiers from CON](https://oldschoolessentials.necroticgnome.com/srd/index.php/Ability_Scores#Constitution_.28CON.29) no longer apply.
</div>

View File

@ -0,0 +1,76 @@
---
title: Automaton
description: The Automaton nonhuman class for Old School Essentials.
date_pub: 2023-02-16T23:36:00-05:00
section: classes
content_type: feature
short_code: ca1
---
| Nonhuman Class | |
| ------------------- | ---------------------- |
| **Requirements** | None |
| **Prime requisite** | CON |
| **Hit Dice** | 1d8 |
| **Maximum Level** | 10 |
| **Armour** | Any, including shields |
| **Weapons** | Any |
| **Languages** | Alignment, Common |
Automatons are magically-powered, fully sentient beings composed of metal and wood. Although some are believed to have been built for a long forgotton war, most were built as arcane experiments by powerful wizards, or to serve as slaves in decadent, high-magic socities. Despite their origins, many are driven to find a purpose beyond their original design.
[[toc]]
### Combat
An automaton can wield any weapon, and can use any armour that has been integrated with their body (see below).
### Integrated Armor
The body of an automaton has built-in defensive layers, which may be enhanced with armour.
- Automatons gain a +1 bonus to Armour Class.
- Automatons can don any armour. To don armour, it must be incorporated into an automaton's body over the course of 1 hour, during which they must remain in contact with the armour. To doff armour, an automaton must spend 1 hour removing it. An automaton can rest while donning or doffing armour in this way.
- While an automaton lives, worn armour can't be removed from their body against their will.
### Resilience
Automatons have remarkable fortitude, represented as follows:
- Automatons dont need to eat, drink, or breathe.
- Automatons are immune to disease.
- Automatons don't need to sleep, and magic can't put them to sleep (although they do need to rest, see below).
- Automatons get +2 on saving throws vs. poison
### Sentry
When an automaton takes a rest, they must spend at least six hours in an inactive, motionless state, rather than sleeping. In this state, the automaton appears inert, but is still concious and can see and hear as normal.
### After Reaching 8th Level
An automaton has the option of creating a [stronghold](https://oldschoolessentials.necroticgnome.com/srd/index.php/Strongholds), usually a secluded tower, although it may take another form based on the desires and goals of the automaton. This will often attract other automatons and/or friendly humanoids.
<div class="dividedTableWrapper levelTable">
| |||| Saving Throws |||||
| Level | XP | HD | THAC0 | D[^1] | W[^1] | P[^1] | B[^1] | S[^1] |
| :---: | :-----: | :-------: | :-----: | :---: | :---: | :---: | :---: | :---: |
| 1 | 0 | 1d8 | 19 [0] | 8 | 9 | 10 | 13 | 12 |
| 2 | 2,200 | 2d8 | 19 [0] | 8 | 9 | 10 | 13 | 12 |
| 3 | 4,400 | 3d8 | 19 [0] | 8 | 9 | 10 | 13 | 12 |
| 4 | 8,800 | 4d8 | 17 [+2] | 6 | 7 | 8 | 10 | 10 |
| 5 | 17,000 | 5d8 | 17 [+2] | 6 | 7 | 8 | 10 | 10 |
| 6 | 35,000 | 6d8 | 17 [+2] | 6 | 7 | 8 | 10 | 10 |
| 7 | 70,000 | 7d8 | 14 [+5] | 4 | 5 | 6 | 7 | 8 |
| 8 | 140,000 | 8d8 | 14 [+5] | 4 | 5 | 6 | 7 | 8 |
| 9 | 270,000 | 9d8 | 14 [+5] | 4 | 5 | 6 | 7 | 8 |
| 10 | 400,000 | 9d8+3[^2] | 12 [+7] | 2 | 3 | 4 | 4 | 6 |
| 11 | 530,000 | 9d8+6[^2] | 12 [+7] | 2 | 3 | 4 | 4 | 6 |
| 12 | 660,000 | 9d8+9[^2] | 12 [+7] | 2 | 3 | 4 | 4 | 6 |
[Automaton Level Progression]
[^1]: D: Death / poison; W: Wands; P: Paralysis / petrify; B: Breath attacks; S: Spells / rods / staves.
[^2]: [Modifiers from CON](https://oldschoolessentials.necroticgnome.com/srd/index.php/Ability_Scores#Constitution_.28CON.29) no longer apply.
</div>

View File

@ -0,0 +1,115 @@
---
title: The Corsair
description: The Corsair class for Old School Essentials.
date_pub: 2023-02-17T00:09:00-05:00
section: classes
content_type: feature
short_code: cc1
---
<div class='headlessTableWrapper'>
| | |
| ------------------- | ------------------- |
| **Requirements** | None |
| **Prime requisite** | DEX |
| **Hit Dice** | 1d4 |
| **Maximum Level** | 14 |
| **Armour** | Leather, no shields |
| **Weapons** | Any |
| **Languages** | Alignment, Common |
</div>
Corsairs are adventurers who live by their skills on the high seas. They have a range of specialised sailing and adventuring skills unavailable to other characters. However, corsairs are not always to be trusted.
[[toc]]
### Boarding
Corsairs who are in the act of boarding another vessel don't suffer the usual boarding penalty to attack rolls and Armour Class.
### Combat
Because of their need for free movement on the open seas, corsairs cannot wear armour heavier than leather and cannot use shields. They can use any weapon.
### Corsair Skills
Corsairs can use the following skills, with the chance of success shown below:
- **Climb sheer surfaces (CS)**: A roll is required for each 100 to be climbed. If the roll fails, the corsair falls at the halfway point, suffering falling damage.
- **Move silently (MS)**: A corsair may attempt to sneak past enemies unnoticed.
- **Seafaring (SF)**: A corsair can keep their feet during rough waters, survive on the open sea, and make repairs when their vessel takes on damage.
- **Tightrope walking (TW)**: Corsairs can walk along tightropes, narrow beams, and ledges at up to half their normal movement rate. A roll is required every 60. Failure indicates that the corsair falls and suffers falling damage. Windy conditions may reduce the chance of success by up to 20%.
<div class="dividedTableWrapper">
| Level | CS | MS | SF | TW |
| :---: | :-: | :-: | :-: | :-: |
| 1 | 87 | 20 | 15 | 60 |
| 2 | 88 | 25 | 20 | 65 |
| 3 | 89 | 30 | 25 | 70 |
| 4 | 90 | 35 | 30 | 75 |
| 5 | 91 | 40 | 35 | 80 |
| 6 | 92 | 45 | 45 | 85 |
| 7 | 93 | 50 | 55 | 90 |
| 8 | 94 | 55 | 65 | 95 |
| 9 | 95 | 60 | 75 | 97 |
| 10 | 96 | 65 | 85 | 98 |
| 11 | 97 | 70 | 90 | 99 |
| 12 | 98 | 73 | 95 | 99 |
| 13 | 99 | 76 | 97 | 99 |
| 14 | 99 | 80 | 99 | 99 |
[Corsair Skills Chance of Success]
</div>
### Rolling Skill Checks
All skills are rolled on d%, with a result of less than or equal to the listed percentage indicating success.
#### Player Knowledge
The referee should roll for hide in shadows and move silently on the players behalf, as the corsair does not immediately know if the attempt was successful. If a hide in shadows or move silently roll fails, the referee knows that the corsair has been noticed and should determine enemies actions appropriately.
### Lore
From 3rd level, a corsair has a 2-in-6 chance of knowing lore pertaining to monsters, magic items, or heroes of sea-related folktale or legend. This ability may be used to identify the nature and powers of sea-related magic items.
### Sneak Attack
When attacking an unaware opponent from behind, a corsair receives a +4 bonus to hit and doubles any damage dealt.
### After Reaching 9th Level
A corsair can establish a crew, attracting 2d6 sailors of 1st level. These corsairs will serve the character with some reliability, and may be used to run one or more ships.
### Corsair Level Progression
<div class="dividedTableWrapper levelTable">
| |||| Saving Throws |||||
| Level | XP | HD | THAC0 | D[^1] | W[^1] | P[^1] | B[^1] | S[^1] |
| :---: | :-----: | :--------: | :-----: | :---: | :---: | :---: | :---: | :---: |
| 1 | 0 | 1d4 | 19 [0] | 13 | 14 | 13 | 16 | 15 |
| 2 | 1,200 | 2d4 | 19 [0] | 13 | 14 | 13 | 16 | 15 |
| 3 | 2,400 | 3d4 | 19 [0] | 13 | 14 | 13 | 16 | 15 |
| 4 | 4,800 | 4d4 | 19 [0] | 13 | 14 | 13 | 16 | 15 |
| 5 | 9,600 | 5d4 | 17 [+2] | 12 | 13 | 11 | 14 | 13 |
| 6 | 20,000 | 6d4 | 17 [+2] | 12 | 13 | 11 | 14 | 13 |
| 7 | 40,000 | 7d4 | 17 [+2] | 12 | 13 | 11 | 14 | 13 |
| 8 | 80,000 | 8d4 | 17 [+2] | 12 | 13 | 11 | 14 | 13 |
| 9 | 160,000 | 9d4 | 14 [+5] | 10 | 11 | 9 | 12 | 10 |
| 10 | 280,000 | 9d4+2[^2] | 14 [+5] | 10 | 11 | 9 | 12 | 10 |
| 11 | 400,000 | 9d4+4[^2] | 14 [+5] | 10 | 11 | 9 | 12 | 10 |
| 12 | 520,000 | 9d4+6[^2] | 14 [+5] | 10 | 11 | 9 | 12 | 10 |
| 13 | 640,000 | 9d4+8[^2] | 12 [+7] | 8 | 9 | 7 | 10 | 8 |
| 14 | 760,000 | 9d4+10[^2] | 12 [+7] | 8 | 9 | 7 | 10 | 8 |
[Corsair Level Progression]
[^1]: D: Death / poison; W: Wands; P: Paralysis / petrify; B: Breath attacks; S: Spells / rods / staves.
[^2]: [Modifiers from CON](https://oldschoolessentials.necroticgnome.com/srd/index.php/Ability_Scores#Constitution_.28CON.29) no longer apply.
</div>

View File

@ -0,0 +1,103 @@
---
title: Dracokin
description: The Dracokin class for Old School Essentials.
date_pub: 2023-02-15T00:26:00-05:00
section: classes
content_type: feature
short_code: cd1
---
- Needs Intro
| Demihuman Class | |
| ------------------- | --------------------------- |
| **Requirements** | Minimum CON 9 |
| **Prime requisite** | STR |
| **Hit Dice** | 1d8 |
| **Maximum Level** | 12 |
| **Armour** | Any, including shields |
| **Weapons** | Any |
| **Languages** | Alignment, Common, Draconic |
_INTRO_
[[toc]]
### Breath Weapon
Dracokin use the power of their draconic ancestory to exhale destructive energy. When a dracokin uses their breath weapon, all creatures in the area must make a saving throw vs. Breath Attacks. A creature takes 2d4 damage on a failed save, half as much on a successful one. The damage increases to 3d4 at 6th level, and 4d4 at 12th. Dracokin may use their breath weapons a number of times per day equal to 1/2 their level, rounded up.
### Breath Resistance
Dracokin get a +2 to all Saving Throws vs. Breath Attacks of the same type that they can produce.
### Combat
Dracokin can use all types of weapons and armour.
### Draconic Ancestry
Dracokin are are distantly related to a particular kind of dragon. Choose a type of dragon from the below list; this determines the damage and area of your breath weapon.
<div class="dividedTableWrapper">
| 1d20 | Color | Breath Weapon |
| :---: | :------: | :------------------------: |
| 1 | Amethyst | 30' Line of Cold |
| 2-3 | Black | 30' Line of Acid |
| 4-5 | Blue | 30' Line of Lightning |
| 6 | Brass | 20' Cone of Sleep Gas[^1] |
| 7 | Bronze | 30' Line of Lightning |
| 8 | Copper | 30' Line of Acid |
| 9 | Emerald | 20' Cone of Acid |
| 10 | Gold | 20' Cone of Fire |
| 11-12 | Green | 10' Cloud of Chlorine Gas |
| 13 | Onyx | 10' Cloud of Chlorine Gas |
| 14-15 | Red | 20' Cone of Fire |
| 16 | Ruby | 30' Line of Fire |
| 17 | Silver | 20' Cone of Cold |
| 18 | Topaz | 10' Cloud of Sleep Gas[^1] |
| 19-20 | White | 20' Cone of Cold |
[Draconic Ancestry]
</div>
[^1]: Rather than dealing damage, targets in area must _save versus breath_ or fall asleep for 1d4 turns
#### Area Descriptions
- _Cloud_: cloud of the indicated width, height, and depth
- _Cone_: 2' wide at the mouth, indicated length and width at far end.
- _Line_: 5' wide line of the indicated length
### After Reaching 9th Level
A dracokin may build a castle or stronghold and control the surrounding lands. The character may be granted a title such as Baron or Baroness. The land under the dracokin's control is then known as a Barony.
### Dracokin Level Progression
<div class="dividedTableWrapper levelTable">
| |||| Saving Throws |||||
| Level | XP | HD | THAC0 | D[^1] | W[^1] | P[^1] | B[^1] | S[^1] |
| :-: | :-----: | :-------: | :-----: | :---: | :---: | :---: | :---: | :---: |
| 1 | 0 | 1d8 | 19 [0] | 12 | 13 | 13 | 15 | 12 |
| 2 | 2,200 | 2d8 | 19 [0] | 12 | 13 | 13 | 15 | 12 |
| 3 | 4,400 | 3d8 | 19 [0] | 12 | 13 | 13 | 15 | 12 |
| 4 | 8,800 | 4d8 | 17 [+2] | 10 | 11 | 11 | 13 | 10 |
| 5 | 17,000 | 5d8 | 17 [+2] | 10 | 11 | 11 | 13 | 10 |
| 6 | 35,000 | 6d8 | 17 [+2] | 10 | 11 | 11 | 13 | 10 |
| 7 | 70,000 | 7d8 | 14 [+5] | 8 | 9 | 9 | 10 | 8 |
| 8 | 140,000 | 8d8 | 14 [+5] | 8 | 9 | 9 | 10 | 8 |
| 9 | 270,000 | 9d8 | 14 [+5] | 8 | 9 | 9 | 10 | 8 |
| 10 | 400,000 | 9d8+3[^2] | 12 [+7] | 6 | 7 | 8 | 8 | 6 |
| 11 | 530,000 | 9d8+6[^2] | 12 [+7] | 6 | 7 | 8 | 8 | 6 |
| 12 | 660,000 | 9d8+9[^2] | 12 [+7] | 6 | 7 | 8 | 8 | 6 |
[Dracokin Level Progression]
</div>
[^1]: D: Death / poison; W: Wands; P: Paralysis / petrify; B: Breath attacks; S: Spells / rods / staves.
[^2]: [Modifiers from CON](https://oldschoolessentials.necroticgnome.com/srd/index.php/Ability_Scores#Constitution_.28CON.29) no longer apply.

View File

@ -0,0 +1,117 @@
---
title: Felinar
description: The Felinar (anthromorphic cat) class for Old School Essentials.
date_pub: 2023-02-15T00:26:00-05:00
section: classes
content_type: feature
short_code: cf1
---
- Needs intro
| Demihuman Class | |
| ------------------- | ------------------------------------------------------------------ |
| **Requirements** | None |
| **Prime requisite** | DEX |
| **Hit Dice** | 1d4 |
| **Maximum Level** | 10 |
| **Armour** | Leather, no shields |
| **Weapons** | Missile weapons, dagger, sword, short sword, polearm, spear, staff |
| **Languages** | Alignment, Common, Felinese |
_INTRO_
[[toc]]
### Claw Attack
Felinar have natural claws that can be used to make an attack, dealing 1d4 damage on a successful hit.
### Combat
Felinar cannot wear armour bulkier than leather and cannot use shields. They are able to use all missile weapons. Their use of melee weapons is restricted to light blades and staff weapons.
### Evasion
When retreating from melee, a felinars ability to tumble negates the opponents usual +2 bonus to hit (see [Combat](https://oldschoolessentials.necroticgnome.com/srd/index.php/Combat)).
### Felinar Skills
Felinar can use the following skills with the chance of success shown opposite.
- **Climb sheer surfaces (CS)**: A roll is required for each 100 to be climbed. If the roll fails, the felinar falls at the halfway point, suffering falling damage.
- **Falling (FA)**: When able to tumble, felinar suffer no damage from the first 10 of any fall. Damage due to falling from a greater height is reduced by the listed percentage (rounding fractions down).
- **Move silently (MS)**: A felinar may attempt to sneak past enemies unnoticed.
- **Tightrope walking (TW)**: Felinar can walk along tightropes, narrow beams, and ledges at up to half their normal movement rate. A roll is required every 60. Failure indicates that the felinar falls and suffers falling damage. Windy conditions may reduce the chance of success by up to 20%. Holding a balance pole increases the chance of success by 10%.
<div class="dividedTableWrapper">
| Level | CS | FA | MS | TW |
| :---: | :-: | :-: | :-: | :-: |
| 1 | 87 | 25 | 20 | 60 |
| 2 | 88 | 25 | 25 | 65 |
| 3 | 89 | 25 | 30 | 70 |
| 4 | 90 | 33 | 35 | 75 |
| 5 | 91 | 33 | 40 | 80 |
| 6 | 92 | 33 | 45 | 85 |
| 7 | 93 | 50 | 50 | 90 |
| 8 | 94 | 50 | 55 | 95 |
| 9 | 95 | 50 | 60 | 99 |
| 10 | 96 | 66 | 65 | 99 |
[Felinar Skill Chance of Success]
</div>
### Rolling Skill Checks
All skills are rolled on d%, with a result of less than or equal to the listed percentage indicating success.
#### Player Knowledge
The referee should roll for move silently on the players behalf, as the felinar always believes the attempt to be successful. If the roll fails, the referee knows that the felinar has been noticed and should determine enemies actions appropriately.
### Infravision
Felinar have infravision to 60 (see _Darkness_ under [Hazards and Challenges](https://oldschoolessentials.necroticgnome.com/srd/index.php/Hazards_and_Challenges#Darkness_)).
### Jumping
With a 20 run-up, a felinar can jump across a 10 wide pit or chasm (or 20 wide when aided by the use of a pole). Also when using a pole, a felinar can jump over a 10 high wall or onto a 10 high ledge. Suitable poles for jumping include: 10 poles, polearms, spears, staves.
### Languages
Felinar know Common, their alignment language, and Felinese (the native language of the felinar race).
### Listening at Doors
Felinar have a 2-in-6 chance of hearing noises (see [Dungeon Adventuring](https://oldschoolessentials.necroticgnome.com/srd/index.php/Dungeon_Adventuring)).
### After Reaching 8th Level
A felinar has the option of creating a [stronghold](https://oldschoolessentials.necroticgnome.com/srd/index.php/Strongholds) that will form the basis of a new community, attracting 2d6 felinar apprentices of 1st level. Felinar communities are usually located in the wilderness (typically a forested or jungle area).
### Felinar Level Progression
<div class="dividedTableWrapper levelTable">
| |||| Saving Throws |||||
| Level | XP | HD | THAC0 | D[^1] | W[^1] | P[^1] | B[^1] | S[^1] |
| :-: | :-----: | :-------: | :-----: | :---: | :---: | :---: | :---: | :---: |
| 1 | 0 | 1d4 | 19 [0] | 13 | 14 | 13 | 16 | 15 |
| 2 | 3,000 | 2d4 | 19 [0] | 13 | 14 | 13 | 16 | 15 |
| 3 | 6,000 | 3d4 | 19 [0] | 13 | 14 | 13 | 16 | 15 |
| 4 | 12,000 | 4d4 | 19 [0] | 13 | 14 | 13 | 16 | 15 |
| 5 | 30,000 | 5d4 | 17 [+2] | 12 | 13 | 11 | 14 | 13 |
| 6 | 60,000 | 6d4 | 17 [+2] | 12 | 13 | 11 | 14 | 13 |
| 7 | 120,000 | 7d4 | 17 [+2] | 12 | 13 | 11 | 14 | 13 |
| 8 | 240,000 | 8d4 | 17 [+2] | 12 | 13 | 11 | 14 | 13 |
| 9 | 400,000 | 9d4 | 14 [+5] | 11 | 11 | 9 | 12 | 11 |
| 10 | 600,000 | 9d4+1[^2] | 14 [+5] | 11 | 11 | 9 | 12 | 11 |
[Felinar Level Progression]
</div>
[^1]: D: Death / poison; W: Wands; P: Paralysis / petrify; B: Breath attacks; S: Spells / rods / staves.
[^2]: [Modifiers from CON](https://oldschoolessentials.necroticgnome.com/srd/index.php/Ability_Scores#Constitution_.28CON.29) no longer apply.

View File

@ -0,0 +1,85 @@
---
title: Firfolk
description: The Firfolk class for Old School Essentials.
date_pub: 2023-02-15T00:26:00-05:00
section: classes
content_type: feature
short_code: cf2
---
- **Needs Intro**
| Demihuman Class | |
| ------------------- | ------------------------------------------ |
| **Requirements** | Minimum WIS 9 |
| **Prime requisite** | STR and WIS |
| **Hit Dice** | 1d8 |
| **Maximum Level** | 10 |
| **Armour** | Any appropriate to size, including shields |
| **Weapons** | Any |
| **Languages** | Alignment, Common, Firspeak |
_INTRO_
**Prime requisites**: A firfolk with at least 13 STR and WIS gains a 5% bonus to experience. A firfolk with at least 13 STR and at least 16 WIS gains a 10% bonus.
[[toc]]
### Combat
Firfolk can use all types of weapons and armour, but it must be tailored to their large size.
**Two-handed melee weapons**: A firfolk can wield any two-handed melee weapon, such as a battle axe, with only one hand.
### Divine Magic
See [Spells](https://oldschoolessentials.necroticgnome.com/srd/index.php/Spells) for full details on divine magic.
**Holy symbol**: A firfolk must carry a holy symbol: a sprig of mistletoe which the character must harvest.
**Deity disfavour**: Firfolk must be faithful to the tenets of their alignment and religion. Firfolk who fall from favour with their deity may incur penalties.
**Magical research**: A firfolk of any level may spend time and money on magical research. This allows them to create new spells or other magical effects associated with their deity. When a firfolk reaches 9th level, they are also able to create magic items.
**Spell casting**: A firfolk may pray to receive spells from nature. The power and number of spells available to a firfolk are determined by the characters experience level. Firfolk cast spells from the Druid spell list (see _Magic_ in Old-School Essentials Advanced). At 1st level, a firfolk may only pray for the illusionist _glamour_ spell, but from 2nd level, the character may pray for it or any spell on the spell list. Firfolk are also able to pray for the magic-user _invisibility_ spell, from 3rd level.
**Using magic items**: As spell casters, firfolk can use magic scrolls of spells on their spell list. They can also use items that may only be used by divine spell casters (e.g. some staves). Firfolk may not use magical books or tomes.
### Languages
Firfolk know Common, their alignment language, and Firspeak (the native language of the firfolk race).
### Open Doors
Firfolk open even barred doors with ease. They are treated as the next highest STR category when it comes to determining their chance of opening doors (see
[Ability Scores](https://oldschoolessentials.necroticgnome.com/srd/index.php/Ability_Scores) in Old-School Essentials). For example, a firfolk with STR 12 is treated as if their STR were in the 1315 category instead.
### After Reaching 8th Level
A firfolk has the option of creating a [stronghold](https://oldschoolessentials.necroticgnome.com/srd/index.php/Strongholds) that will form the basis of a new community of firfolk. Firfolk communities are usually located in the wilderness (typically a forested or hilly area).
### Firfolk Level Progression
<div class="dividedTableWrapper levelTable">
| |||| Saving Throws ||||| Spells |||||
| Level | XP | HD | THAC0 | D[^1] | W[^1] | P[^1] | B[^1] | S[^1] | 1 | 2 | 3 | 4 | 5 |
|:-------:|:-----------:|:--------:|:---------:|:----:|:----:|:----:|:----:|:----:|:---:|:---:|:---:|:---:|:---:|
| 1 | 0 | 1d8 | 19 [0] | 8 | 9 | 10 | 13 | 12 | 1[^2] | - | - | - | - |
| 2 | 4,000 | 2d8 | 19 [0] | 8 | 9 | 10 | 13 | 12 | 2 | - | - | - | - |
| 3 | 8,000 | 3d8 | 19 [0] | 8 | 9 | 10 | 13 | 12 | 2 | 1 | - | - | - |
| 4 | 16,000 | 4d8 | 17 [+2] | 6 | 7 | 8 | 10 | 10 | 2 | 2 | - | - | - |
| 5 | 32,000 | 5d8 | 17 [+2] | 6 | 7 | 8 | 10 | 10 | 2 | 2 | 1 | - | - |
| 6 | 64,000 | 6d8 | 17 [+2] | 6 | 7 | 8 | 19 | 10 | 2 | 2 | 2 | 1 | - |
| 7 | 120,000 | 7d8 | 14 [+5] | 4 | 5 | 6 | 7 | 8 | 3 | 3 | 2 | 2 | 1 |
| 8 | 250,000 | 8d8 | 14 [+5] | 4 | 5 | 6 | 7 | 8 | 3 | 4 | 3 | 2 | 2 |
| 9 | 400,000 | 9d8 | 14 [+5] | 4 | 5 | 6 | 7 | 8 | 4 | 4 | 3 | 3 | 2 |
| 10 | 600,000 | 9d8+1[^3] | 12 [+7] | 2 | 3 | 4 | 4 | 6 | 4 | 4 | 4 | 3 | 3 |
[Firfolk Level Progression]
</div>
[^1]: D: Death / poison; W: Wands; P: Paralysis / petrify; B: Breath attacks; S: Spells / rods / staves.
[^2]: At 1st level, a firfolk may only pray for the glamour spell.
[^3]: [Modifiers from CON](https://oldschoolessentials.necroticgnome.com/srd/index.php/Ability_Scores#Constitution_.28CON.29) no longer apply.

View File

@ -0,0 +1,23 @@
---
title: Custom Character Classes
description: TBD
date_pub: 2023-02-17T00:15:00-05:00
section: classes
content_type: feature
short_code: c1
---
- [Automaton](./automaton.html)
- [Corsair](./corsair.html)
- [Corsair, Astral](./astral-corsair.html)
- [Dracokin](./dracokin.html)
- [Felinar](./felinar.html)
- [Firfolk](./firfolk.html)
- [Mimikin](./mimikin.html)
- [Tortokin](./tortokin.html)
- [Warlock](./warlock.html)
<!--
- [Planetouched](./corsair.md)
- [Summoner](./corsair.md)
-->

Some files were not shown because too many files have changed in this diff Show More