chore: make build process faster/simpler (#833)

This gets rid of the awkward checking-in of `template.html` to git (when
it's a built file) and also makes the rebuilds faster and more
consistent by running everything through the same pipeline. So inline
CSS, SVG, and JS are all partially built on-the-fly.

I've basically reinvented gulp, but it's pretty lightweight and
zero-dep, so I'm happy with it.
This commit is contained in:
Nolan Lawson 2018-12-17 17:21:29 -08:00 committed by GitHub
parent fc30ef1c8c
commit bb7fe6e30a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 202 additions and 207 deletions

1
.gitignore vendored
View file

@ -3,6 +3,7 @@ node_modules
/__sapper__
/mastodon
/mastodon.log
/src/template.html
/static/*.css
/static/robots.txt
/static/inline-script.js.map

View file

@ -21,6 +21,10 @@ To run a dev server with hot reloading:
Now it's running at `localhost:4002`.
**Linux users:** for file changes to work,
you'll probably want to run `export CHOKIDAR_USEPOLLING=1`
because of [this issue](https://github.com/paulmillr/chokidar/issues/237).
## Linting
Pinafore uses [JavaScript Standard Style](https://standardjs.com/).
@ -106,4 +110,4 @@ Check `mastodon.log` if you have any issues.
## Unit tests
There are also some unit tests that run in Node using Mocha. You can find them in `tests/unit` and
run them using `npm run test-unit`.
run them using `npm run test-unit`.

View file

@ -10,12 +10,11 @@ import replace from 'rollup-plugin-replace'
import fromPairs from 'lodash-es/fromPairs'
import { themes } from '../src/routes/_static/themes'
const readFile = pify(fs.readFile.bind(fs))
const writeFile = pify(fs.writeFile.bind(fs))
const themeColors = fromPairs(themes.map(_ => ([_.name, _.color])))
async function main () {
export async function buildInlineScript () {
let inlineScriptPath = path.join(__dirname, '../inline-script.js')
let bundle = await rollup({
@ -36,25 +35,13 @@ async function main () {
sourcemap: true
})
let fullCode = `${code}\n//# sourceMappingURL=/inline-script.js.map`
let fullCode = `${code}//# sourceMappingURL=/inline-script.js.map`
let checksum = crypto.createHash('sha256').update(fullCode).digest('base64')
let checksumFilepath = path.join(__dirname, '../inline-script-checksum.json')
await writeFile(checksumFilepath, JSON.stringify({ checksum }), 'utf8')
await writeFile(path.resolve(__dirname, '../inline-script-checksum.json'),
JSON.stringify({ checksum }), 'utf8')
await writeFile(path.resolve(__dirname, '../static/inline-script.js.map'),
map.toString(), 'utf8')
let htmlTemplateFilepath = path.join(__dirname, '../src/template.html')
let htmlTemplateFile = await readFile(htmlTemplateFilepath, 'utf8')
htmlTemplateFile = htmlTemplateFile.replace(
/<!-- insert inline script here -->[\s\S]+<!-- end insert inline script here -->/,
'<!-- insert inline script here --><script>' + fullCode + '</script><!-- end insert inline script here -->'
)
await writeFile(htmlTemplateFilepath, htmlTemplateFile, 'utf8')
await writeFile(path.resolve(__dirname, '../static/inline-script.js.map'), map.toString(), 'utf8')
return '<script>' + fullCode + '</script>'
}
main().catch(err => {
console.error(err)
process.exit(1)
})

View file

@ -1,40 +1,21 @@
#!/usr/bin/env node
const sass = require('node-sass')
const chokidar = require('chokidar')
const path = require('path')
const debounce = require('lodash/debounce')
const fs = require('fs')
const pify = require('pify')
import sass from 'node-sass'
import path from 'path'
import fs from 'fs'
import pify from 'pify'
const writeFile = pify(fs.writeFile.bind(fs))
const readdir = pify(fs.readdir.bind(fs))
const readFile = pify(fs.readFile.bind(fs))
const render = pify(sass.render.bind(sass))
const now = require('performance-now')
const globalScss = path.join(__dirname, '../scss/global.scss')
const defaultThemeScss = path.join(__dirname, '../scss/themes/_default.scss')
const offlineThemeScss = path.join(__dirname, '../scss/themes/_offline.scss')
const customScrollbarScss = path.join(__dirname, '../scss/custom-scrollbars.scss')
const htmlTemplateFile = path.join(__dirname, '../src/template.html')
const scssDir = path.join(__dirname, '../scss')
const themesScssDir = path.join(__dirname, '../scss/themes')
const assetsDir = path.join(__dirname, '../static')
function doWatch () {
let start = now()
chokidar.watch(scssDir).on('change', debounce(() => {
console.log('Recompiling SCSS...')
Promise.all([
compileGlobalSass(),
compileThemesSass()
]).then(() => {
console.log('Recompiled SCSS in ' + (now() - start) + 'ms')
})
}, 500))
chokidar.watch()
}
async function renderCss (file) {
return (await render({ file, outputStyle: 'compressed' })).css
}
@ -44,16 +25,9 @@ async function compileGlobalSass () {
let offlineStyle = (await renderCss(offlineThemeScss))
let scrollbarStyle = (await renderCss(customScrollbarScss))
let html = await readFile(htmlTemplateFile, 'utf8')
html = html.replace(/<!-- begin inline CSS -->[\s\S]+<!-- end inline CSS -->/,
`<!-- begin inline CSS -->\n` +
`<style>\n${mainStyle}</style>\n` +
return `<style>\n${mainStyle}</style>\n` +
`<style media="only x" id="theOfflineStyle">\n${offlineStyle}</style>\n` +
`<style media="all" id="theScrollbarStyle">\n${scrollbarStyle}</style>\n` +
`<!-- end inline CSS -->`
)
await writeFile(htmlTemplateFile, html, 'utf8')
`<style media="all" id="theScrollbarStyle">\n${scrollbarStyle}</style>\n`
}
async function compileThemesSass () {
@ -65,14 +39,7 @@ async function compileThemesSass () {
}))
}
async function main () {
await Promise.all([compileGlobalSass(), compileThemesSass()])
if (process.argv.includes('--watch')) {
doWatch()
}
export async function buildSass () {
let [ result ] = await Promise.all([compileGlobalSass(), compileThemesSass()])
return result
}
Promise.resolve().then(main).catch(err => {
console.error(err)
process.exit(1)
})

View file

@ -1,17 +1,16 @@
#!/usr/bin/env node
const svgs = require('./svgs')
const path = require('path')
const fs = require('fs')
const pify = require('pify')
const SVGO = require('svgo')
import svgs from './svgs'
import path from 'path'
import fs from 'fs'
import pify from 'pify'
import SVGO from 'svgo'
import $ from 'cheerio'
const svgo = new SVGO()
const $ = require('cheerio')
const readFile = pify(fs.readFile.bind(fs))
const writeFile = pify(fs.writeFile.bind(fs))
async function main () {
export async function buildSvg () {
let result = (await Promise.all(svgs.map(async svg => {
let filepath = path.join(__dirname, '../', svg.src)
let content = await readFile(filepath, 'utf8')
@ -25,18 +24,5 @@ async function main () {
return $.xml($symbol)
}))).join('\n')
result = `<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">\n${result}\n</svg>`
let htmlTemplateFilepath = path.join(__dirname, '../src/template.html')
let htmlTemplateFile = await readFile(htmlTemplateFilepath, 'utf8')
htmlTemplateFile = htmlTemplateFile.replace(
/<!-- insert svg here -->[\s\S]+<!-- end insert svg here -->/,
'<!-- insert svg here -->' + result + '<!-- end insert svg here -->'
)
await writeFile(htmlTemplateFilepath, htmlTemplateFile, 'utf8')
return `<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">\n${result}\n</svg>`
}
main().catch(err => {
console.error(err)
process.exit(1)
})

106
bin/build-template-html.js Normal file
View file

@ -0,0 +1,106 @@
import chokidar from 'chokidar'
import fs from 'fs'
import path from 'path'
import pify from 'pify'
import { buildSass } from './build-sass'
import { buildInlineScript } from './build-inline-script'
import { buildSvg } from './build-svg'
import now from 'performance-now'
import debounce from 'lodash-es/debounce'
const writeFile = pify(fs.writeFile.bind(fs))
const DEBOUNCE = 500
const builders = [
{
watch: 'scss',
comment: '<!-- inline CSS -->',
rebuild: buildSass
},
{
watch: 'inline-script.js',
comment: '<!-- inline JS -->',
rebuild: buildInlineScript
},
{
watch: 'bin/svgs.js',
comment: '<!-- inline SVG -->',
rebuild: buildSvg
}
]
// array of strings and builder functions, we build this on-the-fly
const partials = buildPartials()
function buildPartials () {
let rawTemplate = fs.readFileSync(path.resolve(__dirname, '../src-build/template.html'), 'utf8')
let partials = [rawTemplate]
builders.forEach(builder => {
for (let i = 0; i < partials.length; i++) {
let partial = partials[i]
if (typeof partial !== 'string') {
continue
}
let idx = partial.indexOf(builder.comment)
if (idx !== -1) {
partials.splice(
i,
1,
partial.substring(0, idx),
builder,
partial.substring(idx + builder.comment.length)
)
break
}
}
})
return partials
}
function doWatch () {
// rebuild each of the partials on-the-fly if something changes
partials.forEach(partial => {
if (typeof partial === 'string') {
return
}
chokidar.watch(partial.watch).on('change', debounce(path => {
console.log(`Detected change in ${path}...`)
delete partial.result
buildAll()
}), DEBOUNCE)
})
}
async function buildAll () {
let start = now()
let html = (await Promise.all(partials.map(async partial => {
if (typeof partial === 'string') {
return partial
}
if (!partial.result) {
partial.result = partial.comment + '\n' + (await partial.rebuild())
}
return partial.result
}))).join('')
await writeFile(path.resolve(__dirname, '../src/template.html'), html, 'utf8')
let end = now()
console.log(`Built template.html in ${(end - start).toFixed(2)}ms`)
}
async function main () {
await buildAll()
if (process.argv.includes('--watch')) {
doWatch()
}
}
main().catch(err => {
console.error(err)
process.exit(1)
})

View file

@ -5,19 +5,16 @@
"scripts": {
"lint": "standard && standard --plugin html 'src/routes/**/*.html'",
"lint-fix": "standard --fix && standard --fix --plugin html 'src/routes/**/*.html'",
"dev": "run-s build-svg build-inline-script serve-dev",
"serve-dev": "run-p --race build-sass-watch sapper-dev",
"dev": "run-p --race build-template-html-watch sapper-dev",
"sapper-dev": "cross-env NODE_ENV=development PORT=4002 sapper dev",
"sapper-prod": "cross-env PORT=4002 node __sapper__/build",
"build": "cross-env NODE_ENV=production npm run build-steps",
"build-steps": "run-s build-sass build-svg build-inline-script sapper-build",
"build-steps": "run-s build-template-html sapper-build",
"sapper-build": "sapper build",
"start": "cross-env NODE_ENV=production npm run sapper-prod",
"build-and-start": "run-s build start",
"build-svg": "node ./bin/build-svg.js",
"build-inline-script": "node -r esm ./bin/build-inline-script.js",
"build-sass": "node ./bin/build-sass.js",
"build-sass-watch": "node ./bin/build-sass.js --watch",
"build-template-html": "node -r esm ./bin/build-template-html.js",
"build-template-html-watch": "node -r esm ./bin/build-template-html.js --watch",
"run-mastodon": "node -r esm ./bin/run-mastodon.js",
"test": "cross-env BROWSER=chrome:headless npm run test-browser",
"test-browser": "run-p --race run-mastodon build-and-start test-mastodon",
@ -163,6 +160,7 @@
"original-static",
"scss",
"src",
"src-build",
"static",
"package.json",
"package-lock.json",

View file

@ -31,4 +31,4 @@ html {
::-webkit-scrollbar-corner {
background: var(--scrollbar-background-color);
}
}

58
src-build/template.html Normal file
View file

@ -0,0 +1,58 @@
<!doctype html>
<html lang="en">
<head>
<meta charset='utf-8' >
<meta name="viewport" content="width=device-width, initial-scale=1" >
<meta id='theThemeColor' name='theme-color' content='#4169e1' >
<meta name="description" content="An alternative web client for Mastodon, focused on speed and simplicity." >
%sapper.base%
<link id='theManifest' rel='manifest' href='/manifest.json' >
<link id='theFavicon' rel='icon' type='image/png' href='/favicon.png' >
<link rel="apple-touch-icon" sizes="120x120" href="/apple-icon-120.png" >
<link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180.png" >
<meta name="mobile-web-app-capable" content="yes" >
<meta name="apple-mobile-web-app-title" content="Pinafore" >
<meta name="apple-mobile-web-app-status-bar-style" content="white" >
<!-- inline CSS -->
<noscript>
<style>
.hidden-from-ssr {
opacity: 1 !important;
}
</style>
</noscript>
<!-- Sapper generates a <style> tag containing critical CSS
for the current page. CSS for the rest of the src is
lazily loaded when it precaches secondary pages -->
%sapper.styles%
<!-- This contains the contents of the <:Head> component, if
the current page has one -->
%sapper.head%
</head>
<body>
<!-- inline JS -->
<!-- The application will be rendered inside this element,
because `templates/client.js` references it -->
<div id='sapper'>%sapper.html%</div>
<!-- Toast.html gets rendered here -->
<div id="toast"></div>
<!-- LoadingMask.html gets rendered here -->
<div id="loading-mask" aria-hidden="true"></div>
<!-- inline SVG -->
<!-- Sapper creates a <script> tag containing `templates/client.js`
and anything else it needs to hydrate the src and
initialise the router -->
%sapper.scripts%
</body>
</html>

View file

@ -1,112 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset='utf-8' >
<meta name="viewport" content="width=device-width, initial-scale=1" >
<meta id='theThemeColor' name='theme-color' content='#4169e1' >
<meta name="description" content="An alternative web client for Mastodon, focused on speed and simplicity." >
%sapper.base%
<link id='theManifest' rel='manifest' href='/manifest.json' >
<link id='theFavicon' rel='icon' type='image/png' href='/favicon.png' >
<link rel="apple-touch-icon" sizes="120x120" href="/apple-icon-120.png" >
<link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180.png" >
<meta name="mobile-web-app-capable" content="yes" >
<meta name="apple-mobile-web-app-title" content="Pinafore" >
<meta name="apple-mobile-web-app-status-bar-style" content="white" >
<!-- begin inline CSS -->
<style>
:root{--button-primary-bg: #6081e6;--button-primary-text: #fff;--button-primary-border: #132c76;--button-primary-bg-active: #456ce2;--button-primary-bg-hover: #6988e7;--button-bg: #e6e6e6;--button-text: #333;--button-border: #a7a7a7;--button-bg-active: #bfbfbf;--button-bg-hover: #f2f2f2;--input-border: #dadada;--input-bg: #fff;--anchor-text: #4169e1;--main-bg: #fff;--body-bg: #e8edfb;--body-text-color: #333;--main-border: #dadada;--svg-fill: #4169e1;--form-bg: #f7f7f7;--form-border: #c1c1c1;--nav-bg: #4169e1;--nav-border: #214cce;--nav-a-border: #4169e1;--nav-a-selected-border: #fff;--nav-a-selected-bg: #6d8ce8;--nav-svg-fill: #fff;--nav-text-color: #fff;--nav-a-selected-border-hover: #fff;--nav-a-selected-bg-hover: #839deb;--nav-a-bg-hover: #577ae4;--nav-a-border-hover: #4169e1;--nav-svg-fill-hover: #fff;--nav-text-color-hover: #fff;--action-button-fill-color: #90a8ee;--action-button-fill-color-hover: #a2b6f0;--action-button-fill-color-active: #577ae4;--action-button-fill-color-pressed: #2351dc;--action-button-fill-color-pressed-hover: #3862e0;--action-button-fill-color-pressed-active: #1d44b8;--action-button-deemphasized-fill-color: #666;--action-button-deemphasized-fill-color-hover: #9e9e9e;--action-button-deemphasized-fill-color-active: #737373;--action-button-deemphasized-fill-color-pressed: #545454;--action-button-deemphasized-fill-color-pressed-hover: #616161;--action-button-deemphasized-fill-color-pressed-active: #404040;--settings-list-item-bg: #fff;--settings-list-item-text: #4169e1;--settings-list-item-text-hover: #4169e1;--settings-list-item-border: #dadada;--settings-list-item-bg-active: #e6e6e6;--settings-list-item-bg-hover: #fafafa;--toast-bg: #333;--toast-border: #fafafa;--toast-text: #fff;--mask-bg: #333;--mask-svg-fill: #fff;--mask-opaque-bg: rgba(51,51,51,0.8);--loading-bg: #ededed;--account-profile-bg-backdrop-filter: rgba(255,255,255,0.7);--account-profile-bg: rgba(255,255,255,0.9);--deemphasized-text-color: #666;--focus-outline: #c5d1f6;--very-deemphasized-link-color: rgba(65,105,225,0.6);--very-deemphasized-text-color: rgba(102,102,102,0.6);--status-direct-background: #d2dcf8;--main-theme-color: #4169e1;--warning-color: #e01f19;--alt-input-bg: rgba(255,255,255,0.7);--muted-modal-bg: transparent;--muted-modal-focus: #999;--muted-modal-hover: rgba(255,255,255,0.2);--compose-autosuggest-item-hover: #ced8f7;--compose-autosuggest-item-active: #b8c7f4;--compose-autosuggest-outline: #dbe3f9;--compose-button-halo: rgba(255,255,255,0.1);--file-drop-mask: rgba(255,255,255,0.8)}:root{--scrollbar-face-color: #90a8ee;--scrollbar-track-color: #dbe3f9;--scrollbar-border-radius: 0;--scrollbar-face-color-hover: #99afef;--scrollbar-face-color-active: #839deb;--scrollbar-width: 12px;--scrollbar-height: 12px;--scrollbar-background-color: transparent}
@font-face{font-family:PinaforeRegular;src:local("BlinkMacSystemFont"),local("Segoe UI"),local("Roboto"),local("Oxygen-Sans"),local("Ubuntu"),local("Cantarell"),local("Fira Sans"),local("Droid Sans"),local("Helvetica Neue")}@font-face{font-family:PinaforeEmoji;src:local("Apple Color Emoji"),local("Segoe UI Emoji"),local("Segoe UI Symbol"),local("Twemoji Mozilla"),local("Noto Color Emoji"),local("EmojiOne Color"),local("Android Emoji")}body{margin:0;font-family:system-ui, -apple-system, PinaforeRegular, sans-serif;font-size:14px;line-height:1.4;color:var(--body-text-color);background:var(--body-bg);-webkit-tap-highlight-color:transparent;overflow-x:hidden}.main-content{contain:content;padding-top:42px}@supports (-webkit-overflow-scrolling: touch){.main-content{overflow-x:hidden}}@media (max-width: 991px){.main-content{padding-top:52px}}@media (max-width: 767px){.main-content{padding-top:62px}}main{position:relative;width:602px;max-width:100vw;padding:0;box-sizing:border-box;margin:30px auto 15px;background:var(--main-bg);border:1px solid var(--main-border);border-radius:1px;min-height:70vh}@media (max-width: 767px){main{margin:5px auto 15px}}footer{width:602px;max-width:100vw;box-sizing:border-box;margin:15px auto;border-radius:1px;background:var(--main-bg);font-size:0.9em;padding:20px;border:1px solid var(--main-border)}h1,h2,h3,h4,h5,h6{margin:0 0 0.5em 0;font-weight:400;line-height:1.2}h1{font-size:2em}a{color:var(--anchor-text);text-decoration:none}a:visited{color:var(--anchor-text)}a:hover{text-decoration:underline}input{border:1px solid var(--input-border);padding:5px;box-sizing:border-box}input[type=search]{-webkit-appearance:none}input,textarea{background:inherit;color:inherit}textarea{font-family:system-ui, -apple-system, PinaforeRegular, sans-serif, PinaforeEmoji}button,.button{font-size:1.2em;background:var(--button-bg);border-radius:2px;padding:10px 15px;border:1px solid var(--button-border);cursor:pointer;color:var(--button-text)}button:hover,.button:hover{background:var(--button-bg-hover);text-decoration:none}button:active,.button:active{background:var(--button-bg-active)}button[disabled],.button[disabled]{opacity:0.35;pointer-events:none;cursor:not-allowed}button.primary,.button.primary{border:1px solid var(--button-primary-border);background:var(--button-primary-bg);color:var(--button-primary-text)}button.primary:hover,.button.primary:hover{background:var(--button-primary-bg-hover)}button.primary:active,.button.primary:active{background:var(--button-primary-bg-active)}p,label,input{font-size:1.3em}ul,li,p{padding:0;margin:0}.hidden{opacity:0}*:focus{outline:2px solid var(--focus-outline)}.container:focus{outline:none}button::-moz-focus-inner{border:0}input:required,input:invalid{box-shadow:none}textarea{font-family:inherit;font-size:inherit;box-sizing:border-box}@keyframes spin{0%{transform:rotate(0deg)}25%{transform:rotate(90deg)}50%{transform:rotate(180deg)}75%{transform:rotate(270deg)}100%{transform:rotate(360deg)}}.spin{animation:spin 1.5s infinite linear}.ellipsis::after{content:"\2026"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.inline-custom-emoji{width:1.4em;height:1.4em;margin:-0.1em 0;object-fit:contain;vertical-align:middle}.inline-emoji{font-family:PinaforeEmoji, sans-serif}
</style>
<style media="only x" id="theOfflineStyle">
:root{--button-primary-bg: #ababab;--button-primary-text: #fff;--button-primary-border: #4d4d4d;--button-primary-bg-active: #9c9c9c;--button-primary-bg-hover: #b0b0b0;--button-bg: #e6e6e6;--button-text: #333;--button-border: #a7a7a7;--button-bg-active: #bfbfbf;--button-bg-hover: #f2f2f2;--input-border: #dadada;--input-bg: #fff;--anchor-text: #999;--main-bg: #fff;--body-bg: #fafafa;--body-text-color: #333;--main-border: #dadada;--svg-fill: #999;--form-bg: #f7f7f7;--form-border: #c1c1c1;--nav-bg: #999;--nav-border: gray;--nav-a-border: #999;--nav-a-selected-border: #fff;--nav-a-selected-bg: #b3b3b3;--nav-svg-fill: #fff;--nav-text-color: #fff;--nav-a-selected-border-hover: #fff;--nav-a-selected-bg-hover: #bfbfbf;--nav-a-bg-hover: #a6a6a6;--nav-a-border-hover: #999;--nav-svg-fill-hover: #fff;--nav-text-color-hover: #fff;--action-button-fill-color: #c7c7c7;--action-button-fill-color-hover: #d1d1d1;--action-button-fill-color-active: #a6a6a6;--action-button-fill-color-pressed: #878787;--action-button-fill-color-pressed-hover: #949494;--action-button-fill-color-pressed-active: #737373;--action-button-deemphasized-fill-color: #666;--action-button-deemphasized-fill-color-hover: #9e9e9e;--action-button-deemphasized-fill-color-active: #737373;--action-button-deemphasized-fill-color-pressed: #545454;--action-button-deemphasized-fill-color-pressed-hover: #616161;--action-button-deemphasized-fill-color-pressed-active: #404040;--settings-list-item-bg: #fff;--settings-list-item-text: #999;--settings-list-item-text-hover: #999;--settings-list-item-border: #dadada;--settings-list-item-bg-active: #e6e6e6;--settings-list-item-bg-hover: #fafafa;--toast-bg: #333;--toast-border: #fafafa;--toast-text: #fff;--mask-bg: #333;--mask-svg-fill: #fff;--mask-opaque-bg: rgba(51,51,51,0.8);--loading-bg: #ededed;--account-profile-bg-backdrop-filter: rgba(255,255,255,0.7);--account-profile-bg: rgba(255,255,255,0.9);--deemphasized-text-color: #666;--focus-outline: #bfbfbf;--very-deemphasized-link-color: rgba(153,153,153,0.6);--very-deemphasized-text-color: rgba(102,102,102,0.6);--status-direct-background: #ededed;--main-theme-color: #999;--warning-color: #e01f19;--alt-input-bg: rgba(255,255,255,0.7);--muted-modal-bg: transparent;--muted-modal-focus: #999;--muted-modal-hover: rgba(255,255,255,0.2);--compose-autosuggest-item-hover: #c4c4c4;--compose-autosuggest-item-active: #b8b8b8;--compose-autosuggest-outline: #ccc;--compose-button-halo: rgba(255,255,255,0.1);--file-drop-mask: rgba(255,255,255,0.8)}:root{--scrollbar-face-color: #c7c7c7;--scrollbar-track-color: #f2f2f2;--scrollbar-border-radius: 0;--scrollbar-face-color-hover: #ccc;--scrollbar-face-color-active: #bfbfbf;--scrollbar-width: 12px;--scrollbar-height: 12px;--scrollbar-background-color: transparent}
</style>
<style media="all" id="theScrollbarStyle">
html{scrollbar-face-color:var(--scrollbar-face-color);scrollbar-track-color:var(--scrollbar-track-color);scrollbar-color:var(--scrollbar-face-color) var(--scrollbar-track-color)}::-webkit-scrollbar{width:var(--scrollbar-width);height:var(--scrollbar-height)}::-webkit-scrollbar-thumb{background:var(--scrollbar-face-color);border-radius:var(--scrollbar-border-radius)}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-face-color-hover)}::-webkit-scrollbar-thumb:active{background:var(--scrollbar-face-color-active)}::-webkit-scrollbar-track,::-webkit-scrollbar-track:hover,::-webkit-scrollbar-track:active{background:var(--scrollbar-track-color)}::-webkit-scrollbar-corner{background:var(--scrollbar-background-color)}
</style>
<!-- end inline CSS -->
<noscript>
<style>
.hidden-from-ssr {
opacity: 1 !important;
}
</style>
</noscript>
<!-- Sapper generates a <style> tag containing critical CSS
for the current page. CSS for the rest of the src is
lazily loaded when it precaches secondary pages -->
%sapper.styles%
<!-- This contains the contents of the <:Head> component, if
the current page has one -->
%sapper.head%
</head>
<body>
<!-- auto-generated w/ build-inline-script.js -->
<!-- insert inline script here --><script>!function(){"use strict";let e=document.getElementById("theThemeColor"),t=document.getElementById("theOfflineStyle");function o(){return document.head.querySelector('link[rel=stylesheet][href^="/theme-"]')}window.__themeColors={default:"royalblue",scarlet:"#e04e41",seafoam:"#177380",hotpants:"hotpink",oaken:"saddlebrown",majesty:"blueviolet",gecko:"#4ab92f",ozark:"#5263af",cobalt:"#08439b",sorcery:"#ae91e8",punk:"#e04e41",riot:"hotpink",hacker:"#4ab92f",pitchblack:"#000"};const n=e=>void 0===e||"undefined"===e?void 0:JSON.parse(e),r=(()=>{try{if(localStorage.setItem("__test__","__test__"),!localStorage.length||"__test__"!==localStorage.getItem("__test__"))return!1;localStorage.removeItem("__test__")}catch(e){return!1}return!0})(),l=r&&n(localStorage.store_currentInstance);if(l){let e=document.createElement("link");e.setAttribute("rel","prefetch"),e.setAttribute("href",`${a=l,function(e){return e.startsWith("localhost:")||e.startsWith("127.0.0.1:")}(a)?`http://${a}`:`https://${a}`}/api/v1/instance`),e.setAttribute("crossorigin","anonymous"),document.head.appendChild(e)}var a;if(l&&localStorage.store_instanceThemes){let r=n(localStorage.store_instanceThemes)[n(localStorage.store_currentInstance)];r&&"default"!==r&&function(n){let r=window.__themeColors[n];e.content=r||window.__themeColors.default,"default"!==n?function(e){let n=o(),r=document.createElement("link");r.rel="stylesheet",r.href=e,r.addEventListener("load",function e(){r.removeEventListener("load",e),n&&document.head.removeChild(n)}),document.head.insertBefore(r,t)}(`/theme-${n}.css`):function(){let e=o();e&&document.head.removeChild(e)}()}(r)}if(r&&l||function(){if(document.getElementById("hiddenFromSsrStyle"))return;let e=document.createElement("style");e.setAttribute("id","hiddenFromSsrStyle"),e.textContent=".hidden-from-ssr { opacity: 1 !important; }",document.head.appendChild(e)}(),r&&"true"===localStorage.store_disableCustomScrollbars){document.getElementById("theScrollbarStyle").setAttribute("media","only x")}/mac/i.test(navigator.platform)&&document.documentElement.style.setProperty("--scrollbar-border-radius","50px"),/iP(?:hone|ad|od)/.test(navigator.userAgent)&&document.head.removeChild(document.getElementById("theManifest"))}();
//# sourceMappingURL=/inline-script.js.map</script><!-- end insert inline script here -->
<!-- The application will be rendered inside this element,
because `templates/client.js` references it -->
<div id='sapper'>%sapper.html%</div>
<!-- Toast.html gets rendered here -->
<div id="toast"></div>
<!-- LoadingMask.html gets rendered here -->
<div id="loading-mask" aria-hidden="true"></div>
<!-- auto-generated w/ build-svg.js -->
<!-- insert svg here --><svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
<symbol id="pinafore-logo" viewBox="0 0 100 100"><path d="M92.12 59.93H59.87V8.23C70.4 14.9 87.34 30 92.12 59.93zM31 26.9A122.4 122.4 0 0 1 9.39 60.35H31zM37.76 99h24.48a30.67 30.67 0 0 0 30.67-30.67H50.52V6.27a5.27 5.27 0 0 0-10.52 0v62.06H7.09A30.67 30.67 0 0 0 37.76 99z"></path></symbol>
<symbol id="fa-bell" viewBox="0 0 1792 1792"><path d="M912 1696q0-16-16-16-59 0-101.5-42.5T752 1536q0-16-16-16t-16 16q0 73 51.5 124.5T896 1712q16 0 16-16zm816-288q0 52-38 90t-90 38h-448q0 106-75 181t-181 75-181-75-75-181H192q-52 0-90-38t-38-90q50-42 91-88t85-119.5 74.5-158.5 50-206T384 576q0-152 117-282.5T808 135q-8-19-8-39 0-40 28-68t68-28 68 28 28 68q0 20-8 39 190 28 307 158.5T1408 576q0 139 19.5 260t50 206 74.5 158.5 85 119.5 91 88z"></path></symbol>
<symbol id="fa-users" viewBox="0 0 2048 1792"><path d="M657 896q-162 5-265 128H258q-82 0-138-40.5T64 865q0-353 124-353 6 0 43.5 21t97.5 42.5T448 597q67 0 133-23-5 37-5 66 0 139 81 256zm1071 637q0 120-73 189.5t-194 69.5H587q-121 0-194-69.5T320 1533q0-53 3.5-103.5t14-109T364 1212t43-97.5 62-81 85.5-53.5T666 960q10 0 43 21.5t73 48 107 48 135 21.5 135-21.5 107-48 73-48 43-21.5q61 0 111.5 20t85.5 53.5 62 81 43 97.5 26.5 108.5 14 109 3.5 103.5zM704 256q0 106-75 181t-181 75-181-75-75-181 75-181T448 0t181 75 75 181zm704 384q0 159-112.5 271.5T1024 1024 752.5 911.5 640 640t112.5-271.5T1024 256t271.5 112.5T1408 640zm576 225q0 78-56 118.5t-138 40.5h-134q-103-123-265-128 81-117 81-256 0-29-5-66 66 23 133 23 59 0 119-21.5t97.5-42.5 43.5-21q124 0 124 353zm-128-609q0 106-75 181t-181 75-181-75-75-181 75-181 181-75 181 75 75 181z"></path></symbol>
<symbol id="fa-globe" viewBox="0 0 1792 1792"><path d="M896 128q209 0 385.5 103T1561 510.5 1664 896t-103 385.5-279.5 279.5T896 1664t-385.5-103T231 1281.5 128 896t103-385.5T510.5 231 896 128zm274 521q-2 1-9.5 9.5T1147 668q2 0 4.5-5t5-11 3.5-7q6-7 22-15 14-6 52-12 34-8 51 11-2-2 9.5-13t14.5-12q3-2 15-4.5t15-7.5l2-22q-12 1-17.5-7t-6.5-21q0 2-6 8 0-7-4.5-8t-11.5 1-9 1q-10-3-15-7.5t-8-16.5-4-15q-2-5-9.5-11t-9.5-10q-1-2-2.5-5.5t-3-6.5-4-5.5-5.5-2.5-7 5-7.5 10-4.5 5q-3-2-6-1.5t-4.5 1-4.5 3-5 3.5q-3 2-8.5 3t-8.5 2q15-5-1-11-10-4-16-3 9-4 7.5-12t-8.5-14h5q-1-4-8.5-8.5T1130 438t-13-6q-8-5-34-9.5t-33-.5q-5 6-4.5 10.5t4 14 3.5 12.5q1 6-5.5 13t-6.5 12q0 7 14 15.5t10 21.5q-3 8-16 16t-16 12q-5 8-1.5 18.5T1042 584q2 2 1.5 4t-3.5 4.5-5.5 4-6.5 3.5l-3 2q-11 5-20.5-6T991 570q-7-25-16-30-23-8-29 1-5-13-41-26-25-9-58-4 6-1 0-15-7-15-19-12 3-6 4-17.5t1-13.5q3-13 12-23 1-1 7-8.5t9.5-13.5.5-6q35 4 50-11 5-5 11.5-17t10.5-17q9-6 14-5.5t14.5 5.5 14.5 5q14 1 15.5-11t-7.5-20q12 1 3-17-4-7-8-9-12-4-27 5-8 4 2 8-1-1-9.5 10.5T929 346t-16-5q-1-1-5.5-13.5T898 314q-8 0-16 15 3-8-11-15t-24-8q19-12-8-27-7-4-20.5-5t-19.5 4q-5 7-5.5 11.5t5 8T809 303t11.5 4 8.5 3q14 10 8 14-2 1-8.5 3.5T817 332t-6 4q-3 4 0 14t-2 14q-5-5-9-17.5t-7-16.5q7 9-25 6l-10-1q-4 0-16 2t-20.5 1-13.5-8q-4-8 0-20 1-4 4-2-4-3-11-9.5t-10-8.5q-46 15-94 41 6 1 12-1 5-2 13-6.5t10-5.5q34-14 42-7l5-5q14 16 20 25-7-4-30-1-20 6-22 12 7 12 5 18-4-3-11.5-10T626 339t-15-5q-16 0-22 1-146 80-235 222 7 7 12 8 4 1 5 9t2.5 11 11.5-3q9 8 3 19 1-1 44 27 19 17 21 21 3 11-10 18-1-2-9-9t-9-4q-3 5 .5 18.5T436 685q-7 0-9.5 16t-2.5 35.5-1 23.5l2 1q-3 12 5.5 34.5T452 815q-13 3 20 43 6 8 8 9 3 2 12 7.5t15 10 10 10.5q4 5 10 22.5t14 23.5q-2 6 9.5 20t10.5 23q-1 0-2.5 1t-2.5 1q3 7 15.5 14t15.5 13q1 3 2 10t3 11 8 2q2-20-24-62-15-25-17-29-3-5-5.5-15.5T549 915q2 0 6 1.5t8.5 3.5 7.5 4 2 3q-3 7 2 17.5t12 18.5 17 19 12 13q6 6 14 19.5t0 13.5q9 0 20 10.5t17 19.5q5 8 8 26t5 24q2 7 8.5 13.5t12.5 9.5l16 8 13 7q5 2 18.5 10.5T770 1168q10 4 16 4t14.5-2.5 13.5-3.5q15-2 29 15t21 21q36 19 55 11-2 1 .5 7.5t8 15.5 9 14.5 5.5 8.5q5 6 18 15t18 15q6-4 7-9-3 8 7 20t18 10q14-3 14-32-31 15-49-18 0-1-2.5-5.5t-4-8.5-2.5-8.5 0-7.5 5-3q9 0 10-3.5t-2-12.5-4-13q-1-8-11-20t-12-15q-5 9-16 8t-16-9q0 1-1.5 5.5t-1.5 6.5q-13 0-15-1 1-3 2.5-17.5t3.5-22.5q1-4 5.5-12t7.5-14.5 4-12.5-4.5-9.5-17.5-2.5q-19 1-26 20-1 3-3 10.5t-5 11.5-9 7q-7 3-24 2t-24-5q-13-8-22.5-29t-9.5-37q0-10 2.5-26.5t3-25T780 986q3-2 9-9.5t10-10.5q2-1 4.5-1.5t4.5 0 4-1.5 3-6q-1-1-4-3-3-3-4-3 7 3 28.5-1.5T863 951q15 11 22-2 0-1-2.5-9.5T882 926q5 27 29 9 3 3 15.5 5t17.5 5q3 2 7 5.5t5.5 4.5 5-.5 8.5-6.5q10 14 12 24 11 40 19 44 7 3 11 2t4.5-9.5 0-14-1.5-12.5l-1-8v-18l-1-8q-15-3-18.5-12t1.5-18.5 15-18.5q1-1 8-3.5t15.5-6.5 12.5-8q21-19 15-35 7 0 11-9-1 0-5-3t-7.5-5-4.5-2q9-5 2-16 5-3 7.5-11t7.5-10q9 12 21 2 8-8 1-16 5-7 20.5-10.5t18.5-9.5q7 2 8-2t1-12 3-12q4-5 15-9t13-5l17-11q3-4 0-4 18 2 31-11 10-11-6-20 3-6-3-9.5t-15-5.5q3-1 11.5-.5t10.5-1.5q15-10-7-16-17-5-43 12zm-163 877q206-36 351-189-3-3-12.5-4.5t-12.5-3.5q-18-7-24-8 1-7-2.5-13t-8-9-12.5-8-11-7q-2-2-7-6t-7-5.5-7.5-4.5-8.5-2-10 1l-3 1q-3 1-5.5 2.5t-5.5 3-4 3 0 2.5q-21-17-36-22-5-1-11-5.5t-10.5-7-10-1.5-11.5 7q-5 5-6 15t-2 13q-7-5 0-17.5t2-18.5q-3-6-10.5-4.5t-12 4.5-11.5 8.5-9 6.5-8.5 5.5-8.5 7.5q-3 4-6 12t-5 11q-2-4-11.5-6.5t-9.5-5.5q2 10 4 35t5 38q7 31-12 48-27 25-29 40-4 22 12 26 0 7-8 20.5t-7 21.5q0 6 2 16z"></path></symbol>
<symbol id="fa-gear" viewBox="0 0 1792 1792"><path d="M1152 896q0-106-75-181t-181-75-181 75-75 181 75 181 181 75 181-75 75-181zm512-109v222q0 12-8 23t-20 13l-185 28q-19 54-39 91 35 50 107 138 10 12 10 25t-9 23q-27 37-99 108t-94 71q-12 0-26-9l-138-108q-44 23-91 38-16 136-29 186-7 28-36 28H785q-14 0-24.5-8.5T749 1634l-28-184q-49-16-90-37l-141 107q-10 9-25 9-14 0-25-11-126-114-165-168-7-10-7-23 0-12 8-23 15-21 51-66.5t54-70.5q-27-50-41-99l-183-27q-13-2-21-12.5t-8-23.5V783q0-12 8-23t19-13l186-28q14-46 39-92-40-57-107-138-10-12-10-24 0-10 9-23 26-36 98.5-107.5T465 263q13 0 26 10l138 107q44-23 91-38 16-136 29-186 7-28 36-28h222q14 0 24.5 8.5T1043 158l28 184q49 16 90 37l142-107q9-9 24-9 13 0 25 10 129 119 165 170 7 8 7 22 0 12-8 23-15 21-51 66.5t-54 70.5q26 50 41 98l183 28q13 2 21 12.5t8 23.5z"></path></symbol>
<symbol id="fa-reply" viewBox="0 0 1792 1792"><path d="M1792 1120q0 166-127 451-3 7-10.5 24t-13.5 30-13 22q-12 17-28 17-15 0-23.5-10t-8.5-25q0-9 2.5-26.5t2.5-23.5q5-68 5-123 0-101-17.5-181t-48.5-138.5-80-101-105.5-69.5-133-42.5-154-21.5-175.5-6H640v256q0 26-19 45t-45 19-45-19L19 685Q0 666 0 640t19-45L531 83q19-19 45-19t45 19 19 45v256h224q713 0 875 403 53 134 53 333z"></path></symbol>
<symbol id="fa-reply-all" viewBox="0 0 1792 1792"><path d="M640 1082v70q0 42-39 59-13 5-25 5-27 0-45-19L19 685Q0 666 0 640t19-45L531 83q29-31 70-14 39 17 39 59v69L243 595q-19 19-19 45t19 45zm1152 38q0 58-17 133.5t-38.5 138-48 125-40.5 90.5l-20 40q-8 17-28 17-6 0-9-1-25-8-23-34 43-400-106-565-64-71-170.5-110.5T1024 901v251q0 42-39 59-13 5-25 5-27 0-45-19L403 685q-19-19-19-45t19-45L915 83q29-31 70-14 39 17 39 59v262q411 28 599 221 169 173 169 509z"></path></symbol>
<symbol id="fa-retweet" viewBox="0 0 2048 1792"><path d="M1344 1504q0 13-9.5 22.5t-22.5 9.5H352q-8 0-13.5-2t-9-7-5.5-8-3-11.5-1-11.5V896H128q-26 0-45-19t-19-45q0-24 15-41l320-384q19-22 49-22t49 22l320 384q15 17 15 41 0 26-19 45t-45 19H576v384h576q16 0 25 11l160 192q7 10 7 21zm640-416q0 24-15 41l-320 384q-20 23-49 23t-49-23l-320-384q-15-17-15-41 0-26 19-45t45-19h192V640H896q-16 0-25-12L711 436q-7-9-7-20 0-13 9.5-22.5T736 384h960q8 0 13.5 2t9 7 5.5 8 3 11.5 1 11.5v600h192q26 0 45 19t19 45z"></path></symbol>
<symbol id="fa-star" viewBox="0 0 1792 1792"><path d="M1728 647q0 22-26 48l-363 354 86 500q1 7 1 20 0 21-10.5 35.5T1385 1619q-19 0-40-12l-449-236-449 236q-22 12-40 12-21 0-31.5-14.5T365 1569q0-6 2-20l86-500L89 695q-25-27-25-48 0-37 56-46l502-73L847 73q19-41 49-41t49 41l225 455 502 73q56 9 56 46z"></path></symbol>
<symbol id="fa-ellipsis-h" viewBox="0 0 1792 1792"><path d="M576 736v192q0 40-28 68t-68 28H288q-40 0-68-28t-28-68V736q0-40 28-68t68-28h192q40 0 68 28t28 68zm512 0v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68V736q0-40 28-68t68-28h192q40 0 68 28t28 68zm512 0v192q0 40-28 68t-68 28h-192q-40 0-68-28t-28-68V736q0-40 28-68t68-28h192q40 0 68 28t28 68z"></path></symbol>
<symbol id="fa-spinner" viewBox="0 0 1792 1792"><path d="M526 1394q0 53-37.5 90.5T398 1522q-52 0-90-38t-38-90q0-53 37.5-90.5T398 1266t90.5 37.5T526 1394zm498 206q0 53-37.5 90.5T896 1728t-90.5-37.5T768 1600t37.5-90.5T896 1472t90.5 37.5 37.5 90.5zM320 896q0 53-37.5 90.5T192 1024t-90.5-37.5T64 896t37.5-90.5T192 768t90.5 37.5T320 896zm1202 498q0 52-38 90t-90 38q-53 0-90.5-37.5T1266 1394t37.5-90.5 90.5-37.5 90.5 37.5 37.5 90.5zM558 398q0 66-47 113t-113 47-113-47-47-113 47-113 113-47 113 47 47 113zm1170 498q0 53-37.5 90.5T1600 1024t-90.5-37.5T1472 896t37.5-90.5T1600 768t90.5 37.5T1728 896zm-640-704q0 80-56 136t-136 56-136-56-56-136 56-136T896 0t136 56 56 136zm530 206q0 93-66 158.5T1394 622q-93 0-158.5-65.5T1170 398q0-92 65.5-158t158.5-66q92 0 158 66t66 158z"></path></symbol>
<symbol id="fa-user" viewBox="0 0 1792 1792"><path d="M1536 1399q0 109-62.5 187t-150.5 78H469q-88 0-150.5-78T256 1399q0-85 8.5-160.5t31.5-152 58.5-131 94-89T583 832q131 128 313 128t313-128q76 0 134.5 34.5t94 89 58.5 131 31.5 152 8.5 160.5zm-256-887q0 159-112.5 271.5T896 896 624.5 783.5 512 512t112.5-271.5T896 128t271.5 112.5T1280 512z"></path></symbol>
<symbol id="fa-play-circle" viewBox="0 0 1792 1792"><path d="M896 128q209 0 385.5 103T1561 510.5 1664 896t-103 385.5-279.5 279.5T896 1664t-385.5-103T231 1281.5 128 896t103-385.5T510.5 231 896 128zm384 823q32-18 32-55t-32-55L736 521q-31-19-64-1-32 19-32 56v640q0 37 32 56 16 8 32 8 17 0 32-9z"></path></symbol>
<symbol id="fa-eye" viewBox="0 0 1792 1792"><path d="M1664 960q-152-236-381-353 61 104 61 225 0 185-131.5 316.5T896 1280t-316.5-131.5T448 832q0-121 61-225-229 117-381 353 133 205 333.5 326.5T896 1408t434.5-121.5T1664 960zM944 576q0-20-14-34t-34-14q-125 0-214.5 89.5T592 832q0 20 14 34t34 14 34-14 14-34q0-86 61-147t147-61q20 0 34-14t14-34zm848 384q0 34-20 69-140 230-376.5 368.5T896 1536t-499.5-139T20 1029Q0 994 0 960t20-69q140-229 376.5-368T896 384t499.5 139T1772 891q20 35 20 69z"></path></symbol>
<symbol id="fa-eye-slash" viewBox="0 0 1792 1792"><path d="M555 1335l78-141q-87-63-136-159t-49-203q0-121 61-225-229 117-381 353 167 258 427 375zm389-759q0-20-14-34t-34-14q-125 0-214.5 89.5T592 832q0 20 14 34t34 14 34-14 14-34q0-86 61-147t147-61q20 0 34-14t14-34zm363-191q0 7-1 9-106 189-316 567t-315 566l-49 89q-10 16-28 16-12 0-134-70-16-10-16-28 0-12 44-87-143-65-263.5-173T20 1029Q0 998 0 960t20-69q153-235 380-371t496-136q89 0 180 17l54-97q10-16 28-16 5 0 18 6t31 15.5 33 18.5 31.5 18.5T1291 358q16 10 16 27zm37 447q0 139-79 253.5T1056 1250l280-502q8 45 8 84zm448 128q0 35-20 69-39 64-109 145-150 172-347.5 267T896 1536l74-132q212-18 392.5-137T1664 960q-115-179-282-294l63-112q95 64 182.5 153T1772 891q20 34 20 69z"></path></symbol>
<symbol id="fa-lock" viewBox="0 0 1792 1792"><path d="M640 768h512V576q0-106-75-181t-181-75-181 75-75 181v192zm832 96v576q0 40-28 68t-68 28H416q-40 0-68-28t-28-68V864q0-40 28-68t68-28h32V576q0-184 132-316t316-132 316 132 132 316v192h32q40 0 68 28t28 68z"></path></symbol>
<symbol id="fa-unlock" viewBox="0 0 1792 1792"><path d="M1728 576v256q0 26-19 45t-45 19h-64q-26 0-45-19t-19-45V576q0-106-75-181t-181-75-181 75-75 181v192h96q40 0 68 28t28 68v576q0 40-28 68t-68 28H160q-40 0-68-28t-28-68V864q0-40 28-68t68-28h672V576q0-185 131.5-316.5T1280 128t316.5 131.5T1728 576z"></path></symbol>
<symbol id="fa-envelope" viewBox="0 0 1792 1792"><path d="M1792 710v794q0 66-47 113t-113 47H160q-66 0-113-47T0 1504V710q44 49 101 87 362 246 497 345 57 42 92.5 65.5t94.5 48 110 24.5h2q51 0 110-24.5t94.5-48 92.5-65.5q170-123 498-345 57-39 100-87zm0-294q0 79-49 151t-122 123q-376 261-468 325-10 7-42.5 30.5t-54 38-52 32.5-57.5 27-50 9h-2q-23 0-50-9t-57.5-27-52-32.5-54-38T639 1015q-91-64-262-182.5T172 690q-62-42-117-115.5T0 438q0-78 41.5-130T160 256h1472q65 0 112.5 47t47.5 113z"></path></symbol>
<symbol id="fa-user-times" viewBox="0 0 2048 1792"><path d="M704 896q-159 0-271.5-112.5T320 512t112.5-271.5T704 128t271.5 112.5T1088 512 975.5 783.5 704 896zm1077 320l249 249q9 9 9 23 0 13-9 22l-136 136q-9 9-22 9-14 0-23-9l-249-249-249 249q-9 9-23 9-13 0-22-9l-136-136q-9-9-9-22 0-14 9-23l249-249-249-249q-9-9-9-23 0-13 9-22l136-136q9-9 22-9 14 0 23 9l249 249 249-249q9-9 23-9 13 0 22 9l136 136q9 9 9 22 0 14-9 23zm-498 0l-181 181q-37 37-37 91 0 53 37 90l83 83q-21 3-44 3H267q-121 0-194-69T0 1405q0-53 3.5-103.5t14-109T44 1084t43-97.5 62-81 85.5-53.5T346 832q19 0 39 17 154 122 319 122t319-122q20-17 39-17 28 0 57 6-28 27-41 50t-13 56q0 54 37 91z"></path></symbol>
<symbol id="fa-user-plus" viewBox="0 0 2048 1792"><path d="M704 896q-159 0-271.5-112.5T320 512t112.5-271.5T704 128t271.5 112.5T1088 512 975.5 783.5 704 896zm960 128h352q13 0 22.5 9.5t9.5 22.5v192q0 13-9.5 22.5t-22.5 9.5h-352v352q0 13-9.5 22.5t-22.5 9.5h-192q-13 0-22.5-9.5t-9.5-22.5v-352h-352q-13 0-22.5-9.5t-9.5-22.5v-192q0-13 9.5-22.5t22.5-9.5h352V672q0-13 9.5-22.5t22.5-9.5h192q13 0 22.5 9.5t9.5 22.5v352zm-736 224q0 52 38 90t90 38h256v238q-68 50-171 50H267q-121 0-194-69T0 1405q0-53 3.5-103.5t14-109T44 1084t43-97.5 62-81 85.5-53.5T346 832q19 0 39 17 79 61 154.5 91.5T704 971t164.5-30.5T1023 849q20-17 39-17 132 0 217 96h-223q-52 0-90 38t-38 90v192z"></path></symbol>
<symbol id="fa-external-link" viewBox="0 0 1792 1792"><path d="M1408 928v320q0 119-84.5 203.5T1120 1536H288q-119 0-203.5-84.5T0 1248V416q0-119 84.5-203.5T288 128h704q14 0 23 9t9 23v64q0 14-9 23t-23 9H288q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113V928q0-14 9-23t23-9h64q14 0 23 9t9 23zm384-864v512q0 26-19 45t-45 19-45-19l-176-176-652 652q-10 10-23 10t-23-10L695 983q-10-10-10-23t10-23l652-652-176-176q-19-19-19-45t19-45 45-19h512q26 0 45 19t19 45z"></path></symbol>
<symbol id="fa-search" viewBox="0 0 1792 1792"><path d="M1216 832q0-185-131.5-316.5T768 384 451.5 515.5 320 832t131.5 316.5T768 1280t316.5-131.5T1216 832zm512 832q0 52-38 90t-90 38q-54 0-90-38l-343-342q-179 124-399 124-143 0-273.5-55.5t-225-150-150-225T64 832t55.5-273.5 150-225 225-150T768 128t273.5 55.5 225 150 150 225T1472 832q0 220-124 399l343 343q37 37 37 90z"></path></symbol>
<symbol id="fa-comments" viewBox="0 0 1792 1792"><path d="M1408 768q0 139-94 257t-256.5 186.5T704 1280q-86 0-176-16-124 88-278 128-36 9-86 16h-3q-11 0-20.5-8t-11.5-21q-1-3-1-6.5t.5-6.5 2-6l2.5-5 3.5-5.5 4-5 4.5-5 4-4.5q5-6 23-25t26-29.5 22.5-29 25-38.5 20.5-44q-124-72-195-177T0 768q0-139 94-257t256.5-186.5T704 256t353.5 68.5T1314 511t94 257zm384 256q0 120-71 224.5T1526 1425q10 24 20.5 44t25 38.5 22.5 29 26 29.5 23 25q1 1 4 4.5t4.5 5 4 5 3.5 5.5l2.5 5 2 6 .5 6.5-1 6.5q-3 14-13 22t-22 7q-50-7-86-16-154-40-278-128-90 16-176 16-271 0-472-132 58 4 88 4 161 0 309-45t264-129q125-92 192-212t67-254q0-77-23-152 129 71 204 178t75 230z"></path></symbol>
<symbol id="fa-paperclip" viewBox="0 0 1792 1792"><path d="M1596 1385q0 117-79 196t-196 79q-135 0-235-100L309 784Q196 669 196 513q0-159 110-270t269-111q158 0 273 113l605 606q10 10 10 22 0 16-30.5 46.5T1386 950q-13 0-23-10L757 333q-79-77-181-77-106 0-179 75t-73 181q0 105 76 181l776 777q63 63 145 63 64 0 106-42t42-106q0-82-63-145L825 659q-26-24-60-24-29 0-48 19t-19 48q0 32 25 59l410 410q10 10 10 22 0 16-31 47t-47 31q-12 0-22-10L633 851q-63-61-63-149 0-82 57-139t139-57q88 0 149 63l581 581q100 98 100 235z"></path></symbol>
<symbol id="fa-thumb-tack" viewBox="0 0 1792 1792"><path d="M800 864V416q0-14-9-23t-23-9-23 9-9 23v448q0 14 9 23t23 9 23-9 9-23zm672 352q0 26-19 45t-45 19H979l-51 483q-2 12-10.5 20.5T897 1792h-1q-27 0-32-27l-76-485H384q-26 0-45-19t-19-45q0-123 78.5-221.5T576 896V384q-52 0-90-38t-38-90 38-90 90-38h640q52 0 90 38t38 90-38 90-90 38v512q99 0 177.5 98.5T1472 1216z"></path></symbol>
<symbol id="fa-bars" viewBox="0 0 1792 1792"><path d="M1664 1344v128q0 26-19 45t-45 19H192q-26 0-45-19t-19-45v-128q0-26 19-45t45-19h1408q26 0 45 19t19 45zm0-512v128q0 26-19 45t-45 19H192q-26 0-45-19t-19-45V832q0-26 19-45t45-19h1408q26 0 45 19t19 45zm0-512v128q0 26-19 45t-45 19H192q-26 0-45-19t-19-45V320q0-26 19-45t45-19h1408q26 0 45 19t19 45z"></path></symbol>
<symbol id="fa-ban" viewBox="0 0 1792 1792"><path d="M1440 893q0-161-87-295l-754 753q137 89 297 89 111 0 211.5-43.5T1281 1280t116-174.5 43-212.5zm-999 299l755-754q-135-91-300-91-148 0-273 73T425 619t-73 274q0 162 89 299zm1223-299q0 157-61 300t-163.5 246-245 164-298.5 61-298.5-61-245-164T189 1193t-61-300 61-299.5T352.5 348t245-164T896 123t298.5 61 245 164T1603 593.5t61 299.5z"></path></symbol>
<symbol id="fa-camera" viewBox="0 0 2048 1792"><path d="M1024 672q119 0 203.5 84.5T1312 960t-84.5 203.5T1024 1248t-203.5-84.5T736 960t84.5-203.5T1024 672zm704-416q106 0 181 75t75 181v896q0 106-75 181t-181 75H320q-106 0-181-75t-75-181V512q0-106 75-181t181-75h224l51-136q19-49 69.5-84.5T768 0h512q53 0 103.5 35.5T1453 120l51 136h224zm-704 1152q185 0 316.5-131.5T1472 960t-131.5-316.5T1024 512 707.5 643.5 576 960t131.5 316.5T1024 1408z"></path></symbol>
<symbol id="fa-smile" viewBox="0 0 1792 1792"><path d="M1262 1075q-37 121-138 195t-228 74-228-74-138-195q-8-25 4-48.5t38-31.5q25-8 48.5 4t31.5 38q25 80 92.5 129.5T896 1216t151.5-49.5T1140 1037q8-26 32-38t49-4 37 31.5 4 48.5zM768 640q0 53-37.5 90.5T640 768t-90.5-37.5T512 640t37.5-90.5T640 512t90.5 37.5T768 640zm512 0q0 53-37.5 90.5T1152 768t-90.5-37.5T1024 640t37.5-90.5T1152 512t90.5 37.5T1280 640zm256 256q0-130-51-248.5t-136.5-204-204-136.5T896 256t-248.5 51-204 136.5-136.5 204T256 896t51 248.5 136.5 204 204 136.5 248.5 51 248.5-51 204-136.5 136.5-204 51-248.5zm128 0q0 209-103 385.5T1281.5 1561 896 1664t-385.5-103T231 1281.5 128 896t103-385.5T510.5 231 896 128t385.5 103T1561 510.5 1664 896z"></path></symbol>
<symbol id="fa-exclamation-triangle" viewBox="0 0 1792 1792"><path d="M1024 1375v-190q0-14-9.5-23.5T992 1152H800q-13 0-22.5 9.5T768 1185v190q0 14 9.5 23.5t22.5 9.5h192q13 0 22.5-9.5t9.5-23.5zm-2-374l18-459q0-12-10-19-13-11-24-11H786q-11 0-24 11-10 7-10 21l17 457q0 10 10 16.5t24 6.5h185q14 0 23.5-6.5t10.5-16.5zm-14-934l768 1408q35 63-2 126-17 29-46.5 46t-63.5 17H128q-34 0-63.5-17T18 1601q-37-63-2-126L784 67q17-31 47-49t65-18 65 18 47 49z"></path></symbol>
<symbol id="fa-check" viewBox="0 0 1792 1792"><path d="M1671 566q0 40-28 68l-724 724-136 136q-28 28-68 28t-68-28l-136-136-362-362q-28-28-28-68t28-68l136-136q28-28 68-28t68 28l294 295 656-657q28-28 68-28t68 28l136 136q28 28 28 68z"></path></symbol>
<symbol id="fa-trash" viewBox="0 0 1792 1792"><path d="M704 736v576q0 14-9 23t-23 9h-64q-14 0-23-9t-9-23V736q0-14 9-23t23-9h64q14 0 23 9t9 23zm256 0v576q0 14-9 23t-23 9h-64q-14 0-23-9t-9-23V736q0-14 9-23t23-9h64q14 0 23 9t9 23zm256 0v576q0 14-9 23t-23 9h-64q-14 0-23-9t-9-23V736q0-14 9-23t23-9h64q14 0 23 9t9 23zm128 724V512H448v948q0 22 7 40.5t14.5 27 10.5 8.5h832q3 0 10.5-8.5t14.5-27 7-40.5zM672 384h448l-48-117q-7-9-17-11H738q-10 2-17 11zm928 32v64q0 14-9 23t-23 9h-96v948q0 83-47 143.5t-113 60.5H480q-66 0-113-58.5T320 1464V512h-96q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h309l70-167q15-37 54-63t79-26h320q40 0 79 26t54 63l70 167h309q14 0 23 9t9 23z"></path></symbol>
<symbol id="fa-hourglass" viewBox="0 0 1792 1792"><path d="M1632 1600q14 0 23 9t9 23v128q0 14-9 23t-23 9H160q-14 0-23-9t-9-23v-128q0-14 9-23t23-9h1472zm-1374-64q3-55 16-107t30-95 46-87 53.5-76 64.5-69.5 66-60 70.5-55T671 939t65-43q-43-28-65-43t-66.5-47.5-70.5-55-66-60-64.5-69.5-53.5-76-46-87-30-95-16-107h1276q-3 55-16 107t-30 95-46 87-53.5 76-64.5 69.5-66 60-70.5 55T1121 853t-65 43q43 28 65 43t66.5 47.5 70.5 55 66 60 64.5 69.5 53.5 76 46 87 30 95 16 107H258zM1632 0q14 0 23 9t9 23v128q0 14-9 23t-23 9H160q-14 0-23-9t-9-23V32q0-14 9-23t23-9h1472z"></path></symbol>
<symbol id="fa-pencil" viewBox="0 0 1792 1792"><path d="M491 1536l91-91-235-235-91 91v107h128v128h107zm523-928q0-22-22-22-10 0-17 7l-542 542q-7 7-7 17 0 22 22 22 10 0 17-7l542-542q7-7 7-17zm-54-192l416 416-832 832H128v-416zm683 96q0 53-37 90l-166 166-416-416 166-165q36-38 90-38 53 0 91 38l235 234q37 39 37 91z"></path></symbol>
<symbol id="fa-times" viewBox="0 0 1792 1792"><path d="M1490 1322q0 40-28 68l-136 136q-28 28-68 28t-68-28l-294-294-294 294q-28 28-68 28t-68-28l-136-136q-28-28-28-68t28-68l294-294-294-294q-28-28-28-68t28-68l136-136q28-28 68-28t68 28l294 294 294-294q28-28 68-28t68 28l136 136q28 28 28 68t-28 68l-294 294 294 294q28 28 28 68z"></path></symbol>
<symbol id="fa-volume-off" viewBox="0 0 1792 1792"><path d="M1280 352v1088q0 26-19 45t-45 19-45-19l-333-333H576q-26 0-45-19t-19-45V704q0-26 19-45t45-19h262l333-333q19-19 45-19t45 19 19 45z"></path></symbol>
<symbol id="fa-volume-up" viewBox="0 0 1792 1792"><path d="M832 352v1088q0 26-19 45t-45 19-45-19l-333-333H128q-26 0-45-19t-19-45V704q0-26 19-45t45-19h262l333-333q19-19 45-19t45 19 19 45zm384 544q0 76-42.5 141.5T1061 1131q-10 5-25 5-26 0-45-18.5t-19-45.5q0-21 12-35.5t29-25 34-23 29-36 12-56.5-12-56.5-29-36-34-23-29-25-12-35.5q0-27 19-45.5t45-18.5q15 0 25 5 70 27 112.5 93t42.5 142zm256 0q0 153-85 282.5T1162 1367q-13 5-25 5-27 0-46-19t-19-45q0-39 39-59 56-29 76-44 74-54 115.5-135.5T1344 896t-41.5-173.5T1187 587q-20-15-76-44-39-20-39-59 0-26 19-45t45-19q13 0 26 5 140 59 225 188.5t85 282.5zm256 0q0 230-127 422.5T1263 1602q-13 5-26 5-26 0-45-19t-19-45q0-36 39-59 7-4 22.5-10.5t22.5-10.5q46-25 82-51 123-91 192-227t69-289-69-289-192-227q-36-26-82-51-7-4-22.5-10.5T1212 308q-39-23-39-59 0-26 19-45t45-19q13 0 26 5 211 91 338 283.5T1728 896z"></path></symbol>
<symbol id="fa-link" viewBox="0 0 1792 1792"><path d="M1520 1216q0-40-28-68l-208-208q-28-28-68-28-42 0-72 32 3 3 19 18.5t21.5 21.5 15 19 13 25.5 3.5 27.5q0 40-28 68t-68 28q-15 0-27.5-3.5t-25.5-13-19-15-21.5-21.5-18.5-19q-33 31-33 73 0 40 28 68l206 207q27 27 68 27 40 0 68-26l147-146q28-28 28-67zM817 511q0-40-28-68L583 236q-28-28-68-28-39 0-68 27L300 381q-28 28-28 67 0 40 28 68l208 208q27 27 68 27 42 0 72-31-3-3-19-18.5T607.5 680t-15-19-13-25.5T576 608q0-40 28-68t68-28q15 0 27.5 3.5t25.5 13 19 15 21.5 21.5 18.5 19q33-31 33-73zm895 705q0 120-85 203l-147 146q-83 83-203 83-121 0-204-85l-206-207q-83-83-83-203 0-123 88-209l-88-88q-86 88-208 88-120 0-204-84L164 652q-84-84-84-204t85-203L312 99q83-83 203-83 121 0 204 85l206 207q83 83 83 203 0 123-88 209l88 88q86-88 208-88 120 0 204 84l208 208q84 84 84 204z"></path></symbol>
</svg><!-- end insert svg here -->
<!-- Sapper creates a <script> tag containing `templates/client.js`
and anything else it needs to hydrate the src and
initialise the router -->
%sapper.scripts%
</body>
</html>