finish up the atom news feed API

This commit is contained in:
ngn 2025-01-04 19:59:44 +03:00
parent 26e8909998
commit 337e56de78
33 changed files with 2633 additions and 2633 deletions

View File

@ -65,8 +65,8 @@ class AdminAPI:
return title.lower().replace(" ", "_") return title.lower().replace(" ", "_")
def _check_multilang_field(self, ml: Dict[str, str]) -> bool: def _check_multilang_field(self, ml: Dict[str, str]) -> bool:
for l in self.languages: for lang in self.languages:
if l in ml and ml[l] != "": if lang in ml and ml[lang] != "":
return True return True
return False return False
@ -114,24 +114,26 @@ class AdminAPI:
) )
def add_service(self, service: Dict[str, str]): def add_service(self, service: Dict[str, str]):
if not "name" in service or service["name"] == "": if "name" not in service or service["name"] == "":
raise Exception('Service structure is missing required "name" field') raise Exception('Service structure is missing required "name" field')
if not "desc" in service: if "desc" not in service:
raise Exception('Service structure is missing required "desc" field') raise Exception('Service structure is missing required "desc" field')
if ( if (
(not "clear" in service or service["clear"] == "") ("clear" not in service or service["clear"] == "")
and (not "onion" in service or service["onion"] == "") and ("onion" not in service or service["onion"] == "")
and (not "i2p" in service or service["i2p"] == "") and ("i2p" not in service or service["i2p"] == "")
): ):
raise Exception( raise Exception(
'Service structure is missing "clear", "onion" and "i2p" field, at least one needed' 'Service structure is missing "clear", "onion" '
+ 'and "i2p" field, at least one needed'
) )
if not self._check_multilang_field(service["desc"]): if not self._check_multilang_field(service["desc"]):
raise Exception( raise Exception(
'Service structure field "desc" needs at least one supported language entry' 'Service structure field "desc" needs at least '
+ "one supported language entry"
) )
self.PUT("/v1/admin/service/add", service) self.PUT("/v1/admin/service/add", service)
@ -146,26 +148,28 @@ class AdminAPI:
self.GET("/v1/admin/service/check") self.GET("/v1/admin/service/check")
def add_news(self, news: Dict[str, str]): def add_news(self, news: Dict[str, str]):
if not "id" in news or news["id"] == "": if "id" not in news or news["id"] == "":
raise Exception('News structure is missing required "id" field') raise Exception('News structure is missing required "id" field')
if not "author" in news or news["author"] == "": if "author" not in news or news["author"] == "":
raise Exception('News structure is missing required "author" field') raise Exception('News structure is missing required "author" field')
if not "title" in news: if "title" not in news:
raise Exception('News structure is missing required "title" field') raise Exception('News structure is missing required "title" field')
if not "content" in news: if "content" not in news:
raise Exception('News structure is missing required "content" field') raise Exception('News structure is missing required "content" field')
if not self._check_multilang_field(news["title"]): if not self._check_multilang_field(news["title"]):
raise Exception( raise Exception(
'News structure field "title" needs at least one supported language entry' 'News structure field "title" needs at least '
+ "one supported language entry"
) )
if not self._check_multilang_field(news["content"]): if not self._check_multilang_field(news["content"]):
raise Exception( raise Exception(
'News structure field "content" needs at least one supported language entry' 'News structure field "content" needs at least '
+ "one supported language entry"
) )
self.PUT("/v1/admin/news/add", news) self.PUT("/v1/admin/news/add", news)
@ -205,8 +209,8 @@ def __handle_command(log: Log, api: AdminAPI, cmd: str) -> None:
data["desc"] = {} data["desc"] = {}
data["name"] = log.input("Serivce name") data["name"] = log.input("Serivce name")
for l in api.languages: for lang in api.languages:
data["desc"][l] = log.input("Serivce desc (%s)" % l) data["desc"][lang] = log.input("Serivce desc (%s)" % lang)
data["check_url"] = log.input("Serivce status check URL") data["check_url"] = log.input("Serivce status check URL")
data["clear"] = log.input("Serivce clearnet URL") data["clear"] = log.input("Serivce clearnet URL")
data["onion"] = log.input("Serivce onion URL") data["onion"] = log.input("Serivce onion URL")
@ -216,7 +220,7 @@ def __handle_command(log: Log, api: AdminAPI, cmd: str) -> None:
log.info("Service has been added") log.info("Service has been added")
case "del_service": case "del_service":
api.del_service(self.log.input("Serivce name")) api.del_service(log.input("Serivce name"))
log.info("Service has been deleted") log.info("Service has been deleted")
case "check_services": case "check_services":
@ -229,11 +233,11 @@ def __handle_command(log: Log, api: AdminAPI, cmd: str) -> None:
news["content"] = {} news["content"] = {}
data["id"] = log.input("News ID") data["id"] = log.input("News ID")
for l in api.languages: for lang in api.languages:
data["title"][l] = log.input("News title (%s)" % l) data["title"][lang] = log.input("News title (%s)" % lang)
data["author"] = log.input("News author") data["author"] = log.input("News author")
for l in api.languages: for lang in api.languages:
data["content"][l] = log.input("News content (%s)" % l) data["content"][lang] = log.input("News content (%s)" % lang)
api.add_news(data) api.add_news(data)
log.info("News has been added") log.info("News has been added")
@ -245,12 +249,13 @@ def __handle_command(log: Log, api: AdminAPI, cmd: str) -> None:
case "logs": case "logs":
logs = api.logs() logs = api.logs()
if None == logs["result"] or len(logs["result"]) == 0: if logs["result"] is None or len(logs["result"]) == 0:
return log.info("No available logs") return log.info("No available logs")
for l in logs["result"]: for log in logs["result"]:
log.info( log.info(
"Time: %s | Action: %s" % (__format_time(l["time"]), l["action"]) "Time: %s | Action: %s"
% (__format_time(log["time"]), log["action"])
) )
@ -283,7 +288,7 @@ def __handle_command_with_file(log: Log, api: AdminAPI, cmd: str, file: str) ->
case "logs": case "logs":
logs = api.logs() logs = api.logs()
if None == logs["result"] or len(logs["result"]) == 0: if logs["result"] is None or len(logs["result"]) == 0:
return log.info("No available logs") return log.info("No available logs")
__dump_json_file(logs["result"], file) __dump_json_file(logs["result"], file)
@ -306,7 +311,7 @@ if __name__ == "__main__":
url = getenv(API_URL_ENV) url = getenv(API_URL_ENV)
if url == None: if url is None:
log.error( log.error(
"Please specify the API URL using %s environment variable" % API_URL_ENV "Please specify the API URL using %s environment variable" % API_URL_ENV
) )

View File

@ -16,7 +16,7 @@ func (ml *Multilang) Supports(lang string) bool {
ml_ref := reflect.ValueOf(ml).Elem() ml_ref := reflect.ValueOf(ml).Elem()
for i := 0; i < reflect.Indirect(ml_ref).NumField(); i++ { for i := 0; i < reflect.Indirect(ml_ref).NumField(); i++ {
if name := reflect.Indirect(ml_ref).Field(i).Type().Name(); strings.ToLower(name) == lang { if name := reflect.Indirect(ml_ref).Type().Field(i).Name; strings.ToLower(name) == lang {
return true return true
} }
} }

View File

@ -57,7 +57,7 @@ func (n *News) Scan(rows *sql.Rows) (err error) {
} }
func (n *News) IsValid() bool { func (n *News) IsValid() bool {
return n.Author != "" && n.ID != "" && !n.Title.Empty() && !n.Content.Empty() return n.Time != 0 && n.Author != "" && n.ID != "" && !n.Title.Empty() && !n.Content.Empty()
} }
func (db *Type) NewsNext(n *News) bool { func (db *Type) NewsNext(n *News) bool {

View File

@ -2,7 +2,11 @@ module github.com/ngn13/website/api
go 1.21.3 go 1.21.3
require github.com/gofiber/fiber/v2 v2.52.5 require (
github.com/gofiber/fiber/v2 v2.52.5
github.com/mattn/go-sqlite3 v1.14.24
github.com/russross/blackfriday/v2 v2.1.0
)
require ( require (
github.com/andybalholm/brotli v1.0.5 // indirect github.com/andybalholm/brotli v1.0.5 // indirect
@ -11,9 +15,7 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect

View File

@ -138,6 +138,8 @@ func PUT_AddNews(c *fiber.Ctx) error {
return util.ErrBadJSON(c) return util.ErrBadJSON(c)
} }
news.Time = uint64(time.Now().Unix())
if !news.IsValid() { if !news.IsValid() {
return util.ErrBadReq(c) return util.ErrBadReq(c)
} }

View File

@ -1,7 +1,9 @@
package routes package routes
import ( import (
"sort"
"strings" "strings"
"time"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/ngn13/website/api/config" "github.com/ngn13/website/api/config"
@ -9,12 +11,31 @@ import (
"github.com/ngn13/website/api/util" "github.com/ngn13/website/api/util"
) )
// feed_entry is a temporary struct used to pass the news to the news.xml
type feed_entry struct {
Title string
Author string
Time time.Time
RFC3339 string
Content string
}
// convert UNIX timestamp to RFC3339 (format used by atom feeds)
func (e *feed_entry) From(news *database.News, lang string) {
e.Title = news.Title.Get(lang)
e.Author = news.Author
e.Time = time.Unix(int64(news.Time), 0)
e.RFC3339 = e.Time.Format(time.RFC3339)
e.Content = news.Content.Get(lang)
}
func GET_News(c *fiber.Ctx) error { func GET_News(c *fiber.Ctx) error {
var ( var (
news []database.News entries []feed_entry
n database.News news database.News
feed []byte indx uint64
err error feed []byte
err error
) )
db := c.Locals("database").(*database.Type) db := c.Locals("database").(*database.Type)
@ -27,17 +48,25 @@ func GET_News(c *fiber.Ctx) error {
} }
lang = strings.ToLower(lang) lang = strings.ToLower(lang)
indx = 0
for db.NewsNext(&n) { for db.NewsNext(&news) {
if n.Supports(lang) { if news.Supports(lang) {
news = append(news, n) entries = append(entries, feed_entry{})
entries[indx].From(&news, lang)
indx++
} }
} }
sort.Slice(entries, func(i, j int) bool {
return entries[i].Time.Before(entries[j].Time)
})
if feed, err = util.Render("views/news.xml", fiber.Map{ if feed, err = util.Render("views/news.xml", fiber.Map{
"frontend": frontend, "frontend": frontend,
"updated": time.Now().Format(time.RFC3339),
"entries": entries,
"lang": lang, "lang": lang,
"news": news,
}); err != nil { }); err != nil {
return util.ErrInternal(c, err) return util.ErrInternal(c, err)
} }

View File

@ -1,6 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom"> <?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">
<title>{{.frontend.Host}} news</title> <title>{{.frontend.Host}} news</title>
<updated>2025-01-02T20:46:24Z</updated> <updated>{{.updated}}</updated>
<subtitle>News and updates about my self-hosted services and projects</subtitle> <subtitle>News and updates about my projects and self-hosted services</subtitle>
<link href="{{.frontend.String}}/news"></link> <link href="{{.frontend.JoinPath "/news"}}"></link>
{{ range .entries }}
<entry>
<title>{{.Title}}</title>
<updated>{{.RFC3339}}</updated>
<author>
<name>{{.Author}}</name>
</author>
<content>{{.Content}}</content>
</entry>
{{ end }}
</feed> </feed>

9
app/.prettierrc Normal file
View File

@ -0,0 +1,9 @@
{
"useTabs": false,
"tabWidth": 2,
"singleQuote": false,
"trailingComma": "es5",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

3157
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,28 @@
{ {
"name": "website", "name": "website",
"version": "5.0.0", "version": "5.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "VITE_API_URL_DEV=http://127.0.0.1:7001 vite dev", "dev": "VITE_API_URL_DEV=http://127.0.0.1:7001 vite dev",
"build": "vite build", "build": "vite build",
"preview": "vite preview --host" "preview": "vite preview --host",
}, "lint": "prettier --check .",
"devDependencies": { "format": "prettier --write ."
"@sveltejs/adapter-auto": "^3.3.1", },
"@sveltejs/adapter-node": "^5.2.11", "devDependencies": {
"@sveltejs/kit": "^2.15.1", "@sveltejs/adapter-auto": "^3.3.1",
"@sveltejs/vite-plugin-svelte": "^4.0.3", "@sveltejs/adapter-node": "^5.2.11",
"svelte": "^5.16.0", "@sveltejs/kit": "^2.15.1",
"vite": "^5.4.11" "@sveltejs/vite-plugin-svelte": "^4.0.3",
}, "prettier": "^3.4.2",
"type": "module", "prettier-plugin-svelte": "^3.3.2",
"dependencies": { "svelte": "^5.16.0",
"@types/dompurify": "^3.2.0", "vite": "^5.4.11"
"dompurify": "^3.2.3", },
"marked": "^15.0.4" "type": "module",
} "dependencies": {
"@types/dompurify": "^3.2.0",
"dompurify": "^3.2.3",
"marked": "^15.0.4"
}
} }

View File

@ -1,12 +1,12 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=1024"> <meta name="viewport" content="width=1024" />
<link rel="icon" href="data:;base64,="> <link rel="icon" href="data:;base64,=" />
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>
</body> </body>
</html> </html>

View File

@ -1,52 +1,63 @@
<script> <script>
export let title export let title;
let current = "" let current = "";
let i = 0 let i = 0;
while (title.length > i) { while (title.length > i) {
let c = title[i] let c = title[i];
setTimeout(()=>{ setTimeout(
current += c () => {
}, 100*(i+1)) current += c;
i += 1 },
100 * (i + 1)
);
i += 1;
} }
</script> </script>
<div class="main"> <div class="main">
<div class="title"> <div class="title">
root@ngn.tf:~# {current} root@ngn.tf:~# {current}
</div> </div>
<div class="content"> <div class="content">
<slot></slot> <slot></slot>
</div> </div>
</div> </div>
<style> <style>
.main { .main {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
background: var(--dark-three); background: var(--dark-three);
box-shadow: var(--box-shadow); box-shadow: var(--box-shadow);
border-radius: var(--radius); border-radius: var(--radius);
border: solid 1px var(--border-color); border: solid 1px var(--border-color);
} }
.title { .title {
background: var(--dark-two); background: var(--dark-two);
padding: 25px; padding: 25px;
border-radius: 7px 7px 0px 0px; border-radius: 7px 7px 0px 0px;
font-size: 20px; font-size: 20px;
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; font-family:
color: white; Consolas,
} Monaco,
Lucida Console,
Liberation Mono,
DejaVu Sans Mono,
Bitstream Vera Sans Mono,
Courier New,
monospace;
color: white;
}
.content { .content {
background: var(--dark-three); background: var(--dark-three);
padding: 30px; padding: 30px;
color: white; color: white;
border-radius: 5px; border-radius: 5px;
font-size: 25px; font-size: 25px;
} }
</style> </style>

View File

@ -1,21 +1,24 @@
<script> <script>
export let title export let title;
export let url export let url;
let audio let audio;
let current = "" let current = "";
let i = 0 let i = 0;
while (title.length > i) { while (title.length > i) {
let c = title[i] let c = title[i];
setTimeout(()=>{ setTimeout(
current += c () => {
}, 100*(i+1)) current += c;
i += 1 },
100 * (i + 1)
);
i += 1;
} }
function epicSound() { function epicSound() {
audio.play() audio.play();
} }
</script> </script>
@ -25,46 +28,54 @@
</audio> </audio>
<div class="title"> <div class="title">
{current} {current}
</div> </div>
<div class="content"> <div class="content">
<slot></slot> <slot></slot>
</div> </div>
</a> </a>
<style> <style>
a { a {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
background: var(--dark-three); background: var(--dark-three);
box-shadow: var(--box-shadow); box-shadow: var(--box-shadow);
border-radius: var(--radius); border-radius: var(--radius);
cursor: pointer; cursor: pointer;
transition: .4s; transition: 0.4s;
text-decoration: none; text-decoration: none;
border: solid 1px var(--border-color); border: solid 1px var(--border-color);
} }
a:hover > .title { a:hover > .title {
text-shadow: var(--text-shadow); text-shadow: var(--text-shadow);
} }
.title { .title {
border: solid 1px var(--dark-two); border: solid 1px var(--dark-two);
background: var(--dark-two); background: var(--dark-two);
padding: 25px; padding: 25px;
border-radius: 7px 7px 0px 0px; border-radius: 7px 7px 0px 0px;
font-size: 20px; font-size: 20px;
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; font-family:
color: white; Consolas,
} Monaco,
Lucida Console,
Liberation Mono,
DejaVu Sans Mono,
Bitstream Vera Sans Mono,
Courier New,
monospace;
color: white;
}
.content { .content {
background: var(--dark-three); background: var(--dark-three);
padding: 30px; padding: 30px;
padding-top: 30px; padding-top: 30px;
color: white; color: white;
border-radius: 5px; border-radius: 5px;
font-size: 25px; font-size: 25px;
} }
</style> </style>

View File

@ -1,5 +1,5 @@
<script> <script>
export let subtitle = "" export let subtitle = "";
</script> </script>
<header> <header>
@ -10,32 +10,31 @@ export let subtitle = ""
</header> </header>
<style> <style>
header { header {
background: background: linear-gradient(rgba(11, 11, 11, 0.808), rgba(1, 1, 1, 0.96)),
linear-gradient(rgba(11, 11, 11, 0.808), rgba(1, 1, 1, 0.96)), url("https://files.ngn.tf/banner.png");
url("https://files.ngn.tf/banner.png"); background-size: 50%;
background-size: 50%; width: 100%;
width: 100%; height: 100%;
height: 100%; }
}
h1 { h1 {
font-weight: 900; font-weight: 900;
font-size: 500%; font-size: 500%;
padding: 120px; padding: 120px;
padding-bottom: 0; padding-bottom: 0;
text-align: center; text-align: center;
color: white; color: white;
text-shadow: var(--text-shadow); text-shadow: var(--text-shadow);
text-size-adjust: 80%; text-size-adjust: 80%;
} }
h4 { h4 {
padding-bottom: 120px; padding-bottom: 120px;
font-weight: 600; font-weight: 600;
font-size: 200%; font-size: 200%;
text-align: center; text-align: center;
color: white; color: white;
text-size-adjust: 80%; text-size-adjust: 80%;
} }
</style> </style>

View File

@ -1,51 +1,43 @@
<script> <script>
import { color } from "$lib/util.js";
import NavbarLink from "./navbar_link.svelte"; import NavbarLink from "./navbar_link.svelte";
</script> </script>
<nav> <nav style="border-bottom: solid 2px var(--{color()});">
<div> <h3 style="color: var(--{color()})">[ngn.tf]</h3>
<h3>[ngn.tf]</h3>
</div>
<div> <div>
<NavbarLink link="/">home</NavbarLink> <NavbarLink link="/">home</NavbarLink>
<NavbarLink link="/news">news</NavbarLink>
<NavbarLink link="/services">services</NavbarLink> <NavbarLink link="/services">services</NavbarLink>
<NavbarLink link="/blog">blog</NavbarLink>
<!-- <NavbarLink link="/donate">donate</NavbarLink> --> <!-- <NavbarLink link="/donate">donate</NavbarLink> -->
<NavbarLink link="https://stats.ngn.tf">status</NavbarLink>
</div> </div>
</nav> </nav>
<style> <style>
nav { nav {
background: var(--dark-one); background: var(--dark-one);
padding: 20px 26px 22px 20px; padding: 20px 30px 20px 20px;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
border-bottom: solid 1.5px black; /*animation-name: borderAnimation;
animation-name: borderAnimation;
animation-duration: 10s; animation-duration: 10s;
animation-iteration-count: infinite; animation-iteration-count: infinite;*/
box-shadow: var(--def-shadow); box-shadow: var(--def-shadow);
} }
div { div {
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: right; justify-content: right;
gap: 15px; gap: 15px;
} }
h3 { h3 {
font-weight: 900; font-weight: 900;
font-size: 25px; font-size: 20px;
color: red; }
animation-name: colorAnimation;
animation-iteration-count: infinite;
animation-duration: 10s;
}
</style> </style>

View File

@ -1,47 +1,28 @@
<script> <script>
import { page } from "$app/stores" import { color, click } from "$lib/util.js";
export let link export let link;
export let type
let audio
function epicSound() {
audio.play()
}
</script> </script>
<div> <a
<audio bind:this={audio} preload="auto"> style="text-decoration-color: var(--{color()})"
<source src="/click.wav" type="audio/mpeg" /> data-sveltekit-preload-data
</audio> on:click={click}
{#if type==="icon"} href={link}
<a class="icon" data-sveltekit-preload-data on:click={epicSound} href="{link}"><slot></slot></a> >
{:else} <slot></slot>
<a data-sveltekit-preload-data on:click={epicSound} href="{link}"><slot></slot></a> </a>
{/if}
</div>
<style> <style>
a { a {
font-weight: 700; font-weight: 700;
font-size: 22px; font-size: 20px;
text-decoration: none; text-decoration: none;
color: white; color: white;
cursor: pointer; cursor: pointer;
} }
a:hover { a:hover {
text-decoration: underline; text-decoration: underline;
text-shadow: 3px 4px 7px rgba(81, 67, 21, 0.8); text-shadow: 3px 4px 7px rgba(81, 67, 21, 0.8);
animation-name: underlineAnimation; }
animation-duration: 5s;
animation-iteration-count: infinite;
}
.icon:hover {
text-decoration: none;
text-shadow: 3px 4px 7px rgba(81, 67, 21, 0.8);
animation-name: colorAnimation;
animation-duration: 5s;
animation-iteration-count: infinite;
}
</style> </style>

View File

@ -1,17 +1,17 @@
<script> <script>
export let desc export let desc;
export let url export let url;
let icon = "<i class='nf nf-md-clipboard_multiple'></i>" let icon = "<i class='nf nf-md-clipboard_multiple'></i>";
let audio let audio;
function copy() { function copy() {
audio.play() audio.play();
navigator.clipboard.writeText(url) navigator.clipboard.writeText(url);
icon = "<i class='nf nf-md-clipboard_check'></i>" icon = "<i class='nf nf-md-clipboard_check'></i>";
setTimeout(()=>{ setTimeout(() => {
icon = "<i class='nf nf-md-clipboard_multiple'></i>" icon = "<i class='nf nf-md-clipboard_multiple'></i>";
}, 500) }, 500);
} }
</script> </script>
@ -25,54 +25,56 @@
</div> </div>
<div> <div>
<button on:click={copy}>{@html icon}</button> <button on:click={copy}>{@html icon}</button>
<a href="{url}"><i class="nf nf-oct-link_external"></i></a> <a href={url}><i class="nf nf-oct-link_external"></i></a>
</div> </div>
</main> </main>
<style> <style>
main { main {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
padding: 30px 30px 30px 30px; padding: 30px 30px 30px 30px;
background: var(--dark-two); background: var(--dark-two);
border-radius: var(--radius); border-radius: var(--radius);
box-shadow: var(--box-shadow); box-shadow: var(--box-shadow);
border: solid 1px var(--border-color); border: solid 1px var(--border-color);
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
color: white; color: white;
gap: 100px; gap: 100px;
transition: .4s; transition: 0.4s;
flex-grow: 1; flex-grow: 1;
flex: 1 1 0px; flex: 1 1 0px;
} }
div h1 { div h1 {
animation-name: colorAnimation; animation-name: colorAnimation;
animation-duration: 10s; animation-duration: 10s;
animation-iteration-count: infinite; animation-iteration-count: infinite;
font-size: 30px; font-size: 30px;
} }
div p { div p {
margin-top: 10px; margin-top: 10px;
font-size: 20px; font-size: 20px;
} }
a, button { a,
text-align: center; button {
font-size: 30px; text-align: center;
text-decoration: none; font-size: 30px;
color: white; text-decoration: none;
border: none; color: white;
background: none; border: none;
outline: none; background: none;
cursor: pointer; outline: none;
} cursor: pointer;
}
a:hover, button:hover{ a:hover,
animation-name: colorAnimation; button:hover {
animation-duration: 5s; animation-name: colorAnimation;
animation-iteration-count: infinite; animation-duration: 5s;
} animation-iteration-count: infinite;
}
</style> </style>

16
app/src/lib/util.js Normal file
View File

@ -0,0 +1,16 @@
function click() {
let audio = new Audio("/click.wav");
audio.play();
}
let colors_pos = -1;
const colors = ["yellow", "cyan", "green", "pinkish", "red", "blue"];
function color() {
if (colors_pos < 0) colors_pos = Math.floor(Math.random() * colors.length);
else if (colors_pos >= colors.length) colors_pos = 0;
return colors[colors_pos];
}
export { click, color };

View File

@ -1,8 +1,8 @@
<script> <script>
import { onMount } from "svelte" import { onMount } from "svelte";
import { goto } from "$app/navigation" import { goto } from "$app/navigation";
onMount(()=>{ onMount(() => {
goto("/") goto("/");
}) });
</script> </script>

View File

@ -1,6 +1,6 @@
<script> <script>
import Navbar from "../lib/navbar.svelte"; import Navbar from "../lib/navbar.svelte";
</script> </script>
<main> <main>
<Navbar /> <Navbar />

View File

@ -4,17 +4,27 @@
</script> </script>
<svelte:head> <svelte:head>
<title>[ngn.tf] | homepage</title> <title>[ngn.tf] | homepage</title>
<meta content="[ngn] | homepage" property="og:title" /> <meta content="[ngn] | homepage" property="og:title" />
<meta content="Homepage of my personal website" property="og:description" /> <meta content="Homepage of my personal website" property="og:description" />
<meta content="https://ngn.tf" property="og:url" /> <meta content="https://ngn.tf" property="og:url" />
<meta content="#000000" data-react-helmet="true" name="theme-color" /> <meta content="#000000" data-react-helmet="true" name="theme-color" />
<link rel="alternate" type="application/atom+xml" href="{import.meta.env.VITE_API_URL_DEV+'/blog/feed.atom'}" title="Atom Feed"> <link
<link rel="alternate" type="application/rss+xml" href="{import.meta.env.VITE_API_URL_DEV+'/blog/feed.rss'}" title="RSS Feed"> rel="alternate"
type="application/atom+xml"
href={import.meta.env.VITE_API_URL_DEV + "/blog/feed.atom"}
title="Atom Feed"
/>
<link
rel="alternate"
type="application/rss+xml"
href={import.meta.env.VITE_API_URL_DEV + "/blog/feed.rss"}
title="RSS Feed"
/>
</svelte:head> </svelte:head>
<Header> <Header>
<c>echo</c> <c>echo</c>
hello world! hello world!
</Header> </Header>
@ -23,18 +33,20 @@
<Card title="whoami"> <Card title="whoami">
<div class="whoami-box"> <div class="whoami-box">
<div class="whoami-pic"> <div class="whoami-pic">
<img alt="My profile" src="https://files.ngn.tf/pplow.png"> <img alt="My profile" src="https://files.ngn.tf/pplow.png" />
<a href="https://keyoxide.org/F9E70878C2FB389AEC2BA34CA3654DF5AD9F641D"> <a href="https://keyoxide.org/F9E70878C2FB389AEC2BA34CA3654DF5AD9F641D">
<c><i class="nf nf-oct-key"></i> Keyoxide</c> <c><i class="nf nf-oct-key"></i> Keyoxide</c>
</a> </a>
</div> </div>
<div class="whoami-text"> <div class="whoami-text">
👋 Hello! I'm ngn! 👋 Hello! I'm ngn!
<ul> <ul>
<li>🇹🇷 I'm a high school student from Turkey</li> <li>🇹🇷 I'm a high school student from Turkey</li>
<li>🖥️ I'm interested in cyber security and programming.</li> <li>🖥️ I'm interested in cyber security and programming.</li>
<li>❤️ I love and support Free/Libre and Open Source Software (FLOSS)</li> <li>❤️ I love and support Free/Libre and Open Source Software (FLOSS)</li>
<li>🐧 My GNU/Linux distribution of choice is Artix, however I am currently running Arch</li> <li>
🐧 My GNU/Linux distribution of choice is Artix, however I am currently running Arch
</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -46,34 +58,37 @@
I usually spend my time... I usually spend my time...
<ul> <ul>
<li><c>⌨️</c> building random projects</li> <li><c>⌨️</c> building random projects</li>
<li><c>👥</c> contributing stuff that I like</li> <li><c>👥</c> contributing stuff that I like</li>
<li><c>🚩</c> solving CTFs</li> <li><c>🚩</c> solving CTFs</li>
<li><c>🖥️</c> customizing my desktop</li> <li><c>🖥️</c> customizing my desktop</li>
<li><c>📑</c> posting random stuff on my blog, you should definitely check it out btw (it's very active)</li> <li>
<c>📑</c> posting random stuff on my blog, you should definitely check it out btw (it's very
active)
</li>
</ul> </ul>
</Card> </Card>
<Card title="wall"> <Card title="wall">
Here are some links if you want to get in contact with me, I highly Here are some links if you want to get in contact with me, I highly prefer email and I usually
prefer email and I usually respond to emails in 1 or 2 days, just make respond to emails in 1 or 2 days, just make sure to check your spam folder (turns out running
sure to check your spam folder (turns out running a TOR relay gets your IP into multiple blacklists) a TOR relay gets your IP into multiple blacklists)
<ul> <ul>
<li> <li>
<c><i class="nf nf-cod-github"></i></c> <c><i class="nf nf-cod-github"></i></c>
<a href="https://github.com/ngn13">Github</a> <a href="https://github.com/ngn13">Github</a>
</li> </li>
<li> <li>
<c><i class="nf nf-md-mastodon"></i></c> <c><i class="nf nf-md-mastodon"></i></c>
<a href="https://defcon.social/@ngn" rel="me">Mastodon</a> <a href="https://defcon.social/@ngn" rel="me">Mastodon</a>
</li> </li>
<li> <li>
<c><i class="nf nf-md-email"></i></c> <c><i class="nf nf-md-email"></i></c>
<a href="mailto:ngn@ngn.tf">Email</a> <a href="mailto:ngn@ngn.tf">Email</a>
</li> </li>
<li> <li>
<c><i class="nf nf-md-xmpp"></i></c> <c><i class="nf nf-md-xmpp"></i></c>
<a href="xmpp:ngn@chat.ngn.tf">XMPP</a> <a href="xmpp:ngn@chat.ngn.tf">XMPP</a>
</li> </li>
</ul> </ul>
</Card> </Card>
</div> </div>
@ -84,89 +99,91 @@
</div> </div>
<style> <style>
main{ main {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 28px; gap: 28px;
padding: 50px; padding: 50px;
} }
.flexbox {
display: flex;
gap: 28px;
}
.whoami-box {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 35px;
}
.whoami-pic {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
border-right: solid 1px var(--dark-fife);
padding: 0 35px 0 10px;
}
.whoami-pic img {
width: 200px;
border-radius: 20px;
border: solid 1px var(--border-color);
animation-name: fullBorderAnimation;
animation-duration: 10s;
animation-iteration-count: infinite;
box-shadow: rgba(50, 50, 93, 1) 0px 30px 60px -12px inset, rgba(0, 0, 0, 1) 0px 18px 36px -18px inset;
}
ul {
list-style: inside;
}
li {
padding-top: 15px;
}
a {
color: white;
text-decoration: none;
}
a:hover {
font-weight: 900;
}
.version {
color: var(--dark-fife);
position: fixed;
bottom: 10px;
right: 10px;
font-size: 15px;
}
@media only screen and (max-width: 1200px) {
.flexbox { .flexbox {
flex-direction: column; display: flex;
gap: 28px;
} }
}
@media only screen and (max-width: 900px) {
.whoami-box { .whoami-box {
flex-direction: column; display: flex;
gap: 25px; flex-direction: row;
align-items: center;
justify-content: center;
gap: 35px;
} }
.whoami-pic { .whoami-pic {
border-right: none; display: flex;
padding: 0; flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
border-right: solid 1px var(--dark-fife);
padding: 0 35px 0 10px;
}
.whoami-pic img {
width: 200px;
border-radius: 20px;
border: solid 1px var(--border-color);
animation-name: fullBorderAnimation;
animation-duration: 10s;
animation-iteration-count: infinite;
box-shadow:
rgba(50, 50, 93, 1) 0px 30px 60px -12px inset,
rgba(0, 0, 0, 1) 0px 18px 36px -18px inset;
}
ul {
list-style: inside;
}
li {
padding-top: 15px;
}
a {
color: white;
text-decoration: none;
}
a:hover {
font-weight: 900;
}
.version {
color: var(--dark-fife);
position: fixed;
bottom: 10px;
right: 10px;
font-size: 15px;
}
@media only screen and (max-width: 1200px) {
.flexbox {
flex-direction: column;
}
}
@media only screen and (max-width: 900px) {
.whoami-box {
flex-direction: column;
gap: 25px;
}
.whoami-pic {
border-right: none;
padding: 0;
}
} }
}
</style> </style>

View File

@ -1,9 +1,9 @@
export async function load({ fetch }) { export async function load({ fetch }) {
const api = import.meta.env.VITE_API_URL_DEV const api = import.meta.env.VITE_API_URL_DEV;
const res = await fetch(api+"/blog/sum") const res = await fetch(api + "/blog/sum");
const data = await res.json() const data = await res.json();
return { return {
posts: data["result"] posts: data["result"],
} };
} }

View File

@ -2,18 +2,28 @@
import Header from "../../lib/header.svelte"; import Header from "../../lib/header.svelte";
import CardLink from "../../lib/card_link.svelte"; import CardLink from "../../lib/card_link.svelte";
export let data export let data;
let posts = data.posts let posts = data.posts;
</script> </script>
<svelte:head> <svelte:head>
<title>[ngn.tf] | blog</title> <title>[ngn.tf] | blog</title>
<meta content="[ngn] | blog" property="og:title" /> <meta content="[ngn] | blog" property="og:title" />
<meta content="View my blog posts" property="og:description" /> <meta content="View my blog posts" property="og:description" />
<meta content="https://ngn.tf" property="og:url" /> <meta content="https://ngn.tf" property="og:url" />
<meta content="#000000" data-react-helmet="true" name="theme-color" /> <meta content="#000000" data-react-helmet="true" name="theme-color" />
<link rel="alternate" type="application/atom+xml" href="{import.meta.env.VITE_API_URL_DEV+'/blog/feed.atom'}" title="Atom Feed"> <link
<link rel="alternate" type="application/rss+xml" href="{import.meta.env.VITE_API_URL_DEV+'/blog/feed.rss'}" title="RSS Feed"> rel="alternate"
type="application/atom+xml"
href={import.meta.env.VITE_API_URL_DEV + "/blog/feed.atom"}
title="Atom Feed"
/>
<link
rel="alternate"
type="application/rss+xml"
href={import.meta.env.VITE_API_URL_DEV + "/blog/feed.rss"}
title="RSS Feed"
/>
</svelte:head> </svelte:head>
<Header> <Header>
@ -22,21 +32,24 @@
<main> <main>
<div class="feed-list"> <div class="feed-list">
<a href="{import.meta.env.VITE_API_URL_DEV+'/blog/feed.rss'}"> <a href={import.meta.env.VITE_API_URL_DEV + "/blog/feed.rss"}>
<c><i class="nf nf-fa-rss_square"></i></c> <p>RSS</p> <c><i class="nf nf-fa-rss_square"></i></c>
<p>RSS</p>
</a> </a>
<a href="{import.meta.env.VITE_API_URL_DEV+'/blog/feed.atom'}"> <a href={import.meta.env.VITE_API_URL_DEV + "/blog/feed.atom"}>
<c><i class="nf nf-fae-atom"></i></c> <p>Atom</p> <c><i class="nf nf-fae-atom"></i></c>
<p>Atom</p>
</a> </a>
<a href="{import.meta.env.VITE_API_URL_DEV+'/blog/feed.json'}"> <a href={import.meta.env.VITE_API_URL_DEV + "/blog/feed.json"}>
<c><i class="nf nf-seti-json"></i></c> <p>JSON</p> <c><i class="nf nf-seti-json"></i></c>
<p>JSON</p>
</a> </a>
</div> </div>
<div class="post-list"> <div class="post-list">
{#each posts as post} {#each posts as post}
<CardLink url="/blog/{post.id}" title="{post.title}"> <CardLink url="/blog/{post.id}" title={post.title}>
<p>{post.author} | {post.date}</p> <p>{post.author} | {post.date}</p>
<br> <br />
{post.content}... {post.content}...
</CardLink> </CardLink>
{/each} {/each}
@ -44,62 +57,62 @@
</main> </main>
<style> <style>
.post-list{ .post-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 28px; gap: 28px;
} }
main { main {
padding: 15%; padding: 15%;
padding-top: 50px; padding-top: 50px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 20px; gap: 20px;
} }
p { p {
font-size: 20px; font-size: 20px;
} }
.feed-list{ .feed-list {
text-align: right; text-align: right;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 10px; gap: 10px;
} }
.feed-list a { .feed-list a {
text-decoration: none; text-decoration: none;
padding: 10px 15px 10px 15px; padding: 10px 15px 10px 15px;
background: var(--dark-three); background: var(--dark-three);
border-radius: var(--radius); border-radius: var(--radius);
border: solid 1px var(--border-color); border: solid 1px var(--border-color);
color: var(--white); color: var(--white);
font-size: 20px; font-size: 20px;
font-weight: 900; font-weight: 900;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
gap: 7px; gap: 7px;
transition: .2s; transition: 0.2s;
} }
.feed-list a:hover { .feed-list a:hover {
box-shadow: var(--box-shadow); box-shadow: var(--box-shadow);
} }
.feed-list a i{ .feed-list a i {
font-size: 21px; font-size: 21px;
} }
@media only screen and (max-width: 1316px) { @media only screen and (max-width: 1316px) {
main { main {
padding: 50px; padding: 50px;
}
} }
}
</style> </style>

View File

@ -1,14 +1,14 @@
export async function load({ fetch, params }) { export async function load({ fetch, params }) {
const id = params.id const id = params.id;
const api = import.meta.env.VITE_API_URL_DEV const api = import.meta.env.VITE_API_URL_DEV;
const res = await fetch(api+"/blog/get?id="+id) const res = await fetch(api + "/blog/get?id=" + id);
const data = await res.json() const data = await res.json();
if (data["error"] != "") { if (data["error"] != "") {
return { return {
error: data["error"] error: data["error"],
} };
} }
return data["result"] return data["result"];
} }

View File

@ -1,104 +1,95 @@
<script> <script>
import Header from "../../../lib/header.svelte" import Header from "../../../lib/header.svelte";
import { goto } from "$app/navigation" import { goto } from "$app/navigation";
import { onMount } from "svelte" import { onMount } from "svelte";
import DOMPurify from "dompurify" import DOMPurify from "dompurify";
import { marked } from "marked" import { marked } from "marked";
export let data export let data;
let sanitized let sanitized;
const api = import.meta.env.VITE_API_URL_DEV const api = import.meta.env.VITE_API_URL_DEV;
let upvote_status = "inactive" let upvote_status = "inactive";
let downvote_status = "inactive" let downvote_status = "inactive";
let voted = false let voted = false;
let audio let audio;
async function get_status() { async function get_status() {
const res = await fetch(api+"/blog/vote/get?id="+data.id) const res = await fetch(api + "/blog/vote/get?id=" + data.id);
const json = await res.json() const json = await res.json();
if(json["error"]!= ""){ if (json["error"] != "") {
return return;
} }
if (json["result"] == "upvote") { if (json["result"] == "upvote") {
upvote_status = "active" upvote_status = "active";
downvote_status = "inactive" downvote_status = "inactive";
} else {
downvote_status = "active";
upvote_status = "inactive";
} }
else {
downvote_status = "active" voted = true;
upvote_status = "inactive"
}
voted = true
} }
onMount(async ()=>{ onMount(async () => {
if (data.title == undefined) if (data.title == undefined) goto("/blog");
goto("/blog")
sanitized = DOMPurify.sanitize( sanitized = DOMPurify.sanitize(marked.parse(data.content, { breaks: true }), {
marked.parse(data.content, { breaks: true }), ADD_TAGS: ["iframe"],
{ ADD_ATTR: ["allow", "allowfullscreen", "frameborder", "scrolling"],
ADD_TAGS: ["iframe"], });
ADD_ATTR: ["allow", "allowfullscreen", "frameborder", "scrolling"]
}
)
await get_status()
})
async function upvote(){ await get_status();
audio.play() });
const res = await fetch(api+"/blog/vote/set?id="+data.id+"&to=upvote")
const json = await res.json()
if(json["error"] != ""){ async function upvote() {
return audio.play();
const res = await fetch(api + "/blog/vote/set?id=" + data.id + "&to=upvote");
const json = await res.json();
if (json["error"] != "") {
return;
} }
if (voted){ if (voted) {
data.vote += 2 data.vote += 2;
} } else {
voted = true;
else { data.vote += 1;
voted = true
data.vote += 1
} }
await get_status() await get_status();
} }
async function downvote(){ async function downvote() {
audio.play() audio.play();
const res = await fetch(api+"/blog/vote/set?id="+data.id+"&to=downvote") const res = await fetch(api + "/blog/vote/set?id=" + data.id + "&to=downvote");
const json = await res.json() const json = await res.json();
if(json["error"] != ""){ if (json["error"] != "") {
return return;
} }
if (voted){ if (voted) {
data.vote -= 2 data.vote -= 2;
} } else {
voted = true;
else { data.vote -= 1;
voted = true
data.vote -= 1
} }
await get_status() await get_status();
} }
</script> </script>
<svelte:head> <svelte:head>
<title>[ngn.tf] | {data.title}</title> <title>[ngn.tf] | {data.title}</title>
<meta content="[ngn] | blog" property="og:title" /> <meta content="[ngn] | blog" property="og:title" />
<meta content="{data.content.substring(0, 100)}..." property="og:description" /> <meta content="{data.content.substring(0, 100)}..." property="og:description" />
<meta content="https://ngn.tf" property="og:url" /> <meta content="https://ngn.tf" property="og:url" />
<meta content="#000000" data-react-helmet="true" name="theme-color" /> <meta content="#000000" data-react-helmet="true" name="theme-color" />
<link href="/markdown.css" rel="stylesheet"> <link href="/markdown.css" rel="stylesheet" />
</svelte:head> </svelte:head>
<Header subtitle="{data.author} | {data.date}"> <Header subtitle="{data.author} | {data.date}">
@ -113,79 +104,88 @@
{@html sanitized} {@html sanitized}
</div> </div>
<div class="votes"> <div class="votes">
<button on:click={async ()=>{upvote()}} class="{upvote_status}"> <button
on:click={async () => {
upvote();
}}
class={upvote_status}
>
<i class="nf nf-md-arrow_up_bold"></i> <i class="nf nf-md-arrow_up_bold"></i>
</button> </button>
<p>{data.vote}</p> <p>{data.vote}</p>
<button on:click={async ()=>{downvote()}} class="{downvote_status}"> <button
on:click={async () => {
downvote();
}}
class={downvote_status}
>
<i class="nf nf-md-arrow_down_bold"></i> <i class="nf nf-md-arrow_down_bold"></i>
</button> </button>
</div> </div>
</main> </main>
<style> <style>
main {
padding: 50px 10% 50px 10%;
color: white;
display: flex;
flex-direction: row;
justify-content: center;
align-items: start;
}
@media only screen and (max-width: 816px) {
main { main {
padding: 50px 20% 50px 20%; padding: 50px 10% 50px 10%;
color: white;
display: flex;
flex-direction: row;
justify-content: center;
align-items: start;
} }
}
.content { @media only screen and (max-width: 816px) {
padding: 30px; main {
background: var(--dark-four); padding: 50px 20% 50px 20%;
border-radius: var(--radius); }
border: solid 1px var(--border-color); }
box-shadow: var(--box-shadow);
width: auto;
width: 100%;
}
.votes { .content {
display: flex; padding: 30px;
flex-direction: column; background: var(--dark-four);
text-align: center; border-radius: var(--radius);
text-shadow: var(--text-shadow); border: solid 1px var(--border-color);
gap: 10px; box-shadow: var(--box-shadow);
padding: 15px 5px 15px 5px; width: auto;
margin-left: 10px; width: 100%;
} }
.votes p { .votes {
font-size: 25px; display: flex;
color: var(--dark-six); flex-direction: column;
} text-align: center;
text-shadow: var(--text-shadow);
gap: 10px;
padding: 15px 5px 15px 5px;
margin-left: 10px;
}
.votes button{ .votes p {
display: flex; font-size: 25px;
flex-direction: row; color: var(--dark-six);
gap: 10px; }
background: none;
outline: none;
border: none;
font-size: 30px;
cursor: pointer;
color: var(--dark-six);
}
.votes button:hover { .votes button {
animation-name: colorAnimation; display: flex;
animation-iteration-count: infinite; flex-direction: row;
animation-duration: 10s; gap: 10px;
} background: none;
outline: none;
border: none;
font-size: 30px;
cursor: pointer;
color: var(--dark-six);
}
.active { .votes button:hover {
animation-name: colorAnimation; animation-name: colorAnimation;
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-duration: 10s; animation-duration: 10s;
} }
.active {
animation-name: colorAnimation;
animation-iteration-count: infinite;
animation-duration: 10s;
}
</style> </style>

View File

@ -18,9 +18,9 @@
<main> <main>
<Card title="bash donate.sh"> <Card title="bash donate.sh">
I work on free/libre and open source software and offer free services. General hosting I work on free/libre and open source software and offer free services. General hosting and stuff
and stuff costs around 550₺ (~$17) per month, so feel free to donate in order to help me keep costs around 550₺ (~$17) per month, so feel free to donate in order to help me keep everything
everything up and running! up and running!
<table> <table>
<thead> <thead>
<tr> <tr>
@ -33,56 +33,58 @@
<td>Monero (XMR)</td> <td>Monero (XMR)</td>
<td> <td>
<code> <code>
46q7G7u7cmASvJm7AmrhmNg6ctS77mYMmDAy1QxpDn5w57xV3GUY5za4ZPZHAjqaXdfS5YRWm4AVj5UArLDA1retRkJp47F 46q7G7u7cmASvJm7AmrhmNg6ctS77mYMmDAy1QxpDn5w57xV3GUY5za4ZPZHAjqaXdfS5YRWm4AVj5UArLDA1retRkJp47F
</code> </code>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
Also huge thanks to all of you who has donated so far, even if it's a small amount, I highly Also huge thanks to all of you who has donated so far, even if it's a small amount, I highly appreciate
appreciate it. Thank you! it. Thank you!
</Card> </Card>
</main> </main>
<style> <style>
main{ main {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 35px; gap: 35px;
padding: 50px; padding: 50px;
} }
table { table {
border-collapse: collapse; border-collapse: collapse;
border: none; border: none;
color: white; color: white;
font-size: 20px; font-size: 20px;
width: 100%; width: 100%;
margin: 30px 0 30px 0; margin: 30px 0 30px 0;
box-shadow: var(--box-shadow); box-shadow: var(--box-shadow);
} }
tr,
th,
td {
color: white;
background: var(--dark-two);
}
tr,th,td{ td,
color: white; th {
background: var(--dark-two); border: solid 1px var(--dark-fife);
} padding: 16px;
}
td,th{ th {
border: solid 1px var(--dark-fife); animation-name: colorAnimation;
padding: 16px; animation-duration: 10s;
} animation-iteration-count: infinite;
background: var(--dark-two);
}
th { code {
animation-name: colorAnimation; word-wrap: break-word;
animation-duration: 10s; white-space: pre-wrap;
animation-iteration-count: infinite; word-break: break-word;
background: var(--dark-two); }
}
code {
word-wrap: break-word;
white-space: pre-wrap;
word-break: break-word;
}
</style> </style>

View File

@ -1,43 +1,43 @@
export async function load({ fetch }) { export async function load({ fetch }) {
const api = import.meta.env.VITE_API_URL_DEV const api = import.meta.env.VITE_API_URL_DEV;
const res = await fetch(api+"/services/all") const res = await fetch(api + "/services/all");
const data = await res.json() const data = await res.json();
if (data["error"] != ""){ if (data["error"] != "") {
return { return {
error: data["error"] error: data["error"],
} };
} }
// Some really bad code to convert // Some really bad code to convert
// [service1, service2, service3...] // [service1, service2, service3...]
// to // to
// [[service1, service2], [service4, service5], [service4...]...] // [[service1, service2], [service4, service5], [service4...]...]
// so i can render it in the UI easily // so i can render it in the UI easily
let all = data["result"] let all = data["result"];
let counter = 0 let counter = 0;
let currentlist = [] let currentlist = [];
let services = [] let services = [];
for (let i = 0; i < all.length; i++){ for (let i = 0; i < all.length; i++) {
currentlist.push(all[i]) currentlist.push(all[i]);
counter += 1 counter += 1;
if(i == all.length-1 && counter != 2){ if (i == all.length - 1 && counter != 2) {
services.push(currentlist) services.push(currentlist);
} }
if (counter == 2) { if (counter == 2) {
services.push(currentlist) services.push(currentlist);
currentlist = [] currentlist = [];
counter = 0 counter = 0;
} }
} }
return { return {
services services,
} };
} }

View File

@ -3,11 +3,11 @@
import Service from "../../lib/service.svelte"; import Service from "../../lib/service.svelte";
import Card from "../../lib/card.svelte"; import Card from "../../lib/card.svelte";
export let data export let data;
</script> </script>
<svelte:head> <svelte:head>
<title>[ngn.tf] | services</title> <title>[ngn.tf] | services</title>
<meta content="[ngn] | services" property="og:title" /> <meta content="[ngn] | services" property="og:title" />
<meta content="Stuff that I host" property="og:description" /> <meta content="Stuff that I host" property="og:description" />
<meta content="https://ngn.tf" property="og:url" /> <meta content="https://ngn.tf" property="og:url" />
@ -16,85 +16,86 @@
<Header><c>ls</c> services</Header> <Header><c>ls</c> services</Header>
<main> <main>
<Card title="cat services/*/info.txt"> <Card title="cat services/*/info.txt">
<div class="flexcol"> <div class="flexcol">
{#each data.services as services_list} {#each data.services as services_list}
<div class="flexrow"> <div class="flexrow">
{#each services_list as service} {#each services_list as service}
<Service url="{service.url}" desc="{service.desc}">{service.name}</Service> <Service url={service.url} desc={service.desc}>{service.name}</Service>
{/each} {/each}
</div> </div>
{/each} {/each}
</div> </div>
</Card> </Card>
<Card title="cat services/details.txt"> <Card title="cat services/details.txt">
Here some details for all the services I offer: Here some details for all the services I offer:
<ul> <ul>
<li> <li>
<c><i class="nf nf-cod-account"></i> Registration:</c> All the services are offered for free, and all of them <c><i class="nf nf-cod-account"></i> Registration:</c> All the services are offered for free,
are accessiable to public. And registrations are open for the all services that support account registrations. and all of them are accessiable to public. And registrations are open for the all services that
support account registrations.
</li> </li>
<li> <li>
<c><i class="nf nf-fa-eye_slash"></i> Privacy:</c> To protect user privacy, all the web proxy logs are cleared regularly. <c><i class="nf nf-fa-eye_slash"></i> Privacy:</c> To protect user privacy, all the web proxy
I also do not use any kind of CDN, and provide SSL encrypted connection for all the services. You can also connect all the logs are cleared regularly. I also do not use any kind of CDN, and provide SSL encrypted connection
services over TOR, as I do not block any traffic from TOR. for all the services. You can also connect all the services over TOR, as I do not block any traffic
from TOR.
</li> </li>
<li> <li>
<c><i class="nf nf-oct-graph"></i> Uptime:</c> Some services get restarted regularly, also sometimes I have <c><i class="nf nf-oct-graph"></i> Uptime:</c> Some services get restarted regularly, also sometimes
issues with the hosting, so I cannot provide or guarantee %100 uptime for any of the services. I also cannot guarantee I have issues with the hosting, so I cannot provide or guarantee %100 uptime for any of the services.
the that there won't be any data loss. I do take any regular backups, but your data may be lost in case of something I also cannot guarantee the that there won't be any data loss. I do take any regular backups,
like a SSD failure. but your data may be lost in case of something like a SSD failure.
</li> </li>
<li> <li>
<c><i class="nf nf-md-speedometer"></i> Speed:</c> All the services are located in Turkey, and avaliable <c><i class="nf nf-md-speedometer"></i> Speed:</c> All the services are located in Turkey, and
over an 400 Mbit/s interface. If you are close to Turkey you should have a fairly good experience. avaliable over an 400 Mbit/s interface. If you are close to Turkey you should have a fairly good
If you are not, then you should probably use another provider for the service. experience. If you are not, then you should probably use another provider for the service.
</li> </li>
</ul> </ul>
</Card> </Card>
</main> </main>
<style> <style>
main { main {
display: flex; display: flex;
flex-direction: column;
align-content: center;
justify-content: center;
padding: 50px;
gap: 28px;
}
.flexcol {
display: flex;
flex-direction: column;
align-content: center;
justify-content: center;
gap: 13px;
}
.flexrow {
display: flex;
flex-direction: row;
align-content: center;
justify-content: center;
width: 100%;
gap: 13px;
}
ul {
list-style: inside;
margin-bottom: 20px;
}
li {
padding-top: 30px;
line-height: 35px;
}
@media only screen and (max-width: 1316px) {
.flexrow {
flex-direction: column; flex-direction: column;
align-content: center;
justify-content: center;
padding: 50px;
gap: 28px;
}
.flexcol {
display: flex;
flex-direction: column;
align-content: center;
justify-content: center;
gap: 13px;
}
.flexrow {
display: flex;
flex-direction: row;
align-content: center;
justify-content: center;
width: 100%;
gap: 13px;
}
ul {
list-style: inside;
margin-bottom: 20px;
}
li {
padding-top: 30px;
line-height: 35px;
}
@media only screen and (max-width: 1316px) {
.flexrow {
flex-direction: column;
}
} }
}
</style> </style>

79
app/static/animations.css Normal file
View File

@ -0,0 +1,79 @@
@keyframes colorAnimation {
100%,
0% {
color: rgb(255, 0, 0);
}
8% {
color: rgb(255, 127, 0);
}
16% {
color: rgb(255, 255, 0);
}
25% {
color: rgb(127, 255, 0);
}
33% {
color: rgb(0, 255, 0);
}
41% {
color: rgb(0, 255, 127);
}
50% {
color: rgb(0, 255, 255);
}
58% {
color: rgb(0, 127, 255);
}
66% {
color: rgb(0, 0, 255);
}
75% {
color: rgb(127, 0, 255);
}
83% {
color: rgb(255, 0, 255);
}
91% {
color: rgb(255, 0, 127);
}
}
@keyframes borderAnimation {
100%,
0% {
border-bottom-color: rgb(255, 0, 0);
}
8% {
border-bottom-color: rgb(255, 127, 0);
}
16% {
border-bottom-color: rgb(255, 255, 0);
}
25% {
border-bottom-color: rgb(127, 255, 0);
}
33% {
border-bottom-color: rgb(0, 255, 0);
}
41% {
border-bottom-color: rgb(0, 255, 127);
}
50% {
border-bottom-color: rgb(0, 255, 255);
}
58% {
border-bottom-color: rgb(0, 127, 255);
}
66% {
border-bottom-color: rgb(0, 0, 255);
}
75% {
border-bottom-color: rgb(127, 0, 255);
}
83% {
border-bottom-color: rgb(255, 0, 255);
}
91% {
border-bottom-color: rgb(255, 0, 127);
}
}

View File

@ -1,13 +1,22 @@
@import "./animations.css";
@import "./font.css"; @import "./font.css";
:root { :root {
--white: white; --white: #ffffff;
--dark-one: black; --yellow: #d3b910;
--cyan: #0dd2e8;
--green: #06e00a;
--pinkish: #d506e0;
--red: #e8180d;
--blue: #0536fc;
--dark-one: #000000;
--dark-two: #050505; --dark-two: #050505;
--dark-three: #121212; --dark-three: #121212;
--dark-four: #101010; --dark-four: #101010;
--dark-fife: #3a3b3c; --dark-fife: #3a3b3c;
--dark-six: #C0C0C0; --dark-six: #c0c0c0;
--radius: 8px; --radius: 8px;
/* /*
old shadow animation old shadow animation
@ -53,252 +62,7 @@ body {
background: #282828; background: #282828;
} }
@keyframes colorAnimation { .glitch {
100%,
0% {
color: rgb(255, 0, 0);
}
8% {
color: rgb(255, 127, 0);
}
16% {
color: rgb(255, 255, 0);
}
25% {
color: rgb(127, 255, 0);
}
33% {
color: rgb(0, 255, 0);
}
41% {
color: rgb(0, 255, 127);
}
50% {
color: rgb(0, 255, 255);
}
58% {
color: rgb(0, 127, 255);
}
66% {
color: rgb(0, 0, 255);
}
75% {
color: rgb(127, 0, 255);
}
83% {
color: rgb(255, 0, 255);
}
91% {
color: rgb(255, 0, 127);
}
}
@keyframes borderAnimation {
100%,
0% {
border-bottom-color: rgb(255, 0, 0);
}
8% {
border-bottom-color: rgb(255, 127, 0);
}
16% {
border-bottom-color: rgb(255, 255, 0);
}
25% {
border-bottom-color: rgb(127, 255, 0);
}
33% {
border-bottom-color: rgb(0, 255, 0);
}
41% {
border-bottom-color: rgb(0, 255, 127);
}
50% {
border-bottom-color: rgb(0, 255, 255);
}
58% {
border-bottom-color: rgb(0, 127, 255);
}
66% {
border-bottom-color: rgb(0, 0, 255);
}
75% {
border-bottom-color: rgb(127, 0, 255);
}
83% {
border-bottom-color: rgb(255, 0, 255);
}
91% {
border-bottom-color: rgb(255, 0, 127);
}
}
@keyframes fullBorderAnimation {
100%,
0% {
border-color: rgb(255, 0, 0);
}
8% {
border-color: rgb(255, 127, 0);
}
16% {
border-color: rgb(255, 255, 0);
}
25% {
border-color: rgb(127, 255, 0);
}
33% {
border-color: rgb(0, 255, 0);
}
41% {
border-color: rgb(0, 255, 127);
}
50% {
border-color: rgb(0, 255, 255);
}
58% {
border-color: rgb(0, 127, 255);
}
66% {
border-color: rgb(0, 0, 255);
}
75% {
border-color: rgb(127, 0, 255);
}
83% {
border-color: rgb(255, 0, 255);
}
91% {
border-color: rgb(255, 0, 127);
}
}
@keyframes gayShadowAnimation {
100%,
0% {
box-shadow: rgba(255, 0, 0, 0.07) 0px 1px 2px,
rgba(255, 0, 0, 0.07) 0px 2px 4px, rgba(255, 0, 0, 0.07) 0px 4px 8px,
rgba(255, 0, 0, 0.07) 0px 8px 16px, rgba(255, 0, 0, 0.07) 0px 16px 32px,
rgba(255, 0, 0, 0.07) 0px 32px 64px;
}
8% {
box-shadow: rgba(255, 127, 0, 0.07) 0px 1px 2px,
rgba(255, 127, 0, 0.07) 0px 2px 4px, rgba(255, 127, 0, 0.07) 0px 4px 8px,
rgba(255, 127, 0, 0.07) 0px 8px 16px,
rgba(255, 127, 0, 0.07) 0px 16px 32px,
rgba(255, 127, 0, 0.07) 0px 32px 64px;
}
16% {
box-shadow: rgba(255, 255, 0, 0.07) 0px 1px 2px,
rgba(255, 255, 0, 0.07) 0px 2px 4px, rgba(255, 255, 0, 0.07) 0px 4px 8px,
rgba(255, 255, 0, 0.07) 0px 8px 16px,
rgba(255, 255, 0, 0.07) 0px 16px 32px,
rgba(255, 255, 0, 0.07) 0px 32px 64px;
}
25% {
box-shadow: rgba(127, 255, 0, 0.07) 0px 1px 2px,
rgba(127, 255, 0, 0.07) 0px 2px 4px, rgba(127, 255, 0, 0.07) 0px 4px 8px,
rgba(127, 255, 0, 0.07) 0px 8px 16px,
rgba(127, 255, 0, 0.07) 0px 16px 32px,
rgba(127, 255, 0, 0.07) 0px 32px 64px;
}
33% {
box-shadow: rgba(0, 255, 0, 0.07) 0px 1px 2px,
rgba(0, 255, 0, 0.07) 0px 2px 4px, rgba(0, 255, 0, 0.07) 0px 4px 8px,
rgba(0, 255, 0, 0.07) 0px 8px 16px, rgba(0, 255, 0, 0.07) 0px 16px 32px,
rgba(0, 255, 0, 0.07) 0px 32px 64px;
}
41% {
box-shadow: rgba(0, 255, 127, 0.07) 0px 1px 2px,
rgba(0, 255, 127, 0.07) 0px 2px 4px, rgba(0, 255, 127, 0.07) 0px 4px 8px,
rgba(0, 255, 127, 0.07) 0px 8px 16px,
rgba(0, 255, 127, 0.07) 0px 16px 32px,
rgba(0, 255, 127, 0.07) 0px 32px 64px;
}
50% {
box-shadow: rgba(0, 255, 255, 0.07) 0px 1px 2px,
rgba(0, 255, 255, 0.07) 0px 2px 4px, rgba(0, 255, 255, 0.07) 0px 4px 8px,
rgba(0, 255, 255, 0.07) 0px 8px 16px,
rgba(0, 255, 255, 0.07) 0px 16px 32px,
rgba(0, 255, 255, 0.07) 0px 32px 64px;
}
58% {
box-shadow: rgba(0, 127, 255, 0.07) 0px 1px 2px,
rgba(0, 127, 255, 0.07) 0px 2px 4px, rgba(0, 127, 255, 0.07) 0px 4px 8px,
rgba(0, 127, 255, 0.07) 0px 8px 16px,
rgba(0, 127, 255, 0.07) 0px 16px 32px,
rgba(0, 127, 255, 0.07) 0px 32px 64px;
}
66% {
box-shadow: rgba(0, 0, 255, 0.07) 0px 1px 2px,
rgba(0, 0, 255, 0.07) 0px 2px 4px, rgba(0, 0, 255, 0.07) 0px 4px 8px,
rgba(0, 0, 255, 0.07) 0px 8px 16px, rgba(0, 0, 255, 0.07) 0px 16px 32px,
rgba(0, 0, 255, 0.07) 0px 32px 64px;
}
75% {
box-shadow: rgba(127, 0, 255, 0.07) 0px 1px 2px,
rgba(127, 0, 255, 0.07) 0px 2px 4px, rgba(127, 0, 255, 0.07) 0px 4px 8px,
rgba(127, 0, 255, 0.07) 0px 8px 16px,
rgba(127, 0, 255, 0.07) 0px 16px 32px,
rgba(127, 0, 255, 0.07) 0px 32px 64px;
}
83% {
box-shadow: rgba(255, 0, 255, 0.07) 0px 1px 2px,
rgba(255, 0, 255, 0.07) 0px 2px 4px, rgba(255, 0, 255, 0.07) 0px 4px 8px,
rgba(255, 0, 255, 0.07) 0px 8px 16px,
rgba(255, 0, 255, 0.07) 0px 16px 32px,
rgba(255, 0, 255, 0.07) 0px 32px 64px;
}
91% {
box-shadow: rgba(255, 0, 127, 0.07) 0px 1px 2px,
rgba(255, 0, 127, 0.07) 0px 2px 4px, rgba(255, 0, 127, 0.07) 0px 4px 8px,
rgba(255, 0, 127, 0.07) 0px 8px 16px,
rgba(255, 0, 127, 0.07) 0px 16px 32px,
rgba(255, 0, 127, 0.07) 0px 32px 64px;
}
}
@keyframes underlineAnimation {
100%,
0% {
text-decoration-color: rgb(255, 0, 0);
}
8% {
text-decoration-color: rgb(255, 127, 0);
}
16% {
text-decoration-color: rgb(255, 255, 0);
}
25% {
text-decoration-color: rgb(127, 255, 0);
}
33% {
text-decoration-color: rgb(0, 255, 0);
}
41% {
text-decoration-color: rgb(0, 255, 127);
}
50% {
text-decoration-color: rgb(0, 255, 255);
}
58% {
text-decoration-color: rgb(0, 127, 255);
}
66% {
text-decoration-color: rgb(0, 0, 255);
}
75% {
text-decoration-color: rgb(127, 0, 255);
}
83% {
text-decoration-color: rgb(255, 0, 255);
}
91% {
text-decoration-color: rgb(255, 0, 127);
}
}
.c, c {
animation-name: colorAnimation; animation-name: colorAnimation;
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-duration: 10s; animation-duration: 10s;

View File

@ -5,7 +5,8 @@
margin: 0; margin: 0;
color: #c9d1d9; color: #c9d1d9;
background-color: #000; background-color: #000;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial,
sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-size: 16px; font-size: 16px;
line-height: 1.5; line-height: 1.5;
word-wrap: break-word; word-wrap: break-word;
@ -38,7 +39,7 @@
.markdown-body h6:hover .anchor .octicon-link:before { .markdown-body h6:hover .anchor .octicon-link:before {
width: 16px; width: 16px;
height: 16px; height: 16px;
content: ' '; content: " ";
display: inline-block; display: inline-block;
background-color: currentColor; background-color: currentColor;
-webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>"); -webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>");
@ -83,15 +84,15 @@
} }
.markdown-body h1 { .markdown-body h1 {
margin: .67em 0; margin: 0.67em 0;
font-weight: 600; font-weight: 600;
padding-bottom: .3em; padding-bottom: 0.3em;
font-size: 2em; font-size: 2em;
border-bottom: 1px solid #21262d; border-bottom: 1px solid #21262d;
} }
.markdown-body mark { .markdown-body mark {
background-color: rgba(187,128,9,0.15); background-color: rgba(187, 128, 9, 0.15);
color: #c9d1d9; color: #c9d1d9;
} }
@ -139,7 +140,7 @@
overflow: hidden; overflow: hidden;
background: transparent; background: transparent;
border-bottom: 1px solid #21262d; border-bottom: 1px solid #21262d;
height: .25em; height: 0.25em;
padding: 0; padding: 0;
margin: 24px 0; margin: 24px 0;
background-color: #30363d; background-color: #30363d;
@ -155,31 +156,31 @@
line-height: inherit; line-height: inherit;
} }
.markdown-body [type=button], .markdown-body [type="button"],
.markdown-body [type=reset], .markdown-body [type="reset"],
.markdown-body [type=submit] { .markdown-body [type="submit"] {
-webkit-appearance: button; -webkit-appearance: button;
} }
.markdown-body [type=checkbox], .markdown-body [type="checkbox"],
.markdown-body [type=radio] { .markdown-body [type="radio"] {
box-sizing: border-box; box-sizing: border-box;
padding: 0; padding: 0;
} }
.markdown-body [type=number]::-webkit-inner-spin-button, .markdown-body [type="number"]::-webkit-inner-spin-button,
.markdown-body [type=number]::-webkit-outer-spin-button { .markdown-body [type="number"]::-webkit-outer-spin-button {
height: auto; height: auto;
} }
.markdown-body [type=search]::-webkit-search-cancel-button, .markdown-body [type="search"]::-webkit-search-cancel-button,
.markdown-body [type=search]::-webkit-search-decoration { .markdown-body [type="search"]::-webkit-search-decoration {
-webkit-appearance: none; -webkit-appearance: none;
} }
.markdown-body ::-webkit-input-placeholder { .markdown-body ::-webkit-input-placeholder {
color: inherit; color: inherit;
opacity: .54; opacity: 0.54;
} }
.markdown-body ::-webkit-file-upload-button { .markdown-body ::-webkit-file-upload-button {
@ -225,30 +226,30 @@
cursor: pointer; cursor: pointer;
} }
.markdown-body details:not([open])>*:not(summary) { .markdown-body details:not([open]) > *:not(summary) {
display: none !important; display: none !important;
} }
.markdown-body a:focus, .markdown-body a:focus,
.markdown-body [role=button]:focus, .markdown-body [role="button"]:focus,
.markdown-body input[type=radio]:focus, .markdown-body input[type="radio"]:focus,
.markdown-body input[type=checkbox]:focus { .markdown-body input[type="checkbox"]:focus {
outline: 2px solid #58a6ff; outline: 2px solid #58a6ff;
outline-offset: -2px; outline-offset: -2px;
box-shadow: none; box-shadow: none;
} }
.markdown-body a:focus:not(:focus-visible), .markdown-body a:focus:not(:focus-visible),
.markdown-body [role=button]:focus:not(:focus-visible), .markdown-body [role="button"]:focus:not(:focus-visible),
.markdown-body input[type=radio]:focus:not(:focus-visible), .markdown-body input[type="radio"]:focus:not(:focus-visible),
.markdown-body input[type=checkbox]:focus:not(:focus-visible) { .markdown-body input[type="checkbox"]:focus:not(:focus-visible) {
outline: solid 1px transparent; outline: solid 1px transparent;
} }
.markdown-body a:focus-visible, .markdown-body a:focus-visible,
.markdown-body [role=button]:focus-visible, .markdown-body [role="button"]:focus-visible,
.markdown-body input[type=radio]:focus-visible, .markdown-body input[type="radio"]:focus-visible,
.markdown-body input[type=checkbox]:focus-visible { .markdown-body input[type="checkbox"]:focus-visible {
outline: 2px solid #58a6ff; outline: 2px solid #58a6ff;
outline-offset: -2px; outline-offset: -2px;
box-shadow: none; box-shadow: none;
@ -256,25 +257,32 @@
.markdown-body a:not([class]):focus, .markdown-body a:not([class]):focus,
.markdown-body a:not([class]):focus-visible, .markdown-body a:not([class]):focus-visible,
.markdown-body input[type=radio]:focus, .markdown-body input[type="radio"]:focus,
.markdown-body input[type=radio]:focus-visible, .markdown-body input[type="radio"]:focus-visible,
.markdown-body input[type=checkbox]:focus, .markdown-body input[type="checkbox"]:focus,
.markdown-body input[type=checkbox]:focus-visible { .markdown-body input[type="checkbox"]:focus-visible {
outline-offset: 0; outline-offset: 0;
} }
.markdown-body kbd { .markdown-body kbd {
display: inline-block; display: inline-block;
padding: 3px 5px; padding: 3px 5px;
font: 11px ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; font:
11px ui-monospace,
SFMono-Regular,
SF Mono,
Menlo,
Consolas,
Liberation Mono,
monospace;
line-height: 10px; line-height: 10px;
color: #c9d1d9; color: #c9d1d9;
vertical-align: middle; vertical-align: middle;
background-color: #161b22; background-color: #161b22;
border: solid 1px rgba(110,118,129,0.4); border: solid 1px rgba(110, 118, 129, 0.4);
border-bottom-color: rgba(110,118,129,0.4); border-bottom-color: rgba(110, 118, 129, 0.4);
border-radius: 6px; border-radius: 6px;
box-shadow: inset 0 -1px 0 rgba(110,118,129,0.4); box-shadow: inset 0 -1px 0 rgba(110, 118, 129, 0.4);
} }
.markdown-body h1, .markdown-body h1,
@ -291,7 +299,7 @@
.markdown-body h2 { .markdown-body h2 {
font-weight: 600; font-weight: 600;
padding-bottom: .3em; padding-bottom: 0.3em;
font-size: 1.5em; font-size: 1.5em;
border-bottom: 1px solid #21262d; border-bottom: 1px solid #21262d;
} }
@ -308,12 +316,12 @@
.markdown-body h5 { .markdown-body h5 {
font-weight: 600; font-weight: 600;
font-size: .875em; font-size: 0.875em;
} }
.markdown-body h6 { .markdown-body h6 {
font-weight: 600; font-weight: 600;
font-size: .85em; font-size: 0.85em;
color: #8b949e; color: #8b949e;
} }
@ -326,7 +334,7 @@
margin: 0; margin: 0;
padding: 0 1em; padding: 0 1em;
color: #8b949e; color: #8b949e;
border-left: .25em solid #30363d; border-left: 0.25em solid #30363d;
} }
.markdown-body ul, .markdown-body ul,
@ -355,14 +363,28 @@
.markdown-body tt, .markdown-body tt,
.markdown-body code, .markdown-body code,
.markdown-body samp { .markdown-body samp {
font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; font-family:
ui-monospace,
SFMono-Regular,
SF Mono,
Menlo,
Consolas,
Liberation Mono,
monospace;
font-size: 12px; font-size: 12px;
} }
.markdown-body pre { .markdown-body pre {
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; font-family:
ui-monospace,
SFMono-Regular,
SF Mono,
Menlo,
Consolas,
Liberation Mono,
monospace;
font-size: 12px; font-size: 12px;
word-wrap: normal; word-wrap: normal;
} }
@ -392,11 +414,11 @@
content: ""; content: "";
} }
.markdown-body>*:first-child { .markdown-body > *:first-child {
margin-top: 0 !important; margin-top: 0 !important;
} }
.markdown-body>*:last-child { .markdown-body > *:last-child {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
@ -432,11 +454,11 @@
margin-bottom: 16px; margin-bottom: 16px;
} }
.markdown-body blockquote>:first-child { .markdown-body blockquote > :first-child {
margin-top: 0; margin-top: 0;
} }
.markdown-body blockquote>:last-child { .markdown-body blockquote > :last-child {
margin-bottom: 0; margin-bottom: 0;
} }
@ -481,7 +503,7 @@
.markdown-body h5 code, .markdown-body h5 code,
.markdown-body h6 tt, .markdown-body h6 tt,
.markdown-body h6 code { .markdown-body h6 code {
padding: 0 .2em; padding: 0 0.2em;
font-size: inherit; font-size: inherit;
} }
@ -515,19 +537,19 @@
list-style-type: none; list-style-type: none;
} }
.markdown-body ol[type=a] { .markdown-body ol[type="a"] {
list-style-type: lower-alpha; list-style-type: lower-alpha;
} }
.markdown-body ol[type=A] { .markdown-body ol[type="A"] {
list-style-type: upper-alpha; list-style-type: upper-alpha;
} }
.markdown-body ol[type=i] { .markdown-body ol[type="i"] {
list-style-type: lower-roman; list-style-type: lower-roman;
} }
.markdown-body ol[type=I] { .markdown-body ol[type="I"] {
list-style-type: upper-roman; list-style-type: upper-roman;
} }
@ -535,7 +557,7 @@
list-style-type: decimal; list-style-type: decimal;
} }
.markdown-body div>ol:not([type]) { .markdown-body div > ol:not([type]) {
list-style-type: decimal; list-style-type: decimal;
} }
@ -547,12 +569,12 @@
margin-bottom: 0; margin-bottom: 0;
} }
.markdown-body li>p { .markdown-body li > p {
margin-top: 16px; margin-top: 16px;
} }
.markdown-body li+li { .markdown-body li + li {
margin-top: .25em; margin-top: 0.25em;
} }
.markdown-body dl { .markdown-body dl {
@ -595,11 +617,11 @@
background-color: transparent; background-color: transparent;
} }
.markdown-body img[align=right] { .markdown-body img[align="right"] {
padding-left: 20px; padding-left: 20px;
} }
.markdown-body img[align=left] { .markdown-body img[align="left"] {
padding-right: 20px; padding-right: 20px;
} }
@ -614,7 +636,7 @@
overflow: hidden; overflow: hidden;
} }
.markdown-body span.frame>span { .markdown-body span.frame > span {
display: block; display: block;
float: left; float: left;
width: auto; width: auto;
@ -642,7 +664,7 @@
clear: both; clear: both;
} }
.markdown-body span.align-center>span { .markdown-body span.align-center > span {
display: block; display: block;
margin: 13px auto 0; margin: 13px auto 0;
overflow: hidden; overflow: hidden;
@ -660,7 +682,7 @@
clear: both; clear: both;
} }
.markdown-body span.align-right>span { .markdown-body span.align-right > span {
display: block; display: block;
margin: 13px 0 0; margin: 13px 0 0;
overflow: hidden; overflow: hidden;
@ -690,7 +712,7 @@
overflow: hidden; overflow: hidden;
} }
.markdown-body span.float-right>span { .markdown-body span.float-right > span {
display: block; display: block;
margin: 13px auto 0; margin: 13px auto 0;
overflow: hidden; overflow: hidden;
@ -699,7 +721,7 @@
.markdown-body code, .markdown-body code,
.markdown-body tt { .markdown-body tt {
padding: .2em .4em; padding: 0.2em 0.4em;
margin: 0; margin: 0;
font-size: 85%; font-size: 85%;
white-space: break-spaces; white-space: break-spaces;
@ -724,7 +746,7 @@
font-size: 100%; font-size: 100%;
} }
.markdown-body pre>code { .markdown-body pre > code {
padding: 0; padding: 0;
margin: 0; margin: 0;
word-break: normal; word-break: normal;
@ -963,7 +985,7 @@
.markdown-body g-emoji { .markdown-body g-emoji {
display: inline-block; display: inline-block;
min-width: 1ch; min-width: 1ch;
font-family: "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 1em; font-size: 1em;
font-style: normal !important; font-style: normal !important;
font-weight: 400; font-weight: 400;
@ -988,7 +1010,7 @@
cursor: pointer; cursor: pointer;
} }
.markdown-body .task-list-item+.task-list-item { .markdown-body .task-list-item + .task-list-item {
margin-top: 4px; margin-top: 4px;
} }
@ -997,12 +1019,12 @@
} }
.markdown-body .task-list-item-checkbox { .markdown-body .task-list-item-checkbox {
margin: 0 .2em .25em -1.4em; margin: 0 0.2em 0.25em -1.4em;
vertical-align: middle; vertical-align: middle;
} }
.markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox { .markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox {
margin: 0 -1.6em .25em .2em; margin: 0 -1.6em 0.25em 0.2em;
} }
.markdown-body .contains-task-list { .markdown-body .contains-task-list {

View File

@ -1,14 +1,14 @@
//import adapter from '@sveltejs/adapter-auto'; //import adapter from '@sveltejs/adapter-auto';
import adapter from '@sveltejs/adapter-node'; import adapter from "@sveltejs/adapter-node";
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
kit: { kit: {
adapter: adapter() adapter: adapter(),
}, },
onwarn: (warning, handler) => { onwarn: (warning, handler) => {
if (warning.code === "a11y-click-events-have-key-events") return if (warning.code === "a11y-click-events-have-key-events") return;
handler(warning) handler(warning);
}, },
}; };

View File

@ -1,6 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite'; import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from 'vite'; import { defineConfig } from "vite";
export default defineConfig({ export default defineConfig({
plugins: [sveltekit()] plugins: [sveltekit()],
}); });