commit bd2ba8dd3f9a13c695553e3ca776bb1fc74768d5
Author: Zakhar Timoshenko
Date: Mon Oct 9 03:43:17 2023 +0300
Initial commit
diff --git a/.github/workflow/deploy.yml b/.github/workflow/deploy.yml
new file mode 100644
index 0000000..36e04cd
--- /dev/null
+++ b/.github/workflow/deploy.yml
@@ -0,0 +1,31 @@
+name: Deploy
+on:
+ workflow_dispatch: {}
+ push:
+ branches:
+ - master
+jobs:
+ deploy:
+ if: github.repository_owner == 'ztimms73'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ cache: pnpm
+ - run: pnpm i --frozen-lockfile
+ - name: Build
+ run: pnpm run build
+ env:
+ NODE_OPTIONS: "--max_old_space_size=4096"
+ - uses: easingthemes/ssh-deploy@main
+ env:
+ SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
+ ARGS: '--archive --compress --delete --exclude="/redirect.php"'
+ SOURCE: ".vitepress/dist/"
+ REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
+ REMOTE_USER: ${{ secrets.REMOTE_USER }}
+ TARGET: ${{ secrets.REMOTE_TARGET }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bcb1064
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,19 @@
+.DS_Store
+node_modules/
+.yarn
+
+.env.local
+.env.*.local
+
+.vitepress/cache
+.vitepress/dist
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+.temp
+.idea
+
+package-lock.json
+pnpm-lock.yaml
\ No newline at end of file
diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc
new file mode 100644
index 0000000..07f894b
--- /dev/null
+++ b/.markdownlint.jsonc
@@ -0,0 +1,15 @@
+{
+ "line-length": false,
+ "link-fragments": false,
+ "no-inline-html": {
+ "allowed_elements": ["Badge", "ul", "ol", "li", "small", "sub", "sup", "br"]
+ },
+ "fenced-code-language": false,
+ "no-duplicate-heading": {
+ "allow_different_nesting": true,
+ "siblings_only": true
+ },
+ "single-title": {
+ "front_matter_title": ""
+ }
+}
\ No newline at end of file
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..937f800
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+package-import-method=clone-or-copy
\ No newline at end of file
diff --git a/.vitepress/config/en.ts b/.vitepress/config/en.ts
new file mode 100644
index 0000000..da04440
--- /dev/null
+++ b/.vitepress/config/en.ts
@@ -0,0 +1,223 @@
+import type { DefaultTheme, LocaleConfig } from 'vitepress'
+import type { Theme } from '../theme/types'
+import { components } from '../theme/plugins/component'
+import 'dotenv/config'
+
+export const SITE_NAME = 'Kotatsu Website'
+export const META_DESCRIPTION = 'A simple and convenient open source manga reader from and for the community, where you can find and read your favorite manga easier than ever.'
+
+export const searchLocale: Record>> = {
+ root: {
+ translations: {
+ button: {
+ buttonText: 'Search',
+ buttonAriaLabel: 'Search'
+ },
+ modal: {
+ resetButtonTitle: 'Reset search',
+ backButtonTitle: 'Close search',
+ noResultsText: 'No results for',
+ footer: {
+ selectText: 'to select',
+ selectKeyAriaLabel: 'enter',
+ navigateText: 'to navigate',
+ navigateUpKeyAriaLabel: 'up arrow',
+ navigateDownKeyAriaLabel: 'down arrow',
+ closeText: 'to close',
+ closeKeyAriaLabel: 'escape'
+ }
+ }
+ }
+ },
+}
+
+export const config: LocaleConfig = {
+ root: {
+ label: 'English',
+ lang: 'en',
+ description: META_DESCRIPTION,
+
+ head: [
+ ['meta', { property: 'og:site_name', content: SITE_NAME }],
+ ],
+
+ themeConfig: {
+ nav: getNav(),
+
+ sidebar: {
+ "/": defaultSidebar(),
+ },
+
+ outline: {
+ label: 'On this page',
+ level: 'deep',
+ },
+
+ docFooter: {
+ prev: 'Previous page',
+ next: 'Next page',
+ },
+ editLink: {
+ pattern: 'https://github.com/KotatsuApp/kotatsuapp.github.io/edit/master/website/:path',
+ text: 'Suggest changes to this page',
+ },
+ footer: {
+ message: "GPL-3.0 Licensed | Privacy policy ",
+ copyright: `Copyright © 2020 - ${new Date().getFullYear()} Kotatsu Developers`,
+ },
+
+ components: components.filter(component => component.path.startsWith('components/')),
+ },
+ }
+}
+
+function defaultSidebar(): DefaultTheme.SidebarItem[] {
+ return [
+ {
+ text: "General sections",
+ items: [
+ {
+ text: "Download",
+ link: "/download/",
+ },
+ {
+ text: "Parsers",
+ link: "/parsers/",
+ },
+ {
+ text: "Changelogs",
+ link: "/changelogs/",
+ },
+ {
+ text: "Contribute",
+ link: "/contribute/",
+ },
+ ],
+ },
+ {
+ text: "Frequently Asked Questions",
+ items: [
+ { text: "General", link: "/manuals/faq/general" },
+ {
+ text: "Feed",
+ link: "/manuals/faq/feed",
+ },
+ {
+ text: "Explore",
+ link: "/manuals/faq/explore/",
+ collapsed: true,
+ items: [
+ {
+ text: "Sources",
+ link: "/manuals/faq/explore/sources"
+ },
+ {
+ text: "Local storage",
+ link: "/manuals/faq/explore/local-storage",
+ },
+ {
+ text: "Bookmarks",
+ link: "/manuals/faq/explore/bookmarks",
+ },
+ {
+ text: "Random",
+ link: "/manuals/faq/explore/random",
+ },
+ {
+ text: "Downloads",
+ link: "/manuals/faq/explore/downloads",
+ },
+ {
+ text: "Suggestions",
+ link: "/manuals/faq/explore/suggestions",
+ },
+ ],
+ },
+ {
+ text: "Reader",
+ link: "/manuals/faq/reader",
+ },
+ {
+ text: "Settings",
+ link: "/manuals/faq/settings",
+ },
+ ],
+ },
+ {
+ text: "Guides",
+ items: [
+ {
+ text: "Getting started",
+ link: "/manuals/guides/getting-started",
+ },
+ {
+ text: "Troubleshooting",
+ link: "/manuals/guides/troubleshooting/",
+ collapsed: true,
+ items: [
+ {
+ text: "Common issues",
+ link: "/manuals/guides/troubleshooting/common-issues",
+ },
+ {
+ text: "Diagnosis",
+ link: "/manuals/guides/troubleshooting/diagnosis",
+ },
+ ],
+ },
+ { text: "Backups", link: "/manuals/guides/backups" },
+ { text: "Tracking", link: "/manuals/guides/tracking" },
+ { text: "Categories", link: "/manuals/guides/categories" },
+ {
+ text: "Local storage",
+ link: "/manuals/guides/local-storage/",
+ collapsed: true,
+ items: [
+ {
+ text: "Advanced editing",
+ link: "/manuals/guides/local-source/advanced",
+ },
+ ],
+ },
+ {
+ text: "Reader settings",
+ link: "/manuals/guides/reader-settings",
+ },
+ ],
+ },
+ ]
+}
+
+function getNav(): DefaultTheme.NavItem[] {
+ return [
+ {
+ text: 'Get v{app_version}',
+ activeMatch: "^/*?(download|changelogs)/*?$",
+ items: [
+ {
+ text: "Download",
+ link: "/download/",
+ },
+ {
+ text: "Changelogs",
+ link: "/changelogs/",
+ },
+ ],
+ },
+ {
+ text: "Manual",
+ link: "/manuals/guides/getting-started",
+ activeMatch: "/manuals/",
+ },
+ {
+ text: "News",
+ link: "/news/",
+ activeMatch: "/news/",
+ },
+ {
+ text: "Account",
+ link: "/account/",
+ activeMatch: "/account/"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vitepress/config/hooks/generateMeta.ts b/.vitepress/config/hooks/generateMeta.ts
new file mode 100644
index 0000000..6cecaed
--- /dev/null
+++ b/.vitepress/config/hooks/generateMeta.ts
@@ -0,0 +1,111 @@
+import type { HeadConfig, TransformContext } from "vitepress"
+import type { DocsPageData } from '../../theme/plugins/component'
+
+function generateMeta(context: TransformContext, hostname: string) {
+ const head: HeadConfig[] = []
+ const { pageData }: { pageData: DocsPageData } = context
+
+ const url = `${hostname}/${pageData.relativePath.replace(/((^|\/)index)?\.md$/, "$2")}`
+
+ head.push(["link", { rel: "canonical", href: url }])
+ head.push(["meta", { property: "og:url", content: url }])
+ head.push(["meta", { name: "twitter:url", content: url }])
+ head.push(["meta", { name: "twitter:card", content: "summary_large_image" }])
+
+ if (pageData.frontmatter.theme) {
+ head.push(["meta", { name: "theme-color", content: pageData.frontmatter.theme }])
+ }
+ if (pageData.frontmatter.type) {
+ head.push(["meta", { property: "og:type", content: pageData.frontmatter.type }])
+ }
+ if (pageData.frontmatter.customMetaTitle) {
+ head.push([
+ "meta",
+ {
+ property: "og:title",
+ content: pageData.frontmatter.customMetaTitle,
+ },
+ ])
+ head.push([
+ "meta",
+ {
+ name: "twitter:title",
+ content: pageData.frontmatter.customMetaTitle,
+ },
+ ])
+ head.push(["meta", { property: "og:site_name", content: "" }])
+ } else {
+ head.push(["meta", { property: "og:title", content: pageData.frontmatter.title }])
+ head.push(["meta", { name: "twitter:title", content: pageData.frontmatter.title }])
+ }
+ if (pageData.frontmatter.description) {
+ head.push([
+ "meta",
+ {
+ property: "og:description",
+ content: pageData.frontmatter.description,
+ },
+ ])
+ head.push([
+ "meta",
+ {
+ name: "twitter:description",
+ content: pageData.frontmatter.description,
+ },
+ ])
+ }
+ if (pageData.frontmatter.image) {
+ head.push([
+ "meta",
+ {
+ property: "og:image",
+ content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, "")}`,
+ },
+ ])
+ head.push([
+ "meta",
+ {
+ name: "twitter:image",
+ content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, "")}`,
+ },
+ ])
+ } else {
+ const url = pageData.filePath.replace("index.md", "").replace(".md", "")
+ const imageUrl = `${url}/__og_image__/og.png`.replace(/\/\//g, "/").replace(/^\//, "")
+
+ head.push(["meta", { property: "og:image", content: `${hostname}/${imageUrl}` }])
+ head.push(["meta", { property: "og:image:width", content: "1200" }])
+ head.push(["meta", { property: "og:image:height", content: "628" }])
+ head.push(["meta", { property: "og:image:type", content: "image/png" }])
+ head.push(["meta", { property: "og:image:alt", content: pageData.frontmatter.title }])
+ head.push(["meta", { name: "twitter:image", content: `${hostname}/${imageUrl}` }])
+ head.push(["meta", { name: "twitter:image:width", content: "1200" }])
+ head.push(["meta", { name: "twitter:image:height", content: "628" }])
+ head.push(["meta", { name: "twitter:image:alt", content: pageData.frontmatter.title }])
+ }
+ if (pageData.frontmatter.tag) {
+ head.push(["meta", { property: "article:tag", content: pageData.frontmatter.tag }])
+ }
+ if (pageData.frontmatter.date) {
+ head.push([
+ "meta",
+ {
+ property: "article:published_time",
+ content: pageData.frontmatter.date,
+ },
+ ])
+ }
+ if (pageData.lastUpdated && pageData.frontmatter.lastUpdated !== false) {
+ head.push([
+ "meta",
+ {
+ property: "article:modified_time",
+ content: new Date(pageData.lastUpdated).toISOString(),
+ },
+ ])
+ }
+
+ return head
+}
+
+export default generateMeta
\ No newline at end of file
diff --git a/.vitepress/config/hooks/generateOgImages.ts b/.vitepress/config/hooks/generateOgImages.ts
new file mode 100644
index 0000000..3767c4f
--- /dev/null
+++ b/.vitepress/config/hooks/generateOgImages.ts
@@ -0,0 +1,109 @@
+import { mkdir, readFile, writeFile } from "node:fs/promises"
+import { dirname, resolve } from "node:path"
+import { fileURLToPath } from "node:url"
+import { createContentLoader } from "vitepress"
+import type { ContentData, SiteConfig } from "vitepress"
+import { type SatoriOptions, satoriVue } from "x-satori/vue"
+import { renderAsync } from "@resvg/resvg-js"
+
+const __dirname = dirname(fileURLToPath(import.meta.url))
+const __fonts = resolve(__dirname, "../../fonts")
+
+async function generateOgImages(config: SiteConfig) {
+ const pages = await createContentLoader("**/*.md", { excerpt: true }).load()
+ const template = await readFile(resolve(__dirname, "../../theme/components/OgImageTemplate.vue"), "utf-8")
+
+ const fonts: SatoriOptions["fonts"] = [
+ {
+ name: "Roboto",
+ data: await readFile(resolve(__fonts, "Roboto-Regular.ttf")),
+ weight: 400,
+ style: "normal",
+ },
+ {
+ name: "Roboto",
+ data: await readFile(resolve(__fonts, "Roboto-Medium.ttf")),
+ weight: 500,
+ style: "normal",
+ },
+ {
+ name: "Roboto",
+ data: await readFile(resolve(__fonts, "Roboto-Bold.ttf")),
+ weight: 700,
+ style: "normal",
+ },
+ ]
+
+ const filteredPages = pages.filter((p) => p.frontmatter.image === undefined)
+
+ for (const page of filteredPages) {
+ await generateImage({
+ page,
+ template,
+ outDir: config.outDir,
+ fonts,
+ })
+ }
+}
+
+export default generateOgImages
+
+interface GenerateImagesOptions {
+ page: ContentData
+ template: string
+ outDir: string
+ fonts: SatoriOptions["fonts"]
+}
+
+function getDir(url: string) {
+ if (url.startsWith("/manuals/faq/")) {
+ return "FAQ"
+ } else if (url.startsWith("/manuals/guides/")) {
+ return "Guide"
+ } else if (url.startsWith("/news/") && url !== "/news/") {
+ return "News"
+ } else if (url.startsWith("/sandbox/")) {
+ return "Sandbox"
+ } else if (url !== "/manuals/faq/" && url !== "/manuals/guides/" && url !== "/news/" && url !== "/sandbox/") {
+ return "Other"
+ }
+
+ return undefined
+}
+
+async function generateImage({ page, template, outDir, fonts }: GenerateImagesOptions) {
+ const { frontmatter, url } = page
+
+ const options: SatoriOptions = {
+ width: 1200,
+ height: 628,
+ fonts,
+ props: {
+ title:
+ frontmatter.layout === "home"
+ ? frontmatter.main.name ?? frontmatter.title
+ : frontmatter.customMetaTitle ?? frontmatter.title,
+ description:
+ frontmatter.layout === "home"
+ ? frontmatter.main.tagline ?? frontmatter.description
+ : frontmatter.description,
+ dir: getDir(url),
+ },
+ }
+
+ const svg = await satoriVue(options, template)
+
+ const render = await renderAsync(svg, {
+ fitTo: {
+ mode: "width",
+ value: 1200,
+ },
+ })
+
+ const outputFolder = resolve(outDir, url.substring(1), "__og_image__")
+ const outputFile = resolve(outputFolder, "og.png")
+
+ await mkdir(outputFolder, { recursive: true })
+
+ return await writeFile(outputFile, render.asPng())
+}
\ No newline at end of file
diff --git a/.vitepress/config/index.ts b/.vitepress/config/index.ts
new file mode 100644
index 0000000..d5b17fa
--- /dev/null
+++ b/.vitepress/config/index.ts
@@ -0,0 +1,130 @@
+import type { Theme } from '../theme/types'
+import type { DocsPageData } from '../theme/plugins/component'
+import { type HeadConfig, defineConfigWithTheme } from 'vitepress'
+import { config as ru, searchLocale as searchLocaleRu } from './ru'
+import { config as root, searchLocale as searchLocaleEn } from './en'
+import { config as ua, searchLocale as searchLocaleUa } from './ua'
+import { addPlugins } from '../theme/plugins/markdown'
+import { components, prepareData } from '../theme/plugins/component'
+import { slugify } from 'transliteration'
+import { fileURLToPath, URL } from 'node:url'
+import { telegram } from '../../website/icons'
+import { normalize } from 'vitepress/dist/client/shared'
+import { tabsMarkdownPlugin } from "vitepress-plugin-tabs"
+
+import shortcode_plugin from "markdown-it-shortcode-tag"
+import shortcodes from "./shortcodes"
+
+import generateOgImages from "./hooks/generateOgImages"
+import generateMeta from "./hooks/generateMeta"
+
+const SITE_HOST = 'http://86.57.183.214:4173'
+const SITE_TITLE = 'kotatsu.app'
+const SITE_TITLE_SEPARATOR = ' / '
+
+export default defineConfigWithTheme({
+ lastUpdated: true,
+ cleanUrls: true,
+
+ title: SITE_TITLE,
+ titleTemplate: ':title' + SITE_TITLE_SEPARATOR + SITE_TITLE,
+ srcDir: './website',
+
+ markdown: {
+ theme: {
+ light: 'github-light',
+ dark: 'one-dark-pro',
+ },
+
+ anchor: {
+ slugify(str) {
+ str = str.trim()
+ .replace(/^\d*/g, '') // Удаление чисел из начала строки
+ .replace(/[^a-zA-Zа-яА-ЯЁё0-9\-\s]/g, '') // Удаление ненужных символов
+ .replace(/\s\-\s/, '-').replace(/\-+/g, '-') // Избавление от повторяющихся символов
+ .replace(/^(.{25}[^\s]*).*/, '$1') // Ограничение количества символов
+
+ return encodeURIComponent(slugify(str, { lowercase: true }))
+ }
+ },
+
+ config (md) {
+ addPlugins(md)
+ md.use(tabsMarkdownPlugin)
+ md.use(shortcode_plugin, shortcodes)
+ },
+ },
+
+ head: [
+ ['link', { rel: 'preconnect', href: 'https://fonts.googleapis.com' }],
+ ['link', { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' }],
+ ['link', { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap' }],
+
+ ['link', { rel: 'icon', href: '/favicon.ico?v=2', sizes: 'any' }],
+ ['link', { rel: 'icon', href: '/icon.svg?v=2', type: 'image/svg+xml' }],
+ ['link', { rel: 'apple-touch-icon', href: '/apple-touch-icon.png?v=2' }],
+ ['link', { rel: 'manifest', href: '/site.webmanifest' }],
+ ],
+
+ themeConfig: {
+ titleSeparator: SITE_TITLE_SEPARATOR,
+ i18nRouting: true,
+
+ logo: {
+ light: '/logo.svg',
+ dark: '/logo-dark.svg',
+ },
+
+ socialLinks: [
+ {
+ icon: { svg: telegram },
+ link: 'https://t.me/kotatsuapp',
+ },
+ { icon: 'github', link: 'https://github.com/KotatsuApp/Kotatsu' },
+ ],
+
+ search: {
+ provider: 'local',
+ },
+
+ components,
+ },
+
+ locales: {
+ ...root,
+ ...ru,
+ ...ua,
+ },
+
+ transformPageData(pageData, { siteConfig }) {
+ return prepareData(pageData, siteConfig)
+ },
+
+ transformHead: async (context) => generateMeta(context, SITE_HOST),
+
+ vite: {
+ resolve: {
+ alias: [
+ 'VPSidebar',
+ 'VPNavBarTranslations',
+ 'VPNavScreenTranslations',
+ 'VPNavBar',
+ 'VPNavBarMenu',
+ 'VPNavScreenMenu'
+ ].map(componentName => ({
+ find: new RegExp(`^.*\/${componentName}\.vue$`),
+ replacement: fileURLToPath(
+ new URL(`../theme/components/${componentName.replace(/^VP/, '')}.vue`, import.meta.url)
+ )
+ })),
+ },
+ },
+
+ sitemap: {
+ hostname: SITE_HOST,
+ },
+
+ buildEnd: async (context) => {
+ generateOgImages(context)
+ }
+})
\ No newline at end of file
diff --git a/.vitepress/config/ru.ts b/.vitepress/config/ru.ts
new file mode 100644
index 0000000..7b301aa
--- /dev/null
+++ b/.vitepress/config/ru.ts
@@ -0,0 +1,111 @@
+import type { DefaultTheme, LocaleConfig } from 'vitepress'
+import type { Theme } from '../theme/types'
+import { components } from '../theme/plugins/component'
+import 'dotenv/config'
+
+export const SITE_NAME = 'Kotatsu Website'
+export const META_DESCRIPTION = 'Простая и удобная читалка манги с открытым исходным кодом от сообщества и для сообщества, где вы можете найти и читать вашу любимую мангу проще как никогда.'
+
+export const searchLocale: Record>> = {
+ ru: {
+ translations: {
+ button: {
+ buttonText: 'Поиск',
+ buttonAriaLabel: 'Поиск'
+ },
+ modal: {
+ resetButtonTitle: 'Сбросить',
+ backButtonTitle: 'Закрыть',
+ noResultsText: 'Нет результатов по запросу',
+ footer: {
+ selectText: 'для выбора',
+ selectKeyAriaLabel: 'enter',
+ navigateText: 'для навигации',
+ navigateUpKeyAriaLabel: 'стрелка вверх',
+ navigateDownKeyAriaLabel: 'стрелка вниз',
+ closeText: 'закрыть',
+ closeKeyAriaLabel: 'escape'
+ }
+ }
+ }
+ },
+}
+
+export const config: LocaleConfig = {
+ ru: {
+ label: 'Русский',
+ lang: 'ru',
+ description: META_DESCRIPTION,
+
+ head: [
+ ['meta', { property: 'og:site_name', content: SITE_NAME }],
+ ],
+
+ themeConfig: {
+ nav: getNav(),
+
+ sidebar: {
+
+ },
+
+ outline: {
+ label: 'На этой странице',
+ level: 'deep',
+ },
+
+ returnToTopLabel: 'Наверх',
+ sidebarMenuLabel: 'Меню',
+ darkModeSwitchLabel: 'Тема',
+ langMenuLabel: 'Изменить язык',
+ teamSectionTitle: 'Команда',
+
+ lastUpdated: {
+ text: 'Последнее обновление',
+ },
+
+ docFooter: {
+ prev: 'Предыдущая страница',
+ next: 'Следующая страница',
+ },
+ editLink: {
+ pattern: 'https://github.com/KotatsuApp/kotatsuapp.github.io/edit/master/website/:path',
+ text: 'Предложить изменения на этой странице',
+ },
+ footer: {
+ message: "Лицензия GPL-3.0 | Политика конфиденциальности ",
+ copyright: `Copyright © 2020 - ${new Date().getFullYear()} Kotatsu Developers`,
+ },
+
+ components: components.filter(component => component.path.startsWith('ru/components/')),
+ },
+ }
+}
+
+function getNav(): DefaultTheme.NavItem[] {
+ return [
+ {
+ text: 'v{app_version}',
+ activeMatch: "^/*?(download|changelogs)/*?$",
+ items: [
+ {
+ text: "Скачать",
+ link: "/ru/download/",
+ },
+ {
+ text: "Изменения в версиях",
+ link: "/ru/changelogs/",
+ },
+ ],
+ },
+ {
+ text: "Инструкции",
+ link: "/ru/manuals/guides/getting-started",
+ activeMatch: "/ru/manuals/",
+ },
+ {
+ text: "Новости",
+ link: "/ru/news/",
+ activeMatch: "/ru/news/",
+ },
+ ]
+}
\ No newline at end of file
diff --git a/.vitepress/config/shortcodes.ts b/.vitepress/config/shortcodes.ts
new file mode 100644
index 0000000..ca79d3b
--- /dev/null
+++ b/.vitepress/config/shortcodes.ts
@@ -0,0 +1,89 @@
+const iconMappings = {
+ about: 'information-outline ',
+ bookmark: 'bookmark-outline ',
+ bookPage: ' ',
+ compassOutline: ' ',
+ dataPrivacy: ' ',
+ dice: 'dice-5-outline ',
+ dotsHorizontal: ' ',
+ downloadOutline: ' ',
+ favourite: 'heart-outline ',
+ feed: 'rss ',
+ history: ' ',
+ local: ' ',
+ paletteOutline: ' ',
+ sources: ' ',
+ services: ' ',
+ threeDots: 'dots-vertical ',
+ web: ' '
+}
+
+interface Navigation {
+ name: string
+ icon?: string
+ dependsOn?: string
+}
+
+const navigationMappings: Record = {
+ // Main menus
+ "main_three_dots": { name: "Three dots", icon: iconMappings.threeDots },
+ "main_history": { name: "History", icon: iconMappings.history },
+ "main_favourites": { name: "Favourites", icon: iconMappings.favourite },
+ "main_explore": { name: "Explore", icon: iconMappings.compassOutline },
+ "main_feed": { name: "Feed", icon: iconMappings.feed },
+ "main_settings": { name: "Settings", dependsOn: "main_three_dots" },
+
+ // Explore
+ "explore_local_storage" : { name: "Local storage", icon: iconMappings.local, dependsOn: "main_explore" },
+ "explore_bookmarks" : { name: "Bookmarks", icon: iconMappings.bookmark, dependsOn: "main_explore" },
+ "explore_random" : { name: "Random", icon: iconMappings.dice, dependsOn: "main_explore" },
+ "explore_downloads" : { name: "Downloads", icon: iconMappings.downloadOutline, dependsOn: "main_explore" },
+ "explore_manage_sources" : { name: "Manage", dependsOn: "main_explore" },
+
+ // Details
+ "details_tracking" : { name: "Tracking", dependsOn: "main_three_dots" },
+
+ // Favourites
+ "favourites_three_dots": { name: "Three dots", icon: iconMappings.threeDots, dependsOn: "main_favourites" },
+ "favourites_manage_categories" : { name: "Manage categories", dependsOn: "favourites_three_dots" },
+
+ // Settings submenu
+ "appearance": { name: "Appearance", icon: iconMappings.paletteOutline, dependsOn: "main_settings" },
+ "sources": { name: "Manga sources", icon: iconMappings.sources, dependsOn: "main_settings" },
+ "reader": { name: "Reader settings", icon: iconMappings.bookPage, dependsOn: "main_settings" },
+ "network": { name: "Network", icon: iconMappings.web, dependsOn: "main_settings" },
+ "data": { name: "Data and privacy", icon: iconMappings.dataPrivacy, dependsOn: "main_settings" },
+ "downloads": { name: "Downloads", icon: iconMappings.downloadOutline, dependsOn: "main_settings" },
+ "checking": { name: "Check for new chapters", icon: iconMappings.feed, dependsOn: "main_settings" },
+ "services": { name: "Services", icon: iconMappings.services, dependsOn: "main_settings" },
+ "about": { name: "About", icon: iconMappings.about, dependsOn: "main_settings" },
+}
+
+function generateNavigationHtml(navKey: string) {
+ const navData = navigationMappings[navKey]
+
+ if (!navData) {
+ return "Unsupported Navigation! "
+ }
+
+ const { name, icon, dependsOn } = navData
+
+ const iconHtml = icon ?? ""
+ let html = `${iconHtml}${name} `
+
+ if (dependsOn) {
+ html = `${generateNavigationHtml(dependsOn)} arrow-right-thin ${html}`
+ }
+
+ return html
+}
+
+const shortcodes = {
+ nav: {
+ render({ to }) {
+ return generateNavigationHtml(to)
+ },
+ },
+}
+
+export default shortcodes
\ No newline at end of file
diff --git a/.vitepress/config/ua.ts b/.vitepress/config/ua.ts
new file mode 100644
index 0000000..a8cee5f
--- /dev/null
+++ b/.vitepress/config/ua.ts
@@ -0,0 +1,109 @@
+import type { DefaultTheme, LocaleConfig } from 'vitepress'
+import type { Theme } from '../theme/types'
+import { components } from '../theme/plugins/component'
+
+export const SITE_NAME = 'Kotatsu Website'
+export const META_DESCRIPTION = 'Проста і зручна читалка манги з відкритим вихідним кодом від спільноти і для спільноти, де ви можете знайти і читати вашу улюблену мангу простіше як ніколи.'
+
+export const searchLocale: Record>> = {
+ ru: {
+ translations: {
+ button: {
+ buttonText: 'Пошук',
+ buttonAriaLabel: 'Пошук'
+ },
+ modal: {
+ resetButtonTitle: 'Скинути',
+ backButtonTitle: 'Закрити',
+ noResultsText: 'Немає результатів за запитом',
+ footer: {
+ selectText: 'для вибору',
+ selectKeyAriaLabel: 'enter',
+ navigateText: 'для навігації',
+ navigateUpKeyAriaLabel: 'стрілка вгору',
+ navigateDownKeyAriaLabel: 'стрілка донизу',
+ closeText: 'закрити',
+ closeKeyAriaLabel: 'escape'
+ }
+ }
+ }
+ },
+ }
+
+export const config: LocaleConfig = {
+ ua: {
+ label: 'Українська',
+ lang: 'ua',
+ description: META_DESCRIPTION,
+
+ head: [
+ ['meta', { property: 'og:site_name', content: SITE_NAME }],
+ ],
+
+ themeConfig: {
+ nav: getNav(),
+
+ sidebar: {
+
+ },
+
+ outline: {
+ label: 'На цій сторінці',
+ level: 'deep',
+ },
+
+ returnToTopLabel: 'Наверх',
+ sidebarMenuLabel: 'Меню',
+ darkModeSwitchLabel: 'Тема',
+ langMenuLabel: 'Змінити мову',
+
+ lastUpdated: {
+ text: 'Останнє оновлення',
+ },
+
+ docFooter: {
+ prev: 'Попередня сторінка',
+ next: 'Наступна сторінка',
+ },
+ editLink: {
+ pattern: 'https://github.com/KotatsuApp/kotatsuapp.github.io/edit/master/website/:path',
+ text: 'Запропонувати зміни на цій сторінці',
+ },
+ footer: {
+ message: "Ліцензія GPL-3.0 | Політика конфіденційності ",
+ copyright: `Copyright © 2020 - ${new Date().getFullYear()} Kotatsu Developers`,
+ },
+
+ components: components.filter(component => component.path.startsWith('ru/components/')),
+ }
+ }
+}
+
+function getNav(): DefaultTheme.NavItem[] {
+ return [
+ {
+ text: 'v{app_version}',
+ activeMatch: "^/*?(download|changelogs)/*?$",
+ items: [
+ {
+ text: "Завантажити",
+ link: "/ua/download/",
+ },
+ {
+ text: "Зміни у версіях",
+ link: "/ua/changelogs/",
+ },
+ ],
+ },
+ {
+ text: "Інструкції",
+ link: "/ua/manuals/guides/getting-started",
+ activeMatch: "/ua/manuals/",
+ },
+ {
+ text: "Новини",
+ link: "/ua/news/",
+ activeMatch: "/ua/news/",
+ },
+ ]
+ }
\ No newline at end of file
diff --git a/.vitepress/fonts/Montserrat-Bold.ttf b/.vitepress/fonts/Montserrat-Bold.ttf
new file mode 100644
index 0000000..0927b81
Binary files /dev/null and b/.vitepress/fonts/Montserrat-Bold.ttf differ
diff --git a/.vitepress/fonts/Montserrat-Medium.ttf b/.vitepress/fonts/Montserrat-Medium.ttf
new file mode 100644
index 0000000..4012225
Binary files /dev/null and b/.vitepress/fonts/Montserrat-Medium.ttf differ
diff --git a/.vitepress/fonts/Montserrat-Regular.ttf b/.vitepress/fonts/Montserrat-Regular.ttf
new file mode 100644
index 0000000..f4a266d
Binary files /dev/null and b/.vitepress/fonts/Montserrat-Regular.ttf differ
diff --git a/.vitepress/fonts/Montserrat-SemiBold.ttf b/.vitepress/fonts/Montserrat-SemiBold.ttf
new file mode 100644
index 0000000..189ce9d
Binary files /dev/null and b/.vitepress/fonts/Montserrat-SemiBold.ttf differ
diff --git a/.vitepress/fonts/Roboto-Bold.ttf b/.vitepress/fonts/Roboto-Bold.ttf
new file mode 100644
index 0000000..43da14d
Binary files /dev/null and b/.vitepress/fonts/Roboto-Bold.ttf differ
diff --git a/.vitepress/fonts/Roboto-Medium.ttf b/.vitepress/fonts/Roboto-Medium.ttf
new file mode 100644
index 0000000..ac0f908
Binary files /dev/null and b/.vitepress/fonts/Roboto-Medium.ttf differ
diff --git a/.vitepress/fonts/Roboto-Regular.ttf b/.vitepress/fonts/Roboto-Regular.ttf
new file mode 100644
index 0000000..ddf4bfa
Binary files /dev/null and b/.vitepress/fonts/Roboto-Regular.ttf differ
diff --git a/.vitepress/theme/components/Breadcrumbs.vue b/.vitepress/theme/components/Breadcrumbs.vue
new file mode 100644
index 0000000..7f90e2f
--- /dev/null
+++ b/.vitepress/theme/components/Breadcrumbs.vue
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/Button.vue b/.vitepress/theme/components/Button.vue
new file mode 100644
index 0000000..5ef7e03
--- /dev/null
+++ b/.vitepress/theme/components/Button.vue
@@ -0,0 +1,136 @@
+
+
+
+
+ {{ text }}
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/Changelog.vue b/.vitepress/theme/components/Changelog.vue
new file mode 100644
index 0000000..9606b31
--- /dev/null
+++ b/.vitepress/theme/components/Changelog.vue
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+ View the full release
+
+ here
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/ChangelogsList.vue b/.vitepress/theme/components/ChangelogsList.vue
new file mode 100644
index 0000000..76e6f9c
--- /dev/null
+++ b/.vitepress/theme/components/ChangelogsList.vue
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+ {{ dateFormatter.format(new Date(release.published_at!)) }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/Contributors.vue b/.vitepress/theme/components/Contributors.vue
new file mode 100644
index 0000000..7d704d0
--- /dev/null
+++ b/.vitepress/theme/components/Contributors.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
Contributors
+
+
+ {{ contributorsText }}
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/DownloadButtons.vue b/.vitepress/theme/components/DownloadButtons.vue
new file mode 100644
index 0000000..4797205
--- /dev/null
+++ b/.vitepress/theme/components/DownloadButtons.vue
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+ Unsupported operating system
+
+
+ Kotatsu is an Android app only.
+ Use an Android device to download and install the app.
+
+
+
+
+ Caution
+
+
+ Any app for any operating systems other than Android called
+ Kotatsu (except kotatsu-dl) is not affiliated with this project.
+
+
+ For more information, read the
+ General FAQ .
+
+
+
+
+ Requires Android 5.0 or higher.
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/Feature.vue b/.vitepress/theme/components/Feature.vue
new file mode 100644
index 0000000..75ebc4f
--- /dev/null
+++ b/.vitepress/theme/components/Feature.vue
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/Features.vue b/.vitepress/theme/components/Features.vue
new file mode 100644
index 0000000..a8c73cb
--- /dev/null
+++ b/.vitepress/theme/components/Features.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/Home.vue b/.vitepress/theme/components/Home.vue
new file mode 100644
index 0000000..3b41bdb
--- /dev/null
+++ b/.vitepress/theme/components/Home.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/HomeHero.vue b/.vitepress/theme/components/HomeHero.vue
new file mode 100644
index 0000000..eaea1b5
--- /dev/null
+++ b/.vitepress/theme/components/HomeHero.vue
@@ -0,0 +1,143 @@
+
+
+
+
+
+ {{ data.title }}
+
+
+ {{ data.text }}
+
+
+ {{ data.tagline }}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/Layout.vue b/.vitepress/theme/components/Layout.vue
new file mode 100644
index 0000000..6f1f1a4
--- /dev/null
+++ b/.vitepress/theme/components/Layout.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/List.vue b/.vitepress/theme/components/List.vue
new file mode 100644
index 0000000..02b4bdf
--- /dev/null
+++ b/.vitepress/theme/components/List.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+ {{ item.text }}
+
+
+ {{ item.text }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/NavBar.vue b/.vitepress/theme/components/NavBar.vue
new file mode 100644
index 0000000..810dc64
--- /dev/null
+++ b/.vitepress/theme/components/NavBar.vue
@@ -0,0 +1,232 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/NavBarMenu.vue b/.vitepress/theme/components/NavBarMenu.vue
new file mode 100644
index 0000000..b0cec09
--- /dev/null
+++ b/.vitepress/theme/components/NavBarMenu.vue
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/NavBarTranslations.vue b/.vitepress/theme/components/NavBarTranslations.vue
new file mode 100644
index 0000000..ec202c3
--- /dev/null
+++ b/.vitepress/theme/components/NavBarTranslations.vue
@@ -0,0 +1,48 @@
+
+
+
+
+
+
{{ currentLang.label }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/NavScreenMenu.vue b/.vitepress/theme/components/NavScreenMenu.vue
new file mode 100644
index 0000000..280ef53
--- /dev/null
+++ b/.vitepress/theme/components/NavScreenMenu.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/NavScreenTranslations.vue b/.vitepress/theme/components/NavScreenTranslations.vue
new file mode 100644
index 0000000..5ea139b
--- /dev/null
+++ b/.vitepress/theme/components/NavScreenTranslations.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+ {{ currentLang.label }}
+
+
+
+
+
+ {{ locale.text }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/NotFound.vue b/.vitepress/theme/components/NotFound.vue
new file mode 100644
index 0000000..c7f42a7
--- /dev/null
+++ b/.vitepress/theme/components/NotFound.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
{{ theme.notFound?.code ?? 'Four-Oh-Four' }}
+
{{ theme.notFound?.title ?? 'Page not found' }}
+
+
+
+ {{ theme.notFound?.quote }}
+
+
+ Похоже, что вы перешли по неверной или устаревшей ссылке.
+ Информация, которую вы искали, где-то здесь. Вы можете воспользоваться поиском.
+
+ Этот сайт автоматически генерируется из файлов, расположенных на GitHub, поэтому адреса могут иногда меняться.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/OgImageTemplate.vue b/.vitepress/theme/components/OgImageTemplate.vue
new file mode 100644
index 0000000..04f5fe1
--- /dev/null
+++ b/.vitepress/theme/components/OgImageTemplate.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/ReleaseDate.vue b/.vitepress/theme/components/ReleaseDate.vue
new file mode 100644
index 0000000..5565bdd
--- /dev/null
+++ b/.vitepress/theme/components/ReleaseDate.vue
@@ -0,0 +1,30 @@
+
+
+
+
+ {{ momentInfo.relative }}
+
+
+ {{ momentInfo.exact }}
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/ScreenAuthorWidget.vue b/.vitepress/theme/components/ScreenAuthorWidget.vue
new file mode 100644
index 0000000..e21f99b
--- /dev/null
+++ b/.vitepress/theme/components/ScreenAuthorWidget.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+ {{ translatorLabel }}:
+ {{ translator.name }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/SearchBar.vue b/.vitepress/theme/components/SearchBar.vue
new file mode 100644
index 0000000..b92ac4d
--- /dev/null
+++ b/.vitepress/theme/components/SearchBar.vue
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/Sidebar.vue b/.vitepress/theme/components/Sidebar.vue
new file mode 100644
index 0000000..9df173f
--- /dev/null
+++ b/.vitepress/theme/components/Sidebar.vue
@@ -0,0 +1,186 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/TranslatorWidget.vue b/.vitepress/theme/components/TranslatorWidget.vue
new file mode 100644
index 0000000..4ea00de
--- /dev/null
+++ b/.vitepress/theme/components/TranslatorWidget.vue
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
{{ translator.name }}
+
{{ translatorLabel }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/Tree.vue b/.vitepress/theme/components/Tree.vue
new file mode 100644
index 0000000..7d6fc4e
--- /dev/null
+++ b/.vitepress/theme/components/Tree.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/components/TreeItem.vue b/.vitepress/theme/components/TreeItem.vue
new file mode 100644
index 0000000..e3a2b8a
--- /dev/null
+++ b/.vitepress/theme/components/TreeItem.vue
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+ {{ props.item.text }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vitepress/theme/composables/langs.ts b/.vitepress/theme/composables/langs.ts
new file mode 100644
index 0000000..2224a80
--- /dev/null
+++ b/.vitepress/theme/composables/langs.ts
@@ -0,0 +1,114 @@
+import { Ref, computed } from 'vue'
+import type { DefaultTheme, SiteData } from 'vitepress'
+import type { DocsPageData } from '../plugins/component'
+import { useData } from 'vitepress'
+import { ensureStartingSlash } from '../utils'
+import { getFlatSideBarLinks } from 'vitepress/dist/client/theme-default/support/sidebar'
+
+export function useLangs({
+ removeCurrent = true,
+ correspondingLink = false
+} = {}) {
+ interface Data {
+ site: Ref
+ localeIndex: Ref
+ page: Ref
+ theme: Ref
+ }
+
+ const { site, localeIndex, page, theme }: Data = useData()
+ const currentLang = computed(() => ({
+ label: site.value.locales[localeIndex.value]?.label,
+ link:
+ site.value.locales[localeIndex.value]?.link ||
+ (localeIndex.value === 'root' ? '/' : `/${localeIndex.value}/`)
+ }))
+
+ const localeLinks = computed(() =>
+ Object.entries(site.value.locales).flatMap(([key, value]) => {
+ if (removeCurrent && currentLang.value.label === value.label) {
+ return []
+ }
+
+ const text = value.label
+ const rootLink = value.link || (key === 'root' ? '/' : `/${key}/`)
+ let link = normalizeLink(
+ rootLink,
+ theme.value.i18nRouting !== false && correspondingLink,
+ page.value.relativePath.slice(currentLang.value.link.length - 1),
+ !site.value.cleanUrls
+ )
+
+ if (
+ link === '/' ||
+ Object.entries(site.value.locales).some(([key]) => link.replace(/^\//, '').replace(/\/$/, '') === key)
+ ) {
+ return {
+ text,
+ link
+ }
+ }
+
+ const { themeConfig } = site.value.locales[key]
+ const { nav, sidebar } = themeConfig
+
+ for (const item of nav) {
+ if (Object.prototype.hasOwnProperty.call(item, 'link') && item.link === link) {
+ return {
+ text,
+ link
+ }
+ }
+ }
+
+ for (const data of Object.values(sidebar)) {
+ const flatSidebar = getFlatSideBarLinks(data)
+
+ for (const item of flatSidebar) {
+ if (item.link === link) {
+ return {
+ text,
+ link
+ }
+ }
+ }
+ }
+
+ if (!page.value.component) {
+ return {
+ text: value.label,
+ link: rootLink,
+ }
+ }
+
+ const component = themeConfig.components.find(component => component.title === page.value.component.title)
+ if (!component) {
+ return {
+ text: value.label,
+ link: rootLink,
+ }
+ }
+
+ return {
+ text,
+ link: component.link
+ }
+ }
+ ))
+
+ return { localeLinks, currentLang }
+}
+
+function normalizeLink(
+ link: string,
+ addPath: boolean,
+ path: string,
+ addExt: boolean
+): string {
+ return addPath
+ ? link.replace(/\/$/, '') +
+ ensureStartingSlash(path
+ .replace(/(^|\/)?index.md$/, '$1')
+ .replace(/\.md$/, addExt ? '.html' : ''))
+ : link
+}
\ No newline at end of file
diff --git a/.vitepress/theme/composables/prev-next.ts b/.vitepress/theme/composables/prev-next.ts
new file mode 100644
index 0000000..6e7ce8c
--- /dev/null
+++ b/.vitepress/theme/composables/prev-next.ts
@@ -0,0 +1,66 @@
+import { computed } from 'vue'
+import type { ComputedRef, Ref } from 'vue'
+import type { DocsPageData } from '../plugins/component'
+import { useData } from 'vitepress'
+import { isActive } from 'vitepress/dist/client/shared'
+import { getFlatSideBarLinks } from 'vitepress/dist/client/theme-default/support/sidebar'
+
+export function usePrevNext(): ComputedRef {
+ interface Data {
+ theme: Ref
+ page: Ref
+ frontmatter: Ref
+ }
+
+ const { page, theme, frontmatter }: Data = useData()
+
+ return computed(() => {
+ const component = page.value.component
+
+ if (!component) {
+ return ''
+ }
+
+ const candidates = getFlatSideBarLinks([component])
+ const index = candidates.findIndex((link) => isActive(page.value.relativePath, link.link))
+
+ const hidePrev =
+ (theme.value.docFooter?.prev === false && !frontmatter.value.prev) ||
+ frontmatter.value.prev === false
+
+ const hideNext =
+ (theme.value.docFooter?.next === false && !frontmatter.value.next) ||
+ frontmatter.value.next === false
+
+ return {
+ prev: hidePrev
+ ? undefined
+ : {
+ text: (typeof frontmatter.value.prev === 'string'
+ ? frontmatter.value.prev
+ : typeof frontmatter.value.prev === 'object'
+ ? frontmatter.value.prev.text
+ : undefined) ??
+ candidates[index - 1]?.docFooterText ??
+ candidates[index - 1]?.text,
+ link: (typeof frontmatter.value.prev === 'object'
+ ? frontmatter.value.prev.link
+ : undefined) ?? candidates[index - 1]?.link
+ },
+ next: hideNext
+ ? undefined
+ : {
+ text: (typeof frontmatter.value.next === 'string'
+ ? frontmatter.value.next
+ : typeof frontmatter.value.next === 'object'
+ ? frontmatter.value.next.text
+ : undefined) ??
+ candidates[index + 1]?.docFooterText ??
+ candidates[index + 1]?.text,
+ link: (typeof frontmatter.value.next === 'object'
+ ? frontmatter.value.next.link
+ : undefined) ?? candidates[index + 1]?.link
+ },
+ }
+ })
+}
\ No newline at end of file
diff --git a/.vitepress/theme/composables/zoom.ts b/.vitepress/theme/composables/zoom.ts
new file mode 100644
index 0000000..da838fb
--- /dev/null
+++ b/.vitepress/theme/composables/zoom.ts
@@ -0,0 +1,71 @@
+import 'glightbox/dist/css/glightbox.css'
+import { scrollbarWidth } from '@xobotyi/scrollbar-width'
+
+import {
+ watch,
+ type App,
+} from 'vue'
+import type { Router } from 'vitepress'
+
+export const glightboxSymbol = Symbol('glightbox')
+
+export async function createZoom(app: App, router: Router) {
+ if (import.meta.env.SSR) {
+ return
+ }
+
+ const GLightbox = await import('glightbox')
+
+ const lightbox = GLightbox.default({
+ openEffect: 'fade',
+ closeEffect: 'fade',
+ zoomable: true,
+ skin: 'white',
+ svg: {
+ close: ' ',
+ next: ' ',
+ prev: ' ',
+ },
+ })
+
+ lightbox.reload = () => {
+ const elements = Array.from(document.querySelectorAll('.vp-doc img'))
+ .map((element, index) => {
+ const target = element.parentElement instanceof HTMLAnchorElement ? element.parentElement : element
+ target.addEventListener('click', (e) => {
+ e.preventDefault()
+ lightbox.openAt(index)
+ })
+
+ const href = target.getAttribute(target instanceof HTMLAnchorElement ? 'href' : 'src')
+ const title = element.getAttribute('alt')
+
+ return {
+ href,
+ title,
+ type: 'image',
+ }
+ })
+
+ lightbox.setElements(elements)
+ }
+
+ const scrollWidth = scrollbarWidth()
+
+ lightbox.on('open', () => updateFixedElements(scrollWidth))
+ lightbox.on('close', () => updateFixedElements())
+
+ app.provide(glightboxSymbol, lightbox)
+
+ watch(
+ () => router.route.data.relativePath,
+ () => setTimeout(() => {
+ lightbox.reload()
+ }),
+ { immediate: true }
+ )
+}
+
+function updateFixedElements(scrollWidth?: number) {
+ document.documentElement.style.setProperty('--scrollbar-width', scrollWidth ? scrollWidth + 'px' : '')
+}
\ No newline at end of file
diff --git a/.vitepress/theme/data/changelogs.data.ts b/.vitepress/theme/data/changelogs.data.ts
new file mode 100644
index 0000000..3817512
--- /dev/null
+++ b/.vitepress/theme/data/changelogs.data.ts
@@ -0,0 +1,22 @@
+import { defineLoader } from "vitepress"
+import { Octokit } from "@octokit/rest"
+import type { GetResponseDataTypeFromEndpointMethod } from "@octokit/types"
+
+const octokit = new Octokit()
+
+type GitHubReleaseList = GetResponseDataTypeFromEndpointMethod
+
+declare const data: GitHubReleaseList
+export { data }
+
+export default defineLoader({
+ async load(): Promise {
+ const releases = await octokit.paginate(octokit.repos.listReleases, {
+ owner: "KotatsuApp",
+ repo: "Kotatsu",
+ per_page: 100,
+ })
+
+ return releases
+ },
+})
\ No newline at end of file
diff --git a/.vitepress/theme/data/release.data.ts b/.vitepress/theme/data/release.data.ts
new file mode 100644
index 0000000..db339e4
--- /dev/null
+++ b/.vitepress/theme/data/release.data.ts
@@ -0,0 +1,25 @@
+import { defineLoader } from "vitepress"
+import { Octokit } from "@octokit/rest"
+import type { GetResponseDataTypeFromEndpointMethod } from "@octokit/types"
+
+const octokit = new Octokit()
+
+type GitHubRelease = GetResponseDataTypeFromEndpointMethod
+
+export interface AppRelease {
+ stable: GitHubRelease
+}
+
+declare const data: AppRelease
+export { data }
+
+export default defineLoader({
+ async load(): Promise {
+ const { data: stable } = await octokit.repos.getLatestRelease({
+ owner: "KotatsuApp",
+ repo: "Kotatsu",
+ })
+
+ return { stable }
+ },
+})
\ No newline at end of file
diff --git a/.vitepress/theme/index.ts b/.vitepress/theme/index.ts
new file mode 100644
index 0000000..9113c1e
--- /dev/null
+++ b/.vitepress/theme/index.ts
@@ -0,0 +1,33 @@
+import { type Router, inBrowser } from 'vitepress'
+import { type App, watch, defineComponent } from 'vue'
+import { createZoom } from './composables/zoom'
+import { enhanceAppWithTabs } from "vitepress-plugin-tabs/client"
+import analytics from "./plugins/analytics"
+import DefaultTheme from 'vitepress/theme-without-fonts'
+import Layout from './components/Layout.vue'
+import './styles/global.css'
+import './styles/glightbox.css'
+
+export default {
+ extends: DefaultTheme,
+ Layout: Layout,
+
+ enhanceApp({ app, router }: { app: App, router: Router }) {
+ enhanceAppWithTabs(app)
+ createZoom(app, router)
+ analytics({ id: "G-X37JGMJE4R" })
+ if (
+ import.meta.env.PROD &&
+ inBrowser
+ ) {
+ watch(
+ () => router.route.data.relativePath,
+ (_path, oldPath) => {
+ if (!oldPath) { // Skip initial change
+ return
+ }
+ }
+ )
+ }
+ },
+}
\ No newline at end of file
diff --git a/.vitepress/theme/plugins/analytics.ts b/.vitepress/theme/plugins/analytics.ts
new file mode 100644
index 0000000..db7f3ee
--- /dev/null
+++ b/.vitepress/theme/plugins/analytics.ts
@@ -0,0 +1,37 @@
+// Code based on vitepress-plugin-google-analytics.
+// Customized as the plugin did not consider the script loading time.
+// https://github.com/ZhongxuYang/vitepress-plugin-google-analytics
+
+function mountGoogleAnalytics(id: string) {
+ if (("dataLayer" in window && window.gtag) || window.location.hostname === "localhost") {
+ return
+ }
+
+ const analyticsScript = document.createElement("script")
+
+ analyticsScript.addEventListener("load", () => {
+ // @ts-expect-error Missing types
+ window.dataLayer = window.dataLayer || []
+ function gtag(..._args: any[]) {
+ // @ts-expect-error Missing types
+ // eslint-disable-next-line prefer-rest-params
+ window.dataLayer.push(arguments)
+ }
+
+ gtag("js", new Date())
+ gtag("config", id)
+
+ window.gtag = gtag
+ })
+
+ analyticsScript.src = `https://www.googletagmanager.com/gtag/js?id=${id}`
+
+ document.body.appendChild(analyticsScript)
+}
+
+export default function ({ id }: { id: string }) {
+ // eslint-disable-next-line n/prefer-global/process
+ if (process.env.NODE_ENV === "production" && id && typeof window !== "undefined") {
+ mountGoogleAnalytics(id)
+ }
+}
\ No newline at end of file
diff --git a/.vitepress/theme/plugins/component.ts b/.vitepress/theme/plugins/component.ts
new file mode 100644
index 0000000..2e72adb
--- /dev/null
+++ b/.vitepress/theme/plugins/component.ts
@@ -0,0 +1,109 @@
+import type { DefaultTheme, PageData, SiteConfig } from 'vitepress'
+import { normalize } from 'vitepress/dist/client/shared'
+import { ensureStartingSlash, getTranslator } from '../utils'
+
+import { readFileSync } from 'fs'
+import { basename } from 'path'
+import fg from 'fast-glob'
+import matter from 'gray-matter'
+
+import { generateSidebarItem, getTitleFromContent } from './sidebar'
+
+import type { Translator } from '../../../website/translators'
+import { findPath } from '../utils'
+
+export interface ComponentData {
+ path: string
+ link: string
+ title: string
+ titleLower: string
+ description?: string
+
+ text?: string
+ translator?: Translator
+ logo?: string
+ dependencies?: Array
+ categories?: Array
+
+ repository?: string
+
+ items?: DefaultTheme.SidebarItem[]
+}
+
+export interface DocsPageData extends PageData {
+ component?: ComponentData
+ breadcrumbs?: DefaultTheme.SidebarItem[]
+}
+
+export const components: ComponentData[] = fg
+ .sync([
+ 'website/manuals/faq/explore/*.md',
+ '!website/manuals/faq/explore/index.md',
+ ])
+ .map(file => {
+ const content = readFileSync(file, 'utf-8')
+ const { data } = matter(content)
+ const {
+ title = getTitleFromContent(content) || basename(file),
+ translator,
+ logo,
+ categories = [],
+ dependencies = [],
+ items,
+ repository,
+ description,
+ } = data
+
+ const filePath = file.substring(file.indexOf('/') + 1)
+ const component: ComponentData = {
+ path: filePath,
+ link: ensureStartingSlash(normalize(filePath)),
+ repository,
+ title,
+ titleLower: title.toLowerCase(),
+ text: title,
+ description,
+ logo,
+ dependencies: Array.isArray(dependencies) ? dependencies : Array(dependencies),
+ categories: Array.isArray(categories) ? categories : Array(categories),
+ }
+
+ component.translator = getTranslator(translator)
+
+ if (items) {
+ component.items = generateSidebarItem(items, component.link)
+ }
+
+ return component
+ })
+ .sort((a, b) => (a.text && b.text) ? a.text.localeCompare(b.text) : 0)
+
+export default class DocsComponent {
+ static prepareData(
+ pageData: DocsPageData,
+ siteConfig: SiteConfig,
+ ): DocsPageData {
+ const component = components.find(component => pageData.relativePath.startsWith(component.path.replace(/index\.md$/, '')))
+
+ pageData.component = component
+ pageData.breadcrumbs = findPath(pageData, siteConfig.userConfig)
+
+ pageData.title = !pageData.frontmatter.title && pageData.breadcrumbs.length
+ ? pageData.breadcrumbs.map(item => item.text).reverse().join(siteConfig.userConfig.themeConfig.titleSeparator)
+ : pageData.title
+
+ if (
+ component
+ && !pageData.description
+ && pageData.component.description
+ ) {
+ pageData.description = pageData.component.description
+ }
+
+ return pageData
+ }
+}
+
+export { DocsComponent }
+
+export const { prepareData } = DocsComponent
\ No newline at end of file
diff --git a/.vitepress/theme/plugins/markdown.ts b/.vitepress/theme/plugins/markdown.ts
new file mode 100644
index 0000000..759f6d3
--- /dev/null
+++ b/.vitepress/theme/plugins/markdown.ts
@@ -0,0 +1,280 @@
+import type MarkdownIt from 'markdown-it'
+import type { RenderRule } from 'markdown-it/lib/renderer'
+import type StateBlock from 'markdown-it/lib/rules_block/state_block'
+import { isSpace } from 'markdown-it/lib/common/utils'
+import container from 'markdown-it-container'
+import kbd from 'markdown-it-kbd'
+
+export const addPlugins = (md: MarkdownIt) => {
+ md.use(...createContainer('info', 'Информация', md))
+ .use(...createContainer('tip', 'Подсказка', md))
+ .use(...createContainer('warning', 'Внимание', md))
+ .use(...createContainer('danger', 'Осторожно', md))
+ .use(...createContainer('details', 'Подробнее', md))
+
+ .use(kbd)
+
+ md.block.ruler.at('table', table)
+}
+
+type ContainerArgs = [typeof container, string, { render: RenderRule }]
+
+function createContainer(
+ klass: string,
+ defaultTitle: string,
+ md: MarkdownIt
+): ContainerArgs {
+ return [
+ container,
+ klass,
+ {
+ render(tokens, idx, _options, env) {
+ const token = tokens[idx]
+ const info = token.info.trim().slice(klass.length).trim()
+ const attrs = md.renderer.renderAttrs(token)
+ if (token.nesting === 1) {
+ const title = md.renderInline(info || defaultTitle, {
+ references: env.references
+ })
+ if (klass === 'details')
+ return `${title} \n`
+ return `${title}
\n`
+ } else return klass === 'details' ? `\n` : `
\n`
+ }
+ }
+ ]
+}
+
+// from https://github.com/markdown-it/markdown-it/blob/2b6cac25823af011ff3bc7628bc9b06e483c5a08/lib/rules_block/table.js
+// GFM table, non-standard
+
+function table(
+ state: StateBlock,
+ startLine: number,
+ endLine: number,
+ silent: any
+) {
+ var ch, lineText, pos, i, l, nextLine, headers, columns, columnCount, token,
+ aligns, t, tableLines, tbodyLines, oldParentType, terminate,
+ terminatorRules, firstCh, secondCh;
+
+ // should have at least two lines
+ if (startLine + 2 > endLine) { return false; }
+
+ nextLine = startLine + 1;
+
+ if (state.sCount[nextLine] < state.blkIndent) { return false; }
+
+ // if it's indented more than 3 spaces, it should be a code block
+ if (state.sCount[nextLine] - state.blkIndent >= 4) { return false; }
+
+ // first character of the second line should be '|', '-', ':',
+ // and no other characters are allowed but spaces;
+ // basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp
+
+ pos = state.bMarks[nextLine] + state.tShift[nextLine];
+ if (pos >= state.eMarks[nextLine]) { return false; }
+
+ firstCh = state.src.charCodeAt(pos++);
+ if (firstCh !== 0x7C/* | */ && firstCh !== 0x2D/* - */ && firstCh !== 0x3A/* : */) { return false; }
+
+ if (pos >= state.eMarks[nextLine]) { return false; }
+
+ secondCh = state.src.charCodeAt(pos++);
+ if (secondCh !== 0x7C/* | */ && secondCh !== 0x2D/* - */ && secondCh !== 0x3A/* : */ && !isSpace(secondCh)) {
+ return false;
+ }
+
+ // if first character is '-', then second character must not be a space
+ // (due to parsing ambiguity with list)
+ if (firstCh === 0x2D/* - */ && isSpace(secondCh)) { return false; }
+
+ while (pos < state.eMarks[nextLine]) {
+ ch = state.src.charCodeAt(pos);
+
+ if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */ && !isSpace(ch)) { return false; }
+
+ pos++;
+ }
+
+ lineText = getLine(state, startLine + 1);
+
+ columns = lineText.split('|');
+ aligns = [];
+ for (i = 0; i < columns.length; i++) {
+ t = columns[i].trim();
+ if (!t) {
+ // allow empty columns before and after table, but not in between columns;
+ // e.g. allow ` |---| `, disallow ` ---||--- `
+ if (i === 0 || i === columns.length - 1) {
+ continue;
+ } else {
+ return false;
+ }
+ }
+
+ if (!/^:?-+:?$/.test(t)) { return false; }
+ if (t.charCodeAt(t.length - 1) === 0x3A/* : */) {
+ aligns.push(t.charCodeAt(0) === 0x3A/* : */ ? 'center' : 'right');
+ } else if (t.charCodeAt(0) === 0x3A/* : */) {
+ aligns.push('left');
+ } else {
+ aligns.push('');
+ }
+ }
+
+ lineText = getLine(state, startLine).trim();
+ if (lineText.indexOf('|') === -1) { return false; }
+ if (state.sCount[startLine] - state.blkIndent >= 4) { return false; }
+ columns = escapedSplit(lineText);
+ if (columns.length && columns[0] === '') columns.shift();
+ if (columns.length && columns[columns.length - 1] === '') columns.pop();
+
+ // header row will define an amount of columns in the entire table,
+ // and align row should be exactly the same (the rest of the rows can differ)
+ columnCount = columns.length;
+ headers = [...columns];
+ if (columnCount === 0 || columnCount !== aligns.length) { return false; }
+
+ if (silent) { return true; }
+
+ oldParentType = state.parentType;
+ // @ts-expect-error
+ state.parentType = 'table';
+
+ // use 'blockquote' lists for termination because it's
+ // the most similar to tables
+ terminatorRules = state.md.block.ruler.getRules('blockquote');
+
+ token = state.push('table_open', 'table', 1);
+ token.map = tableLines = [ startLine, 0 ];
+
+ token = state.push('thead_open', 'thead', 1);
+ token.map = [ startLine, startLine + 1 ];
+
+ token = state.push('tr_open', 'tr', 1);
+ token.map = [ startLine, startLine + 1 ];
+
+ for (i = 0; i < columns.length; i++) {
+ token = state.push('th_open', 'th', 1);
+ if (aligns[i]) {
+ token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ];
+ }
+
+ token = state.push('inline', '', 0);
+ token.content = columns[i].trim();
+ token.children = [];
+
+ token = state.push('th_close', 'th', -1);
+ }
+
+ token = state.push('tr_close', 'tr', -1);
+ token = state.push('thead_close', 'thead', -1);
+
+ for (nextLine = startLine + 2; nextLine < endLine; nextLine++) {
+ if (state.sCount[nextLine] < state.blkIndent) { break; }
+
+ terminate = false;
+ for (i = 0, l = terminatorRules.length; i < l; i++) {
+ if (terminatorRules[i](state, nextLine, endLine, true)) {
+ terminate = true;
+ break;
+ }
+ }
+
+ if (terminate) { break; }
+ lineText = getLine(state, nextLine).trim();
+ if (!lineText) { break; }
+ if (state.sCount[nextLine] - state.blkIndent >= 4) { break; }
+ columns = escapedSplit(lineText);
+ if (columns.length && columns[0] === '') columns.shift();
+ if (columns.length && columns[columns.length - 1] === '') columns.pop();
+
+ if (nextLine === startLine + 2) {
+ token = state.push('tbody_open', 'tbody', 1);
+ token.map = tbodyLines = [ startLine + 2, 0 ];
+ }
+
+ token = state.push('tr_open', 'tr', 1);
+ token.map = [ nextLine, nextLine + 1 ];
+
+ for (i = 0; i < columnCount; i++) {
+ token = state.push('td_open', 'td', 1);
+ const attrs = [];
+ if (aligns[i]) {
+ token.attrs = attrs.push([ 'style', 'text-align:' + aligns[i] ]);
+ }
+
+ attrs.push(['data-label', headers[i].trim()]);
+
+ if (attrs.length) {
+ token.attrs = attrs;
+ }
+
+ token = state.push('inline', '', 0);
+ token.content = columns[i] ? columns[i].trim() : '';
+ token.children = [];
+
+ token = state.push('td_close', 'td', -1);
+ }
+ token = state.push('tr_close', 'tr', -1);
+ }
+
+ if (tbodyLines) {
+ token = state.push('tbody_close', 'tbody', -1);
+ tbodyLines[1] = nextLine;
+ }
+
+ token = state.push('table_close', 'table', -1);
+ tableLines[1] = nextLine;
+
+ state.parentType = oldParentType;
+ state.line = nextLine;
+ return true;
+}
+
+function getLine(
+ state: StateBlock,
+ line: number
+): string {
+ var pos = state.bMarks[line] + state.tShift[line],
+ max = state.eMarks[line];
+
+ return state.src.slice(pos, max)
+}
+
+function escapedSplit(str: string): string[] {
+ var result = [],
+ pos = 0,
+ max = str.length,
+ ch,
+ isEscaped = false,
+ lastPos = 0,
+ current = '';
+
+ ch = str.charCodeAt(pos);
+
+ while (pos < max) {
+ if (ch === 0x7c/* | */) {
+ if (!isEscaped) {
+ // pipe separating cells, '|'
+ result.push(current + str.substring(lastPos, pos));
+ current = '';
+ lastPos = pos + 1;
+ } else {
+ // escaped pipe, '\|'
+ current += str.substring(lastPos, pos - 1);
+ lastPos = pos;
+ }
+ }
+
+ isEscaped = (ch === 0x5c/* \ */);
+ pos++;
+
+ ch = str.charCodeAt(pos);
+ }
+
+ result.push(current + str.substring(lastPos));
+
+ return result;
+}
\ No newline at end of file
diff --git a/.vitepress/theme/plugins/sidebar.ts b/.vitepress/theme/plugins/sidebar.ts
new file mode 100644
index 0000000..8fed9e2
--- /dev/null
+++ b/.vitepress/theme/plugins/sidebar.ts
@@ -0,0 +1,97 @@
+import type { DefaultTheme } from 'vitepress'
+import { normalize } from 'vitepress/dist/client/shared'
+import faqCategories from '../../../website/faq/categories.json'
+
+import { readFileSync } from 'fs'
+import { join, basename } from 'path'
+import fg from 'fast-glob'
+import matter from 'gray-matter'
+
+declare interface Options {
+ root: string | Array
+ ignore?: Array
+ collapsed?: boolean
+}
+
+export default class Sidebar {
+ static generateSidebar(
+ options: Options
+ ): DefaultTheme.SidebarItem[] {
+ const entries = fg.sync(options.root, options)
+
+ return entries
+ .map(path => Sidebar.getData(path, options))
+ .sort((a, b) => (a.text && b.text) ? a.text.localeCompare(b.text) : 0)
+ }
+
+ static generateSidebarItem(
+ items: DefaultTheme.SidebarItem[],
+ path: string,
+ ): DefaultTheme.SidebarItem[] {
+ items = items.map(({ text, link, items }) => {
+ const item: DefaultTheme.SidebarItem = { text }
+
+ if (link) {
+ item.link = join(path, link).replace(/\\/g, '/')
+ }
+
+ if (items) {
+ item.collapsed = true
+ item.items = Sidebar.generateSidebarItem(items, path)
+ }
+
+ return item
+ })
+
+ return items
+ }
+
+ static getData(
+ path: string,
+ options?: Partial
+ ): DefaultTheme.SidebarItem {
+ const src = readFileSync(path, 'utf-8')
+ const { data } = matter(src)
+ const {
+ title = Sidebar.getTitleFromContent(src) || basename(path),
+ items,
+ } = data
+
+ const link = normalize(path.replace(/^website/, ''))
+
+ const output: DefaultTheme.SidebarItem = {
+ text: title,
+ link,
+ }
+
+ if (items) {
+ output.collapsed = options.collapsed === null || options.collapsed === undefined || options.collapsed
+ output.items = Sidebar.generateSidebarItem(items, link)
+ }
+
+ return output
+ }
+
+ static getTitleFromContent(
+ content: string,
+ ): string | undefined {
+ const lines = content.split('\n')
+ for (let i = 0, len = lines.length; i < len; i += 1) {
+ let str = lines[i].toString().replace('\r', '')
+ if (str.indexOf('# ') !== -1) {
+ str = str.replace('# ', '')
+ return str
+ }
+ }
+
+ return
+ }
+}
+
+export { Sidebar }
+
+export const {
+ generateSidebar,
+ generateSidebarItem,
+ getTitleFromContent
+} = Sidebar
\ No newline at end of file
diff --git a/.vitepress/theme/styles/glightbox.css b/.vitepress/theme/styles/glightbox.css
new file mode 100644
index 0000000..3c07cf1
--- /dev/null
+++ b/.vitepress/theme/styles/glightbox.css
@@ -0,0 +1,93 @@
+:root {
+ --vp-c-overlay: rgba(255 255 255 / .8);
+ }
+
+ .dark {
+ --vp-c-overlay: rgba(0 0 0 / .8);
+ }
+
+ .vp-doc img {
+ cursor: zoom-in;
+ }
+
+ .glightbox-open.glightbox-mobile .VPLocalNav {
+ position: fixed;
+ }
+
+ .glightbox-white {
+ color: var(--vp-c-text-1);
+ }
+
+ .glightbox-white .goverlay {
+ background: var(--vp-c-overlay);
+ }
+
+ .glightbox-white .gloader {
+ border: 2px solid var(--vp-c-text-1);
+ border-right-color: var(--vp-c-bg);
+ }
+
+ .glightbox-white .gslide-description {
+ background: none;
+ padding-block: 20px;
+ }
+
+ .glightbox-white .gclose {
+ top: 20px;
+ right: 20px;
+ position: absolute;
+ }
+
+ .glightbox-white .gbtn:not(.disabled):hover {
+ background-color: var(--vp-c-gray-3);
+ }
+
+ .glightbox-white .gnext,
+ .glightbox-white .gprev {
+ position: absolute;
+ top: -100%;
+ }
+
+ .glightbox-white .gprev {
+ left: 30px;
+ }
+
+ .glightbox-white .gnext {
+ right: 30px;
+ }
+
+ .glightbox-white .gbtn {
+ width: 40px;
+ height: 40px;
+ border: 1px solid var(--vp-c-gray-soft);
+ border-radius: 50%;
+ box-shadow: var(--vp-shadow-1);
+ background-color: var(--vp-c-bg);
+ }
+
+ .glightbox-white .gbtn svg {
+ fill: currentColor;
+ }
+
+ @media (min-width: 767px) {
+ .glightbox-white .gclose {
+ top: 30px;
+ right: 30px;
+ }
+
+ .glightbox-white .gbtn {
+ width: 50px;
+ height: 50px;
+ }
+
+ .glightbox-white .gprev,
+ .glightbox-white .gnext {
+ top: 45%;
+ }
+ }
+
+ .glightbox-mobile .glightbox-container .gslide-title,
+ .glightbox-mobile .glightbox-container .gslide-description {
+ background: none;
+ color: var(--vp-c-text-1);
+ }
\ No newline at end of file
diff --git a/.vitepress/theme/styles/global.css b/.vitepress/theme/styles/global.css
new file mode 100644
index 0000000..f81cefc
--- /dev/null
+++ b/.vitepress/theme/styles/global.css
@@ -0,0 +1,468 @@
+:root {
+ --vp-font-family-base: 'Montserrat', sans-serif;
+ --vp-shadow-6: 0px 4px 15px rgba(47, 63, 147, 0.08);
+ --vp-border: var(--vp-border-width) solid var(--vp-c-bg-soft);
+ --vp-border-width: 4px;
+ --vp-border-radius: 20px;
+
+ --vp-c-gray-1: #f7f7f7;
+
+ --vp-c-blue-1: #0059C8;
+ --vp-c-blue-2: #0152b6;
+ --vp-c-blue-3: #0764d4;
+ --vp-c-blue-soft: #005ac80e;
+
+ --vp-c-brand-1: var(--vp-c-blue-1);
+ --vp-c-brand-2: var(--vp-c-blue-2);
+ --vp-c-brand-3: var(--vp-c-blue-3);
+ --vp-c-brand-soft: var(--vp-c-blue-soft);
+
+ --vp-c-sponsor-1: #eab11f;
+ --vp-c-sponsor-2: #e0a615;
+ --vp-c-sponsor-3: #ecb732;
+ --vp-c-sponsor-soft: rgba(236, 183, 50, 0.14);
+
+ --vp-button-sponsor-border: var(--vp-c-sponsor-3);
+ --vp-button-sponsor-text: var(--vp-c-black);
+ --vp-button-sponsor-hover-border: var(--vp-c-sponsor-3);
+ --vp-button-sponsor-hover-text: var(--vp-c-black);
+ --vp-button-sponsor-hover-bg: var(--vp-c-sponsor-3);
+ --vp-button-sponsor-active-border: var(--vp-c-sponsor-2);
+ --vp-button-sponsor-active-text: var(--vp-c-black);
+ --vp-button-sponsor-active-bg: var(--vp-c-sponsor-2);
+
+ --vp-code-color: var(--vp-c-blue-3);
+
+ --vp-custom-block-info-border: var(--vp-c-blue-soft);
+ --vp-custom-block-info-bg: var(--vp-c-blue-soft);
+ --vp-custom-block-info-text: var(--vp-c-neutral);
+
+ --vp-custom-block-tip-border: var(--vp-c-blue-soft);
+ --vp-custom-block-tip-bg: var(--vp-c-blue-soft);
+ --vp-custom-block-tip-text: var(--vp-c-neutral);
+
+ --vp-custom-block-warning-border: var(--vp-c-yellow-soft);
+ --vp-custom-block-warning-bg: var(--vp-c-yellow-soft);
+ --vp-custom-block-warning-text: var(--vp-c-neutral);
+
+ --vp-custom-block-danger-border: var(--vp-c-danger-soft);
+ --vp-custom-block-danger-bg: var(--vp-c-danger-soft);
+ --vp-custom-block-danger-text: var(--vp-c-neutral);
+
+ --vp-custom-block-details-border: var(--vp-c-divider);
+ --vp-custom-block-details-bg: var(--vp-c-bg-soft);
+
+ --vp-local-search-highlight-bg: transparent;
+ --vp-local-search-highlight-text: var(--vp-c-blue-1);
+ --vp-local-search-bg: var(--vp-c-bg-soft);
+ --vp-local-search-result-bg: var(--vp-c-white);
+ --vp-local-search-result-shadow: 0 1px 3px 0 #d4d9e1;
+ --vp-local-search-footer-shadow: 0 -1px 0 0 #e0e3e8, 0 -3px 6px 0 rgba(69, 98, 155, .12);
+
+ --vp-home-hero-name-color: transparent;
+ --vp-home-hero-name-background: -webkit-linear-gradient(120deg, var(--vp-c-brand) 30%, var(--vp-c-brand-dark));
+ --vp-home-hero-image-background-image: linear-gradient(-45deg, var(--vp-c-brand-light) 50%, var(--vp-c-brand-lighter) 50%);
+ --vp-home-hero-image-filter: blur(40px);
+ }
+
+ [lang|="ru"] {
+ --vp-code-copy-copied-text-content: 'Скопировано';
+ }
+
+ .dark {
+ --vp-c-blue-1: #7da1e9;
+ --vp-c-blue-2: #6382c2;
+ --vp-c-blue-3: #86a2da;
+ --vp-local-search-result-bg: var(--vp-c-bg-soft-mute);
+ --vp-local-search-result-shadow: none;
+ --vp-local-search-footer-shadow: none;
+ --vp-home-hero-image-background-image: linear-gradient(-45deg, var(--vp-c-brand-darker) 25%, var(--vp-c-brand-darkest) 25%);
+ }
+
+ @media (min-width: 640px) {
+ :root {
+ --vp-home-hero-image-filter: blur(56px);
+ }
+ }
+
+ @media (min-width: 960px) {
+ :root {
+ --vp-home-hero-image-filter: blur(72px);
+ }
+ }
+
+ main figure {
+ margin: 2rem 0;
+ transition: transform var(--vp-tt);
+ }
+
+ main img {
+ margin-left: auto;
+ margin-right: auto;
+ margin-top: 24px;
+ margin-bottom: 24px;
+ border-radius: var(--vp-border-radius);
+ box-shadow: 2px 2px 8px 4px var(--vp-c-bg-alt);
+ }
+
+ figcaption {
+ text-align: center;
+ margin-top: 1.25rem;
+ font-size: 0.875rem;
+ color: var(--vp-c-text-2);
+ }
+
+ main figure > a .external-link-icon {
+ display: none
+ }
+
+ figure figcaption {
+ color: inherit
+ }
+
+ .custom-block.tip figure img {
+ --vp-c-bg-alt: var(--vp-custom-block-tip-bg)
+ }
+
+ main :where(h1, h2, h3, h4, h5, h6) + figure {
+ margin-top: 1.5rem;
+ }
+
+ .Layout {
+ margin-right: var(--scrollbar-width);
+ }
+
+ .VPNav {
+ background-color: var(--vp-c-bg);
+ border-radius: 0 0 var(--vp-border-radius) var(--vp-border-radius);
+ box-shadow: var(--vp-shadow-6);
+ padding-right: var(--scrollbar-width);
+ }
+
+ .VPLocalNav {
+ border-top: none !important;
+ }
+
+ .VPNavBar.has-sidebar {
+ border-bottom: none;
+ }
+
+ .VPNavBarTitle .title {
+ font-size: 0;
+ border: none;
+ }
+
+ @media (min-width: 1440px) {
+ .VPSidebar {
+ left: calc((var(--scrollbar-width) / 2) * -1) !important;
+ }
+ }
+
+ .VPSidebarItem.level-0 {
+ padding-bottom: 10px !important;
+ }
+
+ .VPDocAsideOutline {
+ border: var(--vp-border);
+ border-radius: var(--vp-border-radius);
+ padding: 20px 20px 20px 0;
+ }
+
+ .VPDocAsideOutline .content {
+ padding-left: 20px;
+ border-left: none;
+ }
+
+ .VPDocAsideOutline .outline-marker {
+ width: 4px;
+ left: -4px;
+ }
+
+ .VPDoc .content-container {
+ max-width: unset !important;
+ }
+
+ .pager .pager-link {
+ border: 1px solid var(--vp-c-gray-soft);
+ box-shadow: var(--vp-shadow-1);
+ }
+
+ .pager .pager-link .title {
+ color: var(--vp-c-text-1);
+ }
+
+ .curtain {
+ display: none;
+ }
+
+ kbd {
+ border: 1px solid var(--vp-c-border);
+ box-shadow: var(--vp-c-gray-soft) 0px 1px 1px 0px, var(--vp-c-neutral-inverse) 0px 1px 0px 0px inset;
+ background-color: var(--vp-c-gray-soft);
+ border-radius: 3px;
+ display: inline-block;
+ margin: 0 .1em;
+ overflow-wrap: break-word;
+ padding: .1em .6em;
+ text-shadow: 0 1px 0 var(--vp-c-white);
+ }
+
+ .vp-doc p {
+ line-height: 26px;
+ }
+
+ .vp-doc li + li {
+ margin-top: .25em;
+ }
+
+ .vp-doc table {
+ line-height: 1.5;
+ }
+
+ .vp-doc ol {
+ counter-reset: li;
+ list-style: none;
+ position: relative;
+ }
+
+ .vp-doc ol > li {
+ padding-left: 1.25rem;
+ }
+
+ .vp-doc ol > li::before {
+ width: calc(1.5rem - 2px);
+ height: calc(1.5rem - 2px);
+ font-size: calc(1rem - 2px);
+ margin: 1px 0 0 .5rem;
+ content: counter(li);
+ counter-increment: li;
+ position: absolute;
+ left: 0;
+ color: var(--vp-c-text-1);
+ font-weight: 500;
+ text-align: center;
+ border-radius: 50%;
+ background-color: var(--vp-sidebar-bg-color);
+ }
+
+ .custom-block {
+ border-radius: var(--vp-border-radius);
+ border-width: var(--vp-border-width);
+ }
+
+ .custom-block.info a,
+ .custom-block.info code {
+ color: var(--vp-c-blue-1);
+ }
+
+ .custom-block.info a:hover {
+ color: var(--vp-c-blue-2);
+ }
+
+ .navigation {
+ color: var(--vp-c-brand-2);
+ font-weight: 600;
+ }
+
+ .navigation:hover {
+ color: var(--vp-c-brand-1);
+ cursor: default;
+ }
+
+ .navigation svg,
+ span.name {
+ vertical-align: middle;
+ position: relative;
+ bottom: 1px;
+ }
+
+ .navigation svg {
+ fill: currentColor;
+ height: 1em;
+ width: 1em;
+ display: inline-block;
+ margin-right: 4px;
+ }
+
+ .navigation.direction {
+ fill: currentColor;
+ height: 1em;
+ width: 1em;
+ display: inline-block;
+ }
+
+ .plugin-tabs {
+ border-radius: var(--vp-border-radius);
+ }
+
+ .plugin-tabs--content {
+ padding: 2rem !important;
+ }
+
+ .plugin-tabs--tab {
+ position: relative;
+ padding: 0 12px;
+ line-height: 54px;
+ border-bottom: 2px solid transparent;
+ color: var(--vp-plugin-tabs-tab-text-color);
+ font-size: 14px;
+ font-weight: 500;
+ white-space: nowrap;
+ transition: color 0.50s;
+ }
+
+ @media (max-width: 1023px) {
+ .vp-doc table {
+ border: 0;
+ }
+
+ .vp-doc table tbody {
+ display: block;
+ }
+
+ .vp-doc table thead {
+ border: none;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+ }
+
+ .vp-doc table tr {
+ border-bottom: 1px solid var(--vp-c-divider);
+ display: block;
+ margin-bottom: 1rem;
+ }
+
+ .vp-doc table tr:nth-child(2n) {
+ background-color: inherit;
+ }
+
+ .vp-doc table td {
+ border-top: none;
+ display: block;
+ overflow-x: auto;
+ }
+
+ .vp-doc table td::before {
+ display: block;
+ content: attr(data-label) ':';
+ color: var(--vp-c-text-2);
+ }
+
+ .vp-doc table td:empty {
+ display: none;
+ }
+
+ .vp-doc table td:last-child {
+ border-bottom: 0;
+ }
+ }
+
+ @media (min-width: 768px) {
+ .DocSearch-Button {
+ display: flex;
+ justify-content: space-between;
+ background-color: var(--vp-c-bg-elv);
+ border: 1px solid var(--vp-c-gray-soft);
+ box-shadow: var(--vp-shadow-1);
+ }
+
+ .DocSearch-Button:hover {
+ background-color: var(--vp-c-bg-elv);
+ border: 1px solid var(--vp-c-gray-2);
+ box-shadow: var(--vp-shadow-1);
+ }
+ }
+
+ @media (min-width: 1024px) {
+ .DocSearch-Button {
+ min-width: 200px;
+ }
+ }
+
+ .screenshots {
+ display: flex;
+ margin: 0 auto 0;
+ max-width: 1152px;
+ }
+
+ .shot {
+ flex: 33.33%;
+ padding: 12px;
+ margin: 12px auto 0;
+ }
+
+ /* Parsers lib layout */
+ .dl {
+ color: var(--vp-c-text-1);
+ padding: 1.5rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ gap: 1rem;
+ max-width: 1152px;
+ margin: 0 auto 0;
+ }
+
+ .dl h1 {
+ font-size: 3rem;
+ font-weight: 600;
+ margin: 0.5rem;
+ }
+
+ .dl .btn {
+ position: relative;
+ display: inline-block;
+ border: 4px solid transparent;
+ border-radius: 8px;
+ padding: 8px 18px;
+ font-size: 16px;
+ font-weight: 500;
+ transition: all 0.25s, color 0.25s;
+ }
+
+ /* Parsers lib layout */
+ .parsers {
+ background-color: var(--vp-c-bg-soft);
+ color: var(--vp-c-text-1);
+ padding: 1.5rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ gap: 1rem;
+ max-width: 1152px;
+ margin: 0 auto 0;
+ border-radius: var(--vp-border-radius);
+ }
+
+ .parsers h1 {
+ font-size: 2.2rem;
+ font-weight: 600;
+ margin: 0.5rem;
+ }
+
+ .parsers .btn {
+ position: relative;
+ display: inline-block;
+ border: 4px solid transparent;
+ border-radius: 8px;
+ padding: 8px 18px;
+ font-size: 16px;
+ font-weight: 500;
+ transition: all 0.25s, color 0.25s;
+ }
+
+ .divider {
+ margin: 24px auto 18px;
+ width: 64px;
+ height: 1px;
+ background-color: var(--vp-c-divider);
+ }
\ No newline at end of file
diff --git a/.vitepress/theme/types/index.ts b/.vitepress/theme/types/index.ts
new file mode 100644
index 0000000..4ae9c6a
--- /dev/null
+++ b/.vitepress/theme/types/index.ts
@@ -0,0 +1,23 @@
+import { DefaultTheme } from 'vitepress'
+import type { ComponentData } from '../plugins/component'
+
+export namespace Theme {
+ export interface Sponsor {
+ message?: string
+ linkText?: string
+ link?: string
+ }
+
+ export interface Config extends DefaultTheme.Config {
+ titleSeparator?: string
+ components?: Array
+ teamSectionTitle?: string
+
+ sponsorLink?: string
+ sponsor?: Sponsor
+ }
+
+ export interface TeamMember extends Omit {
+ name: Record
+ }
+}
\ No newline at end of file
diff --git a/.vitepress/theme/utils.ts b/.vitepress/theme/utils.ts
new file mode 100644
index 0000000..9986cf6
--- /dev/null
+++ b/.vitepress/theme/utils.ts
@@ -0,0 +1,111 @@
+import type { DefaultTheme, UserConfig } from 'vitepress'
+import type { ComponentData, DocsPageData } from './plugins/component'
+import { normalize } from 'vitepress/dist/client/shared'
+
+import { type Translator, translators } from '../../website/translators'
+import { Theme } from './types'
+
+export function findPath(
+ pageData: DocsPageData,
+ config: UserConfig,
+): DefaultTheme.SidebarItem[] {
+ let searchable = normalize(pageData.relativePath)
+
+ const localeLinks = Object.entries(config.locales).flatMap(([key]) => ({ key, link: key === 'root' ? '/' : `${key}/` }))
+ if (localeLinks.some(({ link }) => link === searchable)) return []
+
+ const locale = localeLinks.find(locale => locale.link.startsWith(searchable.replace(/(^.*?\/).*$/, '$1'))) || localeLinks[0]
+ searchable = ensureStartingSlash(searchable)
+ const localeConfig: Theme.Config = config.locales[locale.key].themeConfig
+ const root = localeConfig.nav.find(item => {
+ if (!('link' in item)) {
+ return false
+ }
+ const tmp = locale.link === '/' ? item.link.replace(/(^\/.*?\/).*$/, '$1') : item.link.replace(/(^\/.*?\/)(.*?\/).*$/, '$1$2')
+ return searchable.startsWith(tmp)
+ })
+ const path: DefaultTheme.SidebarItem[] = []
+ if (root && ('link' in root)) {
+ path.push({ text: root.text, link: root.link })
+ }
+
+ let tree: DefaultTheme.SidebarItem | ComponentData
+
+ if (pageData.component) {
+ const { title, link, items } = pageData.component
+ tree = pageData.component
+ path.push({ text: title, link, items })
+ } else {
+ const sidebar = Object.entries(localeConfig.sidebar).find(([link]) => searchable.startsWith(link))
+ if (sidebar) tree = sidebar[1]
+ }
+
+ if (!tree) return path
+
+ const keyExists = (tree) => {
+ if (!tree || (typeof tree !== 'object' && !Array.isArray(tree.items) && !Array.isArray(tree))) {
+ return false
+ }
+ else if (tree.hasOwnProperty('link') && ensureStartingSlash(tree.link) === searchable) {
+ return true
+ }
+ else if (Array.isArray(tree)) {
+ for (let i = 0; i < tree.length; i++) {
+ const item = tree[i]
+ path.push(item)
+ const result = keyExists(item)
+ if (result) {
+ return result
+ }
+
+ path.pop()
+ }
+ }
+ else if (Array.isArray(tree.items)) {
+ for (let i = 0; i < tree.items.length; i++) {
+ const item = tree.items[i]
+ path.push(item)
+ const result = keyExists(item)
+ if (result) {
+ return result
+ }
+
+ path.pop()
+ }
+ }
+
+ return false
+ }
+
+ keyExists(tree)
+
+ return path
+}
+
+export function ellipsis(
+ string: string = '',
+ length: number = 0,
+ etc: string = '...'
+): string {
+ if (string.length <= length) {
+ return string
+ }
+
+ return string.substring(0, length) + (string.length > length ? etc : '')
+}
+
+export function ensureStartingSlash(path: string): string {
+ return /^\//.test(path) ? path : `/${path}`
+}
+
+export function getTranslator(translator: string): Translator | undefined {
+ if (!translator) {
+ return
+ }
+
+ if (!Object.prototype.hasOwnProperty.call(translators, translator)) {
+ return
+ }
+
+ return translators[translator]
+}
\ No newline at end of file
diff --git a/markdownlintignore b/markdownlintignore
new file mode 100644
index 0000000..b512c09
--- /dev/null
+++ b/markdownlintignore
@@ -0,0 +1 @@
+node_modules
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..b1b391b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,70 @@
+{
+ "name": "kotatsu-website",
+ "version": "1.0.0",
+ "repository": "https://github.com/KotatsuApp/kotatsuapp.github.io.git",
+ "type": "module",
+ "contributors": [
+ {
+ "name": "Zakhar Timoshenko",
+ "email": "xtimms@live.com"
+ },
+ {
+ "name": "Koitharu",
+ "email": "nvasya95@gmail.com"
+ }
+ ],
+ "scripts": {
+ "preinstall": "npx only-allow pnpm",
+ "test": "pnpm lint && pnpm build && pnpm preview",
+ "dev": "vitepress dev",
+ "build": "vitepress build",
+ "preview": "vitepress preview",
+ "lint": "markdownlint **/*.md --disable MD045"
+ },
+ "devDependencies": {
+ "@docsearch/css": "^3.3.3",
+ "@docsearch/js": "^3.3.3",
+ "@modix/smarty-tmlanguage": "^1.1.0",
+ "@resvg/resvg-js": "2.4.1",
+ "@types/gtag.js": "0.0.14",
+ "@types/markdown-it": "^12.2.3",
+ "@types/node": "^18.16.13",
+ "dotenv": "^16.3.1",
+ "fast-glob": "^3.2.12",
+ "gray-matter": "^4.0.3",
+ "markdown-it": "^13.0.1",
+ "markdown-it-container": "^3.0.0",
+ "markdown-it-kbd": "^2.2.2",
+ "markdown-it-shortcode-tag": "1.1.0",
+ "markdownlint": "^0.29.0",
+ "markdownlint-cli": "^0.34.0",
+ "modx-tmlanguage": "^1.2.0",
+ "plop": "^3.1.2",
+ "stylus": "0.60.0",
+ "transliteration": "^2.3.5",
+ "vue3-carousel": "0.3.1",
+ "vitepress": "^1.0.0-rc.20",
+ "vitepress-plugin-tabs": "0.4.1",
+ "vue": "^3.3.4",
+ "x-satori": "0.1.5"
+ },
+ "pnpm": {
+ "peerDependencyRules": {
+ "ignoreMissing": [
+ "@algolia/client-search"
+ ]
+ }
+ },
+ "dependencies": {
+ "@octokit/rest": "20.0.2",
+ "@octokit/types": "12.0.0",
+ "@vueuse/core": "^10.1.2",
+ "@xobotyi/scrollbar-width": "^1.9.5",
+ "glightbox": "^3.2.0",
+ "moment": "2.29.4",
+ "uuid": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..adc57e7
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "allowSyntheticDefaultImports": true,
+ "resolveJsonModule": true,
+ "types": [
+ "vite/client",
+ "@types/gtag.js"
+ ],
+ "paths": {
+ "@/*": ["./.vitepress/*"]
+ }
+ },
+ "include": [
+ ".vitepress/theme/**/*",
+ "node_modules/vitepress/dist/client/theme-default/components/**/*.vue"
+ ]
+}
\ No newline at end of file
diff --git a/website/account/index.md b/website/account/index.md
new file mode 100644
index 0000000..3869483
--- /dev/null
+++ b/website/account/index.md
@@ -0,0 +1,8 @@
+---
+title: Account
+description: On this page you can log in to your Kotatsu Sync Service account.
+lastUpdated: false
+editLink: false
+prev: false
+next: false
+---
\ No newline at end of file
diff --git a/website/changelogs/index.md b/website/changelogs/index.md
new file mode 100644
index 0000000..a9f433b
--- /dev/null
+++ b/website/changelogs/index.md
@@ -0,0 +1,18 @@
+---
+title: Changelogs
+description: Changelogs of all Kotatsu stable releases.
+lastUpdated: false
+editLink: false
+prev: false
+next: false
+---
+
+
+
+# Changelogs
+
+Changelogs of all Kotatsu stable releases, which are also available [on GitHub](https://github.com/KotatsuApp/Kotatsu/releases).
+
+
\ No newline at end of file
diff --git a/website/contribute/index.md b/website/contribute/index.md
new file mode 100644
index 0000000..d1f3158
--- /dev/null
+++ b/website/contribute/index.md
@@ -0,0 +1,31 @@
+---
+title: Contribute
+description: Find out how to help translate or build the app and parsers.
+lastUpdated: false
+editLink: false
+prev: false
+next: false
+---
+
+# Contribute
+Find out how to help translate or build the app and parsers.
+
+## Code
+Know how to code and want to improve something or you generally want to support the creation of the app?
+
+[](https://github.com/KotatsuApp/Kotatsu)
+
+[](https://github.com/KotatsuApp/kotatsu-parsers)
+
+[](https://github.com/KotatsuApp/kotatsuapp.github.io)
+
+## Translation
+
+
+Want to help translate the app to your language? You can easily help by utilizing a service we use called **Weblate**.
+
+### Helpful links
+* [Translators guide](https://docs.weblate.org/en/latest/user/translating.html)
+* [Secondary languages](https://docs.weblate.org/en/latest/user/profile.html#secondary-languages)
+* [Subscriptions](https://docs.weblate.org/en/latest/user/profile.html#subscriptions)
+* [Glossary](https://docs.weblate.org/en/latest/user/translating.html#glossary)
\ No newline at end of file
diff --git a/website/download/index.md b/website/download/index.md
new file mode 100644
index 0000000..8144111
--- /dev/null
+++ b/website/download/index.md
@@ -0,0 +1,22 @@
+---
+title: Download
+description: Download page that allows users to access and install the latest version of the app.
+lastUpdated: false
+editLink: false
+prev: false
+next: false
+---
+
+
+
+# Download
+
+The latest stable version of **Kotatsu** was released ** **.
+
+
+
+
\ No newline at end of file
diff --git a/website/icons.ts b/website/icons.ts
new file mode 100644
index 0000000..bed7c48
--- /dev/null
+++ b/website/icons.ts
@@ -0,0 +1,8 @@
+const telegram = `
+
+
+ `
+
+export {
+ telegram,
+}
\ No newline at end of file
diff --git a/website/index.md b/website/index.md
new file mode 100644
index 0000000..4c9c991
--- /dev/null
+++ b/website/index.md
@@ -0,0 +1,52 @@
+---
+title: Kotatsu
+layout: home
+
+main:
+ title: Kotatsu
+ text: Manga Reader
+ tagline: A simple and convenient open source manga reader from and for the community, where you can find and read your favorite manga easier than ever.
+ actions:
+ - theme: brand
+ text: Get started
+ link: /manuals/guides/getting-started
+ - theme: alt
+ text: Download
+ link: /download/
+features:
+ - icon:
+ title: Huge count of sources
+ details: Supports over a 500+ sources
+ - icon:
+ title: Supports tracking
+ details: Supports MyAnimeList, Anilist and Shikimori
+ - icon:
+ title: Synchronization
+ details: Easily sync series between your devices.
+---
+
+
+
+
+
kotatsu-dl
+
Cross-platform desktop application for downloading manga from various services. Allows you to search for manga and download it to the CBZ comic archives.
+
View on GitHub
+
+
+
+
+
+
Kotatsu parsers library
+
A Java/Kotlin library that allows you to scrape manga from various online services and create your own applications for reading or downloading manga. It supports Android but does not depend on it, so it can be also used in pure Kotlin/JVM projects.
+
Read more
+
\ No newline at end of file
diff --git a/website/manga/index.md b/website/manga/index.md
new file mode 100644
index 0000000..f9f66a2
--- /dev/null
+++ b/website/manga/index.md
@@ -0,0 +1,24 @@
+---
+title: Hello here
+description: You might need to have a Kotatsu application installed to follow this link.
+search: false
+sidebar: true
+sitemap:
+ exclude: true
+head:
+ - - meta
+ - name: robots
+ content: noindex, nofollow
+---
+
+
+
+# Hello here
+
+You might need to have a Kotatsu application installed to follow this link.
+
+Kotatsu is a free and open source manga reader for Android platform. It supports a lot of online catalogues on different languages with filters and search, offline reading from local storage, favourites, bookmarks, new chapters notifications and more features.
+
+
\ No newline at end of file
diff --git a/website/manuals/faq/explore/bookmarks.md b/website/manuals/faq/explore/bookmarks.md
new file mode 100644
index 0000000..6d43a6b
--- /dev/null
+++ b/website/manuals/faq/explore/bookmarks.md
@@ -0,0 +1,4 @@
+---
+title: Bookmarks
+description: Frequently Asked Questions about Bookmarks.
+---
\ No newline at end of file
diff --git a/website/manuals/faq/explore/downloads.md b/website/manuals/faq/explore/downloads.md
new file mode 100644
index 0000000..5dee847
--- /dev/null
+++ b/website/manuals/faq/explore/downloads.md
@@ -0,0 +1,19 @@
+---
+title: Downloads
+description: Frequently Asked Question about Downloads.
+---
+
+# Downloads
+Frequently Asked Question about Downloads.
+
+## Why did my downloads stop midway?
+Downloads stopping midway may be related to network connection issues or source problems.
+**Kotatsu** will provide notifications regarding encountered errors during download attempts.
+
+## How do I manage what's downloading?
+Navigate to to interact with queued downloads.
+
+You can cancel item by clicking the **Cancel** button.
+
+## Can I use both internal storage and external SD card storage?
+No, you must choose a single location. Internal storage performs better than external SD cards.
\ No newline at end of file
diff --git a/website/manuals/faq/explore/index.md b/website/manuals/faq/explore/index.md
new file mode 100644
index 0000000..989d69a
--- /dev/null
+++ b/website/manuals/faq/explore/index.md
@@ -0,0 +1,8 @@
+---
+title: Explore
+description: Help center of Explore section.
+lastUpdated: false
+editLink: false
+prev: false
+next: false
+---
\ No newline at end of file
diff --git a/website/manuals/faq/explore/local-storage.md b/website/manuals/faq/explore/local-storage.md
new file mode 100644
index 0000000..947e470
--- /dev/null
+++ b/website/manuals/faq/explore/local-storage.md
@@ -0,0 +1,4 @@
+---
+title: Local storage
+description: Frequently Asked Questions about Local storage.
+---
\ No newline at end of file
diff --git a/website/manuals/faq/explore/random.md b/website/manuals/faq/explore/random.md
new file mode 100644
index 0000000..24eb420
--- /dev/null
+++ b/website/manuals/faq/explore/random.md
@@ -0,0 +1,4 @@
+---
+title: Random
+description: Frequently Asked Questions about Random.
+---
\ No newline at end of file
diff --git a/website/manuals/faq/explore/sources.md b/website/manuals/faq/explore/sources.md
new file mode 100644
index 0000000..07f5c1b
--- /dev/null
+++ b/website/manuals/faq/explore/sources.md
@@ -0,0 +1,37 @@
+---
+title: Sources
+description: Frequently Asked Questions about Sources.
+---
+
+# Sources
+Frequently Asked Questions about Sources.
+
+## What are some recommended sources?
+**Kotatsu** does not endorse or recommend any source, and there is no best source.
+
+Instead, we encourage users to spend some time trying out a few sources themselves and discover what sources work best for them.
+What works well for somebody else might not work well for you.
+
+::: info Disclaimer
+**Kotatsu** isn't responsible for slow, down, missing chapters, or subpar image quality of sources as it doesn't host content.
+:::
+
+## What is a scanlator source?
+Non-officially licensed series are translated by scanlators, often found on their websites or [MangaDex](https://mangadex.org/).
+Learn more in this [Wikipedia article](https://en.wikipedia.org/wiki/Scanlation).
+
+## How do I request new sources?
+[Open an issue](https://github.com/KotatsuApp/kotatsu-parsers/issues) on **GitHub** if not already there.
+Check the removed [parsers list](https://github.com/KotatsuApp/kotatsu-parsers/issues/39) first.
+
+## Why was a source removed?
+Extensions can be removed due to several reasons:
+
+* Frequent website changes.
+* Image scrambling.
+* Scanlator removal request.
+* Paywall implementation.
+* Reverse engineering needs.
+* Site shutdown.
+
+Find the list of removed sources [here](https://github.com/KotatsuApp/kotatsu-parsers/issues/39), excluding offline sites.
\ No newline at end of file
diff --git a/website/manuals/faq/explore/suggestions.md b/website/manuals/faq/explore/suggestions.md
new file mode 100644
index 0000000..c932af1
--- /dev/null
+++ b/website/manuals/faq/explore/suggestions.md
@@ -0,0 +1,4 @@
+---
+title: Suggestions
+description: Frequently Asked Questions about Suggestions.
+---
\ No newline at end of file
diff --git a/website/manuals/faq/feed.md b/website/manuals/faq/feed.md
new file mode 100644
index 0000000..4a2ad15
--- /dev/null
+++ b/website/manuals/faq/feed.md
@@ -0,0 +1,15 @@
+---
+title: Feed
+titleTemplate: Frequently Asked Questions
+description: Frequently Asked Questions about the Feed.
+---
+
+# Feed
+Frequently Asked Questions about the Feed.
+
+## Why aren't series updates working?
+Some Android skins (e.g., **MIUI**) aggressively save battery, potentially shutting down apps in the background.
+
+Whitelist **Kotatsu** from your battery saver by going to and tapping **Disable battery optimization**.
+
+If unsuccessful, refer to [Don't Kill My App](https://dontkillmyapp.com/) for how to disable specific battery-saving options for your device.
\ No newline at end of file
diff --git a/website/manuals/faq/general.md b/website/manuals/faq/general.md
new file mode 100644
index 0000000..18668f2
--- /dev/null
+++ b/website/manuals/faq/general.md
@@ -0,0 +1,49 @@
+---
+title: General
+description: Frequently Asked Questions
+---
+
+# General
+Frequently Asked Questions
+
+## Why isn't Kotatsu on the Google Play Store?
+**Kotatsu** won't be on the **Google Play Store**. **Google** might take down the app due to certain content, which the developers wishes to avoid.
+
+To report **Kotatsu** copycats on the **Google Play Store**, fill out [this form](https://support.google.com/googleplay/android-developer/contact/takedown) following the steps below.
+
+:::details Steps to report Kotatsu copycats
+1. Go to the page -> three dots menu -> Flag as inappropriate -> Other objection.
+1. After choosing "Other objection", you may choose to put down any or all of the following:
+
+ The app infringes on the Google Play Developer Policy by
+
+ Encouraging Infringement of Copyright
+
+ > We don’t allow apps that induce or encourage copyright infringement. Before you publish your app, look for ways your app may be encouraging copyright infringement and get legal advice if necessary.
+
+ Trademark Infringement
+
+ > We don’t allow apps that infringe on others’ trademarks. A trademark is a word, symbol, or combination that identifies the source of a good or service. Once acquired, a trademark gives the owner exclusive rights to the trademark usage with respect to certain goods or services.
+ >
+ > Trademark infringement is an improper or unauthorized use of an identical or similar trademark in a way that is likely to cause confusion as to the source of that product. If your app uses another party’s trademarks in a way that is likely to cause confusion, your app may be suspended.
+
+ Misleading Claims, e.g. "MangaTop - Best Manga Reader"
+
+ > We don’t allow apps that contain false or misleading information or claims, including in the description, title, icon, and screenshots. Here are some examples of common violations: Apps that misrepresent or do not accurately and clearly describe their functionality:
+ > - Developer or app names that misrepresent their current status or performance on Play. (E.g. “Editor’s Choice,” “Number 1 App,” “Top Paid”)...
+ > - Apps that are improperly categorized.
+:::
+
+## Is Kotatsu available for iOS/iPadOS?
+There is no iOS or iPadOS version and neither are there plans for one.
+Porting is difficult due to the separate codebases of iOS and Android apps.
+
+Any app proclaiming to be "**Kotatsu for iOS**" is not by us and should be treated as a scam.
+
+## Can I read light novels?
+**Kotatsu** can't read light novels; it's an image parser, not a text parser.
+
+## Can I stream anime?
+**Kotatsu** isn't designed for anime streaming.
+
+Projects using the **Kotatsu** name for anime streaming aren't affiliated with the main project.
\ No newline at end of file
diff --git a/website/manuals/faq/reader.md b/website/manuals/faq/reader.md
new file mode 100644
index 0000000..5ee0483
--- /dev/null
+++ b/website/manuals/faq/reader.md
@@ -0,0 +1,18 @@
+---
+title: Reader
+titleTemplate: Frequently Asked Questions
+description: Frequently Asked Questions about the Reader.
+---
+
+# Reader
+Frequently Asked Questions about the Reader.
+
+## Why didn't the page load?
+Besides network-related problems, **Kotatsu** may occasionally fail to recognize certain images.
+To address this, simply exit and re-enter the reader or click **Try again** button, often resolving the issue.
+
+## Can I see two pages at once?
+Not currently. Creating an effective dual-page reader that accommodates scanlator page inconsistencies and other complexities poses challenges. This feature may be added in the future.
+
+## What do all the settings do?
+For detailed instructions, please consult the guides section on the website here.
\ No newline at end of file
diff --git a/website/manuals/faq/settings.md b/website/manuals/faq/settings.md
new file mode 100644
index 0000000..6e0aea5
--- /dev/null
+++ b/website/manuals/faq/settings.md
@@ -0,0 +1,14 @@
+---
+title: Settings
+description: Frequently Asked Questions about various app settings.
+---
+
+# Settings
+Frequently Asked Questions about various app settings.
+
+## Why is taking screenshots blocked?
+Turn off **Screenshot policy** in .
+
+## What is DNS over HTTPS?
+**DNS over HTTPS** (in ) offers secure DNS resolution through HTTPS, preventing attacks.
+Learn more [here](https://www.cloudflare.com/learning/dns/dns-over-tls/). This may help bypass some basic website blocking.
\ No newline at end of file
diff --git a/website/manuals/guides/backups.md b/website/manuals/guides/backups.md
new file mode 100644
index 0000000..0641af3
--- /dev/null
+++ b/website/manuals/guides/backups.md
@@ -0,0 +1,29 @@
+---
+title: Backups
+description: Backups helps you prevent losing your library if something happens.
+---
+
+# Backups
+
+Backups in **Kotatsu** are compatible between different versions of the app.
+
+::: tip How to create a backup
+1. Go to .
+1. Select **Create data backup** and choose a location to save it.
+
+
+:::
+
+## General backup details
+
+### What's included in a backup?
+- **Reading history**
+- **Favourites**
+- **Categories of favourites**
+- **App settings**
+
+### What's not included in a backup?
+- **Downloaded** chapters
+
+## Restoring a backup
+Restoring a backup can be done through the **Restore from backup** setting.
\ No newline at end of file
diff --git a/website/manuals/guides/categories.md b/website/manuals/guides/categories.md
new file mode 100644
index 0000000..1e03606
--- /dev/null
+++ b/website/manuals/guides/categories.md
@@ -0,0 +1,34 @@
+---
+title: Categories
+description: Organize your favorite series effortlessly with categories.
+---
+
+# Categories
+
+Organize your favorite series effortlessly with categories.
+
+To manage your categories, navigate to .
+
+- You can name and sort categories as you prefer (e.g., by `Source`, `Genre`, `Reading Status`).
+- Add series to multiple categories and control update options through category settings.
+
+## Content
+
+Categories would be useless without any content in them.
+Below are some tips for using them.
+
+:::: tabs
+== Add entries
+### Add series to a category
+
+1. Long press the series you want to add.
+1. Press the **heart** button.
+1. Select which category or categories you want it in.
+
+== Remove entries
+### Remove series from a category
+
+1. Long press the series that you want to remove.
+1. Press the **heart** button.
+1. Deselect the category or categories you want to remove it from.
+::::
\ No newline at end of file
diff --git a/website/manuals/guides/getting-started.md b/website/manuals/guides/getting-started.md
new file mode 100644
index 0000000..6daefb7
--- /dev/null
+++ b/website/manuals/guides/getting-started.md
@@ -0,0 +1,63 @@
+---
+title: Getting started
+description: Essential information to help you get set up with Kotatsu.
+---
+
+
+
+# Getting started
+
+Essential information to help you get set up with Kotatsu.
+
+## Installation guide
+
+### Downloading Kotatsu
+
+1. Visit our [download](/download/) page to get the latest version of **Kotatsu**.
+1. After the download is complete, open the `kotatsu-{{ release.stable.tag_name }}-release.apk` file.
+1. Proceed with the installation process.
+
+### Selecting sources depending on languages
+
+At the first launch, a dialog with a list of source languages available in the application will appear. By default, the languages that are on the Android system and multilingual sources will be enabled.
+
+
+
+::: tip
+You can organize the list of sources in
+:::
+
+### Adding series to your favourites
+
+Here's how you can add series to your favourites:
+
+1. Select the source you'd like to browse.
+1. Once you've found the series that you want to add, tap on it for more details.
+1. Press the heart button on toolbar, choose category or create new one, and the series will be added to your favourites.
+
+
+
+## Additional setup
+
+### Series search options
+
+If you want to search for series across all your sources, you can use the Global Search feature.
+
+Follow these steps:
+
+1. Go to the main section.
+1. Use the search bar in the toolbar to find series from all available sources.
+
+### Trouble finding a specific series?
+
+If you encounter difficulties while searching for a specific series, consider the following points:
+
+* Double-check your spelling and try again, as some sources might use **Japanese romanized** titles instead of **English** ones.
+ > Example: **Boku no Hero Academia** instead of **My Hero Academia**.
+
+* Some sources may use different spellings or wordings for titles.
+ > Example: **Bungo Stray Dogs** instead of **Bungou Stray Dogs**
+
+ > Example: **3-gatsu no Lion** instead of **Sangatsu no Lion**.
\ No newline at end of file
diff --git a/website/manuals/guides/tracking.md b/website/manuals/guides/tracking.md
new file mode 100644
index 0000000..f293fc1
--- /dev/null
+++ b/website/manuals/guides/tracking.md
@@ -0,0 +1,36 @@
+---
+title: Tracking
+description: Tracking helps track your library with different online services.
+---
+
+
+
+# Tracking
+Tracking helps you automatically send read chapters to supported trackers, so you can keep track of what and when you read it online.
+
+## Services
+
+Kotatsu (version {{ release.stable.tag_name }}) supports the following services: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), and [Shikimori](https://shikimori.one/).
+
+- Tracking is manual for each entry.
+- Reading the last page of a chapter marks it as read.
+- Offline progress syncs when online.
+- Completion status auto-updates.
+- Tracking is one-way: **Kotatsu -> Tracker**.
+
+## General questions
+
+### How do I log in to trackers?
+1. Go to .
+1. Tap the desired tracker to begin login.
+
+### How do I set up tracking for each series?
+1. Open the series.
+1. Tap .
+1. Find a series and click **Done**.
+
+::: tip
+You can also change the search query if there is no match.
+:::
\ No newline at end of file
diff --git a/website/manuals/guides/troubleshooting/common-issues.md b/website/manuals/guides/troubleshooting/common-issues.md
new file mode 100644
index 0000000..ff4526a
--- /dev/null
+++ b/website/manuals/guides/troubleshooting/common-issues.md
@@ -0,0 +1,74 @@
+---
+title: Common issues
+description: Facing issues with a source or the app? Here's how to tackle common challenges.
+---
+
+# Common issues
+
+Facing issues with a source or the app?
+Here's how to tackle common challenges.
+
+## Basic issues
+
+### Slow loading
+Sources being slow could stem from site slowness, your internet, or source-imposed rate limits/IP bans.
+
+## Advanced errors
+
+### `Unable to resolve host` / `Connection failed` / `Failed to connect to` / `timeout` / `connection reset`
+These errors indicate connection issues. Possible causes include:
+
+* Weak internet connection.
+* App lacks internet access.
+* Your ISP has blocked the site.
+* The site is down.
+
+Try these solutions:
+
+* Enable **DNS over HTTPS**.
+* Change network (Wi-Fi, mobile data, VPN).
+* Reboot router.
+
+### `java.security.cert.CertPathValidatorException` / `Chain validation failed`
+Validation issue with source's certificate.
+
+Try these solutions:
+
+* Check expired certificate, use SSL checker.
+* Set correct device date and time.
+* Change network (Wi-Fi, mobile data, VPN).
+* Enable **Ignore SSL errors** in
+* Reboot device.
+
+## HTTP errors
+Encountering HTTP errors? Here's what they mean and how to address them.
+
+### `HTTP Error: 403` - Forbidden
+Possible reasons for this error:
+* The selected source has Cloudflare protection. Check the [Cloudflare guide](/manuals/guides/troubleshooting/#cloudflare) for solutions.
+* The source might be down, removed the series, or banned your IP.
+ > Open WebView to confirm.
+
+### `HTTP Error: 404` - Not Found
+This error likely indicates a down source or removed series.
+* Use **WebView** to verify.
+ > Consider switching to a different source for the series.
+
+### `HTTP Error: 429` - Too Many Requests
+This error suggests the source temporarily banned your IP due to fast downloads/reads.
+
+### `HTTP Error: 5xx`
+Errors like `500`, `502`, etc., indicate server-side issues on the source's end.
+
+[Check the source in WebView](/manuals/guides/troubleshooting/#accessing-websites-via-webview) to confirm if it's down.
+
+### `HTTP Error: 1006`
+This error means a temporary IP ban by the source.
+
+### `HTTP Error: 1020`
+This error points to violating a firewall rule set by the site owner.
+The owner might raise Cloudflare protection or block IPs from outside their country.
+
+::: warning
+For unlisted errors or if instructions don't help, refer to [Diagnosis](/manuals/guides/troubleshooting/diagnosis).
+:::
\ No newline at end of file
diff --git a/website/manuals/guides/troubleshooting/diagnosis.md b/website/manuals/guides/troubleshooting/diagnosis.md
new file mode 100644
index 0000000..947a578
--- /dev/null
+++ b/website/manuals/guides/troubleshooting/diagnosis.md
@@ -0,0 +1,63 @@
+---
+title: Diagnosis
+description: Facing issues with a source or the app? Follow these steps to troubleshoot and find solutions.
+---
+
+# Diagnosis
+
+Facing issues with a source or the app?
+Follow these steps to troubleshoot and find solutions.
+
+## Primary diagnosis
+
+1. **Update app**: Go to and tap **Check for updates**.
+1. **Manual series refresh**: Go to and drag down to manually refresh problematic series.
+1. **Test other series**: Try different series from the same source.
+1. **Update WebView**: Ensure your WebView is current.
+1. **Public WebView**: Attempt opening series in public WebView. Solve for CAPTCHA or Cloudflare protection if needed.
+1. **Change connection**: Switch networks (Wi-Fi, mobile data, VPN) and confirm IP change.
+1. **Collaborative check**: Get others to replicate the error.
+1. **Source status**: Verify the source's status in a browser.
+1. **Retry button**: Look for a retry button on the series page.
+1. **Advanced settings**: Under , try these options:
+ - Clear thumbnails cache
+ - Clear page cache
+ - Clear network cache
+ - Clear cookies
+ - Under , try to enable DNS over HTTPS
+1. **Download issues**: Delete the queue and retry downloads.
+1. **Restart Kotatsu**: Force close and reopen the app.
+
+If any of these solutions help, go to [Personalized Issue](#personalized-issue).
+If it is not just you, go to [Widespread Issue](#widespread-issue).
+
+If none of these solutions help, try asking in our [Telegram group](https://t.me/kotatsuapp).
+
+State your app version and the source, series, and chapter with the problem.
+
+::: tip An app update may fix your issue
+Wait or check for an app update if you have not already.
+There are no ETAs for updates.
+:::
+
+## Personalized issue
+If you're the only one facing a problem, you might be encountering an IP ban, or other countermeasures set by website owners against programs like **Kotatsu**.
+
+**To minimize future issues:**
+- Avoid using downloads with the source.
+- Reduce the number of series in your favourites/history from that source.
+
+::: warning
+These are general guidelines as each site has its specific undisclosed limits and triggers.
+:::
+
+## Widespread issue
+When everyone experiences a problem, it could be with the extension or app:
+
+1. Check open issues [for the app](https://github.com/KotatsuApp/Kotatsu/issues) and/or [**parsers**](https://github.com/KotatsuApp/kotatsu-parsers/issues).
+1. Check closed issues ([app](https://github.com/KotatsuApp/Kotatsu/issues?q=is%3Aissue+is%3Aclosed)/[parsers](https://github.com/KotatsuApp/kotatsu-parsers/issues?q=is%3Aissue+is%3Aclosed)) in case it's resolved but not yet released.
+1. If not found, create a new issue.
+
+::: warning
+If the site itself is problematic, patience is the only solution until it becomes functional again.
+:::
\ No newline at end of file
diff --git a/website/manuals/guides/troubleshooting/index.md b/website/manuals/guides/troubleshooting/index.md
new file mode 100644
index 0000000..ea28b25
--- /dev/null
+++ b/website/manuals/guides/troubleshooting/index.md
@@ -0,0 +1,51 @@
+---
+title: Troubleshooting
+description: Facing source or app issues? Here's how to troubleshoot.
+---
+
+# Troubleshooting
+
+Facing source or app issues? Here's how to troubleshoot.
+
+Be sure to check the [Frequently Asked Questions]() for how to address common issues too.
+
+## WebView
+
+### Clearing cookies and WebView data
+This resets your WebView to a clean state, including any login states.
+
+1. Navigate to .
+1. Tap **Clear network cache**.
+1. Tap **Clear cookies**.
+
+### WebView update
+To update WebView, you need to find what WebView implementation is used on your device.
+
+Typical default implementation depends on the Android version as follows:
+
+::: tabs
+== Android 10 and above
+[Android System WebView](https://play.google.com/store/apps/details?id=com.google.android.webview)
+== Android 7 - 9
+[Google Chrome](https://play.google.com/store/apps/details?id=com.android.chrome)
+== Android 6 and below
+[Android System WebView](https://play.google.com/store/apps/details?id=com.google.android.webview)
+:::
+
+::: tip **Android 7** and above
+Newer Android users can check/change WebView in [Developer Options](https://developer.android.com/studio/debug/dev-options).
+:::
+
+::: warning Caution with Non-Standard WebView
+Using non-standard WebView (like Firefox) might cause **Kotatsu** to malfunction or crash.
+
+It's best to use the standard [Android System WebView](https://play.google.com/store/apps/details?id=com.google.android.webview) or [Google Chrome](https://play.google.com/store/apps/details?id=com.android.chrome).
+:::
+
+## General
+
+### Obtaining crash/error logs
+For crash investigations, navigate to and tap **Share logs**.
+
+### Obtaining more logs
+To diagnose abnormal app behavior, record device logs using a [Logcat Reader](https://play.google.com/store/apps/details?id=com.dp.logcatapp).
\ No newline at end of file
diff --git a/website/privacy/index.md b/website/privacy/index.md
new file mode 100644
index 0000000..3f0edf8
--- /dev/null
+++ b/website/privacy/index.md
@@ -0,0 +1,45 @@
+---
+title: Privacy policy
+description: Privacy Policy that explains how Kotatsu collects, uses, and protects users' personal information.
+lastUpdated: false
+editLink: false
+---
+
+# Privacy policy
+
+Kotatsu is an Open Source app.
+This SERVICE is provided at no cost and is intended for use as is.
+
+This page details our policies with the collection, use, and disclosure of Personal Information if anyone decided to use our Service.
+
+If you choose to use our Service, then you agree to the collection and use of information in relation to this policy.
+The Personal Information that we collect is used for providing and improving the Service.
+We will not use or share your information with anyone except as described in this Privacy Policy.
+
+## Log Data
+
+In a case of an error in the app, the Service collects data and information called Log Data.
+This Log Data may include information including your device name, operating system version, the configuration of the app when utilizing our Service, the time and date of your use of the Service, and other statistics.
+
+Log Data can be sent at the discretion of the user.
+
+## External Links
+
+This Service contains links to other sites.
+If you click on a third-party link, you will be directed to that site.
+Note that these external sites are not operated by us.
+Therefore, we strongly advise you to review the Privacy Policy of these websites.
+We have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
+
+This includes the use of external tracking services (e.g. MyAnimeList).
+
+## Changes to This Privacy Policy
+
+We may periodically update our Privacy Policy.
+Thus, you are advised to review this page periodically for any changes.
+
+The current iteration of this policy is effective as of October 2, 2023.
+
+## Contact Us
+
+If you have any questions or suggestions about this Privacy Policy, do not hesitate to reach out to us on [our Telegram group](https://t.me/kotatsuapp).
\ No newline at end of file
diff --git a/website/public/favicon.ico b/website/public/favicon.ico
new file mode 100644
index 0000000..8a460b9
Binary files /dev/null and b/website/public/favicon.ico differ
diff --git a/website/public/icon-192.png b/website/public/icon-192.png
new file mode 100644
index 0000000..e39314e
Binary files /dev/null and b/website/public/icon-192.png differ
diff --git a/website/public/icon-512.png b/website/public/icon-512.png
new file mode 100644
index 0000000..01020c1
Binary files /dev/null and b/website/public/icon-512.png differ
diff --git a/website/public/img/kotatsu-dl.png b/website/public/img/kotatsu-dl.png
new file mode 100644
index 0000000..e65aa3f
Binary files /dev/null and b/website/public/img/kotatsu-dl.png differ
diff --git a/website/public/img/phone_1.png b/website/public/img/phone_1.png
new file mode 100644
index 0000000..90a87a3
Binary files /dev/null and b/website/public/img/phone_1.png differ
diff --git a/website/public/img/phone_2.png b/website/public/img/phone_2.png
new file mode 100644
index 0000000..4203220
Binary files /dev/null and b/website/public/img/phone_2.png differ
diff --git a/website/public/img/phone_3.png b/website/public/img/phone_3.png
new file mode 100644
index 0000000..8d369f0
Binary files /dev/null and b/website/public/img/phone_3.png differ
diff --git a/website/public/logo-dark.svg b/website/public/logo-dark.svg
new file mode 100644
index 0000000..0956f77
--- /dev/null
+++ b/website/public/logo-dark.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/website/public/logo.svg b/website/public/logo.svg
new file mode 100644
index 0000000..26623ab
--- /dev/null
+++ b/website/public/logo.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/website/public/manuals/guides/backups/backup.png b/website/public/manuals/guides/backups/backup.png
new file mode 100644
index 0000000..8c3fb43
Binary files /dev/null and b/website/public/manuals/guides/backups/backup.png differ
diff --git a/website/public/manuals/guides/getting-started/add-to-favourites.png b/website/public/manuals/guides/getting-started/add-to-favourites.png
new file mode 100644
index 0000000..e5b0d3f
Binary files /dev/null and b/website/public/manuals/guides/getting-started/add-to-favourites.png differ
diff --git a/website/public/manuals/guides/getting-started/welcome.png b/website/public/manuals/guides/getting-started/welcome.png
new file mode 100644
index 0000000..1ecc0a9
Binary files /dev/null and b/website/public/manuals/guides/getting-started/welcome.png differ
diff --git a/website/public/manuals/guides/troubleshooting/share-logs.dark.png b/website/public/manuals/guides/troubleshooting/share-logs.dark.png
new file mode 100644
index 0000000..556e9b5
Binary files /dev/null and b/website/public/manuals/guides/troubleshooting/share-logs.dark.png differ
diff --git a/website/public/qr-code.png b/website/public/qr-code.png
new file mode 100644
index 0000000..5c8d201
Binary files /dev/null and b/website/public/qr-code.png differ
diff --git a/website/public/robots.txt b/website/public/robots.txt
new file mode 100644
index 0000000..a519125
--- /dev/null
+++ b/website/public/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Sitemap: https://kotatsu.app/sitemap.xml
\ No newline at end of file
diff --git a/website/public/site.webmanifest b/website/public/site.webmanifest
new file mode 100644
index 0000000..1d76b3f
--- /dev/null
+++ b/website/public/site.webmanifest
@@ -0,0 +1,10 @@
+{
+ "name": "kotatsu.app",
+ "icons": [
+ { "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" },
+ { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}
\ No newline at end of file
diff --git a/website/ru/download/index.md b/website/ru/download/index.md
new file mode 100644
index 0000000..dbbe3a1
--- /dev/null
+++ b/website/ru/download/index.md
@@ -0,0 +1,22 @@
+---
+title: Скачать
+description: Страница загрузки, которая позволяет пользователям получить доступ к последней версии приложения и установить её.
+lastUpdated: false
+editLink: false
+prev: false
+next: false
+---
+
+
+
+# Download
+
+Последняя стабильная версия **Kotatsu** была выпущена ** **.
+
+
+
+
\ No newline at end of file
diff --git a/website/ru/faq/categories.json b/website/ru/faq/categories.json
new file mode 100644
index 0000000..a807200
--- /dev/null
+++ b/website/ru/faq/categories.json
@@ -0,0 +1,8 @@
+{
+ "ace": "Ace",
+ "scripts": "Полезные скрипты",
+ "manager-customize": "Кастомизация панели управления",
+ "moxi": "Moxi",
+ "tinymce-rte": "TinyMCE Rich Text Editor",
+ "zoomx": "ZoomX"
+ }
\ No newline at end of file
diff --git a/website/ru/index.md b/website/ru/index.md
new file mode 100644
index 0000000..2cf8e60
--- /dev/null
+++ b/website/ru/index.md
@@ -0,0 +1,29 @@
+---
+title: Kotatsu
+layout: home
+
+main:
+ title: Kotatsu
+ text: Читалка манги
+ tagline: Простая и удобная читалка манги с открытым исходным кодом от сообщества и для сообщества, где вы можете найти и читать вашу любимую мангу проще как никогда.
+ actions:
+ - theme: brand
+ text: Приступить к изучению
+ link: /manuals/guides/getting-started
+ - theme: alt
+ text: Скачать
+ link: /download/
+ features:
+ - icon: ⚡️
+ title: Adocs, The DX that can't be beat
+ details: Lorem ipsum...
+ - icon: 🎉
+ title: Power of Vue meets Markdown
+ details: Lorem ipsum...
+ - icon: 🔥
+ title: Simple and minimal, always
+ details: Lorem ipsum...
+ - icon: 🎀
+ title: Stylish and cool
+ details: Lorem ipsum...
+---
\ No newline at end of file
diff --git a/website/ru/manuals/contribute.md b/website/ru/manuals/contribute.md
new file mode 100644
index 0000000..ab2c5f7
--- /dev/null
+++ b/website/ru/manuals/contribute.md
@@ -0,0 +1,30 @@
+---
+title: Внести вклад
+description: Узнайте, как помочь перевести или улучшить приложение и парсеры.
+author: ztimms73
+---
+
+# Внести вклад
+Узнайте, как помочь перевести или улучшить приложение и парсеры.
+
+## Код
+Умеете программировать и хотите что-то улучшить или вы вообще хотите поддержать развитие приложения?
+
+[](https://github.com/KotatsuApp/Kotatsu)
+
+[](https://github.com/KotatsuApp/kotatsu-parsers)
+
+[](https://github.com/KotatsuApp/kotatsuapp.github.io)
+
+## Перевод
+
+
+Хотите помочь перевести приложение на ваш язык? Вы можете легко помочь, воспользовавшись сервисом под названием **Weblate**.
+
+### Полезные ссылки
+* [Руководство для переводчиков](https://docs.weblate.org/en/latest/user/translating.html)
+* [Второстепенные языки](https://docs.weblate.org/en/latest/user/profile.html#secondary-languages)
+* [Глоссарий](https://docs.weblate.org/en/latest/user/translating.html#glossary)
+
+## Добровольное пожертвование
+Если вы не можете внести свой вклад ни в код, ни в переводы, но все же хотите помочь, то вы можете внести свой вклад непосредственно основателю проекта [Koitharu](https://github.com/Koitharu/) используя сервис переводов денежных средств [YooMoney](https://yoomoney.ru/to/410012543938752).
\ No newline at end of file
diff --git a/website/ru/manuals/guides/getting-started.md b/website/ru/manuals/guides/getting-started.md
new file mode 100644
index 0000000..b523507
--- /dev/null
+++ b/website/ru/manuals/guides/getting-started.md
@@ -0,0 +1,32 @@
+---
+title: Getting started
+description: Essential information to help you get set up with Kotatsu.
+---
+
+
+
+# Getting started
+
+Essential information to help you get set up with Kotatsu.
+
+## Installation guide
+
+### 1. Downloading Kotatsu
+
+1. Visit our [download](/download/) page to get the latest version of **Kotatsu**.
+1. After the download is complete, open the `kotatsu-{{ release.stable.tag_name }}-release.apk` file.
+1. Proceed with the installation process.
+
+### Trouble finding a specific series?
+
+If you encounter difficulties while searching for a specific series, consider the following points:
+
+* Double-check your spelling and try again, as some sources might use **Japanese romanized** titles instead of **English** ones.
+ > Example: **Boku no Hero Academia** instead of **My Hero Academia**.
+
+* Some sources may use different spellings or wordings for titles.
+ > Example: **Bungo Stray Dogs** instead of **Bungou Stray Dogs**
+
+ > Example: **3-gatsu no Lion** instead of **Sangatsu no Lion**.
\ No newline at end of file
diff --git a/website/ru/privacy/index.md b/website/ru/privacy/index.md
new file mode 100644
index 0000000..7eb657c
--- /dev/null
+++ b/website/ru/privacy/index.md
@@ -0,0 +1,45 @@
+---
+title: Privacy policy
+description: Privacy Policy that explains how Kotatsu collects, uses, and protects users' personal information.
+lastUpdated: false
+editLink: false
+---
+
+# Privacy policy
+
+Kotatsu is an Open Source app.
+This SERVICE is provided at no cost and is intended for use as is.
+
+This page details our policies with the collection, use, and disclosure of Personal Information if anyone decided to use our Service.
+
+If you choose to use our Service, then you agree to the collection and use of information in relation to this policy.
+The Personal Information that we collect is used for providing and improving the Service.
+We will not use or share your information with anyone except as described in this Privacy Policy.
+
+### Log Data
+
+In a case of an error in the app, the Service automatically collects data and information called Log Data.
+This Log Data may include information including your device name, operating system version, the configuration of the app when utilizing our Service, the time and date of your use of the Service, and other statistics.
+
+This can be disabled within the app.
+
+## External Links
+
+This Service contains links to other sites.
+If you click on a third-party link, you will be directed to that site.
+Note that these external sites are not operated by us.
+Therefore, we strongly advise you to review the Privacy Policy of these websites.
+We have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
+
+This includes the use of external tracking services (e.g. MyAnimeList).
+
+## Changes to This Privacy Policy
+
+We may periodically update our Privacy Policy.
+Thus, you are advised to review this page periodically for any changes.
+
+The current iteration of this policy is effective as of October 2, 2023.
+
+## Contact Us
+
+If you have any questions or suggestions about this Privacy Policy, do not hesitate to reach out to us on [our Telegram group](https://t.me/kotatsuapp).
\ No newline at end of file
diff --git a/website/sandbox/index.md b/website/sandbox/index.md
new file mode 100644
index 0000000..64d1ad0
--- /dev/null
+++ b/website/sandbox/index.md
@@ -0,0 +1,69 @@
+---
+title: Sandbox
+description: Area to test and demonstrate different features and capabilities.
+search: false
+sidebar: true
+sitemap:
+ exclude: true
+head:
+ - - meta
+ - name: robots
+ content: noindex, nofollow
+---
+
+# Sandbox
+Sandbox playground for demonstrating and documenting how different website functions work for easier integration by contributors.
+
+- [Style guide](/sandbox/style-guide)
+
+## Shortcodes
+
+### Full list
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+
+
\ No newline at end of file
diff --git a/website/sandbox/style-guide.md b/website/sandbox/style-guide.md
new file mode 100644
index 0000000..ee4ed8a
--- /dev/null
+++ b/website/sandbox/style-guide.md
@@ -0,0 +1,79 @@
+---
+title: Style guide
+description: Style guide to aid website content contributors.
+lang: en-US
+sitemap:
+ exclude: true
+---
+
+# Style guide
+Style guide to aid website content contributors. For the code used for certain segments, please view the code for this page.
+Note that I am not experienced in making these and thus it was created on a whim, if you know any way to improve this page then please submit an edit for it or create an issue.
+
+## FAQ Style
+Some **DO**s and **DO NOT**s for [FAQ](/manuals/faq/general).
+
+- **DO NOT** be afraid to ask for help, this includes clarifying what some parts on this page mean.
+
+- **DO** write each segment as a question:
+> #### How can I see how many chapters I've downloaded?
+
+- **DO** write your instructions as clear as possible, preferably check with other people first and see if they can help you improve your language used.
+
+- **DO NOT** write noticably sloppily, this makes it harder for both us and the user to understand.
+
+- **DO NOT** forget to at least check for basic grammar issues, this includes not having a capital letter in your first word and forgetting commas/periods.
+
+- **DO PREFERABLY**, in relation to the above, use resources that can aid with grammar and general structure:
+> - [Grammarly](https://app.grammarly.com/) - Grammarly's writing app makes sure everything you type is not only correct, but also clear and easy to read.
+> - [Hemmingway App](http://www.hemingwayapp.com/) - Hemingway App makes your writing bold and clear.
+
+- **DO** preferably try to build the enviroment and test it locally first before submitting your PR, ask for help with this if you need it.
+
+- **DO** use guide containers for quickly showing how to achieve something:
+> ::: tip Instructions
+> Enable it by going to → → **Display** section and then checking the **Download badges** option at the bottom.
+> :::
+
+- **DO** try to be consistent with video recording by following these guidelines:
+> ::: tip Video recording guidelines
+> When making videos, it's highly preferable that you use [Android Studio's](https://developer.android.com/studio) emulator for a consistent result each time. The emulator settings > for the front-page images is a **Pixel 3 XL** running **Android 10**.
+> - Record your videos as **MP4** or **WEBM**, whichever works the best for you.
+> - Use Androids built-in **Demo Mode**, it can be found in the Developer settings.
+> - Record with a clean workspace and don't be too hasty or too slow with actions.
+> :::
+
+- **DO** use arrow symbols in instructions, instead of using greater-than symbols:
+> Start → Goal
+
+- **DO** bold important words in your text:
+> ...checking the **Download badges** option...
+
+- **DO** use **[Shortcodes](https://github.com/KotatsuApp/kotatsuapp.github.io/.vitepress/config/shortcodes.ts)** items when applicable:
+>
+
+- **DO** use unordered lists when writing a list of stuff that's not an instruction:
+> - This thing.
+> - And that thing.
+
+- **DO NOT** use unordered lists for instructions.
+- **DO** use ordered lists when writing a longer instruction:
+> 1. Step one.
+> 1. Step two.
+
+- **DO** use tabs for instructions when necessary for a good experience:
+> ::: tabs
+> == Android 8+
+> **Lorem ipsum** dolor sit amet, _consectetur_ adipiscing **elit**.
+> == Android 7 and under
+> **Pellentesque** _habitant_ morbi tristique **senectus** et netus.
+> :::
+
+- **DO** include related info as an aside object:
+> **Lorem ipsum** dolor sit amet, _consectetur_ adipiscing **elit**.
+> ::: tip Note
+> Related GitHub issue: [#1](https://github.com/KotatsuApp/Kotatsu/issues/1)
+> :::
+
+- **DO** use code tags for error codes and file names:
+> Storage related error: `Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException...`
\ No newline at end of file
diff --git a/website/translators.json b/website/translators.json
new file mode 100644
index 0000000..94bac92
--- /dev/null
+++ b/website/translators.json
@@ -0,0 +1,23 @@
+{
+ "Koitharu": {
+ "name": {
+ "en": "Koitharu",
+ "ru": "Koitharu",
+ "ua": "Koitharu"
+ }
+ },
+ "ztimms73": {
+ "name": {
+ "en": "Xtimms",
+ "ru": "Xtimms",
+ "ua": "Xtimms"
+ }
+ },
+ "CakesTwix": {
+ "name": {
+ "en": "CakesTwix",
+ "ru": "CakesTwix",
+ "ua": "CakesTwix"
+ }
+ }
+}
\ No newline at end of file
diff --git a/website/translators.ts b/website/translators.ts
new file mode 100644
index 0000000..c443222
--- /dev/null
+++ b/website/translators.ts
@@ -0,0 +1,51 @@
+import translators from './translators.json'
+import type { Theme } from '../.vitepress/theme/types'
+
+export interface Translator {
+ name: string | Record
+ github: string
+ avatar: string
+ website?: string
+}
+
+const translatorsList: Record = {}
+
+for (const [key, value] of Object.entries(translators)) {
+ const data: Partial = typeof value === 'object' && value !== null ? value : {}
+
+ translatorsList[key] = {
+ ...data,
+ name: typeof value === 'string' ? value : value.name,
+ github: `https://github.com/${key}`,
+ avatar: `https://github.com/${key}.png`,
+ }
+}
+
+export const coreMembers: Theme.TeamMember[] = [
+ {
+ avatar: 'https://github.com/Koitharu.png',
+ name: {
+ ru: 'Koitharu',
+ en: 'Koitharu',
+ },
+ org: 'Lead developer',
+ orgLink: 'https://github.com/KotatsuApp',
+ links: [
+ { icon: 'github', link: 'https://github.com/Koitharu' },
+ ],
+ },
+ {
+ avatar: 'https://github.com/ztimms73.png',
+ name: {
+ ru: 'Xtimms',
+ en: 'Xtimms',
+ },
+ org: 'Developer, UI/UX',
+ orgLink: 'https://github.com/KotatsuApp',
+ links: [
+ { icon: 'github', link: 'https://github.com/ztimms73' },
+ ],
+ },
+]
+
+export { translatorsList as translators }
\ No newline at end of file
diff --git a/website/ua/index.md b/website/ua/index.md
new file mode 100644
index 0000000..960fbdb
--- /dev/null
+++ b/website/ua/index.md
@@ -0,0 +1,9 @@
+---
+title: Kiтатсу
+layout: home
+
+main:
+ title: Kiтатсу
+ text: Що?
+ tagline: Сторінка не готова, вийди звідси, розбійник!
+---
\ No newline at end of file