base: Added /health route.

nfp_moe: Complete re-thinking of all loading. Smarter loading and lighter site. Better user experience among other things.
master
Jonatan Nilsson 2022-08-21 21:54:24 +00:00
parent 4b36820f09
commit 6565409e52
17 changed files with 1585 additions and 1008 deletions

View File

@ -23,8 +23,12 @@ export default class ArticleRoutes {
}
/** GET: /api/articles/[path] */
async getArticle(ctx) {
async getArticle(ctx, onlyReturn = false) {
let res = await ctx.db.safeCallProc('article_get_single', [ctx.params.path])
if (onlyReturn) {
return this.getArticle_resOutput(res)
}
ctx.body = this.getArticle_resOutput(res)
}

View File

@ -22,7 +22,7 @@ export default class ServeHandler {
}
let indexFile = fsSync.readFileSync(path.join(this.root, 'index.html'))
this.template = dot.template(indexFile.toString(), { argName: ['headerDescription', 'headerImage', 'headerTitle', 'headerUrl', 'payloadData', 'payloadTree', 'version', 'nonce'] })
this.template = dot.template(indexFile.toString(), { argName: ['headerDescription', 'headerImage', 'headerTitle', 'headerUrl', 'payloadData', 'payloadTree', 'version', 'nonce', 'type', 'banner'] })
// console.log(indexFile.toString())
}

View File

@ -7,6 +7,7 @@ import PageRoutes from './page/routes.mjs'
import ArticleRoutes from './article/routes.mjs'
import AuthenticationRoutes from './authentication/routes.mjs'
import { authenticate } from './authentication/security.mjs'
import StaticRoutes from './static_routes.mjs'
export default class Server {
constructor(http, port, core, opts = {}) {
@ -30,6 +31,7 @@ export default class Server {
page: new PageRoutes(),
article: new ArticleRoutes(),
auth: new AuthenticationRoutes(),
static: new StaticRoutes(),
}
this.init()
@ -54,6 +56,9 @@ export default class Server {
ctx.db = pool
})
this.flaska.before(QueryHandler())
let healthChecks = 0
let healthCollectLimit = 60 * 60 * 12
this.flaska.after(function(ctx) {
let ended = new Date().getTime()
@ -69,6 +74,18 @@ export default class Server {
level = 'error'
}
if (ctx.url === '/health') {
healthChecks++
if (healthChecks >= healthCollectLimit) {
ctx.log[level]({
duration: Math.round(ended),
status: ctx.status,
}, `<-- ${status}${ctx.method} ${ctx.url} {has happened ${healthChecks} times}`)
healthChecks = 0
}
return
}
ctx.log[level]({
duration: requestTime,
status: ctx.status,

17
base/static_routes.mjs Normal file
View File

@ -0,0 +1,17 @@
import config from './config.mjs'
export default class StaticRoutes {
constructor(opts = {}) {
Object.assign(this, { })
}
register(server) {
server.flaska.get('/health', this.health.bind(this))
}
health(ctx) {
ctx.body = {
environment: config.get('NODE_ENV'),
}
}
}

View File

@ -18,7 +18,7 @@ export function combineFilesWithArticles(articles, files) {
}
export function parseFile(file) {
file.url = 'https://cdn.nfp.is' + file.path
file.path = 'https://cdn.nfp.is' + file.path
file.magnet = null
file.meta = JSON.parse(file.meta || '{}') || {}
if (file.meta.torrent) {
@ -27,6 +27,7 @@ export function parseFile(file) {
+ '&dn=' + encodeURIComponent(file.meta.torrent.name)
+ '&xt=urn:btih:' + file.meta.torrent.hash
+ file.meta.torrent.announce.map(item => ('&tr=' + encodeURIComponent(item))).join('')
file.meta.torrent = { name: file.meta.torrent.name, files: file.meta.torrent.files }
}
return file
}

View File

@ -1,8 +1,22 @@
import path from 'path'
import Parent from '../base/serve.mjs'
import fs from 'fs/promises'
import dot from 'dot'
export default class ServeHandler extends Parent {
traverseTree(set, tree) {
for (let branch of tree) {
if (branch.children && branch.children.length) {
set.add(branch.id)
this.traverseTree(set, branch.children)
}
}
}
async serveIndex(ctx) {
let indexFile = await fs.readFile(path.join(this.root, 'index.html'))
this.template = dot.template(indexFile.toString(), { argName: ['headerDescription', 'headerImage', 'headerTitle', 'headerUrl', 'payloadData', 'payloadTree', 'version', 'nonce', 'type', 'banner', 'media'] })
let payload = {
headerDescription: 'Small fansubbing and scanlation group translating and encoding our favourite shows from Japan.',
headerImage: this.frontend + '/assets/img/heart.png',
@ -12,18 +26,39 @@ export default class ServeHandler extends Parent {
payloadTree: null,
version: this.version,
nonce: ctx.state.nonce,
type: 'page',
banner: false,
media: false,
}
try {
payload.payloadTree = JSON.stringify(await this.pageRoutes.getPageTree(ctx, true))
let tree = await this.pageRoutes.getPageTree(ctx, true)
let setOfBranches = new Set()
this.traverseTree(setOfBranches, tree.tree)
payload.payloadTree = JSON.stringify(tree)
if (ctx.url === '/' || (ctx.url.startsWith('/page/') && ctx.url.lastIndexOf('/') === 5)) {
ctx.params.path = null
if (ctx.url.lastIndexOf('/') === 5) {
ctx.params.path = ctx.url.slice(ctx.url.lastIndexOf('/') + 1)
}
payload.payloadData = JSON.stringify(await this.pageRoutes.getPage(ctx, true))
let data = await this.pageRoutes.getPage(ctx, true)
if (!data.page) {
payload.type = 'frontpage'
}
else if (setOfBranches.has(data.page.id)) {
payload.type = 'page_with_children'
}
payload.media = data.page?.media_avif_preview || false
payload.banner = data.featured?.banner_avif_preview || data.page?.banner_avif_preview || false
payload.payloadData = JSON.stringify(data)
} else if (ctx.url.startsWith('/article/') && ctx.url.lastIndexOf('/') === 8) {
ctx.params.path = ctx.url.slice(ctx.url.lastIndexOf('/') + 1)
let data = await this.articleRoutes.getArticle(ctx, true)
payload.media = data.article?.media_avif_preview || false
payload.payloadData = JSON.stringify(data)
payload.type = 'article'
}
console.log('url', ctx.url)
} catch (e) {
ctx.log.error(e)
}

View File

@ -60,7 +60,7 @@ const Fileinfo = {
m('a', {
target: '_blank',
rel: 'noopener',
href: vnode.attrs.file.url,
href: vnode.attrs.file.path,
}, this.getDownloadName(vnode)),
vnode.attrs.file.magnet
? m('a', {

View File

@ -47,4 +47,4 @@ AVIF.onload = AVIF.onerror = function () {
m.route(document.getElementById('main'), '/', allRoutes)
m.mount(document.getElementById('footer'), Footer)
}
AVIF.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=';
AVIF.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAABcAAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQAMAAAAABNjb2xybmNseAABAA0ABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAAB9tZGF0EgAKCBgAPkgIaDQgMgkf8AAAQAAAr3A=';

View File

@ -1,3 +1,5 @@
const m = require('mithril')
export function generatePictureSource(item, cover) {
if (!item || !item.media_alt_prefix) return null
@ -10,9 +12,56 @@ export function generatePictureSource(item, cover) {
+ item.media_alt_prefix + '_medium.avif' + ' 1300w, '
+ item.media_alt_prefix + '_large.avif 1920w',
cover: cover,
preview: item.media_avif_preview,
}
}
let loadingImage = null
let loader = null
function cancelLoader() {
if (loader) {
loader.src = ''
}
loader = null
}
export function smartBanner(item) {
if (!item) {
if (loader) {
cancelLoader()
}
loadingImage = null
return null
}
if (!item.preview) {
loadingImage = null
cancelLoader()
return item.banner
}
if (loadingImage !== item.banner && loader) {
cancelLoader()
}
if (loadingImage === item.banner && !loader) {
return item.banner
}
if (loadingImage === item.banner) {
return item.preview
}
loadingImage = item.banner
loader = new Image();
loader.src = item.banner;
loader.onload = loader.onerror = function() {
loader = null
m.redraw()
}
return item.preview
}
export function getBannerImage(item, prefix) {
if (!item || !item.banner_alt_prefix) return null
@ -20,7 +69,8 @@ export function getBannerImage(item, prefix) {
path: prefix + item.path,
name: item.name,
original: item.banner_path,
banner: item.banner_alt_prefix
banner: item.banner_alt_prefix,
preview: item.banner_avif_preview,
}
var deviceWidth = window.innerWidth
@ -47,23 +97,24 @@ export function getArticlePicture(pictureData, useRouteLink, path, altText, fall
if (!pictureData) return fallback || null
return m(useRouteLink ? m.route.Link : 'a', {
class: 'cover',
class: 'cover ' + (pictureData.preview ? 'haspreview' : ''),
rel: useRouteLink ? null : 'noopener',
target: useRouteLink ? null : '_blank',
href: path,
},
m('picture', [
m('source', {
srcset: pictureData.avif,
sizes: pictureData.cover,
type: 'image/avif',
}),
m('img', {
srcset: pictureData.jpeg,
sizes: pictureData.cover,
alt: altText,
src: pictureData.fallback,
}),
}, [
pictureData.preview ? m('img', { src: pictureData.preview }) : null,
m('picture', [
m('source', {
srcset: pictureData.avif,
sizes: pictureData.cover,
type: 'image/avif',
}),
m('img', {
srcset: pictureData.jpeg,
sizes: pictureData.cover,
alt: altText,
src: pictureData.fallback,
}),
])
])
)
}

View File

@ -21,7 +21,7 @@ const SiteArticle = {
if (window.__nfpdata) {
this.path = m.route.param('id')
this.data.article = window.__nfpdata
this.data = window.__nfpdata
window.__nfpdata = null
this.afterData()
} else {
@ -83,7 +83,30 @@ const SiteArticle = {
return [
this.loading
? m('div.loading-spinner')
? m('.inside.vertical', [
m('div.actions', m('div.lb-link')),
m('article.fullsize', [
m('h2.title', m('div.lb-main.lb--long')),
m('div.row', [
m('div.cover', m('picture.lb.nobg')),
m('div.description', [
m('div.lb-main.lb--long'),
m('div.lb-main'),
m('div.lb-main.lb--medium'),
m('div.lb-main.lb--medium'),
m('div.lb-main'),
m('p', m.trust('&nbsp;')),
m('fileinfo', [
m('div.lb-main.lb--slim.lb--longest'),
m('ul', [
m('li', m('div.lb-main.lb--slim.lb--longest')),
m('li', m('div.lb-main.lb--slim.lb--longest')),
])
]),
]),
]),
]),
])
: null,
!this.loading && this.error === 'Article not found'
? NotFoundView.view()
@ -96,7 +119,7 @@ const SiteArticle = {
},
}, 'Article error: ' + this.error + '. Click here to try again'))
: null,
(article
(!this.loading && article
? m('.inside.vertical', [
m('div.actions', [
'« ',
@ -134,6 +157,7 @@ const SiteArticle = {
}
}}, m('div.loading-spinner'))
: m('button.comments', {
style: 'display: none',
onclick: function() { window.LoadComments = true },
}, 'Open comment discussion'),
])

View File

@ -15,7 +15,7 @@ const SitePage = {
oninit: function(vnode) {
this.error = ''
this.loading = false
this.showLoading = null
this.treePage = null
this.data = {
page: null,
articles: [],
@ -26,7 +26,6 @@ const SitePage = {
this.children = []
this.currentPage = Number(m.route.param('page')) || 1
console.log('test', window.__nfpdata)
if (window.__nfpdata) {
this.path = m.route.param('id')
this.lastpage = this.currentPage
@ -58,24 +57,13 @@ const SitePage = {
this.lastpage = this.currentPage
this.path = m.route.param('id')
if (this.showLoading) {
clearTimeout(this.showLoading)
}
if (this.data.page) {
this.showLoading = setTimeout(() => {
this.showLoading = null
this.loading = true
m.redraw()
}, 300)
} else {
this.loading = true
}
this.loading = true
if (this.path) {
this.children = PageTree.TreeMap.get(this.path)
this.children = this.children && this.children.children || []
this.treePage = PageTree.TreeMap.get(this.path)
this.children = this.treePage && this.treePage.children || []
} else {
this.treePage = { has_banner: true, has_media: true }
this.children = PageTree.Tree
}
@ -90,8 +78,6 @@ const SitePage = {
this.error = err.message
})
.then(() => {
clearTimeout(this.showLoading)
this.showLoading = null
this.loading = false
m.redraw()
})
@ -111,8 +97,8 @@ const SitePage = {
}
this.picture = media.generatePictureSource(this.data.page,
'(max-width: 840px) calc(100vw - 82px), '
+ '758px')
'(max-width: 1280px) calc(100vw - 2rem), '
+ '1248px')
if (this.lastpage !== 1) {
document.title = 'Page ' + this.lastpage + ' - ' + title
@ -128,107 +114,211 @@ const SitePage = {
return ([
this.loading
? m('div.loading-spinner')
: null,
!this.loading && this.error === 'Page not found'
? NotFoundView.view()
: null,
!this.loading && this.error && this.error !== 'Page not found'
? m('div.wrapper', m('div.error', {
onclick: () => {
this.error = ''
this.fetchPage(vnode)
},
}, 'Page error: ' + this.error + '. Click here to try again'))
: null,
(featuredBanner
? m(m.route.Link, {
class: 'page-banner',
href: featuredBanner.path,
style: { 'background-image': 'url("' + featuredBanner.banner + '")' },
},
m('div.inside', m('div.page-banner-title', featuredBanner.name))
)
: null),
(!featuredBanner && pageBanner
? m('a.page-banner', {
href: pageBanner.original,
target: '_blank',
style: { 'background-image': 'url("' + pageBanner.banner + '")' },
},
)
: null),
(page
? m('.inside.vertical', [
m('div.actions', [
'« ',
m(m.route.Link, {
href: page.parent_path
? '/page/' + page.parent_path
: '/'
}, page.parent_name || 'Home'),
Authentication.currentUser
? [
m('div.filler'),
'Actions:',
m(m.route.Link, { href: '/admin/pages/' + page.id }, 'Edit page'),
]
: null,
]),
m('h2.title', page.name)
])
: null),
(page || this.data.articles.length
? m('.inside', [
this.children.length
? m('aside', { class: page ? '' : 'frontpage' }, [
m('h5', page ? 'View ' + page.name + ':' : 'Categories'),
this.children.map((page) => {
return [
m(m.route.Link, { class: 'root', href: '/page/' + page.path }, page.name),
(page.children && page.children.length
? m('ul', page.children.map(function(subpage) {
return m('li', m(m.route.Link, { class: 'child', href: '/page/' + subpage.path }, subpage.name))
}))
: null),
]
}),
!page
? m('div.asuna.spritesheet')
: null,
? [
this.treePage.has_banner
? m('div.page-banner.lb')
: null,
this.path
? m('div.inside.vertical', [
m('div.actions', m('div.lb-link')),
m('h2.title', m('div.lb-main.lb--long')),
])
: null,
m('div.container', [
(page
? media.getArticlePicture(this.picture, false, page.media_path, 'Image for page ' + page.name)
: null),
(page && page.content
? m('div.content', page.content.blocks.map(block => {
return m(EditorBlock, { block: block })
}))
: null),
(page && this.data.articles.length
? [
m('h5', 'Latest posts under ' + page.name + ':'),
this.data.articles.map(function(article) {
return m(Articleslim, { article: article })
}),
]
: null),
(!page && this.data.articles.length
? this.data.articles.map(function(article) {
return m(Article, { article: article })
})
: null),
m(Paginator, {
base: page ? '/page/' + page.path : '/',
page: this.currentPage,
perPage: ArticlesPerPage,
total: this.data.total_articles,
}),
m('div.inside', [
this.children.length
? m('aside', [
m('h5', m('div.lb-main')),
m('div.lb-link'),
m('ul', [
m('li', m('div.lb-link')),
m('li', m('div.lb-link')),
m('li', m('div.lb-link')),
]),
m('div.lb-link'),
m('ul', [
m('li', m('div.lb-link')),
m('li', m('div.lb-link')),
m('li', m('div.lb-link')),
]),
])
: null,
!this.path
? m('div.container', [
m('article', [
m('h2.title', m('div.lb-main.lb--long')),
m('div.row', [
m('div.lb-main.lb--imgmini'),
m('div', [
m('div', m.trust('&nbsp;')),
m('div.lb-main.lb--long'),
m('div.lb-main'),
m('div.lb-main.lb--medium'),
m('div.lb-main.lb--medium'),
m('div.lb-main'),
]),
]),
]),
m('article', [
m('h2.title', m('div.lb-main.lb--long')),
m('div.row', [
m('div.lb-main.lb--imgmini'),
m('div', [
m('div', m.trust('&nbsp;')),
m('div.lb-main.lb--long'),
m('div.lb-main'),
m('div.lb-main.lb--medium'),
m('div.lb-main.lb--medium'),
m('div.lb-main'),
]),
]),
]),
])
: null,
this.path
? m('div.container', [
this.treePage.has_media
? m('div.cover', m('picture.lb.nobg'))
: null,
m('h5', m('div.lb-main.lb--long')),
m('articleslim', [
m('a.cover.nobg.lb'),
m('div', [
m('div', m('div.lb-link.lb--long')),
m('div.lb-main.lb--longest.lb--slim'),
]),
]),
m('articleslim', [
m('a.cover.nobg.lb'),
m('div', [
m('div', m('div.lb-link.lb--long')),
m('div.lb-main.lb--longest.lb--slim'),
]),
]),
m('articleslim', [
m('a.cover.nobg.lb'),
m('div', [
m('div', m('div.lb-link.lb--long')),
m('div.lb-main.lb--longest.lb--slim'),
]),
]),
m('articleslim', [
m('a.cover.nobg.lb'),
m('div', [
m('div', m('div.lb-link.lb--long')),
m('div.lb-main.lb--longest.lb--slim'),
]),
]),
])
: null,
]),
])
: null),
]
: [
this.error === 'Page not found'
? NotFoundView.view()
: null,
this.error && this.error !== 'Page not found'
? m('div.wrapper', m('div.error', {
onclick: () => {
this.error = ''
this.fetchPage(vnode)
},
}, 'Page error: ' + this.error + '. Click here to try again'))
: null,
(featuredBanner
? m(m.route.Link, {
class: 'page-banner',
href: featuredBanner.path,
style: { 'background-image': 'url("' + featuredBanner.preview + '")' },
}, [
m('div.page-banner-real', {
style: { 'background-image': 'url("' + featuredBanner.banner + '")' },
}, m('div.inside', m('div.page-banner-title', featuredBanner.name))),
]
)
: null),
(!featuredBanner && pageBanner
? m('a.page-banner', {
href: pageBanner.original,
target: '_blank',
style: { 'background-image': 'url("' + pageBanner.preview + '")' },
},
m('div.page-banner-real', {
style: { 'background-image': 'url("' + pageBanner.banner + '")' },
})
)
: null),
(page
? m('.inside.vertical', [
m('div.actions', [
'« ',
m(m.route.Link, {
href: page.parent_path
? '/page/' + page.parent_path
: '/'
}, page.parent_name || 'Home'),
Authentication.currentUser
? [
m('div.filler'),
'Actions:',
m(m.route.Link, { href: '/admin/pages/' + page.id }, 'Edit page'),
]
: null,
]),
m('h2.title', page.name)
])
: null),
(page || this.data.articles.length
? m('.inside', [
this.children.length
? m('aside', { class: page ? '' : 'frontpage' }, [
m('h5', page ? 'View ' + page.name + ':' : 'Categories'),
this.children.map((page) => {
return [
m(m.route.Link, { class: 'root', href: '/page/' + page.path }, page.name),
(page.children && page.children.length
? m('ul', page.children.map(function(subpage) {
return m('li', m(m.route.Link, { class: 'child', href: '/page/' + subpage.path }, subpage.name))
}))
: null),
]
}),
!page
? m('div.asuna.spritesheet')
: null,
])
: null,
m('div.container', [
(page
? media.getArticlePicture(this.picture, false, page.media_path, 'Image for page ' + page.name)
: null),
(page && page.content
? m('div.content', page.content.blocks.map(block => {
return m(EditorBlock, { block: block })
}))
: null),
(page && this.data.articles.length
? [
m('h5', 'Latest posts under ' + page.name + ':'),
this.data.articles.map(function(article) {
return m(Articleslim, { article: article })
}),
]
: null),
(!page && this.data.articles.length
? this.data.articles.map(function(article) {
return m(Article, { article: article })
})
: null),
m(Paginator, {
base: page ? '/page/' + page.path : '/',
page: this.currentPage,
perPage: ArticlesPerPage,
total: this.data.total_articles,
}),
]),
])
: null),
],
])
},
}

View File

@ -306,6 +306,29 @@ dialogue button.cancel {
min-height: 80px !important;
}
@keyframes spinner-loader {
to {transform: rotate(360deg);}
}
.loading-spinner:after {
content: '';
box-sizing: border-box;
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin-top: -10px;
margin-left: -10px;
border-radius: 50%;
border: 2px solid #ccc;
border-top-color: #333;
animation: spinner-loader .6s linear infinite;
z-index: 1000;
}
/*
===================== 3rd party =====================
*/

View File

@ -1,858 +1,3 @@
/*
===================== Variables =====================
*/
:root {
--content-max-width: 1280px;
--primary-darker-bg: #002f6c;
--primary-darker-fg: #fff;
--primary-darker-fg-light: #999;
--primary-darker-link: #ffad42;
--primary-bg: #3d77c7;
--primary-fg: #fff;
--primary-fg-light: #999;
--primary-link: #f57c00;
--bg: #fff;
--bg-content-alt: #eee;
--color: #000;
--light: #757575;
--link: #bb4d00;
--title-bg: #f57c00;
--title-fg: #000;
--seperator: #ccc;
--content-bg: #fff;
--content-border: 0px solid transparent;
--alt-bg: #ccc;
--alt-inside-bg: #fff;
--alt-inside-border: 1px solid #555;
--alt-color: #555;
--footer-bg: #ccc;
--footer-color: #000;
--footer-seperator: #fff;
--footer-link: #8F3C00;
--button-border: 1px solid #f57c00;
--button-bg: #ffad42;
--button-fg: #000;
--error-bg: red;
--error-fg: white;
}
.nightmode {
--content-max-width: 1280px;
--primary-darker-bg: #002f6c;
--primary-darker-fg: #fff;
--primary-darker-fg-light: #999;
--primary-darker-link: #ffad42;
--primary-bg: #28518b;
--primary-fg: #fff;
--primary-fg-light: #999;
--primary-link: #f57c00;
--bg: black;
--bg-content-alt: #333;
--color: #d7dadc;
--light: #bbb;
--link: #e05e00;
--title-bg: #e05e00;
--title-fg: #000;
--title-sublink: #27159C;
--seperator: #ccc;
--content-bg: #1a1a1b;
--content-border: 1px solid #343536;
--alt-bg: #000;
--alt-inside-bg: #343536;
--alt-inside-border: 1px solid #808080;
--alt-color: #d7dadc;
--footer-bg: #343536;
--footer-color: #d7dadc;
--footer-seperator: #666;
--footer-link: #fe791b;
--button-border: 1px solid #f57c00;
--button-bg: #ffad42;
--button-fg: #000;
--error-bg: red;
--error-fg: white;
}
/*
===================== Reset =====================
*/
/* Box sizing rules */
*, *::before, *::after { box-sizing: border-box;
}
/* Remove default margin */
body, h1, h2, h3, h4, p, figure, blockquote, dl, dd {
margin: 0;
}
body {
min-height: 100vh;
text-rendering: optimizeSpeed;
line-height: 1.5;
font-size: 16px;
font-family: 'Inter var', sans-serif;
font-feature-settings: "slnt" 0deg, "case", "frac", "tnum", "ss02", "calt", "ccmp", "kern";
background: var(--bg);
color: var(--color);
}
.italic { font-variation-settings: "slnt" 10deg; }
a {
text-decoration-skip-ink: auto;
}
img {
max-width: 100%;
margin: 0 auto;
display: block;
}
input, button, textarea, select {
font: inherit;
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-play-state: paused !important;
transition: none !important;
scroll-behavior: auto !important;
}
}
h1 {
font-size: 2.488rem;
}
h2 {
font-size: 2.074rem;
}
h3 {
font-size: 1.728rem;
}
h4 {
font-size: 1.44rem;
}
h5 {
font-size: 1.0rem;
}
a, a:visited, button {
text-decoration: none;
border: none;
padding: 0;
margin: 0;
font-weight: bold;
cursor: pointer;
}
input[type=text],
input[type=password],
select {
border: 1px solid var(--color);
background: var(--bg);
color: var(--color);
border-radius: 0;
padding: 0.25rem;
line-height: 1rem;
}
label {
font-size: 0.75rem;
font-weight: 500;
margin-top: 1rem;
margin-bottom: 0.25rem;
display: block;
}
input[type=text]:hover,
input[type=text]:active,
input[type=password]:hover,
input[type=password]:active,
select:hover,
select:active {
border-color: var(--link);
}
button {
background: transparent;
}
/*
===================== Common =====================
*/
.inside {
width: 100%;
max-width: var(--content-max-width);
display: flex;
margin: 0 auto;
}
.inside.vertical {
flex-direction: column;
}
.error {
background: var(--error-bg);
color: var(--error-fg);
cursor: pointer;
text-align: center;
padding: 0.5rem;
}
.wrapper {
background: var(--alt-bg);
color: var(--alt-color);
display: flex;
justify-content: center;
align-items: center;
min-height: calc(100vh - 200px);
padding: 1rem;
}
.filler {
flex: 2 1 auto;
}
.wrapper .inside {
flex-direction: column;
color: var(--alt-color);
background: var(--alt-inside-bg);
border: var(--alt-inside-border);
}
.wrapper .error {
border: 1px solid var(--error-bg);
color: var(--error-bg);
background: transparent;
}
@keyframes spinner-loader {
to {transform: rotate(360deg);}
}
.loading-spinner:after {
content: '';
box-sizing: border-box;
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin-top: -10px;
margin-left: -10px;
border-radius: 50%;
border: 2px solid #ccc;
border-top-color: #333;
animation: spinner-loader .6s linear infinite;
z-index: 1000;
}
.notfound {
color: var(--light);
}
@media screen and (max-width: 639px){
main .inside {
flex-direction: column;
}
.wrapper {
flex-direction: column;
}
}
/*
===================== Header =====================
*/
header {
background: var(--primary-darker-bg);
color: var(--primary-darker-fg);
}
header a,
header a:visited,
header button {
color: var(--primary-darker-link);
}
header p {
color: var(--primary-darker-fg-light);
}
header .title,
header .title:visited {
min-height: 100px;
padding-left: 10px;
display: flex;
align-items: center;
background: 25px center no-repeat;
background-size: auto 91px;
flex: 0 0 auto;
}
header .logo {
background-position: -119px 0px;
width: 81px;
height: 100px;
transform: scale(0.9);
margin-right: 1rem;
}
header .title h1 {
font-weight: 500;
color: var(--primary-darker-fg);
}
header aside {
flex: 2 1 auto;
display: flex;
flex-direction: column;
align-items: flex-end;
font-size: 0.8rem;
padding: 0.5rem 0.5rem;
}
header aside a,
header aside button {
margin-left: 0.5rem;
}
header aside p button {
margin-left: 0;
}
/*
===================== Nav =====================
*/
nav {
background: var(--primary-bg);
}
nav a, nav a:visited, nav .loading-spinner {
flex: 2 0 auto;
text-align: center;
font-weight: 300;
padding: 10px 10px 7px 10px;
border-bottom: 3px solid var(--primary-bg);
color: var(--primary-fg);
}
nav a.active {
border-bottom-color: var(--primary-link);
}
nav .loading-spinner {
position: relative;
}
@media screen and (max-width: 639px){
nav {
font-size: 0.8em;
}
}
/*
===================== main =====================
*/
main {
min-height: calc(100vh - 390px);
}
.page-banner {
background-size: cover;
background-repeat: no-repeat;
background-position: center;
height: 150px;
width: 100%;
display: block;
}
.page-banner-title {
color: white;
text-align: right;
padding: 0.5rem 1rem;
font-size: 1.6rem;
flex: 2 1 auto;
text-shadow: 0 0 .3em #000;
}
.actions {
padding: 0.5rem 1rem;
display: flex;
}
.actions a {
margin-left: 0.375rem;
}
main a,
main a:visited {
color: var(--link);
}
main h5 {
padding: 0 0.5rem 0.5rem;
margin: 0 0 0.75rem;
border-bottom: 1px solid var(--seperator);
font-size: 1rem;
}
main .loading-spinner {
position: fixed;
left: 50%;
top: 50%;
}
main h2.title,
.main h2.title {
font-size: 1.4rem;
background: var(--title-bg);
color: var(--title-fg);
text-align: center;
font-weight: 400;
padding: 0.375rem;
line-height: 1.4rem;
}
main .container {
flex: 2 1 auto;
margin: 1rem;
}
main .cover picture img {
margin-bottom: 1rem;
width: 100%;
}
main button,
main input[type=submit] {
border: var(--button-border);
background: #ffad42;
color: #000;
align-self: center;
padding: 0.25rem 1rem;
margin: 1rem 0 2rem;
}
@media screen and (max-width: 639px){
main .container {
margin: 1rem 0.25rem;
}
}
/* ************** aside ************** */
main aside {
padding: 0.375rem 1rem 0.5rem;
margin: 1rem;
font-size: 0.875rem;
flex: 0 0 250px;
background: var(--content-bg);
border: var(--content-border);
}
main aside a {
display: block;
}
main aside h5 {
margin: 0 -0.5rem 0.25rem;
font-size: 0.9em;
}
main aside ul {
margin: 0 0 0.5rem;
padding-left: 1.5rem;
}
main aside .asuna {
margin-top: 2rem;
width: 200px;
height: 461px;
background-position: 0 -150px;
}
.nightmode main aside .asuna {
background-position: -200px -150px;
}
.daymode .day {
display: block;
}
@media screen and (max-width: 1000px){
main aside {
flex: 0 0 200px;
}
}
@media screen and (max-width: 639px){
main aside {
margin: 1rem 0.25rem;
flex: 0 0 auto;
}
main aside.frontpage {
order: 2;
}
}
/* ************** paginator ************** */
paginator {
display: flex;
justify-content: center;
width: 100%;
}
paginator a {
color: var(--link);
cursor: pointer;
}
paginator a,
paginator div {
display: block;
font-size: 0.8rem;
max-width: 80px;
flex-grow: 2;
text-align: center;
padding: 0.5rem;
margin-top: 1rem;
}
/* ************** articleslim ************** */
articleslim {
display: flex;
margin-bottom: 0.75rem;
padding-right: 0.5rem;
}
articleslim p.description {
font-size: 0.75rem;
}
articleslim .cover {
flex: 0 0 124px;
margin-right: 0.75rem;
}
articleslim .cover picture img {
margin-bottom: 0;
}
articleslim a.nobg {
height: 70px;
background: var(--seperator);
display: block;
}
articleslim a.title {
display: block;
margin-bottom: 0.375rem;
}
/* ************** article ************** */
article {
background: var(--content-bg);
border: var(--content-border);
margin-bottom: 1rem;
}
article .row {
margin: 1rem 0;
display: flex;
}
article .cover {
flex: 0 0 auto;
margin-right: 1rem;
align-self: flex-start;
}
article a.title {
flex: 0 0 100%;
margin-bottom: 0.5rem;
}
article .description {
font-size: 0.875rem;
margin-bottom: 1rem;
padding: 0 0.25rem;
}
article .meta {
font-size: 0.625rem;
line-height: 0.75rem;
color: var(--light);
font-weight: 500;
padding: 1.25rem 0.25rem 0;
}
article.fullsize .row {
margin: 1rem;
flex-direction: column;
}
article.fullsize .cover {
margin-right: 0;
}
@media screen and (max-width: 1000px){
article .row {
flex-direction: column;
}
article.fullsize .row {
margin: 1rem 0.25rem;
}
article .cover {
margin-right: 0;
}
}
/* ************** fileinfo ************** */
fileinfo {
padding-left: 0.25rem;
margin-bottom: 0.5rem;
color: var(--light);
line-height: 1rem;
font-size: 0.75rem;
display: block;
position: relative;
}
fileinfo.slim {
padding: 0;
margin: 0;
}
fileinfo p span,
fileinfo p a {
margin-right: 0.25rem;
}
fileinfo p a {
font-weight: 550;
padding-right: 0.25rem;
border-right: 1px solid var(--seperator);
display: inline-block;
}
fileinfo p span {
font-weight: 700;
}
fileinfo .trimmed {
padding: 0.25rem 0 0.25rem 1rem;
}
fileinfo ul {
margin: 0.5rem 0;
padding-left: 1.5rem;
}
/*
===================== login =====================
*/
.login--first {
flex: 0 0 170px;
}
.login {
align-items: center;
font-size: 1rem;
padding: 1rem 1rem 2rem;
margin: 1rem;
max-width: 400px;
}
.login .title {
font-size: 1.4rem;
font-weight: 200;
margin-bottom: 2rem;
text-align: center;
}
.login input,
.login label {
width: 100%;
max-width: 300px;
}
.login input[type=submit] {
min-width: 150px;
margin-top: 1rem;
}
.login--asuna {
flex: 0 0 auto;
width: 180px;
height: 494px;
background-position: -400px 0;
}
.nightmode .login--asuna {
background-position: -580px 0;
}
@media screen and (max-width: 1000px){
.login--first {
display: none;
}
}
@media screen and (max-width: 639px){
.login {
order: 2;
}
.login--asuna {
max-width: 120px;
}
}
/*
===================== content =====================
*/
.content :is(h1, h2, h3, h4, h5, ul, ol, blockquote, p) {
margin: 0 0 0.75rem;
}
.content :is(h1, h2, h3, h4, h5) {
padding: 0 0.5rem 0.5rem;
border-bottom: 1px solid var(--seperator);
}
.content :is(blockquote, pre) {
background: var(--bg-content-alt);
padding: 0.5rem;
}
.content blockquote p {
margin: 0;
}
/*
===================== footer =====================
*/
footer {
background: var(--footer-bg);
color: var(--footer-color);
text-align: center;
padding: 1rem;
display: flex;
align-items: center;
font-weight: 500;
font-size: 0.625rem;
}
footer .first {
flex: 0 0 119px;
}
footer .middle {
display: flex;
flex-direction: column;
align-items: center;
flex: 2 1 auto;
padding: 0 2rem;
}
footer .asuna {
flex: 0 0 119px;
height: 150px;
width: 119px;
background-position: 0px 0px;
}
footer ul {
margin: 0 0 0.25rem;
padding: 0 0 0.25rem;
border-bottom: 1px solid var(--footer-seperator);
display: flex;
justify-content: center;
flex-wrap: wrap;
min-width: 300px;
}
footer ul li {
padding: 0 0.25rem;
list-style-position: inside;
}
footer a {
color: var(--footer-link);
margin: 0 0 0.25rem;
}
@media screen and (max-width: 1000px){
footer .first {
display: none;
}
}
@media screen and (max-width: 639px){
footer{
flex-direction: column;
}
footer .middle {
padding: 0 0 2rem;
}
footer .asuna {
flex: 0 0 150px;
}
}
/*
===================== 404 page =====================
*/
.not_found {
flex-direction: column;
text-align: center;
}
.not_found .asuna {
width: 120px;
height: 444px;
margin: 2rem 0 0rem;
background-position: -760px 0;
}
.nightmode .not_found .asuna {
background-position: -880px 0;
}
/*
===================== Large assets =====================

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

File diff suppressed because one or more lines are too long