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(" ", "_")
def _check_multilang_field(self, ml: Dict[str, str]) -> bool:
for l in self.languages:
if l in ml and ml[l] != "":
for lang in self.languages:
if lang in ml and ml[lang] != "":
return True
return False
@ -114,24 +114,26 @@ class AdminAPI:
)
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')
if not "desc" in service:
if "desc" not in service:
raise Exception('Service structure is missing required "desc" field')
if (
(not "clear" in service or service["clear"] == "")
and (not "onion" in service or service["onion"] == "")
and (not "i2p" in service or service["i2p"] == "")
("clear" not in service or service["clear"] == "")
and ("onion" not in service or service["onion"] == "")
and ("i2p" not in service or service["i2p"] == "")
):
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"]):
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)
@ -146,26 +148,28 @@ class AdminAPI:
self.GET("/v1/admin/service/check")
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')
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')
if not "title" in news:
if "title" not in news:
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')
if not self._check_multilang_field(news["title"]):
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"]):
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)
@ -205,8 +209,8 @@ def __handle_command(log: Log, api: AdminAPI, cmd: str) -> None:
data["desc"] = {}
data["name"] = log.input("Serivce name")
for l in api.languages:
data["desc"][l] = log.input("Serivce desc (%s)" % l)
for lang in api.languages:
data["desc"][lang] = log.input("Serivce desc (%s)" % lang)
data["check_url"] = log.input("Serivce status check URL")
data["clear"] = log.input("Serivce clearnet 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")
case "del_service":
api.del_service(self.log.input("Serivce name"))
api.del_service(log.input("Serivce name"))
log.info("Service has been deleted")
case "check_services":
@ -229,11 +233,11 @@ def __handle_command(log: Log, api: AdminAPI, cmd: str) -> None:
news["content"] = {}
data["id"] = log.input("News ID")
for l in api.languages:
data["title"][l] = log.input("News title (%s)" % l)
for lang in api.languages:
data["title"][lang] = log.input("News title (%s)" % lang)
data["author"] = log.input("News author")
for l in api.languages:
data["content"][l] = log.input("News content (%s)" % l)
for lang in api.languages:
data["content"][lang] = log.input("News content (%s)" % lang)
api.add_news(data)
log.info("News has been added")
@ -245,12 +249,13 @@ def __handle_command(log: Log, api: AdminAPI, cmd: str) -> None:
case "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")
for l in logs["result"]:
for log in logs["result"]:
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":
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")
__dump_json_file(logs["result"], file)
@ -306,7 +311,7 @@ if __name__ == "__main__":
url = getenv(API_URL_ENV)
if url == None:
if url is None:
log.error(
"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()
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
}
}

View File

@ -57,7 +57,7 @@ func (n *News) Scan(rows *sql.Rows) (err error) {
}
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 {

View File

@ -2,7 +2,11 @@ module github.com/ngn13/website/api
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 (
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-isatty v0.0.20 // 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/russross/blackfriday/v2 v2.1.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.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)
}
news.Time = uint64(time.Now().Unix())
if !news.IsValid() {
return util.ErrBadReq(c)
}

View File

@ -1,7 +1,9 @@
package routes
import (
"sort"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/ngn13/website/api/config"
@ -9,10 +11,29 @@ import (
"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 {
var (
news []database.News
n database.News
entries []feed_entry
news database.News
indx uint64
feed []byte
err error
)
@ -27,17 +48,25 @@ func GET_News(c *fiber.Ctx) error {
}
lang = strings.ToLower(lang)
indx = 0
for db.NewsNext(&n) {
if n.Supports(lang) {
news = append(news, n)
for db.NewsNext(&news) {
if news.Supports(lang) {
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{
"frontend": frontend,
"updated": time.Now().Format(time.RFC3339),
"entries": entries,
"lang": lang,
"news": news,
}); err != nil {
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">
<title>{{.frontend.Host}} news</title>
<updated>2025-01-02T20:46:24Z</updated>
<subtitle>News and updates about my self-hosted services and projects</subtitle>
<link href="{{.frontend.String}}/news"></link>
<updated>{{.updated}}</updated>
<subtitle>News and updates about my projects and self-hosted services</subtitle>
<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>

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" } }]
}

29
app/package-lock.json generated
View File

@ -17,6 +17,8 @@
"@sveltejs/adapter-node": "^5.2.11",
"@sveltejs/kit": "^2.15.1",
"@sveltejs/vite-plugin-svelte": "^4.0.3",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.2",
"svelte": "^5.16.0",
"vite": "^5.4.11"
}
@ -1330,6 +1332,33 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/prettier": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-plugin-svelte": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.2.tgz",
"integrity": "sha512-kRPjH8wSj2iu+dO+XaUv4vD8qr5mdDmlak3IT/7AOgGIMRG86z/EHOLauFcClKEnOUf4A4nOA7sre5KrJD4Raw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"prettier": "^3.0.0",
"svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
}
},
"node_modules/resolve": {
"version": "1.22.9",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz",

View File

@ -5,13 +5,17 @@
"scripts": {
"dev": "VITE_API_URL_DEV=http://127.0.0.1:7001 vite dev",
"build": "vite build",
"preview": "vite preview --host"
"preview": "vite preview --host",
"lint": "prettier --check .",
"format": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.3.1",
"@sveltejs/adapter-node": "^5.2.11",
"@sveltejs/kit": "^2.15.1",
"@sveltejs/vite-plugin-svelte": "^4.0.3",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.2",
"svelte": "^5.16.0",
"vite": "^5.4.11"
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,47 +1,28 @@
<script>
import { page } from "$app/stores"
export let link
export let type
let audio
function epicSound() {
audio.play()
}
import { color, click } from "$lib/util.js";
export let link;
</script>
<div>
<audio bind:this={audio} preload="auto">
<source src="/click.wav" type="audio/mpeg" />
</audio>
{#if type==="icon"}
<a class="icon" data-sveltekit-preload-data on:click={epicSound} href="{link}"><slot></slot></a>
{:else}
<a data-sveltekit-preload-data on:click={epicSound} href="{link}"><slot></slot></a>
{/if}
</div>
<a
style="text-decoration-color: var(--{color()})"
data-sveltekit-preload-data
on:click={click}
href={link}
>
<slot></slot>
</a>
<style>
a {
a {
font-weight: 700;
font-size: 22px;
font-size: 20px;
text-decoration: none;
color: white;
cursor: pointer;
}
}
a:hover {
a:hover {
text-decoration: underline;
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>

View File

@ -1,17 +1,17 @@
<script>
export let desc
export let url
export let desc;
export let url;
let icon = "<i class='nf nf-md-clipboard_multiple'></i>"
let audio
let icon = "<i class='nf nf-md-clipboard_multiple'></i>";
let audio;
function copy() {
audio.play()
navigator.clipboard.writeText(url)
icon = "<i class='nf nf-md-clipboard_check'></i>"
setTimeout(()=>{
icon = "<i class='nf nf-md-clipboard_multiple'></i>"
}, 500)
audio.play();
navigator.clipboard.writeText(url);
icon = "<i class='nf nf-md-clipboard_check'></i>";
setTimeout(() => {
icon = "<i class='nf nf-md-clipboard_multiple'></i>";
}, 500);
}
</script>
@ -25,12 +25,12 @@
</div>
<div>
<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>
</main>
<style>
main {
main {
display: flex;
flex-direction: row;
padding: 30px 30px 30px 30px;
@ -42,24 +42,25 @@ main {
align-items: center;
color: white;
gap: 100px;
transition: .4s;
transition: 0.4s;
flex-grow: 1;
flex: 1 1 0px;
}
}
div h1 {
div h1 {
animation-name: colorAnimation;
animation-duration: 10s;
animation-iteration-count: infinite;
font-size: 30px;
}
}
div p {
div p {
margin-top: 10px;
font-size: 20px;
}
}
a, button {
a,
button {
text-align: center;
font-size: 30px;
text-decoration: none;
@ -68,11 +69,12 @@ a, button {
background: none;
outline: none;
cursor: pointer;
}
}
a:hover, button:hover{
a:hover,
button:hover {
animation-name: colorAnimation;
animation-duration: 5s;
animation-iteration-count: infinite;
}
}
</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>
import { onMount } from "svelte"
import { goto } from "$app/navigation"
import { onMount } from "svelte";
import { goto } from "$app/navigation";
onMount(()=>{
goto("/")
})
onMount(() => {
goto("/");
});
</script>

View File

@ -9,8 +9,18 @@
<meta content="Homepage of my personal website" property="og:description" />
<meta content="https://ngn.tf" property="og:url" />
<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 rel="alternate" type="application/rss+xml" href="{import.meta.env.VITE_API_URL_DEV+'/blog/feed.rss'}" title="RSS Feed">
<link
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>
<Header>
@ -23,7 +33,7 @@
<Card title="whoami">
<div class="whoami-box">
<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">
<c><i class="nf nf-oct-key"></i> Keyoxide</c>
</a>
@ -34,7 +44,9 @@
<li>🇹🇷 I'm a high school student from Turkey</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>🐧 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>
</div>
</div>
@ -49,14 +61,17 @@
<li><c>👥</c> contributing stuff that I like</li>
<li><c>🚩</c> solving CTFs</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>
</Card>
<Card title="wall">
Here are some links if you want to get in contact with me, I highly
prefer email and I usually respond to emails in 1 or 2 days, just make
sure to check your spam folder (turns out running a TOR relay gets your IP into multiple blacklists)
Here are some links if you want to get in contact with me, I highly prefer email and I usually
respond to emails in 1 or 2 days, just make sure to check your spam folder (turns out running
a TOR relay gets your IP into multiple blacklists)
<ul>
<li>
<c><i class="nf nf-cod-github"></i></c>
@ -84,27 +99,27 @@
</div>
<style>
main{
main {
display: flex;
flex-direction: column;
gap: 28px;
padding: 50px;
}
}
.flexbox {
.flexbox {
display: flex;
gap: 28px;
}
}
.whoami-box {
.whoami-box {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 35px;
}
}
.whoami-pic {
.whoami-pic {
display: flex;
flex-direction: column;
align-items: center;
@ -113,9 +128,9 @@ main{
border-right: solid 1px var(--dark-fife);
padding: 0 35px 0 10px;
}
}
.whoami-pic img {
.whoami-pic img {
width: 200px;
border-radius: 20px;
@ -124,41 +139,43 @@ main{
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;
}
box-shadow:
rgba(50, 50, 93, 1) 0px 30px 60px -12px inset,
rgba(0, 0, 0, 1) 0px 18px 36px -18px inset;
}
ul {
ul {
list-style: inside;
}
}
li {
li {
padding-top: 15px;
}
}
a {
a {
color: white;
text-decoration: none;
}
}
a:hover {
a:hover {
font-weight: 900;
}
}
.version {
.version {
color: var(--dark-fife);
position: fixed;
bottom: 10px;
right: 10px;
font-size: 15px;
}
}
@media only screen and (max-width: 1200px) {
@media only screen and (max-width: 1200px) {
.flexbox {
flex-direction: column;
}
}
}
@media only screen and (max-width: 900px) {
@media only screen and (max-width: 900px) {
.whoami-box {
flex-direction: column;
gap: 25px;
@ -168,5 +185,5 @@ a:hover {
border-right: none;
padding: 0;
}
}
}
</style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
import Service from "../../lib/service.svelte";
import Card from "../../lib/card.svelte";
export let data
export let data;
</script>
<svelte:head>
@ -20,7 +20,7 @@
{#each data.services as services_list}
<div class="flexrow">
{#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}
</div>
{/each}
@ -31,70 +31,71 @@
Here some details for all the services I offer:
<ul>
<li>
<c><i class="nf nf-cod-account"></i> Registration:</c> All the services are offered for free, and all of them
are accessiable to public. And registrations are open for the all services that support account registrations.
<c><i class="nf nf-cod-account"></i> Registration:</c> All the services are offered for free,
and all of them are accessiable to public. And registrations are open for the all services that
support account registrations.
</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.
I also do not use any kind of CDN, and provide SSL encrypted connection for all the services. You can also connect all the
services over TOR, as I do not block any traffic from TOR.
<c><i class="nf nf-fa-eye_slash"></i> Privacy:</c> To protect user privacy, all the web proxy
logs are cleared regularly. I also do not use any kind of CDN, and provide SSL encrypted connection
for all the services. You can also connect all the services over TOR, as I do not block any traffic
from TOR.
</li>
<li>
<c><i class="nf nf-oct-graph"></i> Uptime:</c> Some services get restarted regularly, also sometimes I have
issues with the hosting, so I cannot provide or guarantee %100 uptime for any of the services. I also cannot guarantee
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
like a SSD failure.
<c><i class="nf nf-oct-graph"></i> Uptime:</c> Some services get restarted regularly, also sometimes
I have issues with the hosting, so I cannot provide or guarantee %100 uptime for any of the services.
I also cannot guarantee 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 like a SSD failure.
</li>
<li>
<c><i class="nf nf-md-speedometer"></i> Speed:</c> All the services are located in Turkey, and avaliable
over an 400 Mbit/s interface. If you are close to Turkey you should have a fairly good experience.
If you are not, then you should probably use another provider for the service.
<c><i class="nf nf-md-speedometer"></i> Speed:</c> All the services are located in Turkey, and
avaliable over an 400 Mbit/s interface. If you are close to Turkey you should have a fairly good
experience. If you are not, then you should probably use another provider for the service.
</li>
</ul>
</Card>
</main>
<style>
main {
main {
display: flex;
flex-direction: column;
align-content: center;
justify-content: center;
padding: 50px;
gap: 28px;
}
}
.flexcol {
.flexcol {
display: flex;
flex-direction: column;
align-content: center;
justify-content: center;
gap: 13px;
}
}
.flexrow {
.flexrow {
display: flex;
flex-direction: row;
align-content: center;
justify-content: center;
width: 100%;
gap: 13px;
}
}
ul {
ul {
list-style: inside;
margin-bottom: 20px;
}
}
li {
li {
padding-top: 30px;
line-height: 35px;
}
}
@media only screen and (max-width: 1316px) {
@media only screen and (max-width: 1316px) {
.flexrow {
flex-direction: column;
}
}
}
</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";
:root {
--white: white;
--dark-one: black;
--white: #ffffff;
--yellow: #d3b910;
--cyan: #0dd2e8;
--green: #06e00a;
--pinkish: #d506e0;
--red: #e8180d;
--blue: #0536fc;
--dark-one: #000000;
--dark-two: #050505;
--dark-three: #121212;
--dark-four: #101010;
--dark-fife: #3a3b3c;
--dark-six: #C0C0C0;
--dark-six: #c0c0c0;
--radius: 8px;
/*
old shadow animation
@ -53,252 +62,7 @@ body {
background: #282828;
}
@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);
}
}
@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 {
.glitch {
animation-name: colorAnimation;
animation-iteration-count: infinite;
animation-duration: 10s;

View File

@ -5,7 +5,8 @@
margin: 0;
color: #c9d1d9;
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;
line-height: 1.5;
word-wrap: break-word;
@ -38,7 +39,7 @@
.markdown-body h6:hover .anchor .octicon-link:before {
width: 16px;
height: 16px;
content: ' ';
content: " ";
display: inline-block;
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>");
@ -83,15 +84,15 @@
}
.markdown-body h1 {
margin: .67em 0;
margin: 0.67em 0;
font-weight: 600;
padding-bottom: .3em;
padding-bottom: 0.3em;
font-size: 2em;
border-bottom: 1px solid #21262d;
}
.markdown-body mark {
background-color: rgba(187,128,9,0.15);
background-color: rgba(187, 128, 9, 0.15);
color: #c9d1d9;
}
@ -139,7 +140,7 @@
overflow: hidden;
background: transparent;
border-bottom: 1px solid #21262d;
height: .25em;
height: 0.25em;
padding: 0;
margin: 24px 0;
background-color: #30363d;
@ -155,31 +156,31 @@
line-height: inherit;
}
.markdown-body [type=button],
.markdown-body [type=reset],
.markdown-body [type=submit] {
.markdown-body [type="button"],
.markdown-body [type="reset"],
.markdown-body [type="submit"] {
-webkit-appearance: button;
}
.markdown-body [type=checkbox],
.markdown-body [type=radio] {
.markdown-body [type="checkbox"],
.markdown-body [type="radio"] {
box-sizing: border-box;
padding: 0;
}
.markdown-body [type=number]::-webkit-inner-spin-button,
.markdown-body [type=number]::-webkit-outer-spin-button {
.markdown-body [type="number"]::-webkit-inner-spin-button,
.markdown-body [type="number"]::-webkit-outer-spin-button {
height: auto;
}
.markdown-body [type=search]::-webkit-search-cancel-button,
.markdown-body [type=search]::-webkit-search-decoration {
.markdown-body [type="search"]::-webkit-search-cancel-button,
.markdown-body [type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
.markdown-body ::-webkit-input-placeholder {
color: inherit;
opacity: .54;
opacity: 0.54;
}
.markdown-body ::-webkit-file-upload-button {
@ -225,30 +226,30 @@
cursor: pointer;
}
.markdown-body details:not([open])>*:not(summary) {
.markdown-body details:not([open]) > *:not(summary) {
display: none !important;
}
.markdown-body a:focus,
.markdown-body [role=button]:focus,
.markdown-body input[type=radio]:focus,
.markdown-body input[type=checkbox]:focus {
.markdown-body [role="button"]:focus,
.markdown-body input[type="radio"]:focus,
.markdown-body input[type="checkbox"]:focus {
outline: 2px solid #58a6ff;
outline-offset: -2px;
box-shadow: none;
}
.markdown-body a: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=checkbox]: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="checkbox"]:focus:not(:focus-visible) {
outline: solid 1px transparent;
}
.markdown-body a:focus-visible,
.markdown-body [role=button]:focus-visible,
.markdown-body input[type=radio]:focus-visible,
.markdown-body input[type=checkbox]:focus-visible {
.markdown-body [role="button"]:focus-visible,
.markdown-body input[type="radio"]:focus-visible,
.markdown-body input[type="checkbox"]:focus-visible {
outline: 2px solid #58a6ff;
outline-offset: -2px;
box-shadow: none;
@ -256,25 +257,32 @@
.markdown-body a:not([class]):focus,
.markdown-body a:not([class]):focus-visible,
.markdown-body input[type=radio]:focus,
.markdown-body input[type=radio]:focus-visible,
.markdown-body input[type=checkbox]:focus,
.markdown-body input[type=checkbox]:focus-visible {
.markdown-body input[type="radio"]:focus,
.markdown-body input[type="radio"]:focus-visible,
.markdown-body input[type="checkbox"]:focus,
.markdown-body input[type="checkbox"]:focus-visible {
outline-offset: 0;
}
.markdown-body kbd {
display: inline-block;
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;
color: #c9d1d9;
vertical-align: middle;
background-color: #161b22;
border: solid 1px rgba(110,118,129,0.4);
border-bottom-color: 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-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,
@ -291,7 +299,7 @@
.markdown-body h2 {
font-weight: 600;
padding-bottom: .3em;
padding-bottom: 0.3em;
font-size: 1.5em;
border-bottom: 1px solid #21262d;
}
@ -308,12 +316,12 @@
.markdown-body h5 {
font-weight: 600;
font-size: .875em;
font-size: 0.875em;
}
.markdown-body h6 {
font-weight: 600;
font-size: .85em;
font-size: 0.85em;
color: #8b949e;
}
@ -326,7 +334,7 @@
margin: 0;
padding: 0 1em;
color: #8b949e;
border-left: .25em solid #30363d;
border-left: 0.25em solid #30363d;
}
.markdown-body ul,
@ -355,14 +363,28 @@
.markdown-body tt,
.markdown-body code,
.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;
}
.markdown-body pre {
margin-top: 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;
word-wrap: normal;
}
@ -392,11 +414,11 @@
content: "";
}
.markdown-body>*:first-child {
.markdown-body > *:first-child {
margin-top: 0 !important;
}
.markdown-body>*:last-child {
.markdown-body > *:last-child {
margin-bottom: 0 !important;
}
@ -432,11 +454,11 @@
margin-bottom: 16px;
}
.markdown-body blockquote>:first-child {
.markdown-body blockquote > :first-child {
margin-top: 0;
}
.markdown-body blockquote>:last-child {
.markdown-body blockquote > :last-child {
margin-bottom: 0;
}
@ -481,7 +503,7 @@
.markdown-body h5 code,
.markdown-body h6 tt,
.markdown-body h6 code {
padding: 0 .2em;
padding: 0 0.2em;
font-size: inherit;
}
@ -515,19 +537,19 @@
list-style-type: none;
}
.markdown-body ol[type=a] {
.markdown-body ol[type="a"] {
list-style-type: lower-alpha;
}
.markdown-body ol[type=A] {
.markdown-body ol[type="A"] {
list-style-type: upper-alpha;
}
.markdown-body ol[type=i] {
.markdown-body ol[type="i"] {
list-style-type: lower-roman;
}
.markdown-body ol[type=I] {
.markdown-body ol[type="I"] {
list-style-type: upper-roman;
}
@ -535,7 +557,7 @@
list-style-type: decimal;
}
.markdown-body div>ol:not([type]) {
.markdown-body div > ol:not([type]) {
list-style-type: decimal;
}
@ -547,12 +569,12 @@
margin-bottom: 0;
}
.markdown-body li>p {
.markdown-body li > p {
margin-top: 16px;
}
.markdown-body li+li {
margin-top: .25em;
.markdown-body li + li {
margin-top: 0.25em;
}
.markdown-body dl {
@ -595,11 +617,11 @@
background-color: transparent;
}
.markdown-body img[align=right] {
.markdown-body img[align="right"] {
padding-left: 20px;
}
.markdown-body img[align=left] {
.markdown-body img[align="left"] {
padding-right: 20px;
}
@ -614,7 +636,7 @@
overflow: hidden;
}
.markdown-body span.frame>span {
.markdown-body span.frame > span {
display: block;
float: left;
width: auto;
@ -642,7 +664,7 @@
clear: both;
}
.markdown-body span.align-center>span {
.markdown-body span.align-center > span {
display: block;
margin: 13px auto 0;
overflow: hidden;
@ -660,7 +682,7 @@
clear: both;
}
.markdown-body span.align-right>span {
.markdown-body span.align-right > span {
display: block;
margin: 13px 0 0;
overflow: hidden;
@ -690,7 +712,7 @@
overflow: hidden;
}
.markdown-body span.float-right>span {
.markdown-body span.float-right > span {
display: block;
margin: 13px auto 0;
overflow: hidden;
@ -699,7 +721,7 @@
.markdown-body code,
.markdown-body tt {
padding: .2em .4em;
padding: 0.2em 0.4em;
margin: 0;
font-size: 85%;
white-space: break-spaces;
@ -724,7 +746,7 @@
font-size: 100%;
}
.markdown-body pre>code {
.markdown-body pre > code {
padding: 0;
margin: 0;
word-break: normal;
@ -963,7 +985,7 @@
.markdown-body g-emoji {
display: inline-block;
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-style: normal !important;
font-weight: 400;
@ -988,7 +1010,7 @@
cursor: pointer;
}
.markdown-body .task-list-item+.task-list-item {
.markdown-body .task-list-item + .task-list-item {
margin-top: 4px;
}
@ -997,12 +1019,12 @@
}
.markdown-body .task-list-item-checkbox {
margin: 0 .2em .25em -1.4em;
margin: 0 0.2em 0.25em -1.4em;
vertical-align: middle;
}
.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 {

View File

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

View File

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