commit 3e53235230b323ad8baba83ffed68f07f3178e9b Author: Eric Woodward Date: Mon Feb 20 11:46:46 2023 -0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..622a699 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +out/ +IDEAS.md +TODO.md diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..7d1b5f5 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +*.ejs +*.min.js diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..de354ad --- /dev/null +++ b/LICENSE @@ -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. diff --git a/app.js b/app.js new file mode 100644 index 0000000..c805e94 --- /dev/null +++ b/app.js @@ -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); diff --git a/lib/build.js b/lib/build.js new file mode 100644 index 0000000..86e7c0a --- /dev/null +++ b/lib/build.js @@ -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: + '
Contents
', + 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}`, + ` + + #${tag} + ` + ), + 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); + }); +}; diff --git a/lib/defaults.json5 b/lib/defaults.json5 new file mode 100644 index 0000000..c4df493 --- /dev/null +++ b/lib/defaults.json5 @@ -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, + }, +} \ No newline at end of file diff --git a/lib/loadConfig.js b/lib/loadConfig.js new file mode 100644 index 0000000..15842ce --- /dev/null +++ b/lib/loadConfig.js @@ -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; + }, {}), + }; +}; diff --git a/lib/serve.js b/lib/serve.js new file mode 100644 index 0000000..2bf7728 --- /dev/null +++ b/lib/serve.js @@ -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}`); + }); +}; \ No newline at end of file diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..b8b4e0a --- /dev/null +++ b/lib/utils.js @@ -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; + } + }, + + }; +})(); diff --git a/lib/watch.js b/lib/watch.js new file mode 100644 index 0000000..5ff368b --- /dev/null +++ b/lib/watch.js @@ -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}`); + }); +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..12661de --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1728 @@ +{ + "name": "planar-vagabond", + "version": "0.9.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "planar-vagabond", + "version": "0.9.1", + "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" + } + }, + "node_modules/@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "peer": true + }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "peer": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "peer": true + }, + "node_modules/@zeit/schemas": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.29.0.tgz", + "integrity": "sha512-g5QiLIfbg3pLuYUJPlisNKY+epQJTcMDsOnVNkscrDP1oi7vmJnzOANYJI/1pZcVJ6umUkBv3aFtlg1UvUHGzA==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", + "dev": true, + "dependencies": { + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/ejs": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", + "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-url-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", + "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", + "dependencies": { + "punycode": "^1.3.2" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/front-matter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", + "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.1" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.7.0.tgz", + "integrity": "sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jake": { + "version": "10.8.5", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", + "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/markdown-it": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz", + "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/markdown-it-emoji": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz", + "integrity": "sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ==" + }, + "node_modules/markdown-it-footnote": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-it-footnote/-/markdown-it-footnote-3.0.3.tgz", + "integrity": "sha512-YZMSuCGVZAjzKMn+xqIco9d1cLGxbELHZ9do/TSYVzraooV8ypsppKNmUJ0fVH5ljkCInQAtFpm8Rb3eXSrt5w==" + }, + "node_modules/markdown-it-multimd-table": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/markdown-it-multimd-table/-/markdown-it-multimd-table-4.2.0.tgz", + "integrity": "sha512-wFpb8TSQ9josQrAlOg1toEAHHSGYQZ4krBKfpejPQ+lq3oudnxCNW4S6gYMcRbkrtrfX/ND2njr4belnKt3fBg==" + }, + "node_modules/markdown-it-table-of-contents": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/markdown-it-table-of-contents/-/markdown-it-table-of-contents-0.6.0.tgz", + "integrity": "sha512-jHvEjZVEibyW97zEYg19mZCIXO16lHbvRaPDkEuOfMPBmzlI7cYczMZLMfUvwkhdOVQpIxu3gx6mgaw46KsNsQ==", + "engines": { + "node": ">6.4.0" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/network-address": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/network-address/-/network-address-1.1.2.tgz", + "integrity": "sha512-Q6878fmvItA1mE7H9Il46lONgFgTzX2f98zkS0c2YlkCACzNjwvum/8Kq693IQpxe9zy+w+Zm/4p0wQreLEtZw==", + "bin": { + "network-address": "cli.js" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", + "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "dev": true, + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/serve": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.0.tgz", + "integrity": "sha512-+HOw/XK1bW8tw5iBilBz/mJLWRzM8XM6MPxL4J/dKzdxq1vfdEWSwhaR7/yS8EJp5wzvP92p1qirysJvnEtjXg==", + "dev": true, + "dependencies": { + "@zeit/schemas": "2.29.0", + "ajv": "8.11.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.7.4", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.5", + "update-check": "1.5.4" + }, + "bin": { + "serve": "build/main.js" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/serve-handler": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", + "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "fast-url-parser": "1.1.3", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "2.2.1", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/serve-handler/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/serve/node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-check": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", + "dev": true, + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.0.tgz", + "integrity": "sha512-dwqOPg5trmrre9+v8SUo2q/hAwyKoVfu8OC1xPHKJGNdxAvPl4sKxL4vBnh3bQz/ZvvGAFeA5H3ou2kcOY8sQQ==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100755 index 0000000..f817887 --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/site.config.json5 b/site.config.json5 new file mode 100644 index 0000000..54aeb55 --- /dev/null +++ b/site.config.json5 @@ -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, + }, +} diff --git a/src/assets/_root/browserconfig.xml b/src/assets/_root/browserconfig.xml new file mode 100644 index 0000000..989db79 --- /dev/null +++ b/src/assets/_root/browserconfig.xml @@ -0,0 +1,2 @@ + +#0d1852 diff --git a/src/assets/_root/chim/chim-exe.zip b/src/assets/_root/chim/chim-exe.zip new file mode 100644 index 0000000..6ef9b0f Binary files /dev/null and b/src/assets/_root/chim/chim-exe.zip differ diff --git a/src/assets/_root/chim/js-dos-api.js b/src/assets/_root/chim/js-dos-api.js new file mode 100644 index 0000000..21331e6 --- /dev/null +++ b/src/assets/_root/chim/js-dos-api.js @@ -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;p0;g.needAttribute=j!=null;for(c=0;c0;)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")!=-1){d=f.trim(a).toLowerCase();d=d.indexOf("'); + this.canvas = $(''); + this.overlay = $('
'); + this.loaderMessage = $('
'); + this.loader = $('
').append($('
').append($(''))).append(this.loaderMessage); + this.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($('
Powered by  ').append($('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); diff --git a/src/assets/_root/favicon.ico b/src/assets/_root/favicon.ico new file mode 100644 index 0000000..d9f98df Binary files /dev/null and b/src/assets/_root/favicon.ico differ diff --git a/src/assets/_root/favicon.png b/src/assets/_root/favicon.png new file mode 100644 index 0000000..8626f74 Binary files /dev/null and b/src/assets/_root/favicon.png differ diff --git a/src/assets/_root/manifest.json b/src/assets/_root/manifest.json new file mode 100644 index 0000000..c81ae3e --- /dev/null +++ b/src/assets/_root/manifest.json @@ -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" + } + ] +} diff --git a/src/assets/_root/root/favicon.ico b/src/assets/_root/root/favicon.ico new file mode 100644 index 0000000..d9f98df Binary files /dev/null and b/src/assets/_root/root/favicon.ico differ diff --git a/src/assets/_root/root/favicon.png b/src/assets/_root/root/favicon.png new file mode 100644 index 0000000..8626f74 Binary files /dev/null and b/src/assets/_root/root/favicon.png differ diff --git a/src/assets/images/404/hole-bg-1100x1238-50.jpg b/src/assets/images/404/hole-bg-1100x1238-50.jpg new file mode 100644 index 0000000..c7358e0 Binary files /dev/null and b/src/assets/images/404/hole-bg-1100x1238-50.jpg differ diff --git a/src/assets/images/404/hole-bg-1100x900-50.jpg b/src/assets/images/404/hole-bg-1100x900-50.jpg new file mode 100644 index 0000000..1d03f24 Binary files /dev/null and b/src/assets/images/404/hole-bg-1100x900-50.jpg differ diff --git a/src/assets/images/404/hole-bg-1600x900-50.jpg b/src/assets/images/404/hole-bg-1600x900-50.jpg new file mode 100644 index 0000000..77e416d Binary files /dev/null and b/src/assets/images/404/hole-bg-1600x900-50.jpg differ diff --git a/src/assets/images/404/hole-bg.jpg b/src/assets/images/404/hole-bg.jpg new file mode 100644 index 0000000..c7358e0 Binary files /dev/null and b/src/assets/images/404/hole-bg.jpg differ diff --git a/src/assets/images/favicons/android-icon-144x144.png b/src/assets/images/favicons/android-icon-144x144.png new file mode 100644 index 0000000..ca66e27 Binary files /dev/null and b/src/assets/images/favicons/android-icon-144x144.png differ diff --git a/src/assets/images/favicons/android-icon-192x192.png b/src/assets/images/favicons/android-icon-192x192.png new file mode 100644 index 0000000..afd52f1 Binary files /dev/null and b/src/assets/images/favicons/android-icon-192x192.png differ diff --git a/src/assets/images/favicons/android-icon-36x36.png b/src/assets/images/favicons/android-icon-36x36.png new file mode 100644 index 0000000..f146451 Binary files /dev/null and b/src/assets/images/favicons/android-icon-36x36.png differ diff --git a/src/assets/images/favicons/android-icon-48x48.png b/src/assets/images/favicons/android-icon-48x48.png new file mode 100644 index 0000000..9ef4111 Binary files /dev/null and b/src/assets/images/favicons/android-icon-48x48.png differ diff --git a/src/assets/images/favicons/android-icon-72x72.png b/src/assets/images/favicons/android-icon-72x72.png new file mode 100644 index 0000000..bd5dec5 Binary files /dev/null and b/src/assets/images/favicons/android-icon-72x72.png differ diff --git a/src/assets/images/favicons/android-icon-96x96.png b/src/assets/images/favicons/android-icon-96x96.png new file mode 100644 index 0000000..4de3aef Binary files /dev/null and b/src/assets/images/favicons/android-icon-96x96.png differ diff --git a/src/assets/images/favicons/apple-icon-114x114.png b/src/assets/images/favicons/apple-icon-114x114.png new file mode 100644 index 0000000..7334212 Binary files /dev/null and b/src/assets/images/favicons/apple-icon-114x114.png differ diff --git a/src/assets/images/favicons/apple-icon-120x120.png b/src/assets/images/favicons/apple-icon-120x120.png new file mode 100644 index 0000000..4d6e989 Binary files /dev/null and b/src/assets/images/favicons/apple-icon-120x120.png differ diff --git a/src/assets/images/favicons/apple-icon-144x144.png b/src/assets/images/favicons/apple-icon-144x144.png new file mode 100644 index 0000000..ca66e27 Binary files /dev/null and b/src/assets/images/favicons/apple-icon-144x144.png differ diff --git a/src/assets/images/favicons/apple-icon-152x152.png b/src/assets/images/favicons/apple-icon-152x152.png new file mode 100644 index 0000000..3f437ad Binary files /dev/null and b/src/assets/images/favicons/apple-icon-152x152.png differ diff --git a/src/assets/images/favicons/apple-icon-180x180.png b/src/assets/images/favicons/apple-icon-180x180.png new file mode 100644 index 0000000..1649c4b Binary files /dev/null and b/src/assets/images/favicons/apple-icon-180x180.png differ diff --git a/src/assets/images/favicons/apple-icon-57x57.png b/src/assets/images/favicons/apple-icon-57x57.png new file mode 100644 index 0000000..45cf0fd Binary files /dev/null and b/src/assets/images/favicons/apple-icon-57x57.png differ diff --git a/src/assets/images/favicons/apple-icon-60x60.png b/src/assets/images/favicons/apple-icon-60x60.png new file mode 100644 index 0000000..f14bdce Binary files /dev/null and b/src/assets/images/favicons/apple-icon-60x60.png differ diff --git a/src/assets/images/favicons/apple-icon-72x72.png b/src/assets/images/favicons/apple-icon-72x72.png new file mode 100644 index 0000000..bd5dec5 Binary files /dev/null and b/src/assets/images/favicons/apple-icon-72x72.png differ diff --git a/src/assets/images/favicons/apple-icon-76x76.png b/src/assets/images/favicons/apple-icon-76x76.png new file mode 100644 index 0000000..2ad500b Binary files /dev/null and b/src/assets/images/favicons/apple-icon-76x76.png differ diff --git a/src/assets/images/favicons/apple-icon-precomposed.png b/src/assets/images/favicons/apple-icon-precomposed.png new file mode 100644 index 0000000..bd7b484 Binary files /dev/null and b/src/assets/images/favicons/apple-icon-precomposed.png differ diff --git a/src/assets/images/favicons/apple-icon.png b/src/assets/images/favicons/apple-icon.png new file mode 100644 index 0000000..bd7b484 Binary files /dev/null and b/src/assets/images/favicons/apple-icon.png differ diff --git a/src/assets/images/favicons/favicon-32x32.png b/src/assets/images/favicons/favicon-32x32.png new file mode 100644 index 0000000..69d3259 Binary files /dev/null and b/src/assets/images/favicons/favicon-32x32.png differ diff --git a/src/assets/images/favicons/favicon-96x96.png b/src/assets/images/favicons/favicon-96x96.png new file mode 100644 index 0000000..4de3aef Binary files /dev/null and b/src/assets/images/favicons/favicon-96x96.png differ diff --git a/src/assets/images/favicons/favicon.ico b/src/assets/images/favicons/favicon.ico new file mode 100644 index 0000000..d9f98df Binary files /dev/null and b/src/assets/images/favicons/favicon.ico differ diff --git a/src/assets/images/favicons/favicon.png b/src/assets/images/favicons/favicon.png new file mode 100644 index 0000000..8626f74 Binary files /dev/null and b/src/assets/images/favicons/favicon.png differ diff --git a/src/assets/images/favicons/ms-icon-144x144.png b/src/assets/images/favicons/ms-icon-144x144.png new file mode 100644 index 0000000..ca66e27 Binary files /dev/null and b/src/assets/images/favicons/ms-icon-144x144.png differ diff --git a/src/assets/images/favicons/ms-icon-150x150.png b/src/assets/images/favicons/ms-icon-150x150.png new file mode 100644 index 0000000..7bbc031 Binary files /dev/null and b/src/assets/images/favicons/ms-icon-150x150.png differ diff --git a/src/assets/images/favicons/ms-icon-310x310.png b/src/assets/images/favicons/ms-icon-310x310.png new file mode 100644 index 0000000..df6a674 Binary files /dev/null and b/src/assets/images/favicons/ms-icon-310x310.png differ diff --git a/src/assets/images/favicons/ms-icon-70x70.png b/src/assets/images/favicons/ms-icon-70x70.png new file mode 100644 index 0000000..d926644 Binary files /dev/null and b/src/assets/images/favicons/ms-icon-70x70.png differ diff --git a/src/assets/images/portal-header.jpg b/src/assets/images/portal-header.jpg new file mode 100644 index 0000000..0ae1ecb Binary files /dev/null and b/src/assets/images/portal-header.jpg differ diff --git a/src/assets/scripts/1-docready.min.js b/src/assets/scripts/1-docready.min.js new file mode 100644 index 0000000..7225311 --- /dev/null +++ b/src/assets/scripts/1-docready.min.js @@ -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= -r&&n.top-window.innerHeight= -r&&n.left-window.innerWidthparseInt(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 diff --git a/src/assets/scripts/6-classlist.min.js b/src/assets/scripts/6-classlist.min.js new file mode 100644 index 0000000..1af33f4 --- /dev/null +++ b/src/assets/scripts/6-classlist.min.js @@ -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(++nn;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 diff --git a/src/assets/scripts/7-dayjs.min.js b/src/assets/scripts/7-dayjs.min.js new file mode 100644 index 0000000..b9935bc --- /dev/null +++ b/src/assets/scripts/7-dayjs.min.js @@ -0,0 +1,10 @@ +// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT +/* + dayjs v1.11.5 + https://github.com/iamkun/dayjs + The MIT License (MIT) + Copyright (c) 2018-present, iamkun +*/ +!(function(t,e){"object"==typeof exports&&"undefined"!=typeof module?(module.exports=e()):"function"==typeof define&&define.amd?define(e):((t="undefined"!=typeof globalThis?globalThis:t||self).dayjs=e())})(this,function(){"use strict";var t=1e3,e=6e4,n=36e5,r="millisecond",i="second",s="minute",u="hour",a="day",o="week",f="month",h="quarter",c="year",d="date",$="Invalid Date",l=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,y=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,M={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_")},m=function(t,e,n){var r=String(t);return!r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},g={s:m,z:function(t){var e= -t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return(e<=0?"+":"-")+m(r,2,"0")+":"+m(i,2,"0")},m:function t(e,n){if(e.date()1){return t(u[0])}}else{var a=e.name;(D[a]=e),(i=a)}return!r&&i&&(v=i),i||(!r&&v)},w=function(t,e){if(p(t)){return t.clone()}var n="object"==typeof e?e:{};return(n.date=t),(n.args=arguments),new _(n)},O=g;(O.l=S),(O.i=p),(O.w=function(t,e){return w(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})});var _=(function(){function M(t){(this.$L=S(t.locale,null,!0)),this.parse(t)}var m=M.prototype;return((m.parse=function(t){(this.$d=(function(t){var e=t.date,n=t.utc;if(null===e){return new Date(NaN)}if(O.u(e)){return new Date()}if(e instanceof Date){return new Date(e)}if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match(l);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)})(t)),(this.$x=t.x||{}),this.init()}),(m.init=function(){var t=this.$d;(this.$y=t.getFullYear()),(this.$M=t.getMonth()),(this.$D=t.getDate()),(this.$W=t.getDay()),(this.$H=t.getHours()),(this.$m=t.getMinutes()),(this.$s=t.getSeconds()),(this.$ms=t.getMilliseconds())}),(m.$utils=function(){return O}),(m.isValid=function(){return!(this.$d.toString()===$)}),(m.isSame=function(t,e){var n=w(t);return this.startOf(e)<=n&&n<=this.endOf(e)}),(m.isAfter=function(t,e){return w(t)0),p<=y.r||!y.r)){p<=1&&c>0&&(y=h[c-1]);var v=l[y.l];u&&(p=u(""+p)),(a="string"==typeof v?v.replace("%d",p):v(p,n,y.l,s));break}}if(n){return a}var M=s?l.future:l.past;return "function"==typeof M?M(a):M.replace("%s",a)}),(n.to=function(r,e){return i(r,e,this,!0)}),(n.from=function(r,e){return i(r,e,this)});var d=function(r){return r.$u?t.utc():t()};(n.toNow=function(r){return this.to(d(this),r)}),(n.fromNow=function(r){return this.from(d(this),r)})}}); +// @license-end diff --git a/src/assets/scripts/scripts.js b/src/assets/scripts/scripts.js new file mode 100644 index 0000000..2c8888c --- /dev/null +++ b/src/assets/scripts/scripts.js @@ -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 += + ' (' + + dayjs(pub_time).from(dayjs()) + + ")"; + 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 diff --git a/src/assets/styles/fonts.css b/src/assets/styles/fonts.css new file mode 100644 index 0000000..f1035bc --- /dev/null +++ b/src/assets/styles/fonts.css @@ -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; +} diff --git a/src/assets/styles/imports.css b/src/assets/styles/imports.css new file mode 100644 index 0000000..46709b5 --- /dev/null +++ b/src/assets/styles/imports.css @@ -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; + } + } diff --git a/src/assets/styles/styles.css b/src/assets/styles/styles.css new file mode 100644 index 0000000..bbd2c99 --- /dev/null +++ b/src/assets/styles/styles.css @@ -0,0 +1,1483 @@ +/**************************************************************************** + * Planar Vagabond's Playground (planarvagabond.com) + * + * Copyright 2023 Eric Woodward + * Source released under CC0 Public Domain License v1.0 + * http://creativecommons.org/publicdomain/zero/1.0/ + ****************************************************************************/ + +/* + Old Palette + color: #9aa8bc + bg: #0d1852 + link: 049c74 + border: 25baba + big-font: 25baba + black: 040308 + other: 094192 +*/ + +/* + New Palette + color: #e7a07f + bg: #252837 + link: #38B1B6 + border: #C33F3D + big-font: #C33F3D + black: #1A0608 + other: #E8A384 +*/ + +body { + background: #252837; /* Old browsers */ + background: url("/images/portal-header.jpg") top fixed; + color: #e7a07f; + font-family: sans-serif; + font-size: 100%; + line-height: 1.5em; +} + +a { + border: 1px solid transparent; + border-bottom: 1px solid #c33f3d; + color: #38b1b6; + font-weight: bold; + padding: 0 0.2rem; + text-decoration: none; + -webkit-transition: 0.3s background-color, 0.3s color, 0.3s border-radius; + transition: 0.3s background-color, 0.3s color, 0.3s border-radius; +} + +a[target="_blank"]::after { + content: " \29C9"; + font-weight: normal; +} + +a:hover { + border: 1px solid #c33f3d; + background-color: #38b1b6; + border-radius: 0.3rem; + color: #c33f3d; + text-decoration: none; + text-shadow: 0px 1px 1px rgba(16, 16, 16, 0.6); +} + +a:visited { + color: #3d81a5; +} + +a:visited:hover { + color: #c33f3d; +} + +article img { + max-width: 100%; +} + +blockquote { + border-left: 2px solid #04778f; + font-style: italic; + margin: 1em 0; + padding: 0 1em; +} + +code, +kbd { + background-color: #7c4c52; + background-color: rgba(13, 24, 82, 0.5); + color: #ccc; + font-size: 0.9em; + padding: 0.25em; +} + +figcaption { + font-size: 0.9em; +} + +figure { + margin: 1em auto; + display: block; + text-align: center; +} + +h1 { + color: #c33f3d; + font-size: 2em; + line-height: 1.2em; +} + +h2 { + color: #c33f3d; + font-size: 1.5em; + line-height: 1.2em; +} + +h3 { + color: #c33f3d; + font-size: 1.25em; + line-height: 1.2em; + background-color: rgba(37, 40, 55, 0.6); + padding-left: 0.25rem; +} + +h4 { + color: #c33f3d; + font-size: 1.12em; + line-height: 1.2em; +} + +h5 { + color: #c33f3d; + font-size: 1.06em; + line-height: 1.2em; +} + +h6 { + color: #c33f3d; + font-size: 1em; +} + +iframe { + max-width: 100%; +} + +li { + margin-bottom: 0.6em; +} + +/* turn on for that "authentic" OSE feel! */ +/* +ul li::marker { + content: "▶ "; +} +*/ + +li ul { + margin-top: 0.6em; +} + +main { + font-size: 0.95em; +} + +main img { + max-width: 100%; +} + +ol, +ul { + padding-left: 1.2em; +} + +pre { + background-color: #7c4c52; + background-color: rgba(13, 24, 82, 0.5); + color: #ccc; + font-size: 0.9em; + overflow: auto; + padding: 0.25em; +} + +pre code, +pre kbd { + background-color: transparent; +} + +samp { + font-size: 0.9em; +} + +table { + border: 1px solid #e8a384; + border-collapse: collapse; +} + +table caption { + background-color: rgba(37, 40, 55, 0.6); + color: rgba(195, 63, 61, 1); + font-weight: bold; +} + +table tr:hover { + background-color: rgba(195, 63, 61, 0.5); +} + +table td, +table th { + padding: 0.5rem; +} + +.headlessTableWrapper thead { + display: none; +} + +.dividedTableWrapper { + display: inline-block; + overflow-x: auto; +} + +.dividedTableWrapper h4 { + text-align: center; +} + +.dividedTableWrapper table td { + border: 1px dashed #e8a384; +} + +.dividedTableWrapper table th { + border: 1px dashed #e8a384; + border-bottom-style: solid; + border-collapse: collapse; + padding: 0.5rem; +} + +.levelTable table thead tr:first-child th { + border-bottom-style: dashed; +} + +.timelineTableWrapper td:first-child { + white-space: nowrap; +} + +.page { + background: #372734; + background: rgba(55, 39, 52, 0.9); + border: 1px solid #252837; + border-top: none; + border-bottom: none; + display: block; + margin: 0 auto; + max-width: 60em; + min-height: 100vh; + padding: 0; + position: relative; +} + +.table-of-contents { + border: 1px dashed #e8a384; + display: inline-block; +} + +.table-of-contents ul { + display: flex; + flex-direction: column; + list-style: none; + margin: 0 0 0.5rem 0; + padding: 0; +} + +.table-of-contents ul li { + display: flex; + flex-direction: column; + margin: 0; +} + +.table-of-contents ul li a { + border-bottom: 0; + padding: 0 0.5rem; + text-decoration: none; + width: 100%; +} + +.table-of-contents ul li ul { + margin: 0; +} + +.table-of-contents ul li ul li a { + padding: 0 0.5rem 0 1.5rem; + font-size: smaller; +} + +.toc-container-header { + text-align: center; + background-color: rgba(37, 40, 55, 0.6); + color: rgba(195, 63, 61, 1); + font-weight: bold; +} + +.anaCardImg { + height: auto; + margin: 0; + vertical-align: middle; + width: 100%; +} + +.anaCardLink { + display: block; + margin: 0 auto; + max-width: 15em; + padding: 0.5em; +} + +.anaLink-billy { + background: url("/images/anachronism/links/billy_the_kid.jpg") no-repeat + center; + background-size: cover; +} + +.anaLink-constitution { + background: url("/images/anachronism/links/the_us_constitution.jpg") + no-repeat center; + background-size: cover; +} + +.anaLink-dictator { + background: url("/images/anachronism/links/the_dictator.jpg") no-repeat + center; + background-size: cover; +} + +.anaLink-earp { + background: url("/images/anachronism/links/wyatt_earp.jpg") no-repeat center; + background-size: cover; +} + +.anaLink-grant { + background: url("/images/anachronism/links/ulysses_s_grant.jpg") no-repeat + center; + background-size: cover; +} + +.anaLink-lee { + background: url("/images/anachronism/links/robert_e_lee.jpg") no-repeat + center; + background-size: cover; +} + +.anaLink-lincoln { + background: url("/images/anachronism/links/abraham_lincoln.jpg") no-repeat + center; + background-size: cover; +} + +.anaLink-stetson { + background: url("/images/anachronism/links/stetson_hat.jpg") no-repeat + center; + background-size: cover; +} + +.anaLink-washington { + background: url("/images/anachronism/links/george_washington.jpg") no-repeat + center; + background-size: cover; +} + +.anaLogo { + display: block; + margin: 0 auto 1em; + width: 100%; +} + +.asideBio { + line-height: 1.35em; + padding: 0.5em; + text-align: center; +} + +.asideBio-div { + display: none; + margin: 0.5em auto; + text-align: center; +} + +.asideBio-div-img { + max-width: 7.5em; +} + +.asideBio-div-imgLink { + border: 1px solid transparent; + display: inline-block; + padding: 0.5em 0.5em 0.3em; +} + +.asideBio-div-link { + white-space: nowrap; +} + +.asideBio-div-textLink { + display: none; +} + +.asideContent { + border-top: 1px dotted #04778f; +} + +.asideMenu-divider { + border: 1px dashed #c33f3d; + color: #c33f3d; + width: 80%; +} + +.asideMenu-item { + align-self: auto; + display: inline-block; + flex: none; + margin: 0.5em; +} + +.asideMenu-link { + border: 1px solid #726f6a; + border-radius: 0.3em; + display: inline-block; + padding: 0.25em; +} + +.asideMenu-list { + align-content: center; + align-items: center; /* or stretch */ + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + list-style: none; + margin: 0; + padding: 0; + text-align: center; +} + +.asideMenu-subtitle { + font-size: 1.1em; + margin: 1.3em auto; + text-align: center; +} + +.asideMenu-subtitle-link { + border: 1px solid transparent; + display: inline-block; +} + +.asideMenu-title { + font-size: 1.3em; + margin: 1.5em auto; + text-align: center; +} + +.asideMenu-title-link { + border: 1px solid transparent; + display: inline-block; +} + +.asideRight { + padding: 0 1em 1em; +} + +.authorHidden { + display: none; +} + +.backLink { + clear: both; + font-size: 0.8em; + margin: 1em auto; + text-align: center; +} + +.backLink-link { + border: 1px solid #c33f3d; + color: #c33f3d; + display: inline-block; + padding: 0.25em 0.5em; +} + +.boxLink { + border: 1px solid #726f6a; + border-radius: 0.3em; + display: inline-block; + padding: 0.25em; +} + +.boxLink.isCurrent { + background-color: #38b1b6; + background-color: rgba(4, 156, 116, 0.8); + color: #1a0608; + text-decoration: none; +} + +.boxLink.isCurrent:hover { + background-color: #c33f3d; +} + +.dataTable td { + border-left: 1px solid #252837; + border-right: 1px solid #252837; + padding: 0.1em 0.2em; + text-align: center; +} + +.dataTable th { + border: 1px solid #252837; + color: #c33f3d; + padding: 0.2em 0.4em; + text-align: center; +} + +.dataTable thead tr { + background-color: #7c4c52; +} + +.dataTable tr { + border-bottom: 1px solid transparent; + border-top: 1px solid transparent; +} + +.dataTable tbody tr:last-child { + border-bottom: 1px solid #252837; +} + +.dataTable tbody:nth-child(even) tr:nth-child(even) { + background-color: #7c4c52; +} + +.dataTable tr:hover td { + border-bottom: 1px solid #c33f3d; + border-top: 1px solid #c33f3d; + color: #c33f3d; +} + +.dirList { + padding: 0; +} + +.dirList-item { + display: inline-block; + margin: 0.5em; +} + +.embed { + display: block; + margin: 1em auto; + width: 30em; + max-width: 100%; +} + +.embedLink { + padding: 0.2rem; +} + +.embedImg { + width: 100%; +} + +.embedSwitch { + text-align: right; + margin-bottom: 1em; +} + +.embedTwitter { + width: 100%; +} + +.embedVimeo { + height: 15em; + max-height: 50vw; +} + +.embedYoutube { + min-height: 21em; + width: 100%; +} + +.entry-content { + display: block; + min-height: 2.6em; + padding-top: 0.2em; +} + +.entry-reacji { + cursor: default; + float: left; + font-size: 2.2em; + margin-right: 0.2em; + vertical-align: middle; +} + +.entry-reply { + display: block; +} + +.feedLine { + border-color: #c33f3d; + border-color: rgba(37, 171, 171, 0.3); + clear: both; + margin: 2.5em auto; + width: 80%; +} + +.feedLine:last-child { + display: none; +} + +.footnote { + font-size: 0.8em; +} + +.gameCardImg { + border-radius: 1rem; + max-width: 100%; +} + +.gameCardImgLand { + border-radius: 2rem; + max-width: 100%; +} + +.heroImage { + display: block; + clear: both; + margin: auto; + max-width: 100%; +} + +.icon { + display: inline-block; + font-size: 2em; + font-style: normal; + text-transform: none; + vertical-align: middle; +} + +.imgAttrib { + font-size: 0.8em; +} + +.js-momentTime { + display: inline-block; +} + +.licenseImg { + vertical-align: middle; +} + +.licenseLink { + display: inline-block; + border: 1px solid transparent; + border-bottom: 1px solid transparent; + vertical-align: baseline; +} + +.linkButton { + margin: 1em auto; + text-align: center; +} + +.linkButton-link { + border: 1px solid #c33f3d; + color: #c33f3d; + display: inline-block; + padding: 0.25em 0.5em; +} + +.magic-commander-img { + border-radius: 0.75em; + max-width: 100%; + width: 15em; +} + +.mainBio-div-img { + height: auto; + margin: 0; + vertical-align: middle; + width: 100%; +} + +.mainBio-div-imgLink { + border: 1px solid transparent; + display: block; + margin: 0 auto; + max-width: 15em; + padding: 0.5em; +} + +.mainBio-div-imgLink:hover { + border-width: 3px; +} + +.mainBio-div-textLink { + display: none; +} + +.menubar { + background: #282c32; + display: static; +} + +#menu__toggle, +.menu__btn { + display: none; +} + +.navMenu { + align-items: center; + display: flex; + flex-direction: row-reverse; + justify-content: center; +} + +.navMenu a { + border: 1px solid transparent; + display: flex; + padding: 0.5rem 1rem; + text-decoration: none; + width: 100%; +} + +.navMenu a:visited { + color: #38b1b6; +} + +.navMenu ul { + display: flex; + list-style: none; + margin: 0; + padding-left: 0; +} + +.navMenu li { + display: flex; + margin-bottom: 0; + position: relative; + text-decoration: none; + transition-duration: 0.5s; +} + +.navMenu li.hasSubMenu:hover, +.navMenu li.hasSubMenu:focus-within { + background: #c33f3d; +} + +.navMenu li.hasSubMenu:hover a, +.navMenu li.hasSubMenu:hover a:visited { + color: #38b1b6; +} + +.navMenu li.hasSubMenu:hover a:hover, +.navMenu li.hasSubMenu:hover a:visited:hover { + color: #c33f3d; +} + +.navMenu li:focus-within a { + outline: none; +} + +.navMenu ul li ul { + background: #252837; + background: rgba(37, 40, 55, 0.9); + visibility: hidden; + opacity: 0; + min-width: 5rem; + position: absolute; + transition: all 0.5s ease; + margin-top: 2.75rem; + left: 0; + display: none; + z-index: 1; +} + +.navMenu ul li:hover > ul, +.navMenu ul li:focus-within > ul, +.navMenu ul li ul:hover, +.navMenu ul li ul:focus { + display: flex; + flex-direction: column; + align-items: stretch; + opacity: 1; + visibility: visible; + white-space: nowrap; +} + +.navMenu ul li ul li { + clear: both; + width: 100%; +} + +.navMenu-list-link.isCurrentSection { + background-color: #38b1b6; + border: 1px solid #c33f3d; + border-radius: 0.3rem; + color: #1a0608; + text-decoration: none; +} + +.navMenu-list-link.isCurrentSection:hover { + background-color: #c33f3d; +} + +.navMenu-list-item { + display: inline-block; + line-height: 2em; + margin: 0 0.25em; + text-align: center; +} + +.navMenu-search-fieldset { + border: none; + font-size: 0.8em; + padding: 0; +} + +.pageFooter { + border-top: 1px solid #04778f; + margin: 0 auto; + padding: 0.5em 1em; + width: 100%; +} + +.pageFooter a { + text-decoration: none; +} + +.pageFooter-inner { + font-size: 0.8em; + text-align: center; + width: 100%; +} + +.pageFooter-inner p { + text-align: left; +} + +.pageHeader { + width: 100%; +} + +.pageHeader-titleBlock { + width: 100%; +} + +.pageMain { + min-height: 28em; + padding: 1em; +} + +.pageMenu { + clear: both; + margin: 0 auto 2.5em; + max-width: 25em; + padding: 0px; + text-align: center; +} + +.pageMenu-anaCardLink { + border: 1px solid transparent; + display: inline-block; + margin: 0 auto; + padding: 0.5em; +} + +.pageMenu-anaCards { + align-items: center; + display: flex; + flex-direction: column; + max-width: 100%; +} + +.pageMenu-caption { + left: 0; + bottom: 0.5em; + position: absolute; + right: 0; +} +.pageMenu-gameCards { + align-items: center; + display: flex; + flex-direction: column; + justify-content: center; +} + +.pageMenu-gameCardLink { + border: 1px solid transparent; + display: flex; + margin: 0 auto; + max-width: 24em; + padding: 0.5em; +} + +.pageMenu-item { + align-items: center; + border: 2px solid transparent; + border-radius: 0.3rem; + display: flex; + justify-content: center; + margin-left: 0; + max-width: 340px; + text-align: center; + text-decoration: none; +} + +.pageMenu-item:hover { + border-color: #c33f3d; +} + +.pageMenu-link { + display: block; + height: 6.8em; + position: relative; + width: 6.8em; +} + +.pageMenu-link:hover .pageMenu-text { + background-color: #c33f3d; + background-color: rgba(150, 150, 150, 0.8); + color: #1a0608; + text-decoration: none; + width: 100%; +} + +.pageMenu-text { + background: rgba(50, 50, 50, 0.8); + display: inline-block; + margin: 0 auto; + width: 100%; +} + +.pageSection { + word-wrap: break-word; +} + +.postsByYear { + clear: both; + display: block; + float: right; +} + +.postsByYear { + clear: both; + font-size: 0.8em; + margin: 1em auto; + text-align: center; +} + +.postsByYear-list { + display: inline; + margin: 0; + max-width: 25em; + padding: 0px; +} + +.postsByYear-list li { + border-radius: 0.3rem; +} + +.postMenu { + max-height: 0; + overflow: hidden; +} +.postMenu-item { + align-self: auto; + display: inline-block; + flex: none; + margin: 0.5em; +} + +.postMenu-link { + border: 1px solid #726f6a; + border-radius: 0.3em; + display: inline-block; + padding: 0.25em; +} + +.postMenu-list { + align-content: center; + align-items: center; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + list-style: none; + margin: 0; + padding: 0; + text-align: center; +} + +.postMenu-title { + font-size: 1.3em; + margin: 1.5em auto; + text-align: center; +} + +.postMenu-title-link { + border: 1px solid transparent; + display: inline-block; +} + +.pubDate { + font-size: 0.9em; + visibility: hidden; +} + +.raceTable-name { + font-weight: bold; + text-align: left; +} + +.readTime { + font-size: 0.9em; + font-style: italic; + text-align: right; + text-transform: capitalize; +} + +.replyTo { + margin: 0 1.25em; +} + +.replyTo-arrow { + font-size: 1.75em; +} + +.searchBox { + margin-left: auto; + white-space: nowrap; +} + +.searchBox-query { + background-color: #3d81a5; + border-radius: 0.3rem; + color: #000; + width: 10em; +} + +.siteTitle { + text-align: center; + width: 100%; +} + +.siteTitle-link, +.siteTitle-link:visited { + align-items: center; + border: 1px solid transparent; + color: #38b1b6; + border-radius: 1rem; + display: flex; + flex-direction: row; + font-style: normal; + margin: auto; + max-width: 42rem; + padding: 0.2em; + position: relative; +} + +.siteTitle-shipIcon { + max-width: 10rem; + fill: #c33f3d; + fill-opacity: 0.5; + -webkit-transition: 0.3s fill, 0.3s fill-opacity; + transition: 0.3s fill, 0.3s fill-opacity; +} + +.siteTitle-link:hover .siteTitle-shipIcon { + fill: #1a0608; + fill-opacity: 0.7; +} + +.siteTitle-text { + max-width: 48rem; + text-align: left; +} + +.spellTable { + margin-left: -1em; +} + +.spellTable td { + border: 1px solid #e8a384; +} + +.spellTable tbody:nth-child(even) { + background-color: #7c4c52; +} + +.tagPage-counts { + font-size: 0.9em; + font-style: italic; +} + +.tagPage-list { + padding-left: 0; +} + +.tagPage-listItem { + display: inline; + list-style: none; + padding: 0.3em 0.5em; +} + +.teaseLink { + clear: both; + font-size: 0.8em; + margin: 1em auto; + text-align: center; +} + +.teaseLink-link { + color: #c33f3d; + display: inline-block; + padding: 0.25em 0.5em; +} + +a.pageTitle-link, +a.pageTitle-link:visited { + color: #c33f3d; + border-bottom: none; + margin-left: -0.25rem; +} + +.pageTitle-link:active, +.pageTitle-link:hover { + border: 1px solid #c33f3d; +} + +.topAnchor { + border: none; + height: 0; + margin: 0; + padding: 0; +} + +.topLink { + border: 1px solid #726f6a; + border-radius: 0.3em; + display: inline-block; + margin: 1em auto; + padding: 0.25em; + text-align: center; +} + +.twitter-tweet { + margin: 0; +} + +.update-citation { + display: block; + text-align: right; +} + +.update-footer { + align-items: start; + color: #423f4d; + display: flex; + flex-direction: column; + font-size: 0.9em; + font-style: italic; + margin-top: 1em; +} + +.update-footer-author { + display: none; +} + +.update-footer-time { + margin-left: auto; +} + +.update-nav { + border-top: 1px dotted; + display: flex; + margin-top: 1em; + padding-top: 1em; + width: 100%; +} + +.update-nav-link { + border: 1px dashed #726f6a; + border-radius: 0.3em; + display: block; + padding: 0.5em; +} + +.update-nav-linkWrapper { + max-width: 45%; +} + +.update-nav-pubDate { + font-size: 0.8em; +} + +.update-nav-nextWrapper { + margin-left: auto; + text-align: right; +} + +.update-tags { + align-items: center; + display: flex; + flex-wrap: wrap; + margin-top: 1em; + text-align: left; +} + +.update-tags > .boxLink { + margin: 0.5em; +} + +/**************************************************************************** + * State Overrides + ****************************************************************************/ + +.asideBio.hideBio { + display: none; +} + +.asideBio-div.showPhoto { + display: inline-block; +} + +.asideMenu-link.isCurrent { + background-color: #38b1b6; + background-color: rgba(4, 156, 116, 0.8); + color: #1a0608; + text-decoration: none; +} + +.asideUpdates .update-citation { + display: none; +} + +.asideUpdates .update-footer-time { + margin: 1em 0 2em; +} + +.asideUpdates .js-momentTime { + display: none; +} + +.asideUpdates .reacji { + float: left; +} + +.feature hr { + border: 1px 0 0 0; + border-color: #7c4c52; + border-style: solid; + color: #7c4c52; + max-width: 90%; +} + +.js-hasFontsLoaded body { + font-family: "Exo 2", sans-serif; +} + +.pageMenu-link.isCurrent .pageMenu-text { + background-color: #38b1b6; + background-color: rgba(4, 156, 116, 0.8); + color: #1a0608; + text-decoration: none; +} + +.pageMenu-link.isCurrent:hover .pageMenu-text { + background-color: #c33f3d; +} + +.textMenu-link.isCurrent { + background-color: #38b1b6; + background-color: rgba(4, 156, 116, 0.8); + color: #1a0608; + text-decoration: none; +} + +.textMenu-link.isCurrent:hover { + background-color: #c33f3d; +} + +.update.isDraft { + background-color: #282c32; +} + +.update.isEmbargoed { + background-color: #151222; +} + +/**************************************************************************** + * JS Overrides + ****************************************************************************/ + +.js .pubDate.isDone { + -webkit-transition: 0.3s visibility; + transition: 0.3s visibility; + visibility: visible; +} + +.no-js .pubDate { + visibility: visible; +} + +/**************************************************************************** + * Media Queries + ****************************************************************************/ + +/* Mobile */ +@media all and (max-width: 1000px) { + .menubar { + max-height: 1px; + } + #menu__toggle, + .menu__btn { + display: block; + } + + #menu__toggle { + opacity: 0; + } + #menu__toggle:checked + .menu__btn > span { + transform: rotate(45deg); + } + #menu__toggle:checked + .menu__btn > span::before { + top: 0; + transform: rotate(0deg); + } + #menu__toggle:checked + .menu__btn > span::after { + top: 0; + transform: rotate(90deg); + } + #menu__toggle:checked ~ .navMenu { + left: 0 !important; + } + .menu__btn { + position: absolute; + top: 0.5rem; + left: 1rem; + width: 26px; + height: 26px; + padding-top: 1rem; + cursor: pointer; + z-index: 9; + } + .menu__btn > span, + .menu__btn > span::before, + .menu__btn > span::after { + display: block; + position: absolute; + width: 100%; + height: 2px; + background-color: #616161; + transition-duration: 0.25s; + } + .menu__btn > span::before { + content: ""; + top: -8px; + } + .menu__btn > span::after { + content: ""; + top: 8px; + } + + .navMenu { + background: #252837; + display: flex; + position: fixed; + top: 0; + left: -100%; + height: 100%; + margin: 0; + padding: 0 2rem 0 4rem; + box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.4); + transition-duration: 0.25s; + flex-direction: column; + overflow-y: auto; + } + .navMenu ul { + flex-direction: column; + } + .navMenu > ul > li { + margin-left: -1rem; + margin-top: 1rem; + } + .navMenu > ul > li > a { + border: 1px solid #c33f3d; + font-size: 1.25rem; + } + .navMenu > ul > li:first-child { + margin-top: 0; + } + .navMenu li { + display: block; + float: none; + } + .navMenu ul li ul { + display: flex; + margin-top: 0; + opacity: 1; + position: relative; + visibility: visible; + } + .searchBox { + margin: 1rem 0 2rem; + } + + .siteTitle { + margin-top: 3rem; + } +} + +@media all and (min-width: 38em) { + .navMenu { + padding: 0 0.25rem; + } + + .navMenu-list { + float: left; + text-align: left; + } + + .navMenu-list-item { + margin-left: 0.75em; + } + + .searchBox { + float: right; + margin-right: 0.75em; + } +} + +@media all and (min-width: 34em) { + .magic-commander-img { + float: right; + } +} + +@media all and (min-width: 52em) { + body { + font-size: 1.1em; + } +} + +@media all and (min-width: 70em) { + .asideContent { + background: #1a0608; + background: rgba(4, 3, 8, 0.9); + border: 1px solid #252837; + font-size: 0.8em; + position: absolute; + top: 5em; + } + + .asideLeft { + left: -14em; + width: 14em; + } + + .asideMenu-divider { + display: none; + } + + .asideRight { + right: -15em; + width: 15em; + } + + .asideRight .asideMenu-link { + max-width: 13em; + } + + .postMainIndex .feedLine:last-child { + display: block; + } + + .postMenu { + max-height: none; + } + + .socialList-item { + display: inherit; + } +} diff --git a/src/layouts/default.ejs b/src/layouts/default.ejs new file mode 100644 index 0000000..5cd4831 --- /dev/null +++ b/src/layouts/default.ejs @@ -0,0 +1,44 @@ + + + +<%- include('partials/top') %> + + +
+ + +
+
+ + <%- 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 %> + + <% } -%> + +
+
+ + <%- include('partials/menusub') %> + + <%- include('partials/footer') %> + +
+ +<%- include('partials/bottom') %> + + diff --git a/src/layouts/functions.ejs b/src/layouts/functions.ejs new file mode 100644 index 0000000..f488750 --- /dev/null +++ b/src/layouts/functions.ejs @@ -0,0 +1,103 @@ +<% + htmlize = (text) => { + return (text || '').replace(/&/g, '&').replace(//g, '>'); + }; + + 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; + } + }; + +-%> diff --git a/src/layouts/journal-year.ejs b/src/layouts/journal-year.ejs new file mode 100644 index 0000000..fd5cad5 --- /dev/null +++ b/src/layouts/journal-year.ejs @@ -0,0 +1,170 @@ + +<% + const titleLink = (site?.base_uri) ? site.base_uri : '/'; + const { entriesToList = [], pageCount, pageNum, year = '' } = page; +-%> + +<%- include('partials/top') %> + + + +
+ + +
+
+ + <%- include('partials/embed_switch') %> + + <%- include('functions') -%> + + <% if (page?.title && (page.render_opts || '').indexOf('no_title') == -1) { -%> + +

+ Journal Entries By Year: + <%= year %> +

+ <% if (pageCount > 1) { -%> +

+ (Page <%= pageNum %> of <%= pageCount %>) +

+ <% } -%> +

+ Assorted journal entries from <%= year %>. +

+ +
+ +

+ + <% } -%> + + <% if (entriesToList && year) { -%> + <% entriesToList.forEach((entry) => { -%> + <% if (entry?.content_type === 'journal') { -%> + <% + const { + author = {}, + content = '', + date_pub = '', + description = '', + path = '', + readTime = '', + tags = [], + title = '', + tldr = '' + } = entry; + -%> +

+ +
+ + <% if (author?.site && author?.name) { -%> + + <% if (author?.photo) { -%> + + <% } -%> + + + + <% } else if (site?.author?.site && site?.author?.name) { -%> + + <% if (site?.author?.photo) { -%> + + <% } -%> + + + + <% } -%> + +
+ +

+ <%= title %> +

+ + <% if (tldr || description) { -%> +

TL;DR — <%- tldr || description %>

+ <% } -%> + + <% if (readTime) { -%> +

+ 👓 <%= readTime %> +

+ <% } -%> + + <% if (content) { -%> +
+ <%- content %> +
+ <% } -%> + + + +
+ + +
+ + <% } -%> + <% }); -%> + <% } -%> + + <%- include('partials/pageMenu') %> + +
+
+ + <%- include('partials/journal/menusub') %> + + <%- include('partials/bio') %> + + <%- include('partials/footer') %> + +
+ +<%- include('partials/bottom') %> diff --git a/src/layouts/partials/bio.ejs b/src/layouts/partials/bio.ejs new file mode 100644 index 0000000..c168f3e --- /dev/null +++ b/src/layouts/partials/bio.ejs @@ -0,0 +1,76 @@ + + + +<% + 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) { +%> + + + +<% } %> + + diff --git a/src/layouts/partials/bottom.ejs b/src/layouts/partials/bottom.ejs new file mode 100644 index 0000000..4a6f143 --- /dev/null +++ b/src/layouts/partials/bottom.ejs @@ -0,0 +1,16 @@ + + + +
+ + + + + + + + + + + + diff --git a/src/layouts/partials/content_types/feature.ejs b/src/layouts/partials/content_types/feature.ejs new file mode 100644 index 0000000..eaa4d29 --- /dev/null +++ b/src/layouts/partials/content_types/feature.ejs @@ -0,0 +1,15 @@ +<%- include('../../functions') -%> + +<% if (page.title && (page.render_opts || '').indexOf('no_title') == -1) { -%> + +

+ <%= page.title %> +

+<% } -%> + +<%- content %> diff --git a/src/layouts/partials/content_types/journal.ejs b/src/layouts/partials/content_types/journal.ejs new file mode 100644 index 0000000..26b5456 --- /dev/null +++ b/src/layouts/partials/content_types/journal.ejs @@ -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) { -%> + +

+ <%= page.title %> +

+ +<% } -%> + +<% if (page.readTime) { -%> + +

+ 👓 <%= page.readTime %> +

+ +<% } -%> + + +<% if (page.tldr || page.description) { -%> +

TL;DR — <%- page.tldr || page.description %>

+<% } -%> + +
+ <%- content %> +
+ + \ No newline at end of file diff --git a/src/layouts/partials/content_types/magic-deck.ejs b/src/layouts/partials/content_types/magic-deck.ejs new file mode 100644 index 0000000..2bfc678 --- /dev/null +++ b/src/layouts/partials/content_types/magic-deck.ejs @@ -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 ? + "
  • " + info.commander + + " (Commander)
  • " : + "", + commander_img = info.commander && info.commander_img ? + "" + info.commander + " card": + "" + ; +-%> + +

    + + <%= page.title %> + +

    + +

    + Below is my <%= info.name %> deck for Magic: the Gathering + (<%= info.format %> format). + It was last updated on <%= info.date_upd %>. +

    + +

    + Each card listed below is linked to its entry on The Gatherer + (the official MtG card database) and should open in a new window or tab in + your browser. +

    + +

    Decklist

    + +<%- commander_img %> + +
      + <%- commander %> + <% cards.forEach(function(card) { %> +
    • <%= card %>
    • + <% }) %> + <% lands.forEach(function(land) { %> +
    • <%= land.type %> (x<%=land.count%>)
    • + <% }) %> +
    + +<% if (starting_lands && starting_lands.length > 0) { %> +

    Starting Lands

    +

    + 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.

    +
      + <% starting_lands.forEach(function(land) { %> + <% if (typeof land === 'string') { %> +
    • <%= land %>
    • + <% } else {%> +
    • + <%= land.type %> (x<%=land.count%>) +
    • + <% } %> + <% }) %> +
    +<% } %> + +<% if (changes && changes.length > 0) { %> +

    Changes from Previous Versions

    + +
      + <% changes.forEach(function(change) { %> +
    • + Implemented <%= change.date_upd %>: +
        + <% if (change.adds) { %> + <% change.adds.forEach(function(add) { %> +
      • <%= add %> (added)
      • + <% }) %> + <% } %> + <% if (change.dels) { %> + <% change.dels.forEach(function(del) { %> +
      • <%= del %> (removed)
      • + <% }) %> + <% } %> +
      +
    • + <% }) %> +
    +<% } %> + +<%- content %> diff --git a/src/layouts/partials/embed_switch.ejs b/src/layouts/partials/embed_switch.ejs new file mode 100644 index 0000000..973cc2b --- /dev/null +++ b/src/layouts/partials/embed_switch.ejs @@ -0,0 +1,15 @@ + + + +<% + if (page.path && page.path.indexOf('/updates') === 0) { +-%> +
    +
    + + +
    +
    +<% } -%> + + diff --git a/src/layouts/partials/footer.ejs b/src/layouts/partials/footer.ejs new file mode 100644 index 0000000..56e928a --- /dev/null +++ b/src/layouts/partials/footer.ejs @@ -0,0 +1,51 @@ + + + + + + diff --git a/src/layouts/partials/menusub.ejs b/src/layouts/partials/menusub.ejs new file mode 100644 index 0000000..52d4f88 --- /dev/null +++ b/src/layouts/partials/menusub.ejs @@ -0,0 +1,21 @@ + + +<% 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') %> +<% } %> + + diff --git a/src/layouts/partials/navmain.ejs b/src/layouts/partials/navmain.ejs new file mode 100644 index 0000000..318423c --- /dev/null +++ b/src/layouts/partials/navmain.ejs @@ -0,0 +1,100 @@ + + + + + + diff --git a/src/layouts/partials/pageMenu.ejs b/src/layouts/partials/pageMenu.ejs new file mode 100644 index 0000000..cac5679 --- /dev/null +++ b/src/layouts/partials/pageMenu.ejs @@ -0,0 +1,17 @@ +<% const { pageCount, pageNum } = page; -%> + +<% if (pageNum && pageCount > 1) { -%> + +<% } -%> diff --git a/src/layouts/partials/pageTitle.ejs b/src/layouts/partials/pageTitle.ejs new file mode 100644 index 0000000..ffdf63e --- /dev/null +++ b/src/layouts/partials/pageTitle.ejs @@ -0,0 +1,13 @@ +<%- include('../functions') -%> + +<% if (page.title && (page.render_opts || '').indexOf('no_title') == -1) { -%> + +

    + <%= page.title %> +

    + <% } -%> diff --git a/src/layouts/partials/shipIcon.ejs b/src/layouts/partials/shipIcon.ejs new file mode 100644 index 0000000..9f8218e --- /dev/null +++ b/src/layouts/partials/shipIcon.ejs @@ -0,0 +1,206 @@ + + + diff --git a/src/layouts/partials/siteTitle.ejs b/src/layouts/partials/siteTitle.ejs new file mode 100644 index 0000000..be5a5f4 --- /dev/null +++ b/src/layouts/partials/siteTitle.ejs @@ -0,0 +1,13 @@ +<% var titleLink = (site && site.base_uri) ? site.base_uri : '/'; -%> + + diff --git a/src/layouts/partials/toolbar.ejs b/src/layouts/partials/toolbar.ejs new file mode 100644 index 0000000..3ce4733 --- /dev/null +++ b/src/layouts/partials/toolbar.ejs @@ -0,0 +1,11 @@ + + + +
    + + + + Menu +
    + + diff --git a/src/layouts/partials/top.ejs b/src/layouts/partials/top.ejs new file mode 100644 index 0000000..1c9c59d --- /dev/null +++ b/src/layouts/partials/top.ejs @@ -0,0 +1,88 @@ + + +<% + const + getPageField = (field_name) => { + return page[field_name] || site[field_name] || ''; + }, + getUrl = () => site.base_uri + (site.base_uri.endsWith('/') ? '' : '/') + page.path; +-%> + + + + + + + + + + + + + <%= page.title ? page.title + ' | ' : ''%><%= page.sub_title ? page.sub_title + ' | ' : ''%><%= site.title %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<% if (page.image) { %> + +<% } %> +<% if (page.description) { %> + +<% } %> + + +<% if (page.image) { %> + +<% } else { %> + +<% } %> + + + + +<% if (page.author && page.author.twitter) { %> + +<% } else if (site.author && site.author.twitter) { %> + +<% } %> + + + + + + + + + + + diff --git a/src/layouts/partials/topLink.ejs b/src/layouts/partials/topLink.ejs new file mode 100644 index 0000000..e69de29 diff --git a/src/layouts/tag.ejs b/src/layouts/tag.ejs new file mode 100644 index 0000000..381eada --- /dev/null +++ b/src/layouts/tag.ejs @@ -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') %> + + + + + +
    + + +
    +
    + + <%- include('partials/embed_switch') -%> + + <%- include('functions') -%> + + <% if (page.title && (page.render_opts || '').indexOf('no_title') == -1) { -%> + +

    + Journal Entries By Tag: #<%= tag -%> +

    + + <% if (pageCount > 1) { -%> +

    + (Page <%= pageNum %> of <%= pageCount %>) +

    + <% } -%> + +

    + Assorted journal entries with the tag #<%= tag %>. +

    + +
    + +

    + <% } -%> + + <% if (content && Array.isArray(entriesToList)) { -%> + <% entriesToList.forEach((page) => { -%> +

    + +
    + + <% if (page.author && page.author.site && page.author.name) { -%> + + <% if (page.author.photo) { -%> + + <% } -%> + + + + <% } else if (site.author && site.author.site && site.author.name) { -%> + + <% if (site.author.photo) { -%> + + <% } -%> + + + + <% } -%> + +
    + +

    + <%= page.title %> +

    + + <% if (page.tldr || page.description) { -%> +

    TL;DR — <%- page.tldr || page.description %>

    + <% } -%> + + <% if (page.readTime) { -%> +

    + 👓 <%= page.readTime %> +

    + <% } -%> + + <% if (page.content) { -%> +
    + <%- page.content %> +
    + <% } -%> + + + +
    + + +
    + + <% }) -%> + + <% } -%> + + <%- include('partials/pageMenu') %> + + + +
    +
    + +<%- include('partials/journal/menusub') %> + +<%- include('partials/bio') %> + +<%- include('partials/footer') %> + +
    + +<%- include('partials/bottom') %> diff --git a/src/pages/astral/adventuring.md b/src/pages/astral/adventuring.md new file mode 100644 index 0000000..d1332b0 --- /dev/null +++ b/src/pages/astral/adventuring.md @@ -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. + +
    + +| Fleeing Side’s Movement Rate | Chance of Evasion | +| -------------------------------------- | ----------------- | +| Faster than pursuer | 80% | +| 0’–30’ per round slower than pursuer | 50% | +| 31’–60’ per round slower than pursuer | 40% | +| 61’–90’ per round slower than pursuer | 35% | +| 91’–120’ per round slower than pursuer | 25% | +| 121’+ per round slower than pursuer | 10% | + +[Astralborne Evasion Chance] + +
    + +### 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. diff --git a/src/pages/astral/factions.md b/src/pages/astral/factions.md new file mode 100644 index 0000000..6e9dddf --- /dev/null +++ b/src/pages/astral/factions.md @@ -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). diff --git a/src/pages/astral/index.md b/src/pages/astral/index.md new file mode 100644 index 0000000..58a4fb3 --- /dev/null +++ b/src/pages/astral/index.md @@ -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) + + diff --git a/src/pages/astral/vessels.md b/src/pages/astral/vessels.md new file mode 100644 index 0000000..aec8569 --- /dev/null +++ b/src/pages/astral/vessels.md @@ -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. + + + +#### 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 + +
    + +| 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 [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] + +
    + +[^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 ship’s 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 ship’s 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 ship’s 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 + + + +#### 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. diff --git a/src/pages/astral/zenopus-cove.md b/src/pages/astral/zenopus-cove.md new file mode 100644 index 0000000..d22dd49 --- /dev/null +++ b/src/pages/astral/zenopus-cove.md @@ -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. diff --git a/src/pages/campaign/index.md b/src/pages/campaign/index.md new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/campaign/ravager.md b/src/pages/campaign/ravager.md new file mode 100644 index 0000000..f3c954b --- /dev/null +++ b/src/pages/campaign/ravager.md @@ -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 Queen’s 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. diff --git a/src/pages/campaign/sapphire-cove.md b/src/pages/campaign/sapphire-cove.md new file mode 100644 index 0000000..d8adfa5 --- /dev/null +++ b/src/pages/campaign/sapphire-cove.md @@ -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. + + + +### 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 + + + +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. diff --git a/src/pages/campaign/shazz-journals.md b/src/pages/campaign/shazz-journals.md new file mode 100644 index 0000000..2f6cca6 --- /dev/null +++ b/src/pages/campaign/shazz-journals.md @@ -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. diff --git a/src/pages/campaign/timeline.md b/src/pages/campaign/timeline.md new file mode 100644 index 0000000..15906f9 --- /dev/null +++ b/src/pages/campaign/timeline.md @@ -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. diff --git a/src/pages/classes/astral-corsair.md b/src/pages/classes/astral-corsair.md new file mode 100644 index 0000000..6e14f19 --- /dev/null +++ b/src/pages/classes/astral-corsair.md @@ -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 +--- + +
    + +| | | +| ------------------- | ------------------- | +| **Requirements** | None | +| **Prime requisite** | DEX | +| **Hit Dice** | 1d4 | +| **Maximum Level** | 14 | +| **Armour** | Leather, no shields | +| **Weapons** | Any | +| **Languages** | Alignment, Common | + +
    + +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%. + +
    + +| 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] + +
    + +### 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 player’s 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. + +
    + +| |||| 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. + +
    diff --git a/src/pages/classes/automaton.md b/src/pages/classes/automaton.md new file mode 100644 index 0000000..379cae8 --- /dev/null +++ b/src/pages/classes/automaton.md @@ -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 don’t 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. + +
    + +| |||| 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. + +
    diff --git a/src/pages/classes/corsair.md b/src/pages/classes/corsair.md new file mode 100644 index 0000000..af2b5db --- /dev/null +++ b/src/pages/classes/corsair.md @@ -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 +--- + +
    + +| | | +| ------------------- | ------------------- | +| **Requirements** | None | +| **Prime requisite** | DEX | +| **Hit Dice** | 1d4 | +| **Maximum Level** | 14 | +| **Armour** | Leather, no shields | +| **Weapons** | Any | +| **Languages** | Alignment, Common | + +
    + +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%. + +
    + +| 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] + +
    + +### 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 player’s 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 + +
    + +| |||| 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. + +
    diff --git a/src/pages/classes/dracokin.md b/src/pages/classes/dracokin.md new file mode 100644 index 0000000..f91cbaf --- /dev/null +++ b/src/pages/classes/dracokin.md @@ -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. + +
    + +| 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] + +
    + +[^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 + +
    + +| |||| 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] + +
    + +[^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. diff --git a/src/pages/classes/felinar.md b/src/pages/classes/felinar.md new file mode 100644 index 0000000..b3b17e3 --- /dev/null +++ b/src/pages/classes/felinar.md @@ -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 felinar’s ability to tumble negates the opponent’s 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%. + +
    + +| 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] + +
    + +### 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 player’s 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 + +
    + +| |||| 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] + +
    + +[^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. diff --git a/src/pages/classes/firfolk.md b/src/pages/classes/firfolk.md new file mode 100644 index 0000000..2297619 --- /dev/null +++ b/src/pages/classes/firfolk.md @@ -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 character’s 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 13–15 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 + +
    + +| |||| 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] + +
    + +[^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. diff --git a/src/pages/classes/index.md b/src/pages/classes/index.md new file mode 100644 index 0000000..ff25a10 --- /dev/null +++ b/src/pages/classes/index.md @@ -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) + + diff --git a/src/pages/classes/mimikin.md b/src/pages/classes/mimikin.md new file mode 100644 index 0000000..044aeb0 --- /dev/null +++ b/src/pages/classes/mimikin.md @@ -0,0 +1,103 @@ +--- +title: Mimikin +description: The Mimikin (changeling) class for Old School Essentials. +date_pub: 2023-02-17T22:44:00-05:00 +section: classes +content_type: feature +short_code: cm1 +--- + +| Demihuman Class | | +| ------------------- | ---------------------------------------- | +| **Requirements** | Minimum CHA 9 | +| **Prime requisite** | CHA and DEX | +| **Hit Dice** | 1d6 | +| **Maximum Level** | 10 | +| **Armour** | Leather or chainmail, no shields | +| **Weapons** | Any | +| **Languages** | Alignment, Common, any 2 other languages | + +Mimikin are a rarely-seen type of demihuman, able to shift their physical forms at will. Human-like in stature, their true forms usually have naturally pale, grey skin, unnervingly blank facial features, and silvery hair. However, because of their shapeshifting nature, they usuallly pass as humans or other similarly-sized demihumans, only returning to their true forms on death. + +Many mimikin use their gifts as a form of artistic or emotional expression, but some see them as invaluable tools for grifting, spying, and general deception, leading to their distrust by the other races. + +**Prime requisites**: A mimikin with at least 13 CHA and DEX gains a 5% bonus to experience. A mimikin with at least 16 CHA and DEX gains a 10% bonus. + +[[toc]] + +### Back-stab + +When attacking an unaware opponent from behind, a mimikin receives a +4 bonus to hit and doubles any damage dealt. + +### Combat + +Mimikin can use all types of weapons and can use leather armour and chainmail. Because of their need for freedom of movement, they cannot use plate mail or shields. + +### Languages + +Mimikin know Common, their alignment language, and 2 other langauges of the referee's choice. + +### Shapechanger + +Mimikin can change their appearance and voice on demand. This includes choosing specific colorations of eyes, skin, and hair, along with hair length, sex, and even height and weight (the latter two within human standards). This ability even allows them to pass as a member of another (human-sized) race, although their game statistics do not change. They can only duplicate the appearance of a creature that they have seen, and are limited to forms with the same basic arrangement of limbs. The change process takes about 10 seconds, and has no affect on their clothing or equipment. Once the change is complete, in can be maintained until the mimikin either changes again or they die (at which point they revert to their true forms). + +### Mimikin Skills + +Mimikin can use the following skills, with the chance of success shown opposite: + +- **Hide in shadows (HS)**: Requires the mimikin to be motionless - attacking or moving while hiding is not possible. +- **Move silently (MS)**: A mimikin may attempt to sneak past enemies unnoticed. + +| Level | HS | MS | +| :---: | :-: | :-: | +| 1 | 10 | 20 | +| 2 | 15 | 25 | +| 3 | 20 | 30 | +| 4 | 25 | 35 | +| 5 | 30 | 40 | +| 6 | 35 | 45 | +| 7 | 40 | 50 | +| 8 | 45 | 55 | +| 9 | 55 | 65 | +| 10 | 65 | 75 | + +[Mimikin Skills Chance of Success] + +
    + +### 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 player’s behalf, as the mimikin does not immediately know if the attempt was successful. If a hide in shadows or move silently roll fails, the referee knows that the mimikin has been noticed and should determine enemies’ actions appropriately. + +### After Reaching 8th Level + +A mimikin may form a new tribe, attracting 2d4 other mimikin of first level. + +#### Mimikin Level Progression + +
    + +| |||| Saving Throws ||||| +| Level | XP | HD | THAC0 | D[^1] | W[^1] | P[^1] | B[^1] | S[^1] | +| :---: | :-----: | :-------: | :-----: | :---: | :---: | :---: | :---: | :---: | +| 1 | 0 | 1d6 | 19 [0] | 13 | 14 | 13 | 16 | 15 | +| 2 | 1,800 | 2d6 | 19 [0] | 13 | 14 | 13 | 16 | 15 | +| 3 | 3,600 | 3d6 | 19 [0] | 13 | 14 | 13 | 16 | 15 | +| 4 | 7,000 | 4d6 | 19 [0] | 13 | 14 | 13 | 16 | 15 | +| 5 | 14,600 | 5d6 | 17 [+2] | 12 | 13 | 11 | 14 | 13 | +| 6 | 28,000 | 6d6 | 17 [+2] | 12 | 13 | 11 | 14 | 13 | +| 7 | 60,000 | 7d6 | 17 [+2] | 12 | 13 | 11 | 14 | 13 | +| 8 | 120,000 | 8d6 | 17 [+2] | 12 | 13 | 11 | 14 | 13 | +| 9 | 250,000 | 9d6 | 14 [+5] | 10 | 11 | 9 | 12 | 10 | +| 10 | 500,000 | 9d6+2[^2] | 14 [+5] | 10 | 11 | 9 | 12 | 10 | + +[Mimikin 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. + +
    diff --git a/src/pages/classes/tortokin.md b/src/pages/classes/tortokin.md new file mode 100644 index 0000000..5ff36a6 --- /dev/null +++ b/src/pages/classes/tortokin.md @@ -0,0 +1,67 @@ +--- +title: Tortokin +description: TBD +date_pub: 2023-02-15T00:26:00-05:00 +section: classes +content_type: feature +short_code: ct1 +--- + +| Demihuman Class | | +| ------------------- | -------------------------- | +| **Requirements** | Minimum WIS 9 | +| **Prime requisite** | STR | +| **Hit Dice** | 1d6 | +| **Maximum Level** | 10 | +| **Armour** | None (natural armour only) | +| **Weapons** | Any | +| **Languages** | Alignment, Common, Uquan | + +Tortokin are a race of relatively tall (5.5') reptilian-looking humanoids with leathery, earth-toned skin and large, dark, tortoise-like shells on their backs capable of holding their entire bodies. They rarely wear any clothing, preferring instead to use belts and harnesses to carry their belongings. As most tortokin feel as though they have their houses on their backs, they rarely feel homesick and tend to avoid laying roots in any single place, preferring instead to travel and wander. Although they typically aren't good swimmers, their natural buoyancy and ability to hold their breath for extended periods of time helps them traverse swamps and bodies of water with ease. + +[[toc]] + +### Armour Class + +Tortokin have hard shells that serve as natural armor, providing a base Armour Class of 6[13] which improves to 4[15] at 5th level, and 2[17] at 9th level. + +### Claw Attack + +Tortokin have natural claws that can be used to make an attack, dealing 1d4 damage on a successful hit. + +### Combat + +Tortokin can use all weapons and shields, but cannot use armour, instead relying on their hard shells for defence in battle. + +### Hold Breath + +Tortokin can hold their breath for up to 1 hour. + +### After Reaching 9th Level + +A tortokin has the option of creating a stronghold that will form the basis of a new community of tortokin. Tortokin communities can be located either near those of humans or in the wilderness (typically a coastal or swampy area). + +### Tortokin Level Progression + +
    + +| |||| Saving Throws ||||| +| Level | XP | HD | THAC0 | D[^1] | W[^1] | P[^1] | B[^1] | S[^1] | +| :---: | :-----: | :--------: | :-----: | :---: | :---: | :---: | :---: | :---: | +| 1 | 0 | 1d6 | 19 [0] | 12 | 13 | 13 | 15 | 12 | +| 2 | 2,000 | 2d6 | 19 [0] | 12 | 13 | 13 | 15 | 12 | +| 3 | 4,000 | 3d6 | 19 [0] | 12 | 13 | 13 | 15 | 12 | +| 4 | 8,000 | 4d6 | 17 [+2] | 10 | 11 | 11 | 13 | 10 | +| 5 | 16,000 | 5d6 | 17 [+2] | 10 | 11 | 11 | 13 | 10 | +| 6 | 32,000 | 6d6 | 17 [+2] | 10 | 11 | 11 | 13 | 10 | +| 7 | 64,000 | 7d6 | 14 [+5] | 8 | 9 | 9 | 10 | 8 | +| 8 | 120,000 | 8d6 | 14 [+5] | 8 | 9 | 9 | 10 | 8 | +| 9 | 250,000 | 9d6 | 14 [+5] | 8 | 9 | 9 | 10 | 8 | +| 10 | 400,000 | 9d6+2[^2] | 12 [+7] | 6 | 7 | 8 | 8 | 6 | + +[Tortokin 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. diff --git a/src/pages/classes/warlock.md b/src/pages/classes/warlock.md new file mode 100755 index 0000000..f983f29 --- /dev/null +++ b/src/pages/classes/warlock.md @@ -0,0 +1,145 @@ +--- +title: Warlock +description: The Warlock class for Old School Essentials. +date_pub: 2023-02-17T00:26:00-05:00 +section: classes +content_type: feature +short_code: cw1 +--- + +
    + +| | | +| ------------------- | -------------------------------- | +| **Requirements** | None | +| **Prime requisite** | CHA | +| **Hit Dice** | 1d6 | +| **Maximum Level** | 14 | +| **Armour** | Leather or chainmail, no shields | +| **Weapons** | Any 1-handed weapons | +| **Languages** | Alignment, Common | + +
    + +Warlocks are spellcasters that gain access to magic by making pacts with powerful, otherworldly beings. Some make deals with demons or devils; others, with capricious archfey; and still others, with the ineffable Great Old Ones (or other similar cosmic horrors). It's a dangerous shortcut to power, with a price that very few are willing to pay. + +**Alignment**: While warlocks with different patrons may have different alignments, they are never lawful. + +[[toc]] + +### Combat + +Warlocks can use all one-handed missile and melee weapons, and can use leather armour and chainmail. Because of their need for freedom of movement, they cannot use plate mail or shields. + +### Eldritch Blast + +Warlocks can use the power granted by their patron to fire a beam of magical energy. The target must make a **saving throw versus spells** or take 1d6 damage. + +**Uses per Day**: A warlock may use their eldritch blast a number of times per day equal to 1/2 their level (rounded up). + +#### Activating Eldritch Blast + +A warlock must spend a round concentrating in order to activate their eldritch blast. While concentrating, the warlock may not move, attack, or perform any other actions. + +**In combat**: Like spell casting, activating eldritch blast can be disrupted in combat. The player must declare the use of their eldritch blast before initiative is rolled, and the attack is resolved during their side's "Spell Casting" phase of combat. + +### Patron Magic + +See [Spells](https://oldschoolessentials.necroticgnome.com/srd/index.php/Spells) for full details on magic. Patron magic is similar to both arcane magic and divine magic. + +**Memorizing spells**: Beginning at 2nd level, a warlock may memorize spells through the use of ritual invocations to their patrons. When memorizing spells, warlocks may choose any spells in their class’ spell list that they are of high enough level to cast. + +**Reversing spells**: Some warlock spells may be reversed. A warlock can cast the reversed version of a spell by speaking the words and performing the gestures backwards when it is cast. + +**Patron disfavour**: A warlock must be faithful to their patron. If the character ever falls from favour with their patron, penalties (determined by the referee) may be imposed. These may include penalties to eldritch blast (–1 or more), a reduction in spells, or being sent on a perilous quest. In order to regain favour, the warlock must perform some great deed for their patron (as determined by the referee), for example: collecting an artifact, constructing an altar, vanquishing a powerful enemy of the patron, etc. + +**Magical research**: A warlock of 2nd level or higher may spend time and money on magical research. This allows them to create new spells or other magical effects associated with their patron. On reaching 9th level, warlocks are also able to create magic items. + +**Using magic items**: As spell casters, warlocks are able to use magic scrolls of spells on their spell list. They can also use items that may only be used by arcane spell casters (e.g. magic wands). + +#### Spell Casting + +Beginning at 2nd level, a warlock may make a ritual invocation to receieve spells from their patron. The power and number of spells available to a warlock are determined by the character’s experience level. The list of spells available to warlocks is below. More information about each spell can be found in the [Magic User](https://oldschoolessentials.necroticgnome.com/srd/index.php/Magic-User_Spells) or [Cleric](https://oldschoolessentials.necroticgnome.com/srd/index.php/Cleric_Spells) spell list, as indicated (see [Magic](https://oldschoolessentials.necroticgnome.com/srd/index.php/Rules_of_Magic) in Old-School Essentials). + +**Level 1** + +- [Cause Fear (C/1)]() +- [Charm Person (MU/1)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Charm_Person) +- [Darkness (MU/1)]() +- [Detect Magic (MU/1)]() +- [Read Languages (MU/1)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Read_Languages) +- [Read Magic (MU/1)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Read_Magic) + +**Level 2** + +- [ESP (MU/2)](https://oldschoolessentials.necroticgnome.com/srd/index.php/ESP) +- [Invisibility (MU/2)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Invisibility) +- [Levitate (MU/2)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Levitate) +- [Mirror Image (MU/2)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Mirror_Image) +- [Phantasmal Force (MU/2)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Phantasmal_Force) +- [Resist Fire (C/2)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Resist_Fire) + +**Level 3** + +- [Clairvoyance (MU/3)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Clairvoyance) +- [Fly (MU/3)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Fly) +- [Haste (MU/3)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Haste) +- [Infravision (MU/3)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Infravision) +- [Lightning Bolt (MU/3)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Lightning_Bolt) +- [Water Breathing (MU/3)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Water_Breathing) + +**Level 4** + +- [Charm Monster (MU/4)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Charm_Monster) +- [Confusion (MU/4)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Confusion) +- [Curse (MU/4)]() +- [Dimension Door (MU/4)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Dimension_Door) +- [Polymorph Self (MU/4)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Polymorph_Self) +- [Wizard Eye (MU/4)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Wizard_Eye) + +**Level 5** + +- [Contact Higher Plane (MU/5)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Contact_Higher_Plane) +- [Feeblemind (MU/5)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Feeblemind) +- [Magic Jar (MU/5)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Magic_Jar) +- [Raise Dead (Finger of Death) (C/5)]() +- [Telekinesis (MU/5)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Telekinesis) +- [Teleport (MU/5)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Teleport) + +### After Reaching 11th Level + +A warlock may build a stronghold, often a great tower. 1d6 apprentices of levels 1–3 (who are also bound to the warlock's patron) will then arrive to study under the warlock. + +### Warlock Level Progression + +
    + +| |||| Saving Throws ||||| Spells[^spells] ||||| +| Level | XP | HD | THAC0 | D[^1] | W[^1] | P[^1] | B[^1] | S[^1] | 1 | 2 | 3 | 4 | 5 | +|:-------:|:-----------:|:--------:|:---------:|:----:|:----:|:----:|:----:|:----:|:---:|:---:|:---:|:---:|:---:| +| 1 | 0 | 1d6 | 19 [0] | 13 | 14 | 13 | 16 | 15 | - | - | - | - | - | +| 2 | 2,500 | 2d6 | 19 [0] | 13 | 14 | 13 | 16 | 15 | 1 | - | - | - | - | +| 3 | 5,000 | 3d6 | 19 [0] | 13 | 14 | 13 | 16 | 15 | 2 | - | - | - | - | +| 4 | 10,000 | 4d6 | 19 [0] | 13 | 14 | 13 | 16 | 15 | 0 | 2 | - | - | - | +| 5 | 20,000 | 5d6 | 19 [0] | 13 | 14 | 13 | 16 | 15 | 0 | 2 | - | - | - | +| 6 | 40,000 | 6d6 | 17 [+2] | 11 | 12 | 11 | 14 | 12 | 0 | 3 | - | - | - | +| 7 | 80,000 | 7d6 | 17 [+2] | 11 | 12 | 11 | 14 | 12 | 0 | 0 | 3 | - | - | +| 8 | 150,000 | 8d6 | 17 [+2] | 11 | 12 | 11 | 14 | 12 | 0 | 0 | 3 | - | - | +| 9 | 300,000 | 9d6 | 17 [+2] | 11 | 12 | 11 | 14 | 12 | 0 | 0 | 4 | - | - | +| 10 | 450,000 | 9d6+1[^2] | 17 [+2] | 11 | 12 | 11 | 14 | 12 | 0 | 0 | 0 | 4 | - | +| 11 | 600,000 | 9d6+2[^2] | 14 [+5] | 8 | 9 | 8 | 11 | 8 | 0 | 0 | 0 | 4 | - | +| 12 | 750,000 | 9d6+3[^2] | 14 [+5] | 8 | 9 | 8 | 11 | 8 | 0 | 0 | 0 | 4 | - | +| 13 | 900,000 | 9d6+4[^2] | 14 [+5] | 8 | 9 | 8 | 11 | 8 | 0 | 0 | 0 | 5 | - | +| 14 | 1,050,000 | 9d6+5[^2] | 14 [+5] | 8 | 9 | 8 | 11 | 8 | 0 | 0 | 0 | 0 | 5 | + +[Warlock Level Progression] + +
    + +[^spells]: Chosen spells can be of the indicated level or lower. +[^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. + +### Available Races and Max Level + +When using the optional Character races rule, any race that may be a magic-user may also be a warlock, and may advance to the same maximum level listed for the magic-user class (unless otherwise noted). diff --git a/src/pages/index.md b/src/pages/index.md new file mode 100644 index 0000000..fa08ebd --- /dev/null +++ b/src/pages/index.md @@ -0,0 +1,16 @@ +--- +title: Welcome to the Multiverse! +description: TBD +date_pub: 2023-02-15T00:26:00-05:00 +section: main +content_type: feature +short_code: m1 +--- + +My name is Drogo Wanderfoot, but most know me as the Planar Vagabond. + +I've spent decades wandering around the vast multiverse, collecting stories, ... + +And now, I'm presenting them to you! + +Thanks for stopping by! diff --git a/src/pages/magic-items/astral-compass.md b/src/pages/magic-items/astral-compass.md new file mode 100644 index 0000000..9c271e0 --- /dev/null +++ b/src/pages/magic-items/astral-compass.md @@ -0,0 +1,21 @@ +--- +title: Astral Compass +description: +date_pub: 2023-02-19T18:46:00-05:00 +section: magic items +content_type: feature +short_code: miac +--- + +A 1.5" diameter, golden medallion, etched with 4 lines (marking the 4 cardinal directions), and set with a small purple gem in the center. + +- **On the Astral Plane**: Can "see" astral currents, as well as sense the general direction to the shortest path to any location in multiverse (which appears as a faint, golden glow on the horizon). + +- **While Piloting an Astral Console**: Can perceive a 360 degree "tactical" view of the ship, up to 300 yards out, as well as the "optimal" current to ride to reach their desired destination (which itself glows like a beacon). + +- Serves the same function as a navigator on an astral vessel. + + diff --git a/src/pages/magic-items/crystal-skull-of-jund.md b/src/pages/magic-items/crystal-skull-of-jund.md new file mode 100644 index 0000000..686cf3a --- /dev/null +++ b/src/pages/magic-items/crystal-skull-of-jund.md @@ -0,0 +1,36 @@ +--- +title: The Crystal Skull of Jund +description: +date_pub: 2023-02-19T20:59:00-05:00 +section: magic items +content_type: feature +short_code: micsj +--- + +An elongated crystal skull with 3 eye sockets (with the third lying in between and above the other two eyes). + +**Invocation**: _"O great crystal skull of Jund, we beseech you, share your knowledge and give us the guidance we seek,"_ at which point it starts to glow and speaks in a booming voice "The Crystal Skull of Jund awaits your queries". + +**Once invoked**: Will continue to operate for up to 10 minutes (1 turn), answering any questions it is asked, until it is deactivated. + +**Deactivation**: _"O great crystal skull of Jund, we humbly thank you for your guidance,"_ at which point it stops glowing. + +**Usage Frequency**: May be invoked up to three times per day. + +### Other Functions + +**Translation**: Reads (non-magical?) text and translates it by speak it aloud in the desired language. + +**Identify**: May identify magic items, potentially including their functions and history. + +### Statements Made + +- **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. + +- **Dulara** was a material plane that was annihilated 738 years ago by the Eye Tyrant Ravager, along with its 25-million inhabitants. Itwas the last plane destroyed by the Ravager, as of year 5023 of the Common Astral Calendar. + +- **The Arcane Lords of Axion** were an ancient group of wizards thought to be the first to create a permanent stronghold in the astral plane. Established the Common Astral Calendar with the Purple Masters of Prasha 5023 years ago, leading to the creation of the Astral Trade Union. Founded the Axion Academy of Magicks in year 118 of the Common Astral Calendar. Vanished sometime after year 2045 of the Common Astral Calendar, circumstances unknown. + +### GM Notes + +For questions it can't (or won't) answer, it replies with _"The answer is not known to the Crystal Skull of Jund, and may be stored in one of the missing eyes of Jund."_ diff --git a/src/pages/magic-items/index.md b/src/pages/magic-items/index.md new file mode 100644 index 0000000..f0dfe71 --- /dev/null +++ b/src/pages/magic-items/index.md @@ -0,0 +1,17 @@ +--- +title: Magic Items +description: A small collection of magic items that can be encountered in the astral plane. +date_pub: 2023-02-19T18:50:00-05:00 +section: magic items +content_type: feature +short_code: mi1 +--- + +Below you'll find a small sampling of the magic items that can be encountered in the astral plane. + +- [Astral Compass](./astral-compass.html) +- [The Crystal Skull of Jund](./crystal-skull-of-jund.html) + + diff --git a/src/pages/monsters/astral-buccaneer.md b/src/pages/monsters/astral-buccaneer.md new file mode 100644 index 0000000..b9ed365 --- /dev/null +++ b/src/pages/monsters/astral-buccaneer.md @@ -0,0 +1,35 @@ +--- +title: Buccaneer, Astral +description: TBD +date_pub: 2023-02-17T00:26:00-05:00 +section: monsters +content_type: feature +short_code: mab +--- + +Astral sailors who make a living by raiding ports and robbing other ships. Can often be found near astral currents. + +
    + +| | | +| -------------------- | ----------------------------- | +| **Armour Class** | 7 [12] or 5 [14] | +| **Hit Dice** | 1 (4hp) | +| **Attacks** | 1 x weapon (1d6 or by weapon) | +| **THAC0** | 19[0] | +| **Movement** | 120' (40') | +| **Saving Throws** | D12 W13 P14 B15 S16 (1) | +| **Morale** | 6 | +| **Alignment** | N | +| **XP** | 10 | +| **Number appearing** | 0 (see below) | +| **Treasure Type** | A | + +
    + +- **Ships and crew** - 1d3 caravels or 1d3 small warships (1d5+3 × 10 buccaneers each). (See _Astral Vessels_ for details on ships.) +- **Arms** - 60% of group have: leather armour, sword; 30% have: leather armour, sword, crossbow; 10% have: chainmail, sword, crossbow. +- **Leaders and captains** - For every 30 buccaneers, there is a 4th level fighter. Each ship has a captain (7th level fighter). +- **Fleet commander** - 9th level fighter. 30% chance of a magic-user (level 1d2 + 9); 25% chance of a cleric (8th level). +- **Treasure** - Divided between vessels. Instead of carrying aboard, may have a map to where it is buried. +- **Havens** - Lawless, fortified, island outposts may act as a haven for buccaneers and [pirates](./astral-pirate.html). diff --git a/src/pages/monsters/astral-pirate.md b/src/pages/monsters/astral-pirate.md new file mode 100644 index 0000000..401db49 --- /dev/null +++ b/src/pages/monsters/astral-pirate.md @@ -0,0 +1,37 @@ +--- +title: Pirate, Astral +description: TBD +date_pub: 2023-02-17T00:26:00-05:00 +section: monsters +content_type: feature +short_code: map +--- + +Astral sailors who make a living by raiding ports, robbing other ships, and illegal slaving. Can often be found near astral currents. Renowned for their ruthless and evil ways. + +
    + +| | | +| -------------------- | ----------------------------- | +| **Armour Class** | 7 [12] or 5 [14] | +| **Hit Dice** | 1 (4hp) | +| **Attacks** | 1 x weapon (1d6 or by weapon) | +| **THAC0** | 19[0] | +| **Movement** | 120' (40') | +| **Saving Throws** | D12 W13 P14 B15 S16 (1) | +| **Morale** | 7 | +| **Alignment** | C | +| **XP** | 10 | +| **Number appearing** | 0 (see below) | +| **Treasure Type** | A | + +
    + +- **Ships and crew** - 1d3 caravels or 1d3 small warships (1d5+3 × 10 pirates each). (See _Astral Vessels_ for details on ships.) +- **Arms** - 50% of group have +- **Leaders** - For every 30 pirates, there is a 4th level fighter. For every 50 pirates, and for each ship, there is a 5th level fighter. For every 100 pirates, and per fleet, there is an 8th level fighter. +- **Fleet commander** - Fleets of 300 or more pirates are led by a pirate lord (11th level fighter). 75% chance of a magic-user (level 1d2+8). +- **Treacherous** - Will attack other pirates, if they can profit from it. +- **Prisoners** - 25% chance of 1d3 prisoners to be ransomed. +- **Treasure** - Divided between vessels. Instead of carrying with them, may have a map to where it is buried. +- **Havens** - Lawless, fortified, island outposts may act as a haven for [buccaneers](./astral-buccaneer.html) and pirates. diff --git a/src/pages/monsters/astral-whale.md b/src/pages/monsters/astral-whale.md new file mode 100644 index 0000000..6091e3b --- /dev/null +++ b/src/pages/monsters/astral-whale.md @@ -0,0 +1,28 @@ +--- +title: Whale, Astral +description: TBD +date_pub: 2023-02-17T00:26:00-05:00 +section: monsters +content_type: feature +short_code: maw +--- + +Gargantuan whales up to 70’ long that roam the astral plane. + +
    + +| | | +| -------------------- | -------------------------------------------- | +| **Armour Class** | 6 [13] | +| **Hit Dice** | 36 (162hp) | +| **Attacks** | 1 × bite (3d20) or 1 × ram (6d6 hull damage) | +| **THAC0** | 5 [+14] | +| **Movement** | 180’ (60’) | +| **Saving Throws** | D4 W5 P6 B5 S8 (15) | +| **Morale** | 7 | +| **Alignment** | Neutral | +| **XP** | 6,250 | +| **Number appearing** | 0 (1d3) | +| **Treasure Type** | V | + +
    diff --git a/src/pages/monsters/crystalline-dragons.md b/src/pages/monsters/crystalline-dragons.md new file mode 100644 index 0000000..37fe25e --- /dev/null +++ b/src/pages/monsters/crystalline-dragons.md @@ -0,0 +1,164 @@ +--- +title: Dragons, Crystalline +description: TBD +date_pub: 2023-02-17T00:26:00-05:00 +section: monsters +content_type: feature +short_code: mcd +--- + + + +### Amethyst Dragon + +Lair at the top of glacial ridges and snowy peaks. + +
    + +| | | +| -------------------- | -------------------------------------------------- | +| **Armour Class** | 0 [19] | +| **Hit Dice** | 8\*\* (36hp) | +| **Attacks** | [2 × claw (1d6), 1 × bite (3d8)] or breath | +| **THAC0** | 12[+7] | +| **Movement** | 90’ (30’) / 240’ (80’) flying / 480' (160') astral | +| **Saving Throws** | D8 W9 P10 B10 S12 (8) | +| **Morale** | 9 | +| **Alignment** | Neutral | +| **XP** | 1,750 | +| **Number appearing** | 1d4 (1d4) | +| **Treasure Type** | H | + +
    + +--- + +- **Astral gliding** - When in the astral plane, may make use of the currents to move as fast as an astral vessel over long distances. +- **Breath weapon** - 90’ long line of cold (all caught in the area suffer damage equal to the dragon’s current hit points—_save versus breath_ for half) or cloud of charming gas (_save versus breath_ or be charmed for 1d6 turns). +- **Language and spells** - 40%; 3 × 1st level, 3 × 2nd level. +- **Planar roar** - Can be used up to once per week. Opens a 10' diameter portal to the astral plane which remains open for up to 1d6 turns, closing 1 turn after the dragon passes through. 40% chance to reach specific place in the astral plane (if desired), otherwise the portal opens at a random point. +- **Sleeping** - 30%. + +### Emerald Dragon + +Live on isolated peaks. + +
    + +| | | +| -------------------- | -------------------------------------------------- | +| **Armour Class** | -1 [20] | +| **Hit Dice** | 9\*\* (40hp) | +| **Attacks** | [2 × claw (1d6 + 1), 1 × bite (3d8)] or breath | +| **THAC0** | 12[+7] | +| **Movement** | 90’ (30’) / 240’ (80’) flying / 480' (160') astral | +| **Saving Throws** | D8 W9 P10 B10 S12 (9) | +| **Morale** | 9 | +| **Alignment** | Neutral | +| **XP** | 2,300 | +| **Number appearing** | 1d4 (1d4) | +| **Treasure Type** | H | + +
    + +--- + +- **Astral gliding** - When in the astral plane, may make use of the currents to move as fast as an astral vessel over long distances. +- **Breath weapon** - 60’ long cone of acid (all caught in the area suffer damage equal to the dragon’s current hit points—_save vs breath_ for half) or cloud of confusion gas (_save vs breath_ or catatonic for 2d4 turns). +- **Language & spells** - 50%; 4 × 1st level, 4 × 2nd level. +- **Planar roar** - Can be used up to once per week. Opens a 10' diameter portal to the astral plane which remains open for up to 1d6 turns, closing 1 turn after the dragon passes through. 50% chance to reach specific place in the astral plane (if desired), otherwise the portal opens at a random point. +- **Sleeping** - 20%. +- **Shape changing** - May take on the form of an animal. + +### Onyx Dragon + +Dwell deep underground. + +
    + +| | | +| -------------------- | -------------------------------------------------- | +| **Armour Class** | 2 [17] | +| **Hit Dice** | 6\*\* (27hp) | +| **Attacks** | [2 × claw (1d4), 1 × bite (2d8)] or breath | +| **THAC0** | 14[+5] | +| **Movement** | 90’ (30’) / 240’ (80’) flying / 480' (160') astral | +| **Saving Throws** | D10 W11 P12 B13 S14 (6) | +| **Morale** | 8 | +| **Alignment** | Chaotic | +| **XP** | 725 | +| **Number appearing** | 1d4 (1d4) | +| **Treasure Type** | H | + +
    + +--- + +- **Astral gliding** - When in the astral plane, may make use of the currents to move as fast as an astral vessel over long distances. +- **Breath weapon** - Cloud of chlorine gas (all caught in the area suffer damage equal to the dragon’s current hit points—_save versus breath for half_) or cloud of fear gas (_save versus breath_ or flee for 1d4 turns). +- **Language & spells** - 20%; 3 × 1st level. +- **Planar roar** - Can be used up to once per week. Opens a 10' diameter portal to the astral plane which remains open for up to 1d6 turns, closing 1 turn after the dragon passes through. 20% chance to reach specific place in the astral plane (if desired), otherwise the portal opens at a random point. +- **Sleeping** - 50%. + +### Ruby Dragon + +Lair in dormant volcanoes. + +
    + +| | | +| -------------------- | -------------------------------------------------- | +| **Armour Class** | -2 [21] | +| **Hit Dice** | 10\*\* (45hp) | +| **Attacks** | [2 × claw (1d6), 1 × bite (4d8)] or breath | +| **THAC0** | 11 [+8] | +| **Movement** | 90’ (30’) / 240’ (80’) flying / 480' (160') astral | +| **Saving Throws** | D6 W7 P8 B8 S10 (10) | +| **Morale** | 10 | +| **Alignment** | Neutral | +| **XP** | 2,300 | +| **Number appearing** | 1d4 (1d4) | +| **Treasure Type** | H | + +
    + +--- + +- **Astral gliding** - When in the astral plane, may make use of the currents to move as fast as an astral vessel over long distances. +- **Breath weapon** - 100’ long line of fire (all caught in the area suffer damage equal to the dragon’s current hit points—_save versus breath_ for half) or cloud of antimagic gas (ends all spells of non-instantaneous duration, as if Dispel Magic has been cast). +- **Language & spells** - 90%; 3 × 1st level, 3 × 2nd level, 3 × 3rd level. +- **Planar roar** - Can be used up to once per week. Opens a 10' diameter portal to the astral plane which remains open for up to 1d6 turns, closing 1 turn after the dragon passes through. 90% chance to reach specific place in the astral plane (if desired), otherwise the portal opens at a random point. +- **Sleeping** - 10%. +- **Shape changing** - May take on the form of a person or animal. + +### Topaz Dragon + +Live along rocky seacoasts. + +
    + +| | | +| -------------------- | -------------------------------------------------- | +| **Armour Class** | 1 [18] | +| **Hit Dice** | 7\*\* (31hp) | +| **Attacks** | [2 × claw (1d4 + 1), 1 × bite (3d6)] or breath | +| **THAC0** | 13 [+6] | +| **Movement** | 90’ (30’) / 240’ (80’) flying / 480' (160') astral | +| **Saving Throws** | D8 W9 P10 B10 S12 (7) | +| **Morale** | 8 | +| **Alignment** | Lawful | +| **XP** | 1,250 | +| **Number appearing** | 1d4 (1d4) | +| **Treasure Type** | H | + +
    + +--- + +- **Astral gliding** - When in the astral plane, may make use of the currents to move as fast as an astral vessel over long distances. +- **Breath weapon** - 70’ long cone of lightning (all caught in the area suffer damage equal to the dragon’s current hit points—_save versus breath_ for half) or cloud of sleep gas (_save versus breath_ or fall asleep for 4d4 turns). +- **Language & spells** - 30%; 4 × 1st level. +- **Planar Roar** - Can be used up to once per week. Opens a 10' diameter portal to the astral plane which remains open for up to 1d6 turns, closing 1 turn after the dragon passes through. 30% chance to reach specific place in the astral plane (if desired), otherwise the portal opens at a random point. +- **Sleeping** - 40%. diff --git a/src/pages/monsters/drahki.md b/src/pages/monsters/drahki.md new file mode 100644 index 0000000..363d420 --- /dev/null +++ b/src/pages/monsters/drahki.md @@ -0,0 +1,61 @@ +--- +title: Drahki (Dracokin) +description: TBD +date_pub: 2023-02-17T00:26:00-05:00 +section: monsters +content_type: feature +short_code: md1 +--- + +Drahki are a race of Dracokin that hail from Drahkenos, the so-called plane of Dragons, and home to Endrion and the other dragon gods. They are also one of the most active ships in the astral plane. + +
    + +| | | +| -------------------- | ----------------------------- | +| **Armour Class** | 7 [12] or 5 [14] | +| **Hit Dice** | 1 (4hp) | +| **Attacks** | 1 x weapon (1d6 or by weapon) | +| **THAC0** | 19[0] | +| **Movement** | 120’ (40’) | +| **Saving Throws** | D12 W13 P14 B15 S16 (1) | +| **Morale** | 6 | +| **Alignment** | N | +| **XP** | 10 | +| **Number appearing** | 0 (see below) | +| **Treasure Type** | A | + +
    + +- **Breath weapon** - Can be used up to three times per day. Unless noted otherwise, all caught in the area suffer damage equal to the Drahki’s current hit points (save versus breath for half). +- **Draconic Ancestry** - Dracokin are are distantly related to a particular kind of dragon, which determines the colour of their scales, as well as the damage and area of their breath weapon. +- **Ships and crew** - 1d2 large dragonships (each with 1d12+2 x 10 drahki) and/or 1d3 small dragonships (with 1d6 x 10 drahki each). (See [Astral Vessels](/astral/astral-vessels.html) for details on ships.) +- **Arms** - 60% of group have: leather armour, sword; 30% have: leather armour, sword, crossbow; 10% have: chainmail, sword, crossbow. +- **Leaders and captains** - For every 30 drahki, there is a 4th level fighter. Each ship has a captain (7th level fighter). +- **Fleet commander** - 9th level fighter. 30% chance of a magic-user (level 1d2 +9); 30% chance of a cleric (8th level). + +
    + +| 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] + +
    + +[^1]: Rather than dealing damage, targets in area must _save versus breath_ or fall asleep for 1d4 turns diff --git a/src/pages/monsters/ghyffan.md b/src/pages/monsters/ghyffan.md new file mode 100644 index 0000000..794ea03 --- /dev/null +++ b/src/pages/monsters/ghyffan.md @@ -0,0 +1,34 @@ +--- +title: Ghyffan (Hippokin) +description: TBD +date_pub: 2023-02-17T00:26:00-05:00 +section: monsters +content_type: feature +short_code: mg1 +--- + +### Ghyffan (Hippokin) + +
    + +| | | +| -------------------- | ----------------------------- | +| **Armour Class** | 7 [12] or 5 [14] | +| **Hit Dice** | 1 (4hp) | +| **Attacks** | 1 x weapon (1d6 or by weapon) | +| **THAC0** | 19[0] | +| **Movement** | 120’ (40’) | +| **Saving Throws** | D12 W13 P14 B15 S16 (1) | +| **Morale** | 6 | +| **Alignment** | N | +| **XP** | 10 | +| **Number appearing** | 0 (see below) | +| **Treasure Type** | A | + +
    + +- **Musket** - Usually functions as non-throwable long spear (1d8 damage with Brace, Melee, Two-handed). Once per turn, can be used as a (very loud) ranged weapon (5’-40’ / 41’-80’ / 81’-160’) with d10 damage. +- **Ships and crew** - 1d3 small warships (1d5+3 × 10 ghyffa each). (See _Astral Vessels_ for details on ships.) +- **Arms** - 60% of group have +- **Leaders and captains** - For every 30 ghyffa, there is a 4th level fighter. Each ship has a captain (7th level fighter). +- **Fleet commander** - 9th level fighter. 25% chance of a magic-user (level 1d2 +9); 25% chance of a cleric (8th level). diff --git a/src/pages/monsters/index.md b/src/pages/monsters/index.md new file mode 100644 index 0000000..0e34f00 --- /dev/null +++ b/src/pages/monsters/index.md @@ -0,0 +1,18 @@ +--- +title: Astral Monsters +description: A small collection of monsters that can be encountered in the astral plane. +date_pub: 2023-02-17T00:26:00-05:00 +section: monsters +content_type: feature +short_code: mm1 +--- + +Below you'll find a small sampling of the monsters that can be encountered in the astral plane. + +- [Buccaneer, Astral](./astral-buccaneer.html) +- [Dragons, Crystalline](./crystalline-dragons.html) +- [Drahki (Dracokin)](./drahki.html) +- [Ghyffan (Hippokin)](./ghyffan.html) +- [Pirate, Astral](./astral-pirate.html) +- [Warp Kitten](./warp-kitten.html) +- [Whale, Astral](./astral-whale.html) diff --git a/src/pages/monsters/warp-kitten.md b/src/pages/monsters/warp-kitten.md new file mode 100644 index 0000000..632df0f --- /dev/null +++ b/src/pages/monsters/warp-kitten.md @@ -0,0 +1,31 @@ +--- +title: Warp Kitten +description: TBD +date_pub: 2023-02-17T00:26:00-05:00 +section: monsters +content_type: feature +short_code: mwk +--- + +
    + +| | | +| -------------------- | ------------------------ | +| **Armour Class** | 4 [15] | +| **Hit Dice** | 1+2\* (6hp) | +| **Attacks** | 2 x tentacle (1d4) | +| **THAC0** | 18 [+1] | +| **Movement** | 120’ (40’) | +| **Saving Throws** | D12 W13 P14 B15 S16 (F1) | +| **Morale** | 6 | +| **Alignment** | C | +| **XP** | 20 | +| **Number appearing** | 1d8 (2d6) | +| **Treasure Type** | B(?) | + +
    + +- **Lairs** - Usually guarded by 1d4 Warp Beasts +- **Displacement** - Appear 3’ from actual location +- **Save bonus** - +2 to all saves. +- **Hate blink dogs** - Always attack them and their companions. diff --git a/src/pages/npcs/drogo-wanderfoot.md b/src/pages/npcs/drogo-wanderfoot.md new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/npcs/index.md b/src/pages/npcs/index.md new file mode 100644 index 0000000..7b36b25 --- /dev/null +++ b/src/pages/npcs/index.md @@ -0,0 +1,102 @@ +--- +title: NPCs of Note +description: NPCs the party has met on their adventures. +date_pub: 2023-02-19T20:47:00-05:00 +section: npcs +content_type: feature +short_code: n1 +--- + +### Sapphire Cove + +#### 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. + +### The Island of Terror + +#### Drogo Wanderfoot + +- Planar Vagabond +- Halfling male adventurer who wanders the multiverse, collecting stories and documenting sights. + +#### Juzo Javarax + +- Drahki Captain +- female, green dragonborn +- captain of the _Aseonth_ (named for Aseonth the Swift, an legendary green dragon from Drahkos) +- goal: to protect her crew and ship +- attitude: she's hot shit and she knows it +- most of the crew are also green, part of her clan (Javarax) + +#### Prama + +- Automaton with glowing blue chest +- Presumed dead following Korrath's escape + +### The Skyrunner + +#### Jon Tobart / Shazzogrox + +- Eye Tyrant Mage disguised as a human ship captain +- Tobart's eye color kept changing +- Presumed-known spells + - Petrify (Flesh-to-stone) + - Polymorph + - Wizard Lock + +#### Stonecrop (DECEASED) + +- An Eye Tyrant experiment into making bipedal flesh golems +- Fought with Rocky and the party and was killed. + + + +### Others + +#### Azir + +- Strange, handsome man with dark skin +- Appeared on the Skyrunner and spoke briefly to Mezric, giving her dagger before disappearing. diff --git a/src/pages/planes/elemental.md b/src/pages/planes/elemental.md new file mode 100644 index 0000000..f8f596c --- /dev/null +++ b/src/pages/planes/elemental.md @@ -0,0 +1,55 @@ +--- +title: The Elemental Planes +description: A brief description of each of the elemental planes. +date_pub: 2023-02-19T17:02:00-05:00 +section: planes +content_type: feature +short_code: pe1 +--- + +The elemental planes are each fairly uniform in their composition, consisting almost exclusively of the element that the plane is named for. + +### Aeria / The Zephyr (The Elemental Plane of Air) + +- An endless sky of different colors dotted with floating islands +- Subjective gravity - everything is assumed to "fall" 100' / round, and characters can change direction with a thought (CHA check if under duress). +- No breathing difficulties + +### Terru / The Rock (The Elemental Plane of Earth) + +- An endless network of shiny, crystal-filled tunnels, sandwiched between endless layers of rock, stone, and dirt. +- Gravity can change in the tunnels, corkscrewing along the walls. +- The air in the tunnels is dusty, but breathable. + +### Pyrea / The Pyre (The Elemental Plane of Fire) + +- An endless sea of fire, burning across an endless landscape of flames. +- Most creatures from the material plane can't be here for more than a moment before burning to death. +- Even the air burns. + +### Aquos / The Deep (The Elemental Plane of Water) + +- An endless, bottomless ocean that contains a massive growth of coral, thousands of mile long. +- No air to speak of, so any visitors will need to be able to breathe water. + +### Ange / The Firmament (The Elemental Plane of Law) + +- An endless, flat plain. +- Only lawful gods can even exist here for more than a moment - any lesser beings are obliterated and turned into part of the landscape. +- Elementals from this plane are called angels, and are usually crafted by the gods of law with specific purposes in mind. + +### Dema / The Abyss (The Elemental Plane of Chaos) + +- An endless miasma of swirling, bubbling chaos. +- Only powerful chaotic beings can even exist here for more than a moment - any lesser beings are ripped apart by the primal energies (or become corrupted by the power of the plane). + + diff --git a/src/pages/planes/index.md b/src/pages/planes/index.md new file mode 100644 index 0000000..a3cd5dd --- /dev/null +++ b/src/pages/planes/index.md @@ -0,0 +1,19 @@ +--- +title: Planar Cosmology +description: A overview of the various planes of existence. +date_pub: 2023-02-15T00:26:00-05:00 +section: planes +content_type: feature +short_code: p1 +--- + +_Before you can learn how to get to the other planes, you probably need to learn a thing-or-two about what (and where) they are. - PV_ + +The various planes can be thought of as being laid out in a series of concentric circles, like a great wheel that spins throughout the cosmos: + +- the [six elemental planes](./elemental.html) (_air_, _earth_, _fire_, _water_, _law_, and _chaos_) lie at the center; +- the infinite variations of the [material planes](./material.html) are next; +- then come the [outer planes](./outer.html), including the planes of the various afterlives - all of the heavens, hells, purgatories, limbos, and eternal drinking halls that the gods create for their devotees (or for those that lose faith); +- the _ethereal plane_ exists alongside the other planes, providing passage for spirits when they die; +- all of the above are "contained" within the [astral plane](/astral/index.html), the great, swirling void which touches all; +- finally, there are rumors of planes beyond even the astral - strange realms, far weirder than anything that even the most seasoned planar traveler can comprehend. diff --git a/src/pages/planes/infernus.md b/src/pages/planes/infernus.md new file mode 100644 index 0000000..e7c29a7 --- /dev/null +++ b/src/pages/planes/infernus.md @@ -0,0 +1,53 @@ +--- +title: Infernus +description: A brief description of Infernus, the plane of Unwanted Dead. +date_pub: 2023-02-19T17:17:00-05:00 +section: planes +content_type: feature +short_code: pi1 +--- + +### Infernus + +_The Plane of Unwanted Dead_ + +Appears as an enormous underground cavern with walls that can never be reached. + +#### Locations + +**The Ashen Wastes** - A huge island in the center of cavern with gray shores. + +**The Lake of Fire** - An endless inferno which completely surrounds the island. + +**The Great Pit** - A massive hole in the center of the Ashen Wastes that leads down thousands of feet to a portal to the Abyss. + +- This is where most demons spontaneously appear, despite the difficulty involved in escaping from the Abyss. +- The "circles" are the areas around the pit where devils torture doomed souls (and which get more severe as one continues downwards). + +**The Cathedral** - A huge, ornate stone church which rests on a great stone that floats over the pit, held in place by the Chains of Tah'rus. + +### Factions + +**The Authority** - The bureaucracy that runs Infernus, mostly consisting of fallen angels, corrupted high paladins, and the devils who've sworn to serve them. + +- The Authority is also responsible to see that sinners get the torture they "deserve". + +**Devils** - Demons who swear an oath to serve the Authority and/or Infernus itself. + +- Most devils lose ability to shape-change, taking on Shai'kel's preferred form (ex: bone devil for guard) + +### NPCs + +**Shai'kel, the Corrupted** - Fallen angel who rules Infernus, and head of the Authority. + +#### The War in Infernus + +- For eons, Infernus had been ruled by the demon lords under the command of Urkyss the Unkempt, the Demon Emporer. + - The demons would kill their enemies and collect their souls, preventing them from going to thier promised afterlives. + - Demons only ever answered to other demons. +- Then, about ten thousand of years ago, one of the Lawful gods, Etarr the Righteous, created a powerful angel named Shai'kel and sent them to Infernus along with an army of clerics, paladins, and priests, all with a single purpose: to close the portal to the Abyss at the bottom of the Pit. + - The battle didn't go well for the forces of law, with the demon lords crushing the mortal armies. + - Shai'kel, in an act of desperation, flew into the pit and crossed the portal into the abyss itself, intent on destroying the portal from the other side. + - Instead, they were corrupted - their mind twisted by the chaos, they escaped the pit and returned to Infernus with new purpose. + - They rallied the "forgotten" followers of the various gods and goddesses of law and overthrew the demon lords, installing the Authority in their place. + - The incident became known as the "Damned Incursion". diff --git a/src/pages/planes/material.md b/src/pages/planes/material.md new file mode 100644 index 0000000..b2ca0a2 --- /dev/null +++ b/src/pages/planes/material.md @@ -0,0 +1,25 @@ +--- +title: The Material Planes +description: A brief description of several of the known material planes. +date_pub: 2023-02-19T17:02:00-05:00 +section: planes +content_type: feature +short_code: pm1 +--- + +What most adventurers would think of as their "known world" is really only a one of a (possibly infinite) number of planes. + +- Each plane is different from the others (some slightly, some significantly). +- Some planes are single worlds, while others are entire cosmoses unto themselves + +### Ayreon + +- Standard medieval fantasy world +- Home to the Sapphire Coast, the city of Portown, and barony of Vystmark. +- Can be accessed via the portal at Zenopus (Sapphire) Cove. + +### Ghyffu + +- Steampunk fantasy world, ruled by anthropomorphic hippos +- Home to the Ghyffan Expeditionary Force (and its armada), as well as the cities of Suracine, Barnette, and Manchard. +- Can be accessed via the portal at Fort Suraphell. diff --git a/src/pages/planes/outer.md b/src/pages/planes/outer.md new file mode 100644 index 0000000..a7517db --- /dev/null +++ b/src/pages/planes/outer.md @@ -0,0 +1,26 @@ +--- +title: The Outer Planes +description: A brief description of a few of the outer planes. +date_pub: 2023-02-19T17:39:00-05:00 +section: planes +content_type: feature +short_code: po1 +--- + +### Drahkenos + +_The Plane of Dragons_ + +- Home to the dragon gods: + - **Endrion** - The Platinum Lord of Virtue, Paragon of Justice, Protector of the Weak, Peacebringer, and God of Law; + - **Lyndralyth** - The Multichromatic Lady of Doom, Ruiner-of-All, Devourer of Nations, Peacebreaker, and Goddss of Chaos; and + - **Ymmyrr** - The Everlasting, Diamond Master of the Scales, Keeper of the Balance, Slumbering Monarch of Eternity. +- Home plane of the Drahki Federation. + +### Infernus + +_The Plane of Unwanted Dead_ + +- The place where the souls that aren't claimed by other gods wind up. +- Ruled by the Authority, a bureaucracy of corrupted paladins, clerics, and devils. + - Headed by the fallen angel, Shai'kel, the Corrupted. diff --git a/src/pages/races/automaton.md b/src/pages/races/automaton.md new file mode 100644 index 0000000..812eb1b --- /dev/null +++ b/src/pages/races/automaton.md @@ -0,0 +1,62 @@ +--- +title: Automaton +description: TBD +date_pub: 2023-02-15T00:26:00-05:00 +section: races +content_type: feature +short_code: ra1 +--- + +
    + +| | | +| --------------------- | ----------------- | +| **Requirements** | Minimum CON 9 | +| **Ability modifiers** | None | +| **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]] + +### Available Classes and Max Level + +- **Acrobat**: 10th +- **Assassin**: 10th +- **Bard**: 8th +- **Cleric**[^1]: 7th +- **Fighter**: 12th +- **Knight**: 10th +- **Magic-user**: 9th +- **Paladin**: 8th +- **Ranger**: 7th +- **Thief**: 10th + +[^1] At the referee’s option, tortokin clerics may only exist as NPCs. + +### 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 armor. + +- Automatons gain a +1 bonus to Armor 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. A foregling 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 don’t 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 still 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. diff --git a/src/pages/races/dracokin.md b/src/pages/races/dracokin.md new file mode 100644 index 0000000..c9bd6a2 --- /dev/null +++ b/src/pages/races/dracokin.md @@ -0,0 +1,70 @@ +--- +title: Dracokin +description: TBD +date_pub: 2023-02-15T00:26:00-05:00 +section: races +content_type: feature +short_code: rd1 +--- + +
    + +| | | +| --------------------- | --------------------------- | +| **Requirements** | Minimum CON 9 | +| **Ability modifiers** | +1 STR, -1 WIS | +| **Languages** | Alignment, Common, Draconic | + +
    + +[[toc]] + +### Available Classes and Max Level + +- **Assassin**: 10th +- **Bard**: 7th +- **Cleric**: 7th +- **Fighter**: 12th +- **Knight**: 11th +- **Magic-user**: 9th +- **Paladin**: 11th +- **Ranger**: 12th +- **Thief**: 9th + +### 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 increase 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. + +### 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. + +
    + +| 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] + +[^1]: Rather than dealing damage, targets in area must _save versus breath_ or fall asleep for 1d4 turns + +
    diff --git a/src/pages/races/felinar.md b/src/pages/races/felinar.md new file mode 100644 index 0000000..e62931f --- /dev/null +++ b/src/pages/races/felinar.md @@ -0,0 +1,43 @@ +--- +title: Felinar +description: TBD +date_pub: 2023-02-15T00:26:00-05:00 +section: races +content_type: feature +short_code: rf1 +--- + +
    + +| | | +| --------------------- | ----------------- | +| **Requirements** | None | +| **Ability modifiers** | +DEX, -WIS | +| **Languages** | Alignment, Common | + +
    + +_INTRO_ + +[[toc]] + +## Available Classes and Max Level + +- **Acrobat**: 10th +- **Assassin**: 10th +- **Cleric**[^1]: 6th +- **Fighter**: 8th +- **Knight**: 7th +- **Magic-user**: 9th +- **Ranger**: 9th +- **Thief**: 10th + +[^1]: At the referee’s option, felinar clerics may only exist as NPCs. + +## Claw Attack + +Felinar have natural claws that can be used to make an attack, dealing 1d4 damage on a successful hit. + +## Infravision + +Felinar have infravision to 60’ (see _Darkness_ under [Hazards and Challenges](https://oldschoolessentials.necroticgnome.com/srd/index.php/Hazards_and_Challenges#Darkness_)). diff --git a/src/pages/races/firfolk.md b/src/pages/races/firfolk.md new file mode 100644 index 0000000..9dd423e --- /dev/null +++ b/src/pages/races/firfolk.md @@ -0,0 +1,56 @@ +--- +title: Firfolk +description: TBD +date_pub: 2023-02-15T00:26:00-05:00 +section: races +content_type: feature +short_code: rf2 +--- + +
    + +| | | +| --------------------- | --------------------------- | +| **Requirements** | Minimum CON 9 | +| **Ability modifiers** | -1 INT, +1 WIS | +| **Languages** | Alignment, Common, Firspeak | + +
    + +_INTRO_ + +[[toc]] + +### Available Classes and Max Level + +- Assassin: 6th +- Druid[^1]: 10th +- Fighter: 8th +- Knight: 7th +- Ranger: 9th +- Thief: 8th + +[^1]: At the referee’s option, firfolk druids may only exist as NPCs. + +### 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. + +### Innate Magic + +At 2nd level, a firfolk is able to cast the _glamour_ spell once per day and, at 4th level, _invisibility_ once per day. + +### 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 13–15 category instead. + +### Resilience + +Firfolk's natural constitution and resistance to magic grants them a +2 bonus to saving throws versus poison, spells, and magic wands, rods, and staves. diff --git a/src/pages/races/index.md b/src/pages/races/index.md new file mode 100644 index 0000000..d5d1e1b --- /dev/null +++ b/src/pages/races/index.md @@ -0,0 +1,19 @@ +--- +title: Custom Character Races +description: TBD +date_pub: 2023-02-17T00:15:00-05:00 +section: races +content_type: feature +short_code: r1 +--- + +- [Automaton](./automaton.html) +- [Dracokin](./dracokin.html) +- [Felinar](./felinar.html) +- [Firfolk](./firfolk.html) +- [Mimikin](./mimikin.html) +- [Tortokin](./tortokin.html) + + diff --git a/src/pages/races/mimikin.md b/src/pages/races/mimikin.md new file mode 100644 index 0000000..7407fa1 --- /dev/null +++ b/src/pages/races/mimikin.md @@ -0,0 +1,46 @@ +--- +title: Mimikin +description: TBD +date_pub: 2023-02-15T00:26:00-05:00 +section: races +content_type: feature +short_code: rm1 +--- + +
    + +| | | +| --------------------- | ---------------------------------------- | +| **Requirements** | Minimum CHA 9 | +| **Ability modifiers** | None | +| **Languages** | Alignment, Common, any 2 other languages | + +
    + +Mimikin are a rarely-seen type of demihuman, able to shift their physical forms at will. Human-like in stature, their true forms usually have naturally pale, grey skin, unnervingly blank facial features, and silvery hair. However, because of their shapeshifting nature, they usuallly pass as humans or other similarly-sized demihumans, only returning to their true forms on death. + +Many mimikin use their gifts as a form of artistic or emotional expression, but some see them as invaluable tools for grifting, spying, and general deception, leading to their distrust by the other races. + +[[toc]] + +### Available Classes and Max Level + +- **Acrobat**: 10th +- **Assassin**: 10th +- **Bard**: 10th +- **Cleric**[^1]: 5th +- **Druid**[^1]: 8th +- **Fighter**: 6th +- **Magic-user**: 8th +- **Ranger**: 8th +- **Thief**: 10th + +[^1]: At the referee’s option, mimikin clerics and druids may only exist as NPCs. + +### Languages + +Changelings know Common, their alignment language, and 2 other langauges of the referee's choice. + +### Shapechanger + +Mimikin can change their appearance and voice on demand. This includes choosing specific colorations of eyes, skin, and hair, along with hair length, sex, and even height and weight (the latter two within human standards). This ability even allows them to pass as a member of another (human-sized) race, although their game statistics do not change. They can only duplicate the appearance of a creature that they have seen, and are limited to forms with the same basic arrangement of limbs. The change process takes 1 round, and has no affect on their clothing or equipment. Once the change is complete, it can be maintained until the mimikin either changes again or they die (at which point they revert to their true forms). diff --git a/src/pages/races/tortokin.md b/src/pages/races/tortokin.md new file mode 100644 index 0000000..1ad3053 --- /dev/null +++ b/src/pages/races/tortokin.md @@ -0,0 +1,49 @@ +--- +title: Tortokin +description: TBD +date_pub: 2023-02-15T00:26:00-05:00 +section: races +content_type: feature +short_code: rt1 +--- + +
    + +| | | +| --------------------- | ------------------------ | +| **Requirements** | Minimum WIS 9 | +| **Ability modifiers** | –1 DEX, +1 STR | +| **Languages** | Alignment, Common, Uquan | + +
    + +Tortokin are a race of relatively tall (5.5') reptilian-looking humanoids with leathery, earth-toned skin and large, dark, tortoise-like shells on their backs capable of containing their entire bodies. They rarely wear any clothing, preferring instead to use belts and harnesses to carry their belongings. As most tortokin feel as though they have their houses on their backs, they rarely feel homesick and tend to avoid laying roots in any single place, preferring instead to travel and wander. Although they typically aren't good swimmers, their natural buoyancy and ability to hold their breath for extended periods of time helps them traverse swamps and bodies of water with ease. + +[[toc]] + +### Available Classes and Max Level + +- **Cleric**[^1]: 10th +- **Druid**[^1]: 10th +- **Fighter**: 7th +- **Knight**: 8th +- **Magic-user**: 8th +- **Ranger**: 9th + +[^1]: At the referee’s option, tortokin clerics and druids may only exist as NPCs. + +### Armour Class + +Tortokin have hard shells that serve as natural armor, providing a base Armour Class of 6[13] which improves to 4[15] at 5th level, and 2[17] at 9th level. + +### Claw Attack + +Tortokin have natural claws that can be used to make an attack, dealing 1d4 damage on a successful hit. + +### Combat + +Tortokin can use all weapons and shields, but cannot use armour, instead relying on their hard shells for defence in battle. + +### Hold Breath + +Tortokin can hold their breath for up to 1 hour. diff --git a/src/pages/rules/critical-hits.md b/src/pages/rules/critical-hits.md new file mode 100644 index 0000000..ac98dd4 --- /dev/null +++ b/src/pages/rules/critical-hits.md @@ -0,0 +1,43 @@ +--- +title: Critical Hits +description: Critical hit tables +date_pub: 2023-02-19T18:15:00-05:00 +section: rules +content_type: feature +short_code: rch +--- + +A natural 20 is a critical hit. Roll 1d20 on the Critical Hit Table to see what additional effect occurs. + +
    + +| 1d20 | Effect | +| ---- | ---------------------------------- | +| 1-2 | Max Damage | +| 3 | Max Damage + Free Attack[^1] | +| 4 | Max Damage + Target Fumble[^2] | +| 5 | Max Damage + Target Blinded[^3] | +| 6 | Double Damage | +| 7 | Double Damage + Free Attack[^1] | +| 8 | Double Damage + Target Fumble[^2] | +| 9 | Double Damage + Target Blinded[^3] | +| 10 | Double Max Damage | +| 11 | Triple Damage | +| 12 | Triple Damage + Free Attack[^1] | +| 13 | Triple Damage + Target Fumble[^2] | +| 14 | Triple Damage + Target Blinded[^3] | +| 15 | Triple Max Damage | +| 16 | Quadruple Max Damage | +| 17 | Max Damage + Armor Damaged[^4] | +| 18 | Max Damage + Target Amputation[^5] | +| 19 | Max Damage + Target Stunned[^6] | +| 20 | Save vs. Death or die | + +
    + +[^1]: Attempt another hit with same weapon +[^2]: Target must roll on fumble table +[^3]: Blood or debris blinds target for 1d4 rounds +[^4]: Reduce target's AC by 1 until armor repaired / healed +[^5]: Target loses an appendage. +[^6]: Blow stuns target for 1d6 rounds diff --git a/src/pages/rules/custom.md b/src/pages/rules/custom.md new file mode 100644 index 0000000..c1764a8 --- /dev/null +++ b/src/pages/rules/custom.md @@ -0,0 +1,139 @@ +--- +title: Custom Rules +description: The custom rules we play with. +date_pub: 2023-02-19T18:09:00-05:00 +section: rules +content_type: feature +short_code: rcr +--- + +[[toc]] + +### Character Creation + +**Starting abilities** - Roll 4d6d1 (4d6, drop the lowest) for each ability, and assign them as desired. + +**Hit dice** - The hit dice for each character class are increased by a die step (d4 -> d6 -> d8 -> d10 -> d12) + +### Class-Specific Changes + +- **Magic User** - In addition to daggers and staves, Wizards can also use shortswords and crossbows. +- **Ranger** - Animal Companion - #TODO: Needs rules + +### Arcane Magic + +**Read Magic** (NEW) - All arcane spellcasters are assumed to begin with the Read Magic spell in their spellbooks, in addition to any spells they can gain from their class. + +#### New Spells + +New spells for arcane spellcasters are acquired via the following methods: + +- finding new spells through adventuring (for example, discovering a tome of spells in an unearthed crypt); or +- acquiring new spells from another magic-user’s own collection, or under another magic-user’s tutelage (note, however, magic is jealously guarded, and players should expect NPC magic-users to try and extract a high price for such knowledge, whether in actual coin, through servitude, or in return for a favor or the completion of a task). +- Reminder: The content of magical scrolls cannot be transferred to a player’s spell book. In short, these spell scrolls do not actually contain the magical formulae needed to know how to cast a spell, but rather represent spells that have already been prepared in a way so as to be cast by other individuals without the need for ritual preparations. + +#### Creating Potions and Scrolls + +Arcane spellcasters may create potions or scrolls regardless of their current level (although creating ones "above" their current level would be increasingly more difficult, ex: it would take quite a bit of research for a 1st level magic user to create a scroll containing a 3rd level spell). All arcane spellcasters are also alchemists. As such, all a spellcaster requires for a potion is a recipe, a laboratory (portable or permanent), and ingredients (expressed in actual rare ingredients that must be acquired or a simple monetary cost). + +- Alchemical recipes are found in the same collections of arcane lore (i.e., spell books) that spells are; and therefore, they are just as carefully (and jealously) guarded as the spells. +- Players will be notified if such “books” contain any recipes during play. + +#### Copying From a Spellbook + +- Any spellbook can be read with Read Magic. +- Reading a spell in another caster's spellbook can greatly simplify research into that spell, dropping the time required from two weeks per spell level to two days per spell level, and cutting the cost from 1000 gp / level to 200 gp / level. + +### Combat + +**Sequence Per Combat Round (6s)** (REVISED) + +1. Declare spells and melee movement. +2. Initiative: Each side rolls 1d6. +3. Winning side acts: + 1. Monster morale + 2. Movement and Action +4. Other sides act: In initiative order. + +#### Movement and Action + +- A character may move and perform up to one action during each round of combat. +- Except in certain circumstances, the move and action may be made in any order (although the move may not be split up). +- An action usually consists of one of the following: + - Making an attack (missile or melee), + - Casting a spell, or + - Moving again. +- A spellcaster cannot move after casting a spell in a given round. +- A spellcaster may move prior to spellcasting, + +#### Readied Missile Attacks + +- Missile attacks may be declared based on event triggers and held until the trigger occurs (for example: covering a door until a monster arrives). +- Even if the readied attack was declared in the previous round, the attack itself still counts as that character's attack for the round it triggers. + +#### Dual Wielding + +Characters with DEX or STR as a prime requisite may choose to wield two one-handed weapons, as follows: + +- The secondary weapon must be of small size (e.g. a dagger or hand axe). +- (NEW) Rather than making separate attacks with each weapon (at –2 and –4, respectively), a duel-wielding character may elect to only make a single attack, but may roll 2d20 and pick the highest value for the attack. + +**Fighting Defensively** (NEW) - All classes may elect to spend an entire combat round fighting defensively (parrying, blocking, dodging, feinting, etc.). Characters fighting defensively may not attack at all during the round and receive +2 AC bonus (+4 AC bonus for Fighters). This option may be used with the standard Fighting Withdrawal rule. Fighting defensively must be declared prior to determining Initiative. + +**Attacking from Behind** (NEW) - Unaware targets attacked from behind grant the attacker a +2 bonus to hit (note that this bonus does not stack with other class-specific bonuses for backstabbing, like the +4 that Thieves and Assassins get). + +**Mounted Attack Bonus** (NEW) - Mounted attacks (except with small/short weapons like a dagger) receive a +1 bonus to hit against foes on foot (unless the foe is a Huge/Gigantic monster). + +### Healing + +**Natural** - For each full night of rest, a character of level 1 or higher recovers 1 hit die of hit points. +**Rations** - Once per day of adventuring, a character of level 1 or higher may eat a ration while on a resting turn to recover 1 hit die worth of hit points. + +### Languages + +In addition to the standard languages listed Core Rules, the following languages may be chosen by player characters with high Intelligence (at the referee’s discretion): + +- **Aerial** - the language of air elemental and spoken on the elemental plane of air (Aeria / the Zephyr) +- **Angelic** - the language of law elementals and spoken the elemental plane of law (Ange / the Firmament) +- **Aquan** - the language of water elementals and spoken on the elemental plane of water (Aquos / the Deep) +- **Demonic** - the language of chaos elementals and spoken on the elemental plane of chaos (Dema / the Abyss) +- **Infernal** - the language of the devils of Infernus and their masters, the Authority of the Pit. +- **Primordial** - the language shared by all elementals and spoken most places on the elemental planes. +- **Pyrean** - the language of fire elementals and spoken on the elemental plane of fire (Pyrea / the Pyre) +- **Terran** - the language of earth elementals and spoken on the elemental plane of earth (Terru / the Rock) + +### Death & Dying + +#### Bleeding Out + +When a character takes damage that reduces them to 0 HP (or less), that character is considered to be bleeding out. + +- While bleeding out, a character can not make any attacks, and can only move at 1/2 their normal movement rate. +- While a character is bleeding out, they may make a saving throw vs. death during the attack phase of each round to try and stabilize. + - On a successful save, they stabilize at 1 HP, but can't fight and can only move 1/2 speed until treated or healed. + - On a failed save, they lose 1 HP to blood loss, and can try again next round. +- The bleeding out may be stopped (and the character instantly healed back to 1 HP) if another character does one of the following: + - Applies first aid via a successful Wisdom check, + - Feeds the wounded character a healing potion, or + - Cures the wounds via healing magic (Cure Light Wounds, Cure Serious Wounds, etc.). + +#### Poisoning + +When a PC ingests poison, and they fail a Save vs. Poison, they instantly drop to 0 HP and are considered to be poisoned. + +- While poisoned, a character can not make any attacks, and can only move at 1/2 their normal movement rate. +- While a character is poisoned, they may make a saving throw vs. poison during the attack phase of each round to avoid losing a hit point. +- The poison may be stopped (and the character instantly healed back to 0 HP) if another character does one of the following: + - Feeds the poisoned character the antidote to the poison (if one exists), or + - Cures the poison via healing magic (Neutralize Poison). + +#### Dying + +When a character is reduced to a number of HP below 0 equal to thier level (AKA at -[Level] HP), they must make a saving throw vs. death during the attack phase of each round to stay alive. + +- If a character fails one of these saving throws, they are considered dead, and are usually permitted some heroic final words. +- A critical success on a death save restores the character to 1 HP and stops them from bleeding out. +- As with Bleeding Out, the character may be stabilized and instantly healed back to 1 HP if another character does one of the following: + - Applies first aid via a successful Wisdom check, + - Feeds the wounded character a healing potion, or + - Cures the wounds via healing magic (Cure Light Wounds, Cure Serious Wounds, etc.). diff --git a/src/pages/rules/fumbles.md b/src/pages/rules/fumbles.md new file mode 100644 index 0000000..850a40a --- /dev/null +++ b/src/pages/rules/fumbles.md @@ -0,0 +1,39 @@ +--- +title: Fumbles +description: Fumble tables +date_pub: 2023-02-19T18:18:00-05:00 +section: rules +content_type: feature +short_code: rf1 +--- + +A natural 1 is a fumble. Roll 1d20 on the Fumble Table to see what additional effect occurs. + +
    + +| 1d20 | Effect | +| ---- | ------------------------------------------------------ | +| 1 | Stumble, weapon breaks | +| 2 | Stumble, automatic hit[^1] on closest ally within 10' | +| 3 | Stumble, next attack against is automatic critical hit | +| 4 | Stumble, can't attacks until end of next round | +| 5 | Stumble, can't attack until next round | +| 6 | Stumble, next attack against is automatic hit[^1] | +| 7 | Stumble, hit self for maximum damage | +| 8 | Stumble, hit self for regular damage | +| 9 | Stumble, all rolls next round get -2 | +| 10 | Stumble, go last in initiative next round | +| 11 | Fall down (prone) and drop weapon 30' away | +| 12 | Fall down (prone) and drop weapon 20' away | +| 13 | Fall down (prone) and drop weapon 10' away | +| 14 | Fall down (prone) and drop weapon nearby | +| 15 | Fall down (prone) | +| 16 | Drop weapon 30' away | +| 17 | Drop weapon 20' away | +| 18 | Drop weapon 10' away | +| 19 | Drop weapon at feet | +| 20 | Fumble weapon, but maintain hold | + +
    + +[^1]: Roll 1d20 to check for critical, but ignore further fumbles. diff --git a/src/pages/rules/index.md b/src/pages/rules/index.md new file mode 100644 index 0000000..7688a96 --- /dev/null +++ b/src/pages/rules/index.md @@ -0,0 +1,17 @@ +--- +title: House Rules +description: House rules for playing OSE +date_pub: 2023-02-19T18:16:00-05:00 +section: rules +content_type: feature +short_code: r1 +--- + +- [Official OSE Rules](./ose.html) + +- [Custom Rules](./custom.html) + +### Tables + +- [Critical Hit Table](./critical-hits.html) +- [Fumble Table](./custom.html) diff --git a/src/pages/rules/ose.md b/src/pages/rules/ose.md new file mode 100644 index 0000000..4d1ffc5 --- /dev/null +++ b/src/pages/rules/ose.md @@ -0,0 +1,51 @@ +--- +title: Official OSE Rules +description: The official OSE rules we play with +date_pub: 2023-02-19T18:16:00-05:00 +section: rules +content_type: feature +short_code: r1 +--- + +The official (but optional) OSE rules we play by. + +[[toc]] + +### Core Rules + +The core rules are available for free in the [OSE System Reference Document (SRD)](https://oldschoolessentials.necroticgnome.com/srd/index.php/Main_Page). They can also be found in either of the Player's Rules Tomes (Classic or Advanced). + +### Additional (Official Optional) Rules + +#### From the Core Rules + +In addition to the core rules outlined in the SRD / Classic Fantasy Rules Tome, we use the following optional rules: + +- Ascending AC +- Re-Rolling 1s and 2s on HP +- Monster Morale +- Monster Invulnerabilities +- Subduing (for truly suicidal characters) + +#### From the Advanced Fantasy Rules + +We also use the following rules from the Advanced Fantasy Genre Rules / Advanced Player's Rules Tome: + +- Extra Character Classes +- Character Races +- Magic-Users and Staves (but see below) +- Most of The Combat Options +- Attacking with Two Weapons +- Charging into Melee +- Parrying +- Splash Weapons +- Both of The Magic Options +- Raising the Dead +- Spell Books and Learning Spells +- Secondary Skills + +### From Carcass Crawler #1 + +#### Class-Specific Changes + +- **Fighter** - Combat Talents diff --git a/src/pages/weapons/index.md b/src/pages/weapons/index.md new file mode 100644 index 0000000..1ba665e --- /dev/null +++ b/src/pages/weapons/index.md @@ -0,0 +1,23 @@ +--- +title: New Weapons +description: A list of new weapons to use in an astral campaign. +date_pub: 2023-02-19T21:01:00-05:00 +section: weapons +content_type: feature +short_code: w1 +--- + +Ghyffan muskets are something to behold - a streamlined amalgamation of wood and iron, shaped (and tipped) like a spear, but which can fire a metal slug at great speed (and for heavy damage). Their superior construction makes them less prone to breaking, but their S-L-O-W reload time makes them useless as ranged weapons (beyond an opening shot or two). + +
    + +| Weapon | Weight (Coins) | Damage | Qualities | +| -------------- | -------------- | ------ | ------------------------------------------------------------------------------------ | +| Ghyffan Musket | 75 | 1d8 | Loud, Melee, Missile (5’–40’ / 41’–80’ / 81’–120’), One-shot (d10), Slow, Two-handed | + +
    + +### Weapon Qualities + +- _Loud_ (taken from CC#1): The first time in an encounter a weapon with this quality is fired, the noise triggers a wandering monster check and causes animals (except those trained for battle) to make a morale check or flee. At the referee’s discretion, human-like creatures of 2 HD or less from cultures without firearms may also be affected. +- _One-shot_: The Missile attack for this weapon may only be fired once per turn, for the damage listed. diff --git a/src/support/_sitemap.ejs.broken b/src/support/_sitemap.ejs.broken new file mode 100644 index 0000000..40c60fc --- /dev/null +++ b/src/support/_sitemap.ejs.broken @@ -0,0 +1,83 @@ +<% + /* + Not sure it's worth the effort for this ATM. + */ + + var + generateSitemapList = function(the_head, the_tail) { + var + sortForIndex = function(a,b) { + if (a === 'index.html' || ab) { + return 1; + } + return 0; + }, + filter = /(\.html$)/, + replace = /(\.html$)|(^index\.html$)/, + tree = function (head, tail) { + var + output = '', + tree_output = '', + data, content; + for (var key in head) { + var + val = head[key]; + if (key !== '.git') { + if (key == '_data') { + data = val; + } else if (key == '_contents') { + content = val; + } else { + tree_output += tree(val, tail + key + "/"); + } + } + } + + if (content && data) { + content + .sort(sortForIndex) + .forEach (function(file) { + var + slug = file.replace(filter, ""), + file_data = data && data[slug] ? data[slug] : {}, + now = new Date(), + show_item = false, + title, date; + if (filter.test(file)) { + show_item = true; + if (file_data) { + var + is_draft = file_data.is_draft || false, + is_sys_file = file_data.is_sys_file || false, + title = (file_data.title || '').trim(); + if (is_draft || is_sys_file || title === '') { + show_item = false; + } else { + date = file_data.date_last_mod ? new Date(file_data.date_last_mod) : ''; + date = date !== '' && file_data.date_pub ? new Date(file_data.date_pub) : ''; + if ((date && date.getTime() > now.getTime())) { + show_item = false; + } + } + } + } + if (show_item) { + output += + (file === 'index.html' ? '\n'; + } + return output + tree_output; + }; + return tree(the_head, the_tail); + }; +%> +

    Site Map

    +
      + <%- generateSitemapList(public, "/") %> +
    diff --git a/src/support/browserconfig.xml.ejs b/src/support/browserconfig.xml.ejs new file mode 100644 index 0000000..794ae8d --- /dev/null +++ b/src/support/browserconfig.xml.ejs @@ -0,0 +1,2 @@ + +#9aa8bc diff --git a/src/support/errors/404.html.ejs b/src/support/errors/404.html.ejs new file mode 100644 index 0000000..1ff9991 --- /dev/null +++ b/src/support/errors/404.html.ejs @@ -0,0 +1,84 @@ + + + + + It's Eric Woodward! (dotcom) | Page Not Found + + + +
    +

    Page Not Found

    +

    Sorry, but the page you were trying to view does not exist.

    +

    It looks like this was the result of either:

    + +

    You can go back and try again, or just start over at It's Eric Woodward's front page.

    +
    + + + + + + + + +

    Powered by DuckDuckGo

    +
    +
    + + diff --git a/src/support/errors/offline.ejs.off b/src/support/errors/offline.ejs.off new file mode 100644 index 0000000..198c72c --- /dev/null +++ b/src/support/errors/offline.ejs.off @@ -0,0 +1,9 @@ +<% + var + title = "Offline", + text = [ + '

    Sorry, but you are currently offline, and don't have that page in your cache.

    ', + '

    You should still be able to go to any page you've already visited (just not this one, apparently).

    ' + ].join('\n'); +-%> +<%- partial("_error_page", {title: title, text: text, show_search: false}) %> diff --git a/src/support/feed.json.ejs b/src/support/feed.json.ejs new file mode 100644 index 0000000..630d785 --- /dev/null +++ b/src/support/feed.json.ejs @@ -0,0 +1,68 @@ +<% + // There's an issue here with trailing commas. + // A better solution would probably be to pull the data in I need, build an object, and then dump it via JSON.stringify() + // reference: https://jsonfeed.org/version/1.1 + + const + + prependSlash = (pagePath) => `${pagePath.indexOf('/') === 0 ? '' : '/'}${pagePath}`, + + { author, copyright, language, tags } = site, + + feedData = { + version: 'https://jsonfeed.org/version/1.1', + title: site.title, + home_page_url: `${site.uri}`, + feed_url: `${site.uri}/feed.json`, + authors: [], + items: [], + }; + + if (author) { + + const { name, email, photo, site: aSite } = author || {}; + const newAuthor = {}; + + if (email) newAuthor.email = email; + if (name) newAuthor.name = name; + if (photo) newAuthor.photo = `${site.uri}${photo}`; + if (aSite) newAuthor.site = aSite; + + feedData.authors.push(newAuthor); + + } + + if (Array.isArray(site?.pages)) { + + feedData.items.push(...site.pages + .sort((p1, p2) => { + + const p1Date = new Date(p1.date_upd || p1.date_pub); + const p2Date = new Date(p2.date_upd || p2.date_pub); + + return p2Date - p1Date; + + }) + .map((page) => { + + const { body, content, date_pub, date_upd, description, path, tags } = page || {}; + const p = { + id: `${site.uri}${prependSlash(path)}`, + url: `${site.uri}${prependSlash(path)}`, + }; + + if (body) p.content_text = body; + if (content) p.content_html = content; + if (date_pub) p.date_published = date_pub; + if (date_upd) p.date_modified = date_upd; + if (description) p.summary = description; + if (Array.isArray(tags)) p.tags = tags; + + return { ...p }; + + }) + ); + + } +-%> +<%- JSON.stringify(feedData, null, 2) -%> \ No newline at end of file diff --git a/src/support/feed.xml.ejs b/src/support/feed.xml.ejs new file mode 100644 index 0000000..59a1a03 --- /dev/null +++ b/src/support/feed.xml.ejs @@ -0,0 +1,73 @@ + + +<%- include('../layouts/functions') -%> +<% + // validator: https://validator.w3.org/feed/#validate_by_input + const { author, copyright, language, tags } = site; +-%> + + + + <%- site.title %> + + <%- (new Date()).toJSON() -%> + <%- site.uri %> + + + <% if (author.name) { -%> + <%- author.name %> + <% } -%> + <% if (author.email) { -%> + <%- author.email %> + <% } -%> + <% if (author.site) { -%> + <%- author.site %> + <% } -%> + + + + <% if (copyright) { -%> + <%= copyright %> + <% } -%> + <% if (author.photo) { -%> + <%= author.photo %> + <% } -%> + ItsEricWoodward.com + + <% + if (typeof tags === 'string') { + const keywords = tags.split(/\W+/); + for (var i=0; i < keywords.length; i++) { + if (keywords[i]) { + -%> + <%= keywords[i] %> + <% + } + } + } + -%> + + <% if (site && Array.isArray(site.pages)) { + site.pages + .sort((p1, p2) => { + const p1Date = new Date(p1.date_upd || p1.date_pub); + const p2Date = new Date(p2.date_upd || p2.date_pub); + return p2Date - p1Date; + }) + .forEach((page) => { -%> + + + <%= page.title %> + <%- site.uri %><%- page.path %> + + <%= (new Date(page.date_upd || page.date_pub)).toJSON() %> + <% if (page.description) { -%> + <%= page.description %> + <% } -%> + + + <% }); + } -%> + + + diff --git a/src/support/manifest.json.ejs b/src/support/manifest.json.ejs new file mode 100644 index 0000000..9d001ad --- /dev/null +++ b/src/support/manifest.json.ejs @@ -0,0 +1,41 @@ +{ + "name": "App", + "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" + } + ] +} diff --git a/src/support/opensearch.xml.ejs b/src/support/opensearch.xml.ejs new file mode 100644 index 0000000..bdedef8 --- /dev/null +++ b/src/support/opensearch.xml.ejs @@ -0,0 +1,13 @@ +<% + var + uri = (site.base_uri || '').replace(/^https?:\/\//, ''); +-%> + + + itsericwoodward.com + Search itsericwoodward.com + UTF-8 + + diff --git a/src/support/robots.txt.ejs b/src/support/robots.txt.ejs new file mode 100644 index 0000000..93abf99 --- /dev/null +++ b/src/support/robots.txt.ejs @@ -0,0 +1,2 @@ +User-agent: * +Disallow: /.well-known/ diff --git a/src/support/root/index.html.ejs b/src/support/root/index.html.ejs new file mode 100644 index 0000000..501dbbe --- /dev/null +++ b/src/support/root/index.html.ejs @@ -0,0 +1,306 @@ + + + + + + + + + + + + + <%= site.title %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    + +

    + My name is Eric Woodward, and I am a + geek, + coder, + gamer, + tinkerer, + husband, + father, + server admin, + web developer, + and American + cyborg, + though not necessarily in that order. +

    + +

    You can find out more + about me by + checking out + my website + or perusing + my code. + If you want to reach me, you can send me an Email, + + or you can find me on + LinkedIn, + GitHub, or + BoardGameGeek. + Alternatively, you can grab my + vCard and add me to your address book. +

    +

    + + An IndieWeb Webring 🕸💍 + +

    +
    +
    + + diff --git a/src/support/sitemap-v2.xml.ejs.broken b/src/support/sitemap-v2.xml.ejs.broken new file mode 100644 index 0000000..5f3b5fa --- /dev/null +++ b/src/support/sitemap-v2.xml.ejs.broken @@ -0,0 +1,77 @@ +<% + /* + This is broken too, adds an unnecessary "undefine" to output. + */ + + var + generateSitemapXML = function(the_head, the_tail, the_base) { + var + filter = /(\.html$)/, + replace = /(\.html$)|(^index\.html$)/, + tree = function (head, tail, base) { + var + output = '', + tree_output = '', + data, content; + for (var key in head) { + var + val = head[key]; + if (key && val && key !== '.git') { + if (key == '_data') { + data = val; + } else if (key == '_contents') { + content = val; + } else { + tree_output += tree(val, tail + key + "/", base); + } + } + } + + if (content) { + for (var i in content) { + var + file = content[i], + slug = file.replace(filter, ""), + file_data = data && data[slug] ? data[slug] : {}, + now = new Date(), + show_item = false, + upd_date; + if (filter.test(file)) { + show_item = true; + if (file_data) { + var + is_draft = file_data.is_draft || false, + is_sys_file = file_data.is_sys_file || false, + title = (file_data.title || '').trim(); + if (is_draft || is_sys_file || title === '') { + show_item = false; + } else { + upd_date = file_data.date_last_mod ? new Date(file_data.date_last_mod) : ''; + upd_date = upd_date !== '' && file_data.date_pub ? new Date(file_data.date_pub) : ''; + if ((upd_date && upd_date.getTime() > now.getTime())) { + show_item = false; + } + } + } else { + show_item = false; + } + } + if (show_item) { + output += + '\n\n ' + the_base + tail + file + '\n' + + (upd_date ? '\n ' + upd_date.toUTCString() + '' : '') + '\n'; + } + } + return output + (['','undefined'].indexOf(tree_output.trim()) == -1 ? tree_output : ''); + } + }; + return tree(the_head, the_tail, the_base) + .replace('undefined', ''); // hack, not sure where the extra "undefined" is coming from. TODO: Fix this + } +-%> + + + <%- generateSitemapXML(public, "/", site.uri.production) %> + diff --git a/src/support/sitemap.html.ejs.off b/src/support/sitemap.html.ejs.off new file mode 100644 index 0000000..b0c72f3 --- /dev/null +++ b/src/support/sitemap.html.ejs.off @@ -0,0 +1,62 @@ +<% + var + generateSitemapList = function(the_head, the_tail) { + var + filter = /(\.html$)/, + replace = /(\.html$)|(^index\.html$)/, + tree = function (head, tail) { + var + output = '', + tree_output = '', + data, content; + for (var key in head) { + var + val = head[key]; + if (key !== '.git') { + if (key == '_data') { + data = val; + } else if (key == '_contents') { + content = val; + } else { + tree_output += tree(val, tail + key + "/"); + } + } + } + + if (content && data) { + for (var i in content) { + var + file = content[i], + slug = file.replace(replace, ""), + file_data = data && data[slug] ? data[slug] : {}, + now = new Date(), + show_item = false, + title, date; + if (filter.test(file) && !(/^404\.html/).test(file)) { + show_item = true; + if (file_data) { + var is_draft = file_data.is_draft || false; + date = file_data.date_pub ? new Date(file_data.date_pub) : ''; + title = file_data.title || ''; + if (is_draft || title === '' || (date && date.getTime() > now.getTime())) { + show_item = false; + } + } + } + if (show_item) { +// file = file.replace(replace, ""); + output += + '\n
  • ' + title + '
  • \n'; + } + } + } + return output + tree_output; + }; + return tree(the_head, the_tail); + }; +%> + + + <%- generateSitemapList(public, "/") %> + + diff --git a/src/support/sitemap.xml.ejs b/src/support/sitemap.xml.ejs new file mode 100644 index 0000000..374dd9d --- /dev/null +++ b/src/support/sitemap.xml.ejs @@ -0,0 +1,16 @@ + +<%- include('../layouts/functions') -%> + + + <% if (site && Array.isArray(site.pages)) { + site.pages + .sort(sortByPath) + .forEach((page) => { -%> + + <%= page.path %> + <%= (new Date(page.date_upd || page.date_pub)).toJSON() %> + + <% }); + } -%> + +