Initial commit

This commit is contained in:
Code Ninjas Garden Grove 2020-08-02 11:11:08 -07:00
commit ede3d63e7e
23 changed files with 1718 additions and 0 deletions

6
.env.sample Normal file
View file

@ -0,0 +1,6 @@
# The following secrets are required in order to deploy to GitHub pages
GH_USERNAME= # USERNAME ATTACHED TO YOUR GITHUB ACCOUNT
GH_NAME= # FULL NAME ATTACHED TO YOUR GITHUB ACCOUNT
GH_EMAIL= # EMAIL ATTACHED TO YOUR GITHUB ACCOUNT
PROJECT_NAME= # NAME OF YOUR REPOSITORY
PROJECT_TITLE= # TITLE OF DOCUMENTATION

30
.github/workflows/deploy.yaml vendored Normal file
View file

@ -0,0 +1,30 @@
name: Deploy
on: [push]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
with:
persist-credentials: false
- name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built.
run: |
npm install
npm run build
env:
GH_USERNAME: ${{secrets.GH_USERNAME}}
GH_NAME: ${{secrets.GH_NAME}}
GH_EMAIL: ${{secrets.GH_EMAIL}}
PROJECT_NAME: ${{secrets.PROJECT_NAME}}
PROJECT_TITLE: ${{secrets.PROJECT_TITLE}}
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@3.5.9
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages # The branch the action should deploy to.
FOLDER: dist # The folder the action should deploy.:

107
.gitignore vendored Normal file
View file

@ -0,0 +1,107 @@
# Dist
dist
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port

11
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"eslint.format.enable": true,
"eslint.lintTask.enable": false,
"deno.enable": false,
}

21
LICENSE Normal file
View file

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

50
README.md Normal file
View file

@ -0,0 +1,50 @@
# Online Docs Template 🌐
> A template for creating documentation with markdown files online.
[![GitHub forks](https://img.shields.io/github/forks/EthanThatOneKid/online-docs-template?style=social)](https://github.com/EthanThatOneKid/online-docs-template/fork)
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/EthanThatOneKid/online-docs-template/Deploy)](https://github.com/EthanThatOneKid/online-docs-template/actions?query=workflow%3ADeploy)
![GitHub repo size](https://img.shields.io/github/repo-size/EthanThatOneKid/online-docs-template)
Check out the [demo](https://ethanthatonekid.github.io/online-docs-template/)!
## Getting Started 🍎
1. Create a new repository using this one as a [template](https://github.com/EthanThatOneKid/online-docs-template/generate).
1. Clone your new repository to your desktop.
1. Initialize your repository...
1. Install the dependencies with `npm i`.
1. Rename [`.env.sample`](.env.sample) to `.env` and fill it with the appropriate info.
1. Customize the metadata within your [`package.json`](package.json).
1. Make it your own by editing the [`pages`](pages) directory!
> 💡 Note: The [`README.md`](pages/README.md) file in the [`pages`](pages) directory represents the *homepage* of the site!
## Editing Documents 🖊
### Edit Existing (Online) 📝
1. Visit your live site.
1. Navigate to the page that you wish to edit.
1. Click the link at the top of the page to edit the document.
1. Commit your changes.
### Create New (Online) 💡
1. Visit the [`pages`](pages) directory on GitHub.
1. Navigate: `Add file > Create new file`.
1. Commit the new file.
### Work on Desktop
1. Open your repository in your favorite code editor.
1. Edit the contents of the [`pages`](pages) directory.
1. Commit your changes.
## Deployment 🚀
1. Set your [`.env`](.env.sample) secrets in your GitHub repository: `Settings > Secrets > New Secret`.
1. Now, every push will trigger a deployment!
---
[![Buy me a Coffee](https://img.shields.io/badge/buy%20me%20a-coffee-%23FF813F)][bmac]
Drafted with 🧠 by [EthanThatOneKid][creator_site]
[bmac]: http://buymeacoff.ee/etok
[creator_site]: http://ethandavidson.com/

1050
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

16
package.json Normal file
View file

@ -0,0 +1,16 @@
{
"name": "online-docs-template",
"description": "A template for creating documentation with markdown files online.",
"version": "0.0.1",
"scripts": {
"dev": "nodemon ./src/index.mjs",
"build": "node ./src/index.mjs"
},
"dependencies": {
"marked": "^1.1.1"
},
"devDependencies": {
"dotenv": "^8.2.0",
"nodemon": "^2.0.4"
}
}

39
pages/Getting Started.md Normal file
View file

@ -0,0 +1,39 @@
# Getting Started
> This page explains how to get started with the [Online-Docs-Template](https://github.com/EthanThatOneKid/online-docs-template)
By the end of these instructions, you should have a working GitHub-powered content management system ([CMS](https://en.wikipedia.org/wiki/Content_management_system)) that updates your site as you choose to edit it!
If you are unfamiliar with markdown, visit [this link](https://commonmark.org/help/) to help you out! ⚔
## Getting Started 🍎
1. Create a new repository using this one as a [template](https://github.com/EthanThatOneKid/online-docs-template/generate).
1. Clone your new repository to your desktop.
1. Initialize your repository...
1. Install the dependencies with `npm i`.
1. Rename [`.env.sample`](.env.sample) to `.env` and fill it with the appropriate info.
1. Customize the metadata within your [`package.json`](package.json).
1. Make it your own by editing the [`pages`](pages) directory!
> 💡 Note: The [`README.md`](pages/README.md) file in the [`pages`](pages) directory represents the *homepage* of the site!
## Editing Documents 🖊
### Edit Existing (Online) 📝
1. Visit your live site.
1. Navigate to the page that you wish to edit.
1. Click the link at the top of the page to edit the document.
1. Commit your changes.
### Create New (Online) 💡
1. Visit the [`pages`](pages) directory on GitHub.
1. Navigate: `Add file > Create new file`.
1. Commit the new file.
### Work on Desktop
1. Open your repository in your favorite code editor.
1. Edit the contents of the [`pages`](pages) directory.
1. Commit your changes.
## Deployment 🚀
1. Set your [`.env`](.env.sample) secrets in your GitHub repository: `Settings > Secrets > New Secret`.
1. Now, every push will trigger a deployment!

3
pages/README.md Normal file
View file

@ -0,0 +1,3 @@
# Welcome to the Home Page!
First things first, click the `Getting Started` article on the sidebar!

View file

@ -0,0 +1,16 @@
### Hopefully
* When this change is pushed
* It will auto-generate the page
---
### What a
#### *WACKY*
##### **SILLY**
###### TeSt ArTiClE ~~~
> ...this is
```js
console.log("Hello, World!");
```

134
src/App.mjs Normal file
View file

@ -0,0 +1,134 @@
// import Anchors from "./components/Anchors.mjs";
import Sidebar from "./components/Sidebar.mjs";
export default ({ title, pages, content, style, base, location }) => `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${process.env.PROJECT_TITLE}${!!title && title.length > 0 ? ` | ${title}` : ""}</title>
<link rel="icon" href="https://github.com/${process.env.GH_USERNAME}/${process.env.PROJECT_NAME}/blob/master/static/favicon.svg?raw=true"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/4.0.0/github-markdown.min.css">
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
<style>${style}</style>
</head>
<body>
<div class="h-screen">
<div class="h-screen flex overflow-hidden bg-white dark:bg-light-black-900">
<!-- Start Popup Sidebar -->
<div id="sidebar" class="hidden md:hidden enter-done">
<div class="fixed inset-0 flex z-40">
<div class="fixed inset-0 appear-done enter-done">
<div class="absolute inset-0 bg-gray-600 opacity-75"></div>
</div>
${Sidebar({ isPopup: true, navigation: { pages, base } })}
<div class="flex-shrink-0 w-14"></div>
</div>
</div>
<!-- End Popup Sidebar -->
<div class="hidden md:flex md:flex-shrink-0">
${Sidebar({ navigation: { pages, base } })}
</div>
<div class="flex flex-col w-0 flex-1 overflow-hidden">
<div class="relative z-10 flex-shrink-0 flex h-16 bg-white dark:bg-black shadow md:hidden border-b border-gray-200 dark:border-light-black-800">
<a class="px-4 flex items-center justify-center md:hidden" href="/${process.env.PROJECT_NAME}">
<img src="https://github.com/${process.env.GH_USERNAME}/${process.env.PROJECT_NAME}/blob/master/static/favicon.svg?raw=true" alt="logo" class="w-auto h-10">
</a>
<div class="flex-1 px-4 flex justify-between">
<div class="flex-1 flex"></div>
</div>
<button onclick="${inline(onSidebarOpen)}" class="px-4 text-gray-500 dark:text-gray-200 focus:outline-none focus:bg-gray-100 dark:focus:bg-light-black-950 focus:text-gray-600 dark:focus:text-gray-400 md:hidden" aria-label="Open sidebar">
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7"></path>
</svg>
</button>
</div>
<main class="flex-1 relative z-0 overflow-y-auto focus:outline-none" tabindex="0">
<div class="max-w-screen-lg px-4 sm:px-6 md:px-8 pb-12">
<div class="py-6">
<a href="https://${getGithubLink(location)}">
📝 ${getGithubLink(location)}
</a>
<div>
<div class="markdown-body">${content}</div>
</div>
</div>
</div>
</main>
</div>
</div>
</div>
</body>
</html>
`;
const onSidebarOpen = () => {
const sidebar = document.querySelector('#sidebar');
sidebar.classList.remove('hidden');
};
const inline = fn => `(${fn.toString()})()`;
const getGithubLink = (location) => {
return `github.com/${process.env.GH_USERNAME}/${process.env.PROJECT_NAME}/edit/master/pages/${location.replace("\\", "/")}.md`;
};
/*
${Navigation({ pages, base })}
<div class="markdown-body">${content}</div>
${Anchors({ anchors })}
*/
/*
NOTES
* Crazy Sidebar starts under:
* `relative flex-1 flex flex-col max-w-xs w-full bg-white dark:bg-light-black-950 appear-done enter-done`
* Normal Sidebar starts under:
* `hidden md:flex md:flex-shrink-0`
*/
/*
const t = `<div class="h-screen">
<div class="h-screen flex overflow-hidden bg-white dark:bg-light-black-900">
<!-- Start Popup Sidebar -->
<div id="sidebar" class="hidden md:hidden enter-done">
<div class="fixed inset-0 flex z-40">
<div class="fixed inset-0 appear-done enter-done">
<div class="absolute inset-0 bg-gray-600 opacity-75"></div>
</div>
${Sidebar({ isPopup: true, navigation: { pages, base } })}
<div class="flex-shrink-0 w-14"></div>
</div>
</div>
<!-- End Popup Sidebar -->
<div class="hidden md:flex md:flex-shrink-0">
${Sidebar({ navigation: { pages, base } })}
</div>
<div class="flex flex-col w-0 flex-1 overflow-hidden">
<div class="relative z-10 flex-shrink-0 flex h-16 bg-white dark:bg-black shadow md:hidden border-b border-gray-200 dark:border-light-black-800">
<a class="px-4 flex items-center justify-center md:hidden" href="/"><img src="/favicon.svg" alt="logo" class="w-auto h-10"></a>
<div class="flex-1 px-4 flex justify-between">
<div class="flex-1 flex"></div>
</div>
<button onclick="${inline(onSidebarOpen)}" class="px-4 text-gray-500 dark:text-gray-200 focus:outline-none focus:bg-gray-100 dark:focus:bg-light-black-950 focus:text-gray-600 dark:focus:text-gray-400 md:hidden" aria-label="Open sidebar">
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7"></path>
</svg>
</button>
</div>
<main class="flex-1 relative z-0 overflow-y-auto focus:outline-none" tabindex="0">
<div class="max-w-screen-lg px-4 sm:px-6 md:px-8 pb-12">
<div class="py-6">
<a href="${getGithubLink(base, path)}">${getGithubLink(base, path)}</a>
<a class="break-words cursor-pointer link" href="https://deno.land/std/fs/mod.ts">https://deno.land/std/fs/mod.ts</a>
<div>
<div class="markdown-body">${content}</div>
</div>
</div>
</div>
</main>
</div>
</div>
</div>`;
*/

View file

@ -0,0 +1,13 @@
export default ({ anchors }) => `
<ul>
${
Object.entries(anchors)
.map(([id, content]) => `
<li>
<a href="#${id}">${content}</a>
</li>
`)
.join("")
}
</ul>
`;

22
src/components/Header.mjs Normal file
View file

@ -0,0 +1,22 @@
export default () => `
<div class="bg-gray-100 dark:bg-black pb-4 pt-4 border-b border-gray-200 dark:border-light-black-800">
<a class="flex items-center flex-shrink-0 px-4" href="/${process.env.PROJECT_NAME}">
<img src="https://github.com/${process.env.GH_USERNAME}/${process.env.PROJECT_NAME}/blob/master/static/favicon.svg?raw=true" alt="logo" class="w-auto h-12">
<div class="mx-4 flex flex-col justify-center">
<div class="font-bold text-gray-900 dark:text-gray-200 leading-6 text-2xl tracking-tight">
${process.env.PROJECT_TITLE}
</div>
</div>
</a>
<header class="px-4 pt-3 sm:px-6 sm:pt-4">
<div class="mt-1 text-sm text-gray-600 dark:text-gray-400">
Last published ${getDateTime()}.
</div>
</header>
</div>
`;
const getDateTime = () => {
const localeString = `${(new Date()).toLocaleString('en-US', { timeZone: 'America/Los_Angeles' })} PST`;
return localeString.replace(",", ",<br>");
};

View file

@ -0,0 +1,19 @@
const Navigation = ({ pages, base }) => `
<ul class="list-disc">
${
Object.entries(pages || {})
.map(([title, link]) => `
<li class="ml-3">
${
typeof link === "string"
? `<a class="underline hover:text-blue-500" href="${new URL(link, base)}">${title}</a>`
: title + Navigation({ pages: link, base })
}
</li>
`)
.join("")
}
</ul>
`;
export default Navigation;

View file

@ -0,0 +1,33 @@
import Navigation from "./Navigation.mjs";
import Header from "./Header.mjs";
const onSidebarClose = () => {
const sidebar = document.querySelector('#sidebar');
sidebar.classList.add('hidden');
};
const inline = fn => `(${fn.toString()})()`;
export default ({ isPopup, navigation }) => `
<div class="relative flex-1 flex flex-col max-w-xs w-full bg-white dark:bg-light-black-950 appear-done enter-done">
${
isPopup
? `<div class="absolute top-0 right-0 -mr-14 p-1">
<button onclick="${inline(onSidebarClose)}" role="button" class="flex items-center justify-center h-12 w-12 rounded-full focus:outline-none focus:bg-gray-600" aria-label="Close sidebar">
<svg class="h-6 w-6 text-gray-600 dark:text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>`
: ""
}
${Header()}
<div class="pt-2 pb-8 h-0 flex-1 flex flex-col overflow-y-auto">
<nav class="flex-1 px-4">
<div class="mt-2 mb-4">
${Navigation({ ...navigation })}
</div>
</nav>
</div>
</div>
`;

52
src/helpers.mjs Normal file
View file

@ -0,0 +1,52 @@
import marked from "marked";
import path from "path";
import fs from "fs";
export const getAnchors = (html) => {
const pattern = /<([^\s]+).*?id="([^"]*?)".*?>(.+?)<\/\1>/gi;
return html !== null
? html.match(pattern).reduce((result, cur) => {
const [,, id, content] = pattern.exec(cur);
result[id] = content;
return result;
}, {})
: null;
};
export const getHTML = (path) => {
const md = String(fs.readFileSync(path));
const html = marked(md);
return html;
};
export const getDocument = (tree, leaves) => {
const name = leaves.shift();
return name in tree
? typeof tree[name] === "string"
? tree[name]
: getDocument(tree[name], leaves)
: null;
};
export const getHome = (root) => {
return getHTML(path.join(root, "../pages/README.md"));
};
export const readdirRecursive = (dir, root = dir) => {
const result = {};
fs.readdirSync(dir).forEach((file) => {
const filePath = path.join(dir, file);
const fileStat = fs.lstatSync(filePath);
const { name, ext } = path.parse(filePath);
if (fileStat.isDirectory()) {
result[name] = readdirRecursive(filePath, root);
} else if (ext === ".md" && name !== "README") {
result[name] = path.relative(root, filePath).slice(0, -3);
}
});
return result;
};
export const getStyle = (root) => {
return String(fs.readFileSync(path.join(root, "./style/index.css")));
}

71
src/index.mjs Normal file
View file

@ -0,0 +1,71 @@
import fs from "fs";
import path from "path";
import App from "./App.mjs";
import {
readdirRecursive,
getAnchors,
getDocument,
getHTML,
getStyle,
getHome
} from "./helpers.mjs";
import { fileURLToPath } from 'url';
import dotenv from "dotenv";
dotenv.config();
const savePage = ({ location, title, content }) => {
const savePath = path.join(dist, location === "README" ? "" : location);
fs.mkdirSync(savePath, { recursive: true });
fs.writeFileSync(
path.join(savePath, "index.html"),
App({ title, pages, content, style, base, location })
);
console.log(`Saved '${title}' as '${path.join(savePath, "index.html")}'!`);
};
const createHome = (root) => {
const content = getHome(root);
savePage({
location: "README",
title: "",
anchors: getAnchors(content),
content
});
};
const createPage = (title, location, root) => {
const doc = getDocument(pages, location.split(/\\|\//g));
if (doc !== null) {
const content = getHTML(path.join(root, `${doc}.md`));
savePage({ location, title, content });
}
};
const createApp = (pages, dir, root = dir) => {
Object.entries(pages || {})
.forEach(([title, location]) => {
typeof location === "string"
? createPage(title, location, root)
: createApp(location, path.join(dir, title), root);
});
};
const dirname = path.dirname(fileURLToPath(import.meta.url));
const dist = path.join(dirname, `../dist`);
const root = path.join(dirname, "../pages");
const pages = readdirRecursive(root);
const style = getStyle(dirname);
const base = process.env.npm_lifecycle_event !== "dev"
? `https://${process.env.GH_USERNAME}.github.io/${process.env.PROJECT_NAME}/`
: "http://localhost:3000/";
try {
fs.rmdirSync(dist, { recursive: true });
fs.mkdirSync(dist, { recursive: true });
console.log({ pages });
} catch (error) {
console.log({ error });
}
createApp(pages, root);
createHome(root);

View file

@ -0,0 +1,3 @@
// Colors
$backgroundColor: #eee;
$textColor: #111;

5
src/style/index.css Normal file
View file

@ -0,0 +1,5 @@
body {
color: #111;
background-color: #eee;
}
/*# sourceMappingURL=index.css.map */

10
src/style/index.css.map Normal file
View file

@ -0,0 +1,10 @@
{
"version": 3,
"mappings": "AAEA,AAAA,IAAI,CAAC;EACD,KAAK,ECDG,IAAI;EDEZ,gBAAgB,ECHF,IAAI;CDIrB",
"sources": [
"index.scss",
"_variables.scss"
],
"names": [],
"file": "index.css"
}

6
src/style/index.scss Normal file
View file

@ -0,0 +1,6 @@
@import "./variables";
body {
color: $textColor;
background-color: $backgroundColor;
}

1
static/favicon.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='512' height='512' viewBox='0 0 512 512'><title>ionicons-v5_logos</title><path d='M475,64H37C16.58,64,0,81.38,0,102.77V409.19C0,430.59,16.58,448,37,448H475c20.38,0,37-17.41,37-38.81V102.77C512,81.38,495.42,64,475,64ZM288,368H224V256l-48,64-48-64V368H64V144h64l48,80,48-80h64Zm96,0L304,256h48.05L352,144h64V256h48Z'/></svg>

After

Width:  |  Height:  |  Size: 368 B