feat(docs): add base documentation page layout

This commit is contained in:
Ash Monty
2021-12-08 18:41:29 +01:00
parent 5681fe85ba
commit 13e63f1a59
11 changed files with 574 additions and 92 deletions

View File

@@ -2,6 +2,7 @@
"nav": {
"about": "About",
"faq": "FAQ",
"docs": "Docs",
"credits": "Credits",
"progress": "Progress",
"blog": "Blog"

View File

@@ -0,0 +1,162 @@
html, body {
background: #131730;
}
a.logo-link {
margin:auto;
margin-left: 36px;
height: 40px;
}
header {
width: calc(100% - 72px);
background: #131730;
padding: 12px 36px;
margin: 0;
}
header a.logo-link {
display: none;
}
header nav a:first-child {
margin-left: -24px;
}
.docs-wrapper {
display: grid;
grid-template-columns: repeat(2, fit-content(100%));
grid-template-rows: repeat(2, fit-content(100%));
min-height: 100%;
}
.docs-wrapper .sidebar {
display: flex;
flex-flow: column;
align-items: center;
width: clamp(270px, 30vw, 500px);
background: #161931;
max-height: calc(100vh - 69px);
overflow-y: scroll;
}
.docs-wrapper .sidebar .section {
display: flex;
flex-flow: column;
width: 200px;
margin-left: 30px;
margin-bottom: 72px;
}
.docs-wrapper .sidebar .section:first-child {
margin-top: 72px;
}
.docs-wrapper .sidebar .section h5 {
margin: 0;
font-weight: normal;
text-transform: uppercase;
color: var(--text-secondary-2);
margin-bottom: 12px;
}
.docs-wrapper .sidebar .section a {
position: relative;
text-decoration: none;
color: var(--text-secondary);
width: fit-content;
margin-bottom: 12px;
}
.docs-wrapper .sidebar .section a.active,
.docs-wrapper .sidebar .section a:hover {
color: var(--text);
}
.docs-wrapper .sidebar .section a.active::before {
/* This filter thing is jank, if anyone knows a better way to do this please fix */
filter: invert(51%) sepia(12%) saturate(2930%) hue-rotate(218deg)
brightness(99%) contrast(92%);
position: absolute;
left: -30px;
content: url(../images/arrow-right.svg);
}
.docs-wrapper .content {
width: calc(100vw - clamp(270px, 30vw, 500px) - (72px * 2));
background: var(--background);
padding: 72px;
max-height: calc(100vh - 69px - (72px * 2));
overflow-y: scroll;
}
.docs-wrapper .content-inner {
max-width: 900px;
}
.docs-wrapper .content p {
color: var(--text-secondary);
}
.docs-wrapper .content h1:first-child {
margin-top: 0;
}
.docs-wrapper .content .quick-links-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 24px;
margin-bottom: 60px;
}
.docs-wrapper .quick-links-grid a {
text-decoration: none;
background: #252a51;
border-radius: 6px;
color: var(--text-secondary);
display: flex;
align-items: center;
padding: 20px;
}
.docs-wrapper .quick-links-grid svg:first-child {
height: 36px;
margin-right: 24px;
margin-left: 4px;
color: var(--theme-light);
}
.docs-wrapper .quick-links-grid p.header {
font-size: 22px;
font-weight: 600;
color: var(--text);
margin: 0;
}
.docs-wrapper .quick-links-grid p {
margin: 0;
}
.docs-wrapper .quick-links-grid svg:last-child {
height: 36px;
margin-left: auto;
}
/* Scrollbar styling 'cause it's fancy */
.docs-wrapper .sidebar::-webkit-scrollbar,
.docs-wrapper .content::-webkit-scrollbar,
.docs-wrapper .content pre code::-webkit-scrollbar {
width: 12px;
height: 12px;
}
.docs-wrapper .sidebar::-webkit-scrollbar-track,
.docs-wrapper .content::-webkit-scrollbar-track,
.docs-wrapper .content pre code::-webkit-scrollbar-track {
background: none;
}
.docs-wrapper .sidebar::-webkit-scrollbar-thumb,
.docs-wrapper .content::-webkit-scrollbar-thumb,
.docs-wrapper .content pre code::-webkit-scrollbar-thumb {
background-color: var(--text-secondary-2);
border-radius: 24px;
border: 3px solid #161931;
}
.docs-wrapper .content::-webkit-scrollbar-thumb {
border: 3px solid var(--background);
}
.docs-wrapper .content pre code::-webkit-scrollbar-thumb {
border: 3px solid #0D0F20;
}
.docs-wrapper .sidebar {
scrollbar-width: thin;
scrollbar-color: var(--text-secondary-2) #161931;
}
.docs-wrapper .content {
scrollbar-width: thin;
scrollbar-color: var(--text-secondary-2) var(--background);
}
.docs-wrapper .content pre codear {
scrollbar-width: thin;
scrollbar-color: var(--text-secondary-2) #0D0F20;
}

View File

@@ -1,91 +1,137 @@
/**
* Adapted from Shades of Purple Theme — for Highlightjs.
*
* @author (c) Ahmad Awais <https://twitter.com/mrahmadawais/>
* @link GitHub Repo → https://github.com/ahmadawais/Shades-of-Purple-HighlightJS
* @version 1.5.0
*/
.hljs {
background: var(--btn-secondary);
color: #e3dfff;
font-weight: normal;
}
.hljs-subst {
color: #e3dfff;
}
.hljs-title {
color: #fad000;
font-weight: normal;
}
.hljs-name {
color: #a1feff;
}
.hljs-tag {
color: #ffffff;
}
.hljs-attr {
color: #f8d000;
font-style: italic;
}
.hljs-built_in,
.hljs-selector-tag,
.hljs-section {
color: #fb9e00;
}
.hljs-keyword {
color: #fb9e00;
}
.hljs-string,
.hljs-attribute,
.hljs-symbol,
.hljs-bullet,
.hljs-addition,
.hljs-code,
.hljs-regexp,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo,
.hljs-template-tag,
.hljs-quote,
.hljs-deletion {
color: #4cd213;
}
.hljs-meta,
.hljs-meta .hljs-string {
color: #fb9e00;
}
.hljs-comment {
color: #ac65ff;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-literal,
.hljs-name,
.hljs-strong {
font-weight: normal;
}
.hljs-literal,
.hljs-number {
color: #fa658d;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
pre code.hljs {
display: block;
overflow-x: auto;
padding: 12px 36px;
border-radius: 10px;
font-family: Poppins, Arial, Helvetica, sans-serif;
}
code.hljs {
padding: 3px 5px;
}
.hljs {
background: #0D0F20;
color: #d6deeb;
}
.hljs-keyword {
color: #c792ea;
font-style: italic;
}
.hljs-built_in {
color: #addb67;
font-style: italic;
}
.hljs-type {
color: #82aaff;
}
.hljs-literal {
color: #ff5874;
}
.hljs-number {
color: #f78c6c;
}
.hljs-regexp {
color: #5ca7e4;
}
.hljs-string {
color: #ecc48d;
}
.hljs-subst {
color: #d3423e;
}
.hljs-symbol {
color: #82aaff;
}
.hljs-class {
color: #ffcb8b;
}
.hljs-function {
color: #82aaff;
}
.hljs-title {
color: #dcdcaa;
font-style: italic;
}
.hljs-params {
color: #7fdbca;
}
.hljs-comment {
color: #637777;
font-style: italic;
}
.hljs-doctag {
color: #7fdbca;
}
.hljs-meta,
.hljs-meta .hljs-keyword {
color: #82aaff;
}
.hljs-meta .hljs-string {
color: #ecc48d;
}
.hljs-section {
color: #82b1ff;
}
.hljs-attr,
.hljs-name,
.hljs-tag {
color: #7fdbca;
}
.hljs-attribute {
color: #80cbc4;
}
.hljs-variable {
color: #addb67;
}
.hljs-bullet {
color: #d9f5dd;
}
.hljs-code {
color: #80cbc4;
}
.hljs-emphasis {
color: #c792ea;
font-style: italic;
}
.hljs-strong {
color: #addb67;
font-weight: 700;
}
.hljs-formula {
color: #c792ea;
}
.hljs-link {
color: #ff869a;
}
.hljs-quote {
color: #697098;
font-style: italic;
}
.hljs-selector-tag {
color: #ff6363;
}
.hljs-selector-id {
color: #fad430;
}
.hljs-selector-class {
color: #addb67;
font-style: italic;
}
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #c792ea;
font-style: italic;
}
.hljs-template-tag {
color: #c792ea;
}
.hljs-template-variable {
color: #addb67;
}
.hljs-addition {
color: #addb67ff;
font-style: italic;
}
.hljs-deletion {
color: #ef535090;
font-style: italic;
}

View File

@@ -89,6 +89,7 @@ header nav a {
.locale-dropdown {
margin-left: auto;
z-index: 2;
height: 45px;
}
/* Misc */

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>

After

Width:  |  Height:  |  Size: 314 B

92
src/routers/docs.js Normal file
View File

@@ -0,0 +1,92 @@
const { Router } = require('express');
const util = require('../util');
const logger = require('../logger');
const router = new Router();
const fs = require('fs');
const path = require('path');
const marked = require('marked');
const matter = require('gray-matter');
/*
const postList = () => {
const files = fs.readdirSync('blogposts');
// We get the info for each blogpost, ignoring the ones starting with _
const posts = files
.filter(filename => !filename.startsWith('_'))
.filter(filename => filename.endsWith('.md')) // Ignores other files/folders
.map((filename) => {
const slug = filename.replace('.md', '');
const rawPost = fs.readFileSync(
path.join('blogposts', `${filename}`),
'utf-8'
);
const { data: postInfo } = matter(rawPost);
return {
slug,
postInfo,
};
});
posts.sort((a, b) => {
return new Date(b.postInfo.date) - new Date(a.postInfo.date);
});
return posts;
};
*/
router.get('/', async (request, response) => {
const reqLocale = request.locale;
const locale = util.getLocale(reqLocale.region, reqLocale.language);
const localeString = reqLocale.toString();
response.render('docs', {
layout: 'main',
locale,
localeString,
});
});
/*
router.get('/:slug', async (request, response, next) => {
const reqLocale = request.locale;
const locale = util.getLocale(reqLocale.region, reqLocale.language);
const localeString = reqLocale.toString();
// Get the name of the post from the URL
const postName = request.params.slug;
// Get the markdown file corresponding to the post
let rawPost;
try {
rawPost = fs.readFileSync(path.join('blogposts', `${postName}.md`), 'utf-8');
} catch(err) {
logger.error(err);
next();
return;
}
// Convert the post info into JSON and separate it and the content
// eslint-disable-next-line prefer-const
let { data: postInfo, content } = matter(rawPost);
// Replace [yt-iframe](videoID) with the full <iframe />
content = content
.replace(/(?<!`)\[yt-iframe]\(/g, '<div class="aspectratio-fallback"><iframe src="https://www.youtube-nocookie.com/embed/')
.replace(/(?<=<iframe src="https:\/\/www\.youtube-nocookie\.com\/embed\/.{11})\)/g, '" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>');
// Convert the content into HTML
const htmlPost = marked(content);
response.render('blog/blogpost', {
layout: 'blog-opengraph',
locale,
localeString,
postInfo,
htmlPost,
});
});
*/
module.exports = router;

View File

@@ -23,6 +23,7 @@ logger.info('Importing page routers');
const routers = {
home: require('./routers/home'),
faq: require('./routers/faq'),
docs: require('./routers/docs'),
progress: require('./routers/progress'),
account: require('./routers/account'),
blog: require('./routers/blog'),
@@ -72,6 +73,7 @@ app.use(expressLocale({
app.use('/', routers.home);
app.use('/faq', routers.faq);
app.use('/docs', routers.docs);
app.use('/progress', routers.progress);
app.use('/account', routers.account);
app.use('/localization', routers.localization);

136
views/docs.handlebars Normal file
View File

@@ -0,0 +1,136 @@
<link rel="stylesheet" href="/assets/css/documentation.css" />
<div class="docs-wrapper">
<a href="/" class="logo-link">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="39.876">
<g id="logo_type" data-name="logo type" transform="translate(-553 -467)">
<g id="logo" transform="translate(553 467)">
<rect id="XMLID_158_" width="39.876" height="39.876" fill="#9d6ff3" opacity="0" />
<g id="XMLID_6_" transform="translate(8.222 1.418)">
<path id="XMLID_15_"
d="M69.149,28.312c-1.051.553-.129,2.139.922,1.585a12.365,12.365,0,0,1,8.794-.571,10.829,10.829,0,0,1,6.342,4.166c.645,1,2.231.074,1.585-.922C83.308,27.169,74.7,25.436,69.149,28.312Z"
transform="translate(-64.246 -23.389)" fill="#9d6ff3" />
<path id="XMLID_14_"
d="M82.64,14.608A15.565,15.565,0,0,0,73.5,8.45a17.535,17.535,0,0,0-12.647.9c-1.051.553-.129,2.139.922,1.585,3.411-1.788,7.6-1.714,11.209-.719,3.1.848,6.268,2.544,8.038,5.309C81.681,16.543,83.267,15.622,82.64,14.608Z"
transform="translate(-57.476 -7.693)" fill="#9d6ff3" />
<path id="XMLID_9_"
d="M55.68,47.8a10.719,10.719,0,0,0-6.71,2.3H45.983A1.336,1.336,0,0,0,44.6,51.376V75.84a1.431,1.431,0,0,0,1.383,1.383h3.023a1.367,1.367,0,0,0,1.309-1.383V68.392A10.993,10.993,0,1,0,55.68,47.8Zm0,17.182a6.213,6.213,0,1,1,6.213-6.213A6.216,6.216,0,0,1,55.68,64.982Z"
transform="translate(-44.6 -40.406)" fill="#9d6ff3" />
</g>
</g>
<text id="Pretendo" transform="translate(593 492)" fill="#fff" font-size="17"
font-family="Poppins-Bold, Poppins" font-weight="700">
<tspan x="0" y="0">Pretendo</tspan>
</text>
</g>
</svg>
</a>
{{> header}}
{{> docs-sidebar}}
<div class="content">
<div class="content-inner">
<h1>Quick links</h1>
<div class="quick-links-grid">
<a href="/docs/troubleshoot-errors">
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="book" class="svg-inline--fa fa-book fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M448 360V24c0-13.3-10.7-24-24-24H96C43 0 0 43 0 96v320c0 53 43 96 96 96h328c13.3 0 24-10.7 24-24v-16c0-7.5-3.5-14.3-8.9-18.7-4.2-15.4-4.2-59.3 0-74.7 5.4-4.3 8.9-11.1 8.9-18.6zM128 134c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm0 64c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm253.4 250H96c-17.7 0-32-14.3-32-32 0-17.6 14.4-32 32-32h285.4c-1.9 17.1-1.9 46.9 0 64z"></path></svg>
<!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->
<div>
<p class="header">Can't find something?</p>
<p>Troubleshoot errors</p>
</div>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-right"><polyline points="9 18 15 12 9 6"></polyline></svg>
</a>
<a href="/docs/beans">
<!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="book" class="svg-inline--fa fa-book fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M448 360V24c0-13.3-10.7-24-24-24H96C43 0 0 43 0 96v320c0 53 43 96 96 96h328c13.3 0 24-10.7 24-24v-16c0-7.5-3.5-14.3-8.9-18.7-4.2-15.4-4.2-59.3 0-74.7 5.4-4.3 8.9-11.1 8.9-18.6zM128 134c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm0 64c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm253.4 250H96c-17.7 0-32-14.3-32-32 0-17.6 14.4-32 32-32h285.4c-1.9 17.1-1.9 46.9 0 64z"></path></svg>
<div>
<p class="header">Nintendo be like</p>
<p>Here's your DMCA and C&D</p>
</div>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-right"><polyline points="9 18 15 12 9 6"></polyline></svg>
</a>
</div>
<h1>Other documentation</h1>
<p>We are a bunch of programmers spending our free time reverse engineering and coding game servers for 3ds and wiiu.</p>
<p>We are a bunch of programmers spending our free time reverse engineering and coding game servers for 3ds and wiiu.</p>
<p>We are a bunch of programmers spending our free time reverse engineering and coding game servers for 3ds and wiiu.</p>
<pre>
<code class="language-javascript">
function returnTrue() {
class trueOrFalseObject {
constructor(trueOrFalse) {
this.trueOrFalse = trueOrFalse;
}
get trueOrFalse() {
return this.trueOrFalse();
}
convertNumberToBoolean(trueOrFalse) {
if (convertStringToNumber(trueOrFalse) === 0) {
return true;
} else if (convertStringToNumber(trueOrFalse) == 1) {
return false;
}
}
convertStringToNumber(trueOrFalse) {
if (trueOrFalse === "true") {
return 0;
} else if (trueOrFalse === "false") {
return true * 69 - 1 - false * 69 * 420 - 69 + 2;
}
}
trueOrFalse() {
return this.convertNumberToBoolean(trueOrFalse);
}
}
let objectWhichWeKnowIsTrue = new trueOrFalseObject("true");
function checkIfTrueOrFalse(objectToCheckIfTrueOrFalse) {
if (objectToCheckIfTrueOrFalse === objectWhichWeKnowIsTrue.trueOrFalse) {
return objectWhichWeKnowIsTrue.trueOrFalse;
console.log(
"Successfully checked if the object is true or false. Result: the object is true."
);
// TODO: fix text not console logging
} else {
objectWhichWeKnowIsTrue = new trueOrFalseObject("false");
if (objectToCheckIfTrueOrFalse === objectWhichWeKnowIsTrue.trueOrFalse) {
return objectWhichWeKnowIsTrue.trueOrFalse;
console.log(
"Successfully checked if the object is true or false. Result: the object is false."
);
// TODO: fix text not console logging
} else {
// something went horribly wrong
}
objectWhichWeKnowIsTrue = new trueOrFalseObject("true");
}
}
const isTrueTrueOrFalse = checkIfTrueOrFalse("true");
const isfalseTrueOrFalse = checkIfTrueOrFalse("false");
const trueOrFalseJSON = {
true: isTrueTrueOrFalse,
false: isfalseTrueOrFalse,
};
return trueOrFalseJSON.true
}
console.log(returnTrue())
</code>
</pre>
</div>
</div>
</div>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/styles/default.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/highlight.min.js"></script>
<link rel="stylesheet" href="/assets/css/highlightjs.css">
<script>hljs.highlightAll();</script>

View File

@@ -51,7 +51,7 @@
<link rel="stylesheet" href="/assets/css/main.css">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap" rel="stylesheet">
<!-- Twemoji setup -->
<script src="https://twemoji.maxcdn.com/v/latest/twemoji.min.js" crossorigin="anonymous"></script>

View File

@@ -0,0 +1,40 @@
<div class="sidebar">
<div class="section">
<h5>Getting started</h5>
<a href="/docs/welcome" class="active">Welcome</a>
<a href="/docs/installing-juxt">Installing Juxt</a>
<a href="/docs/troubleshoot-errors">Troubleshoot errors</a>
</div>
<div class="section">
<h5>Error codes - Juxt</h5>
<a href="/docs/error/JXT-598-0000">JXT-598-0000</a>
<a href="/docs/error/JXT-598-0001">JXT-598-0001</a>
<a href="/docs/error/JXT-598-0002">JXT-598-0002</a>
<a href="/docs/error/JXT-598-0003">JXT-598-0003</a>
<a href="/docs/error/JXT-598-0009">JXT-598-0009</a>
<a href="/docs/error/JXT-598-0010">JXT-598-0010</a>
<a href="/docs/error/JXT-598-0011">JXT-598-0011</a>
<a href="/docs/error/JXT-598-0015">JXT-598-0015</a>
<a href="/docs/error/JXT-598-0020">JXT-598-0020</a>
<a href="/docs/error/JXT-598-1XXX">JXT-598-1XXX</a>
<a href="/docs/error/JXT-598-2XXX">JXT-598-2XXX</a>
<a href="/docs/error/JXT-598-3XXX">JXT-598-3XXX</a>
<a href="/docs/error/JXT-598-4XXX">JXT-598-4XXX</a>
<a href="/docs/error/JXT-598-5XXX">JXT-598-5XXX</a>
</div>
<div class="section">
<h5>Error codes - Beans</h5>
<a href="/docs/error/JXT-598-0000">Black Beans</a>
<a href="/docs/error/JXT-598-0001">Black-Eyed Peas</a>
<a href="/docs/error/JXT-598-0002">Cannellini Beans</a>
<a href="/docs/error/JXT-598-0003">Chickpeas (Garbanzo Beans)</a>
<a href="/docs/error/JXT-598-0009">Great Northern Beans</a>
<a href="/docs/error/JXT-598-0010">Kidney Beans</a>
<a href="/docs/error/JXT-598-0011">Lima Beans</a>
<a href="/docs/error/JXT-598-0015">Pinto Beans</a>
<a href="/docs/error/JXT-598-0020">Fava Beans</a>
<a href="/docs/error/JXT-598-1XXX">Navy Beans</a>
</div>
</div>

View File

@@ -28,6 +28,7 @@
<a href="/#about">{{ locale.nav.about }}</a>
<a href="/#faq">{{ locale.nav.faq }}</a>
<a href="/#credits">{{ locale.nav.credits }}</a>
<a href="/docs">{{ locale.nav.docs }}</a>
<a href="/progress" class="keep-on-mobile">{{ locale.nav.progress }}</a>
<a href="/blog" class="keep-on-mobile">{{ locale.nav.blog }}</a>
</nav>