frontend redesign

This commit is contained in:
ngn
2025-01-07 00:13:11 +03:00
parent 337e56de78
commit fc11748e57
37 changed files with 1545 additions and 900 deletions

7
app/src/hooks.server.js Normal file
View File

@ -0,0 +1,7 @@
import { locale } from "svelte-i18n";
export const handle = async ({ event, resolve }) => {
const lang = event.request.headers.get("accept-language")?.split(",")[0];
if (lang) locale.set(lang);
return resolve(event);
};

21
app/src/lib/api.js Normal file
View File

@ -0,0 +1,21 @@
const version = "v1";
const url = new URL(version + "/", import.meta.env.VITE_API_URL).href;
function join(path) {
if (null === path || path === "") return url;
if (path[0] === "/") path = path.slice(1);
return new URL(path, url).href;
}
async function services(fetch) {
const res = await fetch(join("/services"));
const json = await res.json();
if (!("result" in json)) return [];
return json.result;
}
export { version, join, services };

View File

@ -1,46 +1,24 @@
<script>
export let title;
let current = "";
let i = 0;
while (title.length > i) {
let c = title[i];
setTimeout(
() => {
current += c;
},
100 * (i + 1)
);
i += 1;
}
</script>
<div class="main">
<div class="title">
root@ngn.tf:~# {current}
</div>
<div class="content">
<main>
<h1 class="title">{title}</h1>
<div>
<slot></slot>
</div>
</div>
</main>
<style>
.main {
main {
flex: 1;
flex-basis: 30%;
display: flex;
flex-direction: column;
width: 100%;
background: var(--dark-three);
box-shadow: var(--box-shadow);
border-radius: var(--radius);
border: solid 1px var(--border-color);
}
.title {
background: var(--dark-two);
padding: 25px;
border-radius: 7px 7px 0px 0px;
font-size: 20px;
main .title {
font-family:
Consolas,
Monaco,
@ -50,14 +28,23 @@
Bitstream Vera Sans Mono,
Courier New,
monospace;
color: white;
color: var(--white-1);
}
.content {
background: var(--dark-three);
padding: 30px;
color: white;
border-radius: 5px;
font-size: 25px;
main .title::before {
content: "#";
margin: 0 10px 0 0;
color: var(--white-3);
}
main div {
border-left: solid 1px var(--black-4);
padding: 25px 25px 10px 25px;
font-size: var(--size-4);
color: var(--white-1);
word-wrap: break-word;
align-items: center;
margin-left: 7px;
flex: 1;
}
</style>

View File

@ -1,7 +1,7 @@
<script>
import { click } from "$lib/util.js";
export let title;
export let url;
let audio;
let current = "";
let i = 0;
@ -16,16 +16,9 @@
);
i += 1;
}
function epicSound() {
audio.play();
}
</script>
<a on:click={epicSound} data-sveltekit-preload-data href={url}>
<audio bind:this={audio} preload="auto">
<source src="/click.wav" type="audio/mpeg" />
</audio>
<a on:click={click} data-sveltekit-preload-data href={url}>
<div class="title">
{current}
</div>

View File

@ -0,0 +1,25 @@
<script>
export let class_name;
</script>
<main class={class_name}>
<slot></slot>
</main>
<style>
main {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: stretch;
padding: 50px;
gap: 28px;
}
@media only screen and (max-width: 900px) {
main {
flex-direction: column;
}
}
</style>

77
app/src/lib/footer.svelte Normal file
View File

@ -0,0 +1,77 @@
<script>
import Link from "$lib/link.svelte";
import { color } from "$lib/util.js";
import { _ } from "svelte-i18n";
let visitor_count = 1001;
function should_congrat() {
return visitor_count % 1000 == 0;
}
</script>
<footer style="border-top: solid 2px var(--{color()});">
<div class="info">
<div class="links">
<span>
<Link href="/" bold={true}>{$_("footer.source")}</Link>
</span>
<span>/</span>
<span>
<Link href="/" bold={true}>{$_("footer.license")}</Link>
</span>
<span>/</span>
<span>
<Link href="/" bold={true}>{$_("footer.privacy")}</Link>
</span>
</div>
<span>
{$_("footer.powered")}
</span>
</div>
<div class="useless">
<span>
{$_("footer.number", { values: { count: visitor_count } })}
{#if should_congrat()}
<span style="color: var(--{color()})">({$_("footer.congrats")})</span>
{/if}
</span>
<span>
{$_("footer.version", { values: { api_version: "v1", frontend_version: pkg.version } })}
</span>
</div>
</footer>
<style>
footer {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
background: var(--black-1);
}
div {
display: flex;
color: var(--white-2);
font-size: var(--size-2);
flex-direction: column;
gap: 5px;
}
.useless {
margin: 25px 50px 25px 0;
text-align: right;
}
.info {
margin: 25px 0 25px 50px;
text-align: left;
}
.info .links {
display: flex;
flex-direction: row;
gap: 5px;
}
</style>

15
app/src/lib/head.svelte Normal file
View File

@ -0,0 +1,15 @@
<script>
import { frontend_url, api_url } from "$lib/util.js";
export let desc, title;
</script>
<svelte:head>
<title>[ngn.tf] | {title}</title>
<meta content="[ngn.tf] | {title}" property="og:title" />
<meta content={desc} property="og:description" />
<meta content={frontend_url()} property="og:url" />
<meta content="#000000" data-react-helmet="true" name="theme-color" />
<link rel="alternate" type="application/atom+xml" href={api_url("/news/en")} title="Atom Feed" />
</svelte:head>

View File

@ -1,12 +1,25 @@
<script>
export let subtitle = "";
import { color } from "$lib/util.js";
import { _ } from "svelte-i18n";
export let picture = "";
export let title = "";
let current = "";
for (let i = 0; i < title.length; i++) {
setTimeout(
() => {
current += title[i];
},
100 * (i + 1)
);
}
</script>
<header>
<h1>
<slot></slot>
</h1>
<h4><c>{subtitle}</c></h4>
<h1 style="color: var(--{color()})">{current}</h1>
<img src="/profile/{picture}.png" alt="" />
</header>
<style>
@ -16,25 +29,54 @@
background-size: 50%;
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: end;
}
img {
padding: 50px 50px 0 50px;
width: 220px;
bottom: 0;
left: 0;
}
h1 {
font-weight: 900;
font-size: 500%;
padding: 120px;
padding-bottom: 0;
font-size: var(--size-7);
font-family:
Consolas,
Monaco,
Lucida Console,
Liberation Mono,
DejaVu Sans Mono,
Bitstream Vera Sans Mono,
Courier New,
monospace;
padding: 50px 50px 30px 50px;
white-space: nowrap;
text-align: center;
color: white;
text-shadow: var(--text-shadow);
text-size-adjust: 80%;
}
h4 {
padding-bottom: 120px;
font-weight: 600;
font-size: 200%;
text-align: center;
color: white;
text-size-adjust: 80%;
h1::after {
content: "_";
display: inline-block;
animation: blink 1.5s steps(2) infinite;
}
@media only screen and (max-width: 900px) {
header {
display: block;
}
h1 {
padding: 80px;
}
img {
display: none;
}
}
</style>

6
app/src/lib/icon.svelte Normal file
View File

@ -0,0 +1,6 @@
<script>
import { color } from "$lib/util.js";
export let icon = "";
</script>
<i style="color: var(--{color()});" class="nf {icon}"></i>

4
app/src/lib/lang.js Normal file
View File

@ -0,0 +1,4 @@
export default [
{ code: "en", name: "English", icon: "🇬🇧", path: "../locales/en.json" },
{ code: "tr", name: "Turkish", icon: "🇹🇷", path: "../locales/tr.json" },
];

22
app/src/lib/link.svelte Normal file
View File

@ -0,0 +1,22 @@
<script>
import Icon from "$lib/icon.svelte";
import { color } from "$lib/util.js";
const default_color = "white-1";
export let active = false;
export let link = "";
export let icon = "";
let style = `text-decoration-color: var(--${color()});`;
if (active) style += `color: var(--${color()});`;
else style += `color: var(--${default_color});`;
</script>
{#if icon != ""}
<Icon {icon} />
{/if}
<a {style} href={link}>
<slot></slot>
</a>

View File

@ -1,30 +1,29 @@
<script>
import { color } from "$lib/util.js";
import NavbarLink from "./navbar_link.svelte";
import NavbarSwitch from "./navbar_switch.svelte";
import { color } from "$lib/util.js";
import { _ } from "svelte-i18n";
</script>
<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="/donate">donate</NavbarLink> -->
<NavbarLink link="/">{$_("navbar.home")}</NavbarLink>
<NavbarLink link="/services">{$_("navbar.services")}</NavbarLink>
<NavbarLink link="/donate">{$_("navbar.donate")}</NavbarLink>
<NavbarSwitch />
</div>
</nav>
<style>
nav {
background: var(--dark-one);
background: var(--black-1);
padding: 20px 30px 20px 20px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
/*animation-name: borderAnimation;
animation-duration: 10s;
animation-iteration-count: infinite;*/
box-shadow: var(--def-shadow);
}
@ -38,6 +37,6 @@
h3 {
font-weight: 900;
font-size: 20px;
font-size: var(--size-4);
}
</style>

View File

@ -1,10 +1,16 @@
<script>
import { color, click } from "$lib/util.js";
import { page } from "$app/stores";
export let link;
function is_active() {
return $page.url.pathname == link;
}
</script>
<a
style="text-decoration-color: var(--{color()})"
style="text-decoration-color: var(--{color()}); {is_active() ? `color: var(--${color()})` : ''}"
data-sveltekit-preload-data
on:click={click}
href={link}
@ -14,15 +20,8 @@
<style>
a {
font-weight: 700;
font-size: 20px;
text-decoration: none;
color: white;
cursor: pointer;
}
a:hover {
text-decoration: underline;
text-shadow: 3px 4px 7px rgba(81, 67, 21, 0.8);
font-weight: 900;
font-size: var(--size-4);
color: var(--white-1);
}
</style>

View File

@ -0,0 +1,38 @@
<script>
import { locale } from "svelte-i18n";
import languages from "$lib/lang.js";
let icon = "",
indx = 0,
len = languages.length;
function next() {
if (indx >= languages.length) indx = 0;
icon = languages[indx].icon;
locale.set(languages[indx++].code);
}
for (indx = 0; indx < len; indx++) {
if (languages[indx].code == $locale.slice(0, 2)) {
icon = languages[indx++].icon;
break;
}
}
</script>
<button on:click={next}>{icon}</button>
<style>
button {
background: var(--black-2);
color: var(--white-1);
font-size: var(--size-4);
outline: none;
border: none;
transition: 0.4s;
}
button:hover {
background: var(--black-1);
}
</style>

View File

@ -1,80 +1,102 @@
<script>
export let desc;
export let url;
import Icon from "$lib/icon.svelte";
import Link from "$lib/link.svelte";
let icon = "<i class='nf nf-md-clipboard_multiple'></i>";
let audio;
import { color, time_from_ts } from "$lib/util.js";
import { locale } from "svelte-i18n";
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);
}
export let service = {};
let style = "";
if (service.check_res == 0) style = "opacity: 70%";
</script>
<main>
<audio bind:this={audio} preload="auto">
<source src="/click.wav" type="audio/mpeg" />
</audio>
<div>
<h1><slot></slot></h1>
<p>{desc}</p>
<main {style}>
<div class="info">
<div class="title">
<h1>{service.name}</h1>
<p>{service.desc[$locale.slice(0, 2)]}</p>
</div>
<div class="links">
<Link link={service.clear}><Icon icon="nf-oct-link" /></Link>
{#if service.onion != ""}
<Link link={service.onion}><Icon icon="nf-linux-tor" /></Link>
{/if}
{#if service.i2p != ""}
<Link link={service.i2p}><span style="color: var(--{color()})">I2P</span></Link>
{/if}
</div>
</div>
<div>
<button on:click={copy}>{@html icon}</button>
<a href={url}><i class="nf nf-oct-link_external"></i></a>
<div class="check">
<h1>Last checked at {time_from_ts(service.check_time)}</h1>
{#if service.check_res == 0}
<span style="background: var(--white-2)">Down</span>
{:else if service.check_res == 1}
<span style="background: var(--{color()})">Up</span>
{:else if service.check_res == 2}
<span style="background: var(--white-2)">Slow</span>
{/if}
</div>
</main>
<style>
main {
display: flex;
flex-direction: column;
background: var(--black-3);
border: solid 1px var(--black-4);
text-align: left;
flex: 1;
flex-basis: 40%;
}
main .info {
padding: 25px;
display: flex;
flex-direction: row;
padding: 30px 30px 30px 30px;
background: var(--dark-two);
border-radius: var(--radius);
box-shadow: var(--box-shadow);
border: solid 1px var(--border-color);
justify-content: space-between;
align-items: center;
color: white;
gap: 100px;
transition: 0.4s;
flex-grow: 1;
flex: 1 1 0px;
justify-content: space-between;
color: var(--white-1);
}
div h1 {
animation-name: colorAnimation;
animation-duration: 10s;
animation-iteration-count: infinite;
font-size: 30px;
main .info .title h1 {
font-size: var(--size-5);
margin-bottom: 8px;
}
div p {
margin-top: 10px;
font-size: 20px;
main .info .title p {
font-size: var(--size-4);
color: var(--white-2);
}
a,
button {
text-align: center;
font-size: 30px;
text-decoration: none;
color: white;
border: none;
background: none;
outline: none;
cursor: pointer;
main .info .links {
display: flex;
flex-direction: row;
gap: 10px;
font-size: var(--size-6);
}
a:hover,
button:hover {
animation-name: colorAnimation;
animation-duration: 5s;
animation-iteration-count: infinite;
main .check {
border-top: solid 1px var(--black-4);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
color: var(--white-1);
}
main .check h1 {
padding: 15px 25px 15px 25px;
font-size: var(--size-4);
font-weight: 100;
}
main .check span {
padding: 15px 25px 15px 25px;
font-size: var(--size-5);
text-transform: uppercase;
color: var(--black-1);
font-weight: 1000;
}
</style>

View File

@ -1,10 +1,15 @@
function click() {
let audio = new Audio("/click.wav");
audio.play();
}
import { join } from "$lib/api.js";
const colors = [
"yellow",
"cyan",
"green",
"pinkish",
"red",
// "blue" (looks kinda ass)
];
let colors_pos = -1;
const colors = ["yellow", "cyan", "green", "pinkish", "red", "blue"];
let api_url = join;
function color() {
if (colors_pos < 0) colors_pos = Math.floor(Math.random() * colors.length);
@ -13,4 +18,18 @@ function color() {
return colors[colors_pos];
}
export { click, color };
function click() {
let audio = new Audio("/click.wav");
audio.play();
}
function frontend_url(path) {
if (null !== path && path !== "") return new URL(path, import.meta.env.VITE_FRONTEND_URL).href;
else return new URL(import.meta.env.VITE_FRONTEND_URL).href;
}
function time_from_ts(ts) {
return new Date(ts * 1000).toLocaleTimeString();
}
export { api_url, frontend_url, click, color, time_from_ts };

49
app/src/locales/en.json Normal file
View File

@ -0,0 +1,49 @@
{
"navbar": {
"home": "home",
"services": "services",
"news": "news",
"donate": "donate"
},
"home": {
"title": "hello world!",
"welcome": {
"title": "about",
"desc": "Welcome to my website, I'm ngn",
"whoami": "I'm a privacy, security and freedom addvocate high-schooler from Turkey",
"interest": "I'm interested in system security and software development",
"support": "I love and support Free/Libre and Open Source Software (FLOSS)"
},
"work": {
"title": "work",
"desc": "I don't currently have a job, so I spend most of my time...",
"build": "building stupid shit",
"ctf": "solving CTF challenges",
"contribute": "contributing to random projects",
"wiki": "expanding my wiki"
},
"links": {
"title": "contact",
"desc": "Here are some useful links if you want to get in contact with me",
"prefer": "preferred"
},
"info": {
"title": "services",
"desc": "A part from working on stupid shit, I host free (as in freedom, and price) services available for all",
"speed": "All of these services are available over a 600 Mbit/s interface",
"security": "All use SSL encrypted connection and they are all privacy-respecting",
"privacy": "Accessible from clearnet, TOR and I2P, no region or network blocks",
"bullshit": "No CDNs, no cloudflare, no CAPTCHA, no analytics, no bullshit",
"link": "see all the services"
}
},
"footer": {
"source": "Source",
"license": "License",
"privacy": "Privacy",
"powered": "Powered by Svelte, Go, SQLite and donations",
"number": "You are the visitor number {count}",
"congrat": "congrats!!",
"version": "Using API version {api_version}, frontend version {frontend_version}"
}
}

46
app/src/locales/tr.json Normal file
View File

@ -0,0 +1,46 @@
{
"navbar": {
"home": "anasayfa",
"news": "haberler",
"services": "servisler",
"language": "dil"
},
"home": {
"welcome": {
"title": "Websiteme hoşgeldiniz, ben ngn",
"whoami": "Türkiye'den, güvenlik, gizlik ve özgürlük savunucusu bir liseliyim",
"interest": "Sistem güvenliği ve yazılım geliştirmek ile ilgileniyorum",
"support": "Özgür/Libre ve Açık Kaynaklı Yazılımı (FLOSS) seviyorum ve destekliyorum"
},
"work": {
"title": "Zamanım çoğunlukla şunlar ile geçiyor...",
"build": "salak şeyler inşa etmek",
"fix": "salak şeyleri düzeltmek",
"ctf": "CTF challenge'ları çözmek",
"contribute": "rastgele projelere katkıda bulunmak",
"wiki": "wikimi genişletmek"
},
"links": {
"title": "Eğer benim ile iletişime geçmek istiyorsanız, işte bazı faydalı linkler",
"prefer": "tercihim"
},
"info": {
"title": "Salak şeyler inşa etmenin yanı sıra, herkes için kullanıma açık özgür ve ücretsiz servisler host ediyorum",
"speed": "Tüm servisler 600 Mbit/s ağ arayüzü üzerinden erişilebilir",
"security": "Hepsi SSL şifreli bağlantı kullanıyor ve hepsi gizliğinize saygı gösteriyor",
"privacy": "Accessible from clearnet, TOR and I2P, no region or network blocks",
"privacy": "Açık ağdan, TOR ve I2P'den erişilebilirler, bölge ya da ağ blokları yok",
"bullshit": "CDN yok, cloudflare yok, CAPTCHA yok, analitikler yok, boktan saçmalıklar yok",
"link": "tüm servisleri incele"
}
},
"footer": {
"source": "Kaynak",
"license": "Lisans",
"privacy": "Gizlilik",
"powered": "Svelte, Go, SQLite ve yemek param tarafından destekleniyor",
"number": "{count}. ziyaretçisiniz",
"congrat": "tebrikler!!",
"version": "Kullan API versiyonu {api_version}, arayüz versiyonu {frontend_version}"
}
}

19
app/src/routes/+layout.js Normal file
View File

@ -0,0 +1,19 @@
import { locale, waitLocale } from "svelte-i18n";
import { init, register } from "svelte-i18n";
import { browser } from "$app/environment";
import languages from "$lib/lang.js";
const defaultLocale = languages[0].code;
for (let i = 0; i < languages.length; i++)
register(languages[i].code, () => import(/* @vite-ignore */ languages[i].path));
init({
fallbackLocale: defaultLocale,
initialLocale: browser ? window.navigator.language.slice(0, 2).toLowerCase() : defaultLocale,
});
export const load = async () => {
if (browser) locale.set(window.navigator.language);
await waitLocale();
};

View File

@ -1,12 +1,27 @@
<script>
import Navbar from "../lib/navbar.svelte";
import Navbar from "$lib/navbar.svelte";
import Footer from "$lib/footer.svelte";
</script>
<main>
<Navbar />
<slot></slot>
<div class="content">
<slot></slot>
</div>
<Footer />
</main>
<style>
@import "../../static/global.css";
main {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.content {
background: var(--black-1);
flex-grow: 1;
}
</style>

View File

@ -1,189 +1,101 @@
<script>
import Header from "../lib/header.svelte";
import Card from "../lib/card.svelte";
import Header from "$lib/header.svelte";
import Content from "$lib/content.svelte";
import Head from "$lib/head.svelte";
import Card from "$lib/card.svelte";
import Link from "$lib/link.svelte";
import { color } from "$lib/util.js";
import { _ } from "svelte-i18n";
</script>
<svelte:head>
<title>[ngn.tf] | homepage</title>
<meta content="[ngn] | homepage" property="og:title" />
<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"
/>
</svelte:head>
<Head title="home" desc="home page of my personal website" />
<Header title={$_("home.title")} picture="tired" />
<Header>
<c>echo</c>
hello world!
</Header>
<main>
<div class="flexbox">
<Card title="whoami">
<div class="whoami-box">
<div class="whoami-pic">
<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>
</div>
<div class="whoami-text">
👋 Hello! I'm ngn!
<ul>
<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>
</ul>
</div>
<Content>
<Card title={$_("home.welcome.title")}>
<span> 👋 {$_("home.welcome.desc")}</span>
<ul>
<li>🇹🇷 {$_("home.welcome.whoami")}</li>
<li>🖥️ {$_("home.welcome.interest")}</li>
<li>❤️ {$_("home.welcome.support")}</li>
</ul>
</Card>
<Card title={$_("home.work.title")}>
<span>{$_("home.work.desc")}</span>
<ul>
<li>⌨️ {$_("home.work.build")}</li>
<li>🚩 {$_("home.work.ctf")}</li>
<li>👥 {$_("home.work.contribute")}</li>
<li>📑 {$_("home.work.wiki")}</li>
</ul>
</Card>
<Card title={$_("home.links.title")}>
<span>{$_("home.links.desc")}:</span>
<ul>
<li>
<Link icon="nf-fa-key" link="https://keyoxide.org/F9E70878C2FB389AEC2BA34CA3654DF5AD9F641D">
PGP
</Link>
</li>
<li>
<Link icon="nf-md-mastodon" link="https://defcon.social/@ngn">Mastodon</Link>
</li>
<li>
<Link icon="nf-cod-github" link="https://github.com/ngn13">Github</Link>
</li>
<li>
<Link icon="nf-md-email" link="mailto:ngn@ngn.tf">Email</Link>
<span class="prefer">({$_("home.links.prefer")})</span>
</li>
</ul>
</Card>
<Card title={$_("home.info.title")}>
<div class="services">
<div class="info">
<span>
{$_("home.info.desc")}
</span>
<ul>
<li>
<i style="color: var(--{color()});" class="nf nf-md-speedometer_slow"></i>
{$_("home.info.speed")}
</li>
<li>
<i style="color: var(--{color()});" class="nf nf-fa-lock"></i>
{$_("home.info.security")}
</li>
<li>
<i style="color: var(--{color()});" class="nf nf-fa-network_wired"></i>
{$_("home.info.privacy")}
</li>
<li>
<i style="color: var(--{color()});" class="nf nf-md-eye_off"></i>
{$_("home.info.bullshit")}
</li>
</ul>
</div>
</Card>
</div>
<div class="flexbox">
<Card title="ps -eaf">
I usually spend my time...
<ul>
<li><c>⌨️</c> building random projects</li>
<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>
</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)
<ul>
<li>
<c><i class="nf nf-cod-github"></i></c>
<a href="https://github.com/ngn13">Github</a>
</li>
<li>
<c><i class="nf nf-md-mastodon"></i></c>
<a href="https://defcon.social/@ngn" rel="me">Mastodon</a>
</li>
<li>
<c><i class="nf nf-md-email"></i></c>
<a href="mailto:ngn@ngn.tf">Email</a>
</li>
<li>
<c><i class="nf nf-md-xmpp"></i></c>
<a href="xmpp:ngn@chat.ngn.tf">XMPP</a>
</li>
</ul>
</Card>
</div>
</main>
<div class="version">
<p>v5.0</p>
</div>
</div>
</Card>
</Content>
<style>
main {
display: flex;
flex-direction: column;
gap: 28px;
padding: 50px;
.prefer {
color: var(--white-2);
font-style: italic;
}
.flexbox {
display: flex;
gap: 28px;
}
.whoami-box {
.services {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 35px;
justify-content: space-between;
flex-wrap: wrap;
gap: 28px;
}
.whoami-pic {
.services .info {
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 {
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>

View File

@ -1,9 +0,0 @@
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();
return {
posts: data["result"],
};
}

View File

@ -1,118 +0,0 @@
<script>
import Header from "../../lib/header.svelte";
import CardLink from "../../lib/card_link.svelte";
export let data;
let posts = data.posts;
</script>
<svelte:head>
<title>[ngn.tf] | blog</title>
<meta content="[ngn] | blog" property="og:title" />
<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"
/>
</svelte:head>
<Header>
<c>/dev/</c>blog
</Header>
<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>
<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>
</div>
<div class="post-list">
{#each posts as post}
<CardLink url="/blog/{post.id}" title={post.title}>
<p>{post.author} | {post.date}</p>
<br />
{post.content}...
</CardLink>
{/each}
</div>
</main>
<style>
.post-list {
display: flex;
flex-direction: column;
gap: 28px;
}
main {
padding: 15%;
padding-top: 50px;
display: flex;
flex-direction: column;
gap: 20px;
}
p {
font-size: 20px;
}
.feed-list {
text-align: right;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 10px;
}
.feed-list a {
text-decoration: none;
padding: 10px 15px 10px 15px;
background: var(--dark-three);
border-radius: var(--radius);
border: solid 1px var(--border-color);
color: var(--white);
font-size: 20px;
font-weight: 900;
display: flex;
flex-direction: row;
align-items: center;
gap: 7px;
transition: 0.2s;
}
.feed-list a:hover {
box-shadow: var(--box-shadow);
}
.feed-list a i {
font-size: 21px;
}
@media only screen and (max-width: 1316px) {
main {
padding: 50px;
}
}
</style>

View File

@ -1,14 +0,0 @@
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();
if (data["error"] != "") {
return {
error: data["error"],
};
}
return data["result"];
}

View File

@ -1,191 +0,0 @@
<script>
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;
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();
if (json["error"] != "") {
return;
}
if (json["result"] == "upvote") {
upvote_status = "active";
downvote_status = "inactive";
} else {
downvote_status = "active";
upvote_status = "inactive";
}
voted = true;
}
onMount(async () => {
if (data.title == undefined) goto("/blog");
sanitized = DOMPurify.sanitize(marked.parse(data.content, { breaks: true }), {
ADD_TAGS: ["iframe"],
ADD_ATTR: ["allow", "allowfullscreen", "frameborder", "scrolling"],
});
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();
if (json["error"] != "") {
return;
}
if (voted) {
data.vote += 2;
} else {
voted = true;
data.vote += 1;
}
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;
}
if (voted) {
data.vote -= 2;
} else {
voted = true;
data.vote -= 1;
}
await get_status();
}
</script>
<svelte:head>
<title>[ngn.tf] | {data.title}</title>
<meta content="[ngn] | blog" property="og:title" />
<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" />
</svelte:head>
<Header subtitle="{data.author} | {data.date}">
{data.title}
</Header>
<main>
<audio bind:this={audio} preload="auto">
<source src="/click.wav" type="audio/mpeg" />
</audio>
<div class="content markdown-body">
{@html sanitized}
</div>
<div class="votes">
<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}
>
<i class="nf nf-md-arrow_down_bold"></i>
</button>
</div>
</main>
<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 {
padding: 50px 20% 50px 20%;
}
}
.content {
padding: 30px;
background: var(--dark-four);
border-radius: var(--radius);
border: solid 1px var(--border-color);
box-shadow: var(--box-shadow);
width: auto;
width: 100%;
}
.votes {
display: flex;
flex-direction: column;
text-align: center;
text-shadow: var(--text-shadow);
gap: 10px;
padding: 15px 5px 15px 5px;
margin-left: 10px;
}
.votes p {
font-size: 25px;
color: var(--dark-six);
}
.votes button {
display: flex;
flex-direction: row;
gap: 10px;
background: none;
outline: none;
border: none;
font-size: 30px;
cursor: pointer;
color: var(--dark-six);
}
.votes button:hover {
animation-name: colorAnimation;
animation-iteration-count: infinite;
animation-duration: 10s;
}
.active {
animation-name: colorAnimation;
animation-iteration-count: infinite;
animation-duration: 10s;
}
</style>

View File

@ -1,65 +1,70 @@
<script>
import Header from "../../lib/header.svelte";
import Card from "../../lib/card.svelte";
import Header from "$lib/header.svelte";
import Head from "$lib/head.svelte";
import { color } from "$lib/util.js";
</script>
<svelte:head>
<title>[ngn.tf] | donate</title>
<meta content="[ngn] | donate" property="og:title" />
<meta content="Give me all of your life savings" property="og:description" />
<meta content="https://ngn.tf" property="og:url" />
<meta content="#000000" data-react-helmet="true" name="theme-color" />
</svelte:head>
<Header>
<c>bash</c>
donate.sh
</Header>
<Head title="donate" desc="give me all of your life savings" />
<Header title="donate money!" picture="money" />
<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!
<table>
<thead>
<tr>
<th>Platform</th>
<th>Address/Link</th>
</tr>
</thead>
<tbody>
<tr>
<td>Monero (XMR)</td>
<td>
<code>
46q7G7u7cmASvJm7AmrhmNg6ctS77mYMmDAy1QxpDn5w57xV3GUY5za4ZPZHAjqaXdfS5YRWm4AVj5UArLDA1retRkJp47F
</code>
</td>
</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!
</Card>
<span
>I spend a lot of time working on different projects and maintaining different services.</span
>
<span
>I also spend a lot of money, but unlike time, you don't usually get much of it for free.</span
>
<span
>I mostly pay for hosting and electricity. Which when added up costs around 550₺ per month, that
is Turkish Lira, equals to ~$15 at time of writing.</span
>
<br />
<br />
<span
>So even a small donation would be highly appreciated and it would help me keep everything up
and running.</span
>
<table>
<thead>
<tr>
<th style="color: var(--{color()})">Platform</th>
<th style="color: var(--{color()})">Address/Link</th>
</tr>
</thead>
<tbody>
<tr>
<td>Monero (XMR)</td>
<td>
<code>
46q7G7u7cmASvJm7AmrhmNg6ctS77mYMmDAy1QxpDn5w57xV3GUY5za4ZPZHAjqaXdfS5YRWm4AVj5UArLDA1retRkJp47F
</code>
</td>
</tr>
</tbody>
</table>
<span
>Also huge thanks to all of you who has donated so far, as I said, I highly appreciate it. Thank
you!</span
>
</main>
<style>
main {
display: flex;
flex-direction: column;
gap: 35px;
padding: 50px;
}
main span {
font-size: var(--size-4);
color: var(--white-1);
}
table {
border-collapse: collapse;
border: none;
color: white;
font-size: 20px;
width: 100%;
margin: 30px 0 30px 0;
box-shadow: var(--box-shadow);
background: var(--black-3);
border-collapse: collapse;
font-size: var(--size-3);
margin: 30px 0 30px 0;
width: 100%;
}
tr,
@ -67,19 +72,22 @@
td {
color: white;
background: var(--dark-two);
text-align: left;
}
td,
th {
border: solid 1px var(--dark-fife);
border: solid 1px var(--black-4);
padding: 16px;
}
th {
animation-name: colorAnimation;
animation-duration: 10s;
animation-iteration-count: infinite;
background: var(--dark-two);
font-weight: 1000;
}
td {
color: var(--white-2);
font-weight: 400;
}
code {

View File

@ -1,43 +1,7 @@
import { services } from "$lib/api.js";
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();
if (data["error"] != "") {
return {
error: data["error"],
};
}
// Some really bad code to convert
// [service1, service2, service3...]
// to
// [[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 = [];
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 (counter == 2) {
services.push(currentlist);
currentlist = [];
counter = 0;
}
}
return {
services,
list: await services(fetch),
};
}

View File

@ -1,100 +1,74 @@
<script>
import Header from "../../lib/header.svelte";
import Service from "../../lib/service.svelte";
import Card from "../../lib/card.svelte";
import Service from "$lib/service.svelte";
import Header from "$lib/header.svelte";
import Link from "$lib/link.svelte";
import Head from "$lib/head.svelte";
import { api_url } from "$lib/util.js";
import { locale } from "svelte-i18n";
export let data;
let list = data.list,
services = list;
let value = "";
function change(input) {
value = input.target.value.toLowerCase();
services = [];
if (value === "") {
services = list;
return;
}
list.forEach((s) => {
if (s.name.toLowerCase().includes(value)) services.push(s);
});
}
</script>
<svelte:head>
<title>[ngn.tf] | services</title>
<meta content="[ngn] | services" property="og:title" />
<meta content="Stuff that I host" property="og:description" />
<meta content="https://ngn.tf" property="og:url" />
<meta content="#000000" data-react-helmet="true" name="theme-color" />
</svelte:head>
<Header><c>ls</c> services</Header>
<main>
<Card title="cat services/*/info.txt">
<div class="flexcol">
{#each data.services as services_list}
<div class="flexrow">
{#each services_list as service}
<Service url={service.url} desc={service.desc}>{service.name}</Service>
{/each}
</div>
{/each}
</div>
</Card>
<Head title="services" desc="my self-hosted services and projects" />
<Header title="service status" picture="cool" />
<Card title="cat services/details.txt">
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.
</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.
</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.
</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.
</li>
</ul>
</Card>
<main>
<div class="title">
<input on:input={change} type="text" placeholder="Search for a service" />
<div>
<Link icon="nf-fa-feed" link={api_url("/news/" + $locale.slice(0, 2))}>News and updates</Link>
</div>
</div>
<div class="services">
{#each services as service}
<Service {service} />
{/each}
</div>
</main>
<style>
main {
display: flex;
flex-direction: column;
align-content: center;
justify-content: center;
padding: 50px;
text-align: right;
}
main .title {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
main .services {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: stretch;
margin-top: 20px;
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 {
@media only screen and (max-width: 1200px) {
main .services {
flex-direction: column;
}
}