remove bloat fonts, get rid of svelte-i18n
Signed-off-by: ngn <ngn@ngn.tf>
12
app/.gitignore
vendored
@@ -1,11 +1,9 @@
|
||||
.DS_Store
|
||||
.svelte-kit
|
||||
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
package
|
||||
build
|
||||
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
nerdfonts.*
|
||||
|
@@ -1 +0,0 @@
|
||||
engine-strict=true
|
@@ -4,6 +4,7 @@
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 80,
|
||||
"arrowParens": "avoid",
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
|
14
app/Makefile
@@ -1,17 +1,7 @@
|
||||
NF_CSS = static/css/nerdfonts.css
|
||||
NF_WOFF = static/assets/nerdfonts.woff2
|
||||
|
||||
all: $(NF_CSS)
|
||||
all:
|
||||
npm run build
|
||||
|
||||
$(NF_CSS): $(NF_WOFF)
|
||||
wget "https://www.nerdfonts.com/assets/css/webfont.css" -O $@
|
||||
sed 's/\.\.\/fonts\/Symbols-2048-em Nerd Font Complete\.woff2/\/assets\/nerdfonts\.woff2/g' -i $@
|
||||
|
||||
$(NF_WOFF):
|
||||
wget "https://www.nerdfonts.com/assets/fonts/Symbols-2048-em%20Nerd%20Font%20Complete.woff2" -O $@
|
||||
|
||||
run: $(NF_CSS)
|
||||
run:
|
||||
npm run dev
|
||||
|
||||
format:
|
||||
|
2157
app/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "website",
|
||||
"version": "6.3",
|
||||
"version": "6.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -10,19 +10,18 @@
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
"@sveltejs/adapter-node": "^5.2.11",
|
||||
"@sveltejs/kit": "^2.15.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.3",
|
||||
"@sveltejs/adapter-node": "^5.2.13",
|
||||
"@sveltejs/kit": "^2.25.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.1.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.3.2",
|
||||
"svelte": "^5.16.0",
|
||||
"vite": "^5.4.11"
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"svelte": "^5.36.10",
|
||||
"vite": "^7.0.0"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"dompurify": "^3.2.3",
|
||||
"marked": "^16.0.0",
|
||||
"svelte-i18n": "^4.0.1"
|
||||
"js-yaml": "^4.1.0",
|
||||
"marked": "^16.0.0"
|
||||
}
|
||||
}
|
||||
|
58
app/src/components/card.svelte
Normal file
@@ -0,0 +1,58 @@
|
||||
<script>
|
||||
export let title = "";
|
||||
export let id = "";
|
||||
</script>
|
||||
|
||||
<main {id}>
|
||||
{#if title === ""}
|
||||
<div><slot></slot></div>
|
||||
{:else}
|
||||
<h1>{title}</h1>
|
||||
<div class="padded"><slot></slot></div>
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: min-content;
|
||||
}
|
||||
|
||||
main h1 {
|
||||
font-family: var(--monospace);
|
||||
font-size: var(--size-6);
|
||||
|
||||
color: var(--white-1);
|
||||
background: var(--black-1);
|
||||
|
||||
position: relative;
|
||||
top: 20px;
|
||||
right: 7px;
|
||||
|
||||
width: min-content;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
main h1::before {
|
||||
color: var(--white-3);
|
||||
content: "#";
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
|
||||
main div {
|
||||
color: var(--white-2);
|
||||
|
||||
font-size: var(--size-3);
|
||||
line-height: 1.5em;
|
||||
word-wrap: break-word;
|
||||
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main .padded {
|
||||
padding: 25px 20px 18px 20px;
|
||||
border: solid 1px var(--black-3);
|
||||
}
|
||||
</style>
|
94
app/src/components/footer.svelte
Normal file
@@ -0,0 +1,94 @@
|
||||
<script>
|
||||
import { _ } from "$lib/locale.js";
|
||||
import { date, date_from_ts } from "$lib/util.js";
|
||||
import api from "$lib/api.js";
|
||||
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let data = null;
|
||||
|
||||
onMount(async () => {
|
||||
data = await api.metrics(fetch);
|
||||
});
|
||||
</script>
|
||||
|
||||
<footer>
|
||||
<ul>
|
||||
<li>
|
||||
<a href={import.meta.env.WEBSITE_SOURCE_URL}>{$_("footer.source")}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/doc/license">{$_("footer.license")}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/doc/privacy">{$_("footer.privacy")}</a>
|
||||
</li>
|
||||
</ul>
|
||||
{#if data === null}
|
||||
<span>
|
||||
{$_("footer.render", {
|
||||
time: date(new Date()),
|
||||
})}
|
||||
</span>
|
||||
{:else}
|
||||
<span>
|
||||
{$_("footer.number", {
|
||||
total: data.total,
|
||||
since: date_from_ts(data.since),
|
||||
})}
|
||||
{#if data.total % 1000 === 0}
|
||||
<span class="wow">({$_("footer.wow")})</span>
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
footer {
|
||||
background: var(--glass);
|
||||
border-top: solid 2px var(--color);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
box-sizing: border-box;
|
||||
padding: 15px 40px;
|
||||
}
|
||||
|
||||
footer ul {
|
||||
list-style: none;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 5px;
|
||||
|
||||
font-size: var(--size-2);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
footer ul li a {
|
||||
text-decoration: none;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
footer ul li a:hover {
|
||||
color: var(--color);
|
||||
}
|
||||
|
||||
footer ul li:not(:last-of-type)::after {
|
||||
content: " / ";
|
||||
color: var(--white-3);
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
footer span {
|
||||
color: var(--white-2);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
footer .wow {
|
||||
color: var(--color);
|
||||
}
|
||||
</style>
|
@@ -1,24 +1,32 @@
|
||||
<script>
|
||||
import { api_urljoin } from "$lib/api.js";
|
||||
import { locale } from "$lib/locale.js";
|
||||
import api from "$lib/api.js";
|
||||
|
||||
export let desc, title;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>[ngn.tf] | {title}</title>
|
||||
<title>ngn.tf | {title}</title>
|
||||
|
||||
<!-- standart metadata tags -->
|
||||
|
||||
<meta name="description" content={desc} />
|
||||
<meta name="author" content="ngn" />
|
||||
<meta name="keywords" content="ngn,ngn13,ngn1,ngn.tf" />
|
||||
<meta name="keywords" content="ngn,ngn13,ngn.tf" />
|
||||
<meta name="color-scheme" content="only dark" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<meta property="og:title" content="[ngn.tf] | {title}" />
|
||||
<!-- open graph meta tags -->
|
||||
|
||||
<meta property="og:title" content="ngn.tf | {title}" />
|
||||
<meta property="og:description" content={desc} />
|
||||
|
||||
<!-- atom feed for the service updates -->
|
||||
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/atom+xml"
|
||||
href={api_urljoin("/news/en")}
|
||||
href={api.join("/news/" + $locale.code)}
|
||||
title="Service news and updates"
|
||||
/>
|
||||
</svelte:head>
|
@@ -1,22 +1,32 @@
|
||||
<script>
|
||||
import { browser } from "$app/environment";
|
||||
import { color } from "$lib/util.js";
|
||||
import { onMount } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { browser } from "$app/environment";
|
||||
|
||||
import { _ } from "$lib/locale.js";
|
||||
|
||||
export let picture = "";
|
||||
export let title = "";
|
||||
|
||||
let title_cur = title;
|
||||
let show_animation = false;
|
||||
let javascript = false;
|
||||
|
||||
// TODO: make the animation first delete and then type
|
||||
|
||||
// do the typing animation
|
||||
function animate(title) {
|
||||
if (!browser) return;
|
||||
// animation is displayed in the browser obv
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
// clear any previous timeouts
|
||||
let id = window.setTimeout(function () {}, 0);
|
||||
while (id--) {
|
||||
clearTimeout(id);
|
||||
}
|
||||
|
||||
while (id--) clearTimeout(id);
|
||||
|
||||
// reset the current title and add each letter with a timeout to give the
|
||||
// epic typing effect
|
||||
title_cur = "";
|
||||
|
||||
for (let i = 0; i < title.length; i++) {
|
||||
@@ -27,7 +37,7 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
show_animation = true;
|
||||
javascript = true;
|
||||
});
|
||||
|
||||
$: animate(title);
|
||||
@@ -35,20 +45,19 @@
|
||||
|
||||
<header>
|
||||
<div>
|
||||
{#if show_animation}
|
||||
<h1 class="title" style="color: var(--{color()})">{title_cur}</h1>
|
||||
<h1 class="cursor" style="color: var(--{color()})">_</h1>
|
||||
{#if javascript}
|
||||
<h1 class="title">{title_cur}</h1>
|
||||
<h1 class="cursor">_</h1>
|
||||
{:else}
|
||||
<h1 class="title" style="color: var(--{color()})">{title}</h1>
|
||||
<h1 class="title">{title}</h1>
|
||||
{/if}
|
||||
</div>
|
||||
<img src="/profile/{picture}.png" alt="" />
|
||||
<img src="/assets/{picture}.png" alt="" />
|
||||
</header>
|
||||
|
||||
<style>
|
||||
header {
|
||||
background: var(--background);
|
||||
background-size: 50%;
|
||||
background: var(--transparent);
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -62,7 +71,7 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: end;
|
||||
padding: 50px 50px 30px 50px;
|
||||
padding: 40px 40px 10px 40px;
|
||||
font-size: var(--size-6);
|
||||
font-family:
|
||||
Consolas,
|
||||
@@ -78,6 +87,10 @@
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
color: var(--color);
|
||||
}
|
||||
|
||||
header div .title {
|
||||
text-shadow: var(--text-shadow);
|
||||
overflow: hidden;
|
||||
@@ -90,10 +103,8 @@
|
||||
}
|
||||
|
||||
header img {
|
||||
width: 220px;
|
||||
padding: 50px 50px 0 50px;
|
||||
width: var(--profile-size);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 900px) {
|
34
app/src/components/language.svelte
Normal file
@@ -0,0 +1,34 @@
|
||||
<script>
|
||||
import { localizer, next } from "$lib/locale.js";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
// "show" is simply used to not display the locale button when javascript is
|
||||
// not enabled, bc it does not function without javascript
|
||||
let show = false;
|
||||
|
||||
onMount(() => {
|
||||
show = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if show}
|
||||
<!-- TODO: make this work without javascript -->
|
||||
<button on:click={() => localizer.switch()}>
|
||||
{$next.icon}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
button {
|
||||
background: none;
|
||||
color: var(--white-1);
|
||||
font-size: var(--size-4);
|
||||
outline: none;
|
||||
border: none;
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: var(--black-1);
|
||||
}
|
||||
</style>
|
46
app/src/components/navbar.svelte
Normal file
@@ -0,0 +1,46 @@
|
||||
<script>
|
||||
import Page from "$components/page.svelte";
|
||||
import Language from "$components/language.svelte";
|
||||
|
||||
import { _ } from "$lib/locale.js";
|
||||
</script>
|
||||
|
||||
<nav>
|
||||
<h3>ngn.tf</h3>
|
||||
<div>
|
||||
<Page link="/">{$_("navbar.home")}</Page>
|
||||
<Page link="/services">{$_("navbar.services")}</Page>
|
||||
<Page link="/donate">{$_("navbar.donate")}</Page>
|
||||
<Language />
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
nav {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
background: var(--glass);
|
||||
box-shadow: var(--box-shadow);
|
||||
border-bottom: solid 2px var(--color);
|
||||
|
||||
padding: 15px 40px;
|
||||
}
|
||||
|
||||
nav div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
justify-content: right;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
nav h3 {
|
||||
color: var(--color);
|
||||
font-family: var(--monospace);
|
||||
font-size: var(--size-4);
|
||||
font-weight: 900;
|
||||
}
|
||||
</style>
|
35
app/src/components/page.svelte
Normal file
@@ -0,0 +1,35 @@
|
||||
<script>
|
||||
import { click } from "$lib/util.js";
|
||||
import { page } from "$app/stores";
|
||||
|
||||
export let link;
|
||||
</script>
|
||||
|
||||
<a
|
||||
class={$page.url.pathname === link ? "active" : "inactive"}
|
||||
data-sveltekit-preload-data
|
||||
on:click={click}
|
||||
href={link}
|
||||
>
|
||||
<slot></slot>
|
||||
</a>
|
||||
|
||||
<style>
|
||||
a {
|
||||
font-weight: 600;
|
||||
font-size: var(--size-3);
|
||||
|
||||
color: var(--white-1);
|
||||
text-decoration: none;
|
||||
text-decoration-color: var(--color);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.active {
|
||||
color: var(--color);
|
||||
}
|
||||
</style>
|
138
app/src/components/service.svelte
Normal file
@@ -0,0 +1,138 @@
|
||||
<script>
|
||||
import { time_from_ts } from "$lib/util.js";
|
||||
import { _, locale } from "$lib/locale.js";
|
||||
|
||||
export let service = {};
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div class="info">
|
||||
<div>
|
||||
<h1>{service.name}</h1>
|
||||
<p>{service.desc[$locale.code]}</p>
|
||||
</div>
|
||||
<ul>
|
||||
<li><a href={service.clear}> Clear</a></li>
|
||||
<li><a href={service.onion}>TOR</a></li>
|
||||
<li><a href={service.i2p}> I2P</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="check">
|
||||
<h1>
|
||||
{$_("services.last", {
|
||||
time: time_from_ts(service.check_time),
|
||||
})}
|
||||
</h1>
|
||||
{#if service.check_res == 0}
|
||||
<span style="background: var(--white-2)">
|
||||
{$_("services.status.down")}
|
||||
</span>
|
||||
{:else if service.check_res == 1}
|
||||
<span style="background: var(--color)">
|
||||
{$_("services.status.up")}
|
||||
</span>
|
||||
{:else if service.check_res == 2}
|
||||
<span style="background: var(--color); filter: brightness(50%);">
|
||||
{$_("services.status.slow")}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
background: var(--black-2);
|
||||
border: solid 1px var(--black-3);
|
||||
text-align: left;
|
||||
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
main .info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
flex: 1;
|
||||
|
||||
color: var(--white-1);
|
||||
padding: 15px 18px;
|
||||
}
|
||||
|
||||
main .info div h1 {
|
||||
font-size: var(--size-4);
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
main .info div p {
|
||||
font-size: var(--size-2);
|
||||
color: var(--white-2);
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
main .info ul {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 5px;
|
||||
|
||||
text-align: right;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
main .info ul li:not(:last-of-type)::after {
|
||||
content: " / ";
|
||||
color: var(--white-3);
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
main .info li a {
|
||||
color: var(--color);
|
||||
text-decoration: none;
|
||||
|
||||
font-size: var(--size-2);
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
main .info li a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
main .info li a[href=""] {
|
||||
color: var(--white-3);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
main .info li a[href=""]:hover {
|
||||
text-decoration: none;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
main .check {
|
||||
border-top: solid 1px var(--black-3);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: var(--white-1);
|
||||
}
|
||||
|
||||
main .check h1 {
|
||||
font-size: var(--size-2);
|
||||
font-weight: 100;
|
||||
|
||||
color: var(--white-2);
|
||||
padding: 5px 18px;
|
||||
}
|
||||
|
||||
main .check span {
|
||||
font-size: var(--size-4);
|
||||
font-weight: 1000;
|
||||
|
||||
color: var(--black-1);
|
||||
padding: 5px 18px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
14
app/src/hooks.server.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
|
||||
/** @type {import('@sveltejs/kit').HandleServerError} */
|
||||
export async function handleError({ error, status }) {
|
||||
// for unknown routes, just redirect to /
|
||||
if (status === 404) {
|
||||
return redirect(303, "/");
|
||||
}
|
||||
|
||||
// for other errors, pass the message which will be used by the error page
|
||||
return {
|
||||
message: `${error}`,
|
||||
};
|
||||
}
|
@@ -1,51 +1,59 @@
|
||||
import { browser } from "$app/environment";
|
||||
import { urljoin } from "$lib/util.js";
|
||||
|
||||
const api_version = "v1";
|
||||
class API {
|
||||
constructor() {
|
||||
this.version = "v1";
|
||||
}
|
||||
|
||||
function api_urljoin(path = null, query = {}) {
|
||||
let api_url = "";
|
||||
// join given path and queries into an API URL
|
||||
join(path = null, query = {}) {
|
||||
let base = "";
|
||||
|
||||
if (browser) api_url = urljoin(import.meta.env.WEBSITE_API_PATH, api_version);
|
||||
else api_url = urljoin(import.meta.env.WEBSITE_API_URL, api_version);
|
||||
if (browser) {
|
||||
base = urljoin(import.meta.env.WEBSITE_API_PATH, this.version);
|
||||
} else {
|
||||
base = urljoin(import.meta.env.WEBSITE_API_URL, this.version);
|
||||
}
|
||||
|
||||
return urljoin(api_url, path, query);
|
||||
return urljoin(base, path, query);
|
||||
}
|
||||
|
||||
// check given JSON body for errors
|
||||
check_err(json) {
|
||||
if (!("error" in json))
|
||||
throw new Error('API response is missing the "error" key');
|
||||
|
||||
if (json["error"] != "")
|
||||
throw new Error(`API returned an error: ${json["error"]}`);
|
||||
|
||||
if (!("result" in json))
|
||||
throw new Error('API response is missing the "result" key');
|
||||
}
|
||||
|
||||
// make a HTTP GET request to the given URL
|
||||
async GET(fetch, url) {
|
||||
const res = await fetch(url);
|
||||
const json = await res.json();
|
||||
this.check_err(json);
|
||||
return json["result"];
|
||||
}
|
||||
|
||||
// get visitor metrics
|
||||
async metrics(fetch) {
|
||||
return await this.GET(fetch, this.join("/metrics"));
|
||||
}
|
||||
|
||||
// get service list
|
||||
async services(fetch) {
|
||||
return await this.GET(fetch, this.join("/services"));
|
||||
}
|
||||
|
||||
// get projects list
|
||||
async projects(fetch) {
|
||||
return await this.GET(fetch, this.join("/projects"));
|
||||
}
|
||||
}
|
||||
|
||||
function api_check_err(json) {
|
||||
if (!("error" in json))
|
||||
throw new Error('API response is missing the "error" key');
|
||||
|
||||
if (json["error"] != "")
|
||||
throw new Error(`API returned an error: ${json["error"]}`);
|
||||
|
||||
if (!("result" in json))
|
||||
throw new Error('API response is missing the "result" key');
|
||||
}
|
||||
|
||||
async function api_http_get(fetch, url) {
|
||||
const res = await fetch(url);
|
||||
const json = await res.json();
|
||||
api_check_err(json);
|
||||
return json["result"];
|
||||
}
|
||||
|
||||
async function api_get_metrics(fetch) {
|
||||
return await api_http_get(fetch, api_urljoin("/metrics"));
|
||||
}
|
||||
|
||||
async function api_get_services(fetch) {
|
||||
return await api_http_get(fetch, api_urljoin("/services"));
|
||||
}
|
||||
|
||||
async function api_get_projects(fetch) {
|
||||
return await api_http_get(fetch, api_urljoin("/projects"));
|
||||
}
|
||||
|
||||
export {
|
||||
api_version,
|
||||
api_urljoin,
|
||||
api_get_metrics,
|
||||
api_get_services,
|
||||
api_get_projects,
|
||||
};
|
||||
const api = new API();
|
||||
export default api;
|
||||
|
@@ -1,50 +0,0 @@
|
||||
<script>
|
||||
export let title;
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<h1 class="title">{title}</h1>
|
||||
<div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
flex: 1;
|
||||
flex-basis: 30%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main .title {
|
||||
font-family:
|
||||
Consolas,
|
||||
Monaco,
|
||||
Lucida Console,
|
||||
Liberation Mono,
|
||||
DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono,
|
||||
Courier New,
|
||||
monospace;
|
||||
color: var(--white-1);
|
||||
}
|
||||
|
||||
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>
|
@@ -1,3 +1,5 @@
|
||||
// TODO: clean this up like api.js
|
||||
|
||||
import { urljoin } from "$lib/util.js";
|
||||
|
||||
function doc_urljoin(path = null, query = {}) {
|
||||
|
@@ -1,62 +0,0 @@
|
||||
<script>
|
||||
import Link from "$lib/link.svelte";
|
||||
import { color } from "$lib/util.js";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
export let error = "";
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<h1 style="color: var(--{color()})">{$_("error.title")}</h1>
|
||||
<code>
|
||||
{#if error === ""}
|
||||
Unknown error
|
||||
{:else}
|
||||
{error}
|
||||
{/if}
|
||||
</code>
|
||||
<Link link={import.meta.env.WEBSITE_REPORT_URL}>
|
||||
{$_("error.report")}
|
||||
</Link>
|
||||
<img src="/profile/sad.png" alt="" />
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
position: fixed;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: end;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
padding: 50px;
|
||||
font-size: var(--size-4);
|
||||
|
||||
background: var(--background);
|
||||
background-size: 50%;
|
||||
}
|
||||
|
||||
main h1 {
|
||||
font-size: var(--size-6);
|
||||
}
|
||||
|
||||
main code {
|
||||
font-size: var(--size-4);
|
||||
color: var(--white-2);
|
||||
}
|
||||
|
||||
main img {
|
||||
width: var(--profile-size);
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
</style>
|
@@ -1,85 +0,0 @@
|
||||
<script>
|
||||
import { color, date_from_ts } from "$lib/util.js";
|
||||
import { api_get_metrics } from "$lib/api.js";
|
||||
import Link from "$lib/link.svelte";
|
||||
|
||||
import { onMount } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
let show_counter = false,
|
||||
data = {};
|
||||
|
||||
onMount(async () => {
|
||||
show_counter = true;
|
||||
data = await api_get_metrics(fetch);
|
||||
});
|
||||
</script>
|
||||
|
||||
<footer style="border-top: solid 2px var(--{color()});">
|
||||
<div class="links">
|
||||
<span>
|
||||
<Link link={import.meta.env.WEBSITE_SOURCE_URL} bold={true}
|
||||
>{$_("footer.source")}</Link
|
||||
>
|
||||
</span>
|
||||
<span>/</span>
|
||||
<span>
|
||||
<Link link="/doc/license" bold={true}>{$_("footer.license")}</Link>
|
||||
</span>
|
||||
<span>/</span>
|
||||
<span>
|
||||
<Link link="/doc/privacy" bold={true}>{$_("footer.privacy")}</Link>
|
||||
</span>
|
||||
</div>
|
||||
{#if show_counter}
|
||||
<span class="counter">
|
||||
{$_("footer.number", {
|
||||
values: {
|
||||
total: data.total,
|
||||
since: date_from_ts(data.since),
|
||||
},
|
||||
})}
|
||||
{#if data.number % 1000 == 0}
|
||||
<span style="color: var(--{color()})">({$_("footer.wow")})</span>
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="counter">{$_("footer.js")}</span>
|
||||
{/if}
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: var(--black-1);
|
||||
|
||||
box-sizing: border-box;
|
||||
padding: 20px 50px 20px 50px;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
font-size: var(--size-2);
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--white-2);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.counter {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.links {
|
||||
text-align: left;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 5px;
|
||||
}
|
||||
</style>
|
@@ -1,6 +0,0 @@
|
||||
<script>
|
||||
import { color } from "$lib/util.js";
|
||||
export let icon = "";
|
||||
</script>
|
||||
|
||||
<i style="color: var(--{color()});" class="nf {icon}"></i>
|
@@ -1,37 +0,0 @@
|
||||
<script>
|
||||
import Icon from "$lib/icon.svelte";
|
||||
import { color } from "$lib/util.js";
|
||||
|
||||
const default_color = "white-1";
|
||||
|
||||
export let active = false;
|
||||
export let highlight = true;
|
||||
export let link = "";
|
||||
export let icon = "";
|
||||
|
||||
let style = "";
|
||||
|
||||
if (highlight) style = `text-decoration-color: var(--${color()});`;
|
||||
|
||||
if (active) style += `color: var(--${color()});`;
|
||||
else style += `color: var(--${default_color});`;
|
||||
</script>
|
||||
|
||||
{#if icon != ""}
|
||||
<Icon {icon} />
|
||||
{/if}
|
||||
{#if highlight}
|
||||
<a data-sveltekit-preload-data {style} href={link}>
|
||||
<slot></slot>
|
||||
</a>
|
||||
{:else}
|
||||
<a data-sveltekit-preload-data {style} class="no-highlight" href={link}>
|
||||
<slot></slot>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.no-highlight:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
@@ -1,67 +1,225 @@
|
||||
import { init, locale, register, waitLocale } from "svelte-i18n";
|
||||
import { derived, get, writable } from "svelte/store";
|
||||
import { browser } from "$app/environment";
|
||||
import { get, writable } from "svelte/store";
|
||||
import yaml from "js-yaml";
|
||||
|
||||
const locale_default = "en";
|
||||
let locale_index = writable(0);
|
||||
let locale_list = [];
|
||||
// defines a single locale
|
||||
class Locale {
|
||||
constructor(code, icon) {
|
||||
// regex for "render"ing the locale
|
||||
this.ref_regex = /\[[0-9]*:.*?\]/gm;
|
||||
this.link_regex = /\[[^\]]*]\([^ ]*\)/gm;
|
||||
this.bold_regex = /\*\*.*?\*\*/gm;
|
||||
|
||||
function locale_setup() {
|
||||
// english
|
||||
register("en", () => import("../locales/en.json"));
|
||||
locale_list.push({ code: "en", name: "English", icon: "🇬🇧" });
|
||||
|
||||
// turkish
|
||||
register("tr", () => import("../locales/tr.json"));
|
||||
locale_list.push({ code: "tr", name: "Turkish", icon: "🇹🇷" });
|
||||
|
||||
init({
|
||||
fallbackLocale: locale_default,
|
||||
initialLocale: get(locale),
|
||||
});
|
||||
}
|
||||
|
||||
function locale_from_browser() {
|
||||
if (browser) return window.navigator.language.slice(0, 2).toLowerCase();
|
||||
else return locale_default;
|
||||
}
|
||||
|
||||
function locale_select(l = null) {
|
||||
if (l === null) {
|
||||
if (browser && null !== (l = localStorage.getItem("locale")))
|
||||
locale_select(l);
|
||||
else locale_select(locale_from_browser());
|
||||
return;
|
||||
this.code = code; // BCP 47 language tag
|
||||
this.icon = icon; // icon for the locale
|
||||
this.all = {}; // all the locales
|
||||
}
|
||||
|
||||
l = l.slice(0, 2);
|
||||
|
||||
for (let i = 0; i < locale_list.length; i++) {
|
||||
if (l !== locale_list[i].code) continue;
|
||||
|
||||
if (browser) localStorage.setItem("locale", l);
|
||||
|
||||
locale.set(l);
|
||||
locale_index.set(i);
|
||||
|
||||
return;
|
||||
// load the locale
|
||||
async load() {
|
||||
const text = await import(`../locales/${this.code}.yaml?raw`);
|
||||
this.all = yaml.load(text.default);
|
||||
}
|
||||
|
||||
locale.set(locale_default);
|
||||
locale_index.set(0);
|
||||
// renders a given locale using given values and links
|
||||
render(locale, values, links) {
|
||||
// get rid of newlines and remove trailing/repeating spaces and stuff
|
||||
locale = locale.replaceAll("\r", "").replaceAll("\n", " ");
|
||||
locale = locale.trim();
|
||||
|
||||
// find and replace all the values
|
||||
for (let name in values) {
|
||||
// values cannot be objects
|
||||
if (typeof values[name] === "object") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// "{name}" will be replaced by the values
|
||||
locale = locale.replaceAll(`{${name}}`, values[name]);
|
||||
}
|
||||
|
||||
// find all the references
|
||||
const refs = [...locale.matchAll(this.ref_regex)];
|
||||
|
||||
// and repalce them
|
||||
for (let i = 0; i < refs.length; i++) {
|
||||
let ref = refs[i][0];
|
||||
let name = ref.replaceAll(/(^\[|\]$)/g, "");
|
||||
let indx = parseInt(name.charAt(0)) - 1;
|
||||
name = name.substring(2);
|
||||
|
||||
// check the index
|
||||
if (indx >= links.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// replace the reference with a link
|
||||
locale = locale.replaceAll(ref, `<a href="${links[indx]}">${name}</a>`);
|
||||
}
|
||||
|
||||
// look for []() patterns, which are used adding links, kinda like markdown
|
||||
// but this is shittier
|
||||
links = [...locale.matchAll(this.link_regex)];
|
||||
|
||||
// replace the found links
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
let link = links[i][0];
|
||||
let name = link.match(/(?<=\[).*?(?=])/g);
|
||||
let url = link.match(/(?<=\()[^ ]*(?=\))/g);
|
||||
|
||||
// if we fail to extract the link name and/or URL, skip this match
|
||||
if (null === name || null === url) continue;
|
||||
|
||||
locale = locale.replaceAll(link, `<a href="${url[0]}">${name[0]}</a>`);
|
||||
}
|
||||
|
||||
// look for double stars which is used for bold text
|
||||
const bolds = [...locale.matchAll(this.bold_regex)];
|
||||
|
||||
// replace bold text with actual bold text
|
||||
for (let i = 0; i < bolds.length; i++) {
|
||||
let bold = bolds[i][0];
|
||||
let text = bold.match(/(?<=\*\*).*?(?=\*\*)/g);
|
||||
|
||||
// if we fail to extract the text content, skip this match
|
||||
if (null === text) continue;
|
||||
|
||||
locale = locale.replaceAll(bold, `<b>${text}</b>`);
|
||||
}
|
||||
|
||||
return locale;
|
||||
}
|
||||
|
||||
// resolve the given locale
|
||||
resolve(key, vars = {}) {
|
||||
let cur = this.all;
|
||||
let keys = key.split(".");
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
cur = cur[keys[i]];
|
||||
|
||||
if (cur === undefined) {
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
|
||||
// locale needs to be a string
|
||||
if (typeof cur !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// extract links from the vars
|
||||
let links = vars.links;
|
||||
delete vars.links;
|
||||
|
||||
return this.render(cur, vars, links);
|
||||
}
|
||||
}
|
||||
|
||||
async function locale_wait() {
|
||||
await waitLocale();
|
||||
// localizer stores and defines all the locales
|
||||
class Localizer {
|
||||
constructor() {
|
||||
// list of supported locales
|
||||
this.list = [
|
||||
new Locale("en", "🇬🇧"), // English
|
||||
new Locale("tr", "🇹🇷"), // Turkish
|
||||
];
|
||||
|
||||
this.current = writable(this.list[0]); // current locale
|
||||
this.next = writable(this.list[1]); // next locale
|
||||
this.fallback = this.list[0]; // fallback locale
|
||||
}
|
||||
|
||||
// get the name of a language using the current locale
|
||||
name(code) {
|
||||
return get(this.current).name(code);
|
||||
}
|
||||
|
||||
// get the current browser locale tag
|
||||
browser() {
|
||||
if (browser) {
|
||||
window.navigator.language.slice(0, 2).toLowerCase();
|
||||
} else {
|
||||
return this.fallback.code;
|
||||
}
|
||||
}
|
||||
|
||||
/* load all the locales, attempt to set the provided locale as the current
|
||||
* locale, if the provided locale is not available just use the default */
|
||||
async setup(code) {
|
||||
for (let i = 0; i < this.list.length; i++) {
|
||||
await this.list[i].load();
|
||||
}
|
||||
|
||||
// find the locale index by it's code
|
||||
let indx = this.list.findIndex(locale => {
|
||||
return locale.code === code;
|
||||
});
|
||||
|
||||
// check the index to see if we found the locale
|
||||
if (indx < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we actually found it, set it as the current locale
|
||||
this.current.set(this.list[indx]);
|
||||
|
||||
// set the next locale
|
||||
if (++indx >= this.list.length) {
|
||||
this.next.set(this.list[0]);
|
||||
} else {
|
||||
this.next.set(this.list[indx]);
|
||||
}
|
||||
}
|
||||
|
||||
// switch to the next locale
|
||||
switch() {
|
||||
// find the next locale's index
|
||||
let indx = this.list.findIndex(locale => {
|
||||
return locale === get(this.next);
|
||||
});
|
||||
|
||||
// set next locale as the new current locale
|
||||
this.current.set(get(this.next));
|
||||
document.cookie = `locale=${get(this.next).code}`;
|
||||
|
||||
// get the next locale based on the index
|
||||
if (indx === this.list.length - 1) {
|
||||
this.next.set(this.list[(indx = 0)]);
|
||||
} else {
|
||||
this.next.set(this.list[++indx]);
|
||||
}
|
||||
}
|
||||
|
||||
resolve(key, vars = {}) {
|
||||
// attempt to resolve the given key
|
||||
let res = get(this.current).resolve(key, vars);
|
||||
|
||||
if (res !== undefined) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// if we fail to resolve the key, try to resolve it using the fallback
|
||||
// locale, if that fails too then we are kinda fucked so yeah we just throw
|
||||
// an error
|
||||
if (get(this.current) === this.fallback) {
|
||||
throw new Error(`missing key: ${key}`);
|
||||
} else {
|
||||
return this.fallback.resolve(key, vars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
export const localizer = new Localizer(); // global localizer
|
||||
export const locale = localizer.current; // current locale
|
||||
export const next = localizer.next; // next locale
|
||||
|
||||
// resolve a given locale by it's key
|
||||
export const _ = derived(
|
||||
locale,
|
||||
locale_list,
|
||||
locale_index,
|
||||
locale_default,
|
||||
locale_setup,
|
||||
locale_wait,
|
||||
locale_select,
|
||||
locale_from_browser,
|
||||
};
|
||||
() =>
|
||||
(key, vars = {}) =>
|
||||
localizer.resolve(key, vars)
|
||||
);
|
||||
|
||||
export default localizer;
|
||||
|
@@ -1,42 +0,0 @@
|
||||
<script>
|
||||
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="/">{$_("navbar.home")}</NavbarLink>
|
||||
<NavbarLink link="/services">{$_("navbar.services")}</NavbarLink>
|
||||
<NavbarLink link="/donate">{$_("navbar.donate")}</NavbarLink>
|
||||
<NavbarSwitch />
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
nav {
|
||||
box-shadow: var(--box-shadow-1);
|
||||
background: var(--black-1);
|
||||
padding: 20px 30px 20px 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
justify-content: right;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: 900;
|
||||
font-size: var(--size-4);
|
||||
}
|
||||
</style>
|
@@ -1,26 +0,0 @@
|
||||
<script>
|
||||
import { color, click } from "$lib/util.js";
|
||||
import { page } from "$app/stores";
|
||||
|
||||
export let link;
|
||||
</script>
|
||||
|
||||
<a
|
||||
style="
|
||||
text-decoration-color: var(--{color()});
|
||||
{$page.url.pathname === link ? `color: var(--${color()});` : ''}
|
||||
"
|
||||
data-sveltekit-preload-data
|
||||
on:click={click}
|
||||
href={link}
|
||||
>
|
||||
<slot></slot>
|
||||
</a>
|
||||
|
||||
<style>
|
||||
a {
|
||||
font-weight: 900;
|
||||
font-size: var(--size-4);
|
||||
color: var(--white-1);
|
||||
}
|
||||
</style>
|
@@ -1,45 +0,0 @@
|
||||
<script>
|
||||
import { locale_list, locale_select, locale_index } from "$lib/locale.js";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let len = locale_list.length;
|
||||
let show = false;
|
||||
|
||||
function get_next(indx) {
|
||||
let new_indx = 0;
|
||||
|
||||
if (indx + 1 >= len) indx = 0;
|
||||
else new_indx = indx + 1;
|
||||
|
||||
return locale_list[new_indx];
|
||||
}
|
||||
|
||||
function next() {
|
||||
locale_select(get_next($locale_index).code);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
show = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if show}
|
||||
<button on:click={next}>
|
||||
{get_next($locale_index).icon}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<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>
|
@@ -1,118 +0,0 @@
|
||||
<script>
|
||||
import Icon from "$lib/icon.svelte";
|
||||
import Link from "$lib/link.svelte";
|
||||
|
||||
import { color, time_from_ts } from "$lib/util.js";
|
||||
import { locale, _ } from "svelte-i18n";
|
||||
|
||||
export let service = {};
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div class="info">
|
||||
<div class="title">
|
||||
<h1>{service.name}</h1>
|
||||
<p>{service.desc[$locale]}</p>
|
||||
</div>
|
||||
<div class="links">
|
||||
<Link highlight={false} link={service.clear}
|
||||
><Icon icon="nf-oct-link" /></Link
|
||||
>
|
||||
{#if service.onion != ""}
|
||||
<Link highlight={false} link={service.onion}
|
||||
><Icon icon="nf-linux-tor" /></Link
|
||||
>
|
||||
{/if}
|
||||
{#if service.i2p != ""}
|
||||
<Link highlight={false} link={service.i2p}
|
||||
><span style="color: var(--{color()})">I2P</span></Link
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="check">
|
||||
<h1>
|
||||
{$_("services.last", {
|
||||
values: { time: time_from_ts(service.check_time) },
|
||||
})}
|
||||
</h1>
|
||||
{#if service.check_res == 0}
|
||||
<span style="background: var(--white-2)">
|
||||
{$_("services.status.down")}
|
||||
</span>
|
||||
{:else if service.check_res == 1}
|
||||
<span style="background: var(--{color()})">
|
||||
{$_("services.status.up")}
|
||||
</span>
|
||||
{:else if service.check_res == 2}
|
||||
<span style="background: var(--{color()}); filter: brightness(50%);">
|
||||
{$_("services.status.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;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: var(--white-1);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
main .info .title h1 {
|
||||
font-size: var(--size-5);
|
||||
margin-bottom: 8px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
main .info .title p {
|
||||
font-size: var(--size-4);
|
||||
color: var(--white-2);
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
main .info .links {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
font-size: var(--size-6);
|
||||
}
|
||||
|
||||
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>
|
@@ -1,61 +1,60 @@
|
||||
import { locale_from_browser } from "$lib/locale.js";
|
||||
import { localizer } from "$lib/locale.js";
|
||||
|
||||
const colors = [
|
||||
"yellow",
|
||||
"cyan",
|
||||
"green",
|
||||
"pinkish",
|
||||
"red",
|
||||
// "blue" (looks kinda ass)
|
||||
];
|
||||
// colors defined in static/css/global.css
|
||||
const colors = ["yellow", "cyan", "green", "pinkish", "red", "blue"];
|
||||
|
||||
let colors_pos = -1;
|
||||
|
||||
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];
|
||||
// randomly select a color
|
||||
export function color() {
|
||||
return colors[Math.floor(Math.random() * colors.length)];
|
||||
}
|
||||
|
||||
function click() {
|
||||
// play a click sound
|
||||
export function click() {
|
||||
let audio = new Audio("/assets/click.wav");
|
||||
audio.play();
|
||||
}
|
||||
|
||||
function urljoin(url, path = null) {
|
||||
if (undefined === url || null === url) return;
|
||||
// join a given path to the URL
|
||||
export function urljoin(url, path = null) {
|
||||
if (null === path || path.length === 0) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (url[url.length - 1] != "/") url += "/";
|
||||
if (url[url.length - 1] != "/") {
|
||||
url += "/";
|
||||
}
|
||||
|
||||
if (path[0] === "/") {
|
||||
path = path.slice(1);
|
||||
}
|
||||
|
||||
if (null === path || "" === path) return url;
|
||||
if (path[0] === "/") return url + path.slice(1);
|
||||
return url + path;
|
||||
}
|
||||
|
||||
function time_from_ts(ts) {
|
||||
if (ts === 0 || ts === undefined) return;
|
||||
|
||||
let ts_date = new Date(ts * 1000);
|
||||
let ts_zone = ts_date.toString().match(/([A-Z]+[\+-][0-9]+)/)[1];
|
||||
|
||||
return (
|
||||
new Intl.DateTimeFormat(locale_from_browser(), {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
}).format(ts_date) + ` (${ts_zone})`
|
||||
);
|
||||
}
|
||||
|
||||
function date_from_ts(ts) {
|
||||
if (ts === 0 || ts === undefined) return;
|
||||
|
||||
return new Intl.DateTimeFormat(locale_from_browser(), {
|
||||
// convert Date() to readable date
|
||||
export function date(date) {
|
||||
return new Intl.DateTimeFormat(localizer.browser(), {
|
||||
month: "2-digit",
|
||||
year: "2-digit",
|
||||
day: "2-digit",
|
||||
}).format(new Date(ts * 1000));
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
export { color, click, urljoin, time_from_ts, date_from_ts };
|
||||
// convert timestamp to readable time
|
||||
export function time_from_ts(ts) {
|
||||
const date = new Date(ts * 1000);
|
||||
const zone = date.toString().match(/([A-Z]+[\+-][0-9]+)/)[1];
|
||||
|
||||
return (
|
||||
new Intl.DateTimeFormat(localizer.browser(), {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
}).format(date) + ` (${zone})`
|
||||
);
|
||||
}
|
||||
|
||||
// convert timestamp to readable date
|
||||
export function date_from_ts(ts) {
|
||||
return date(new Date(ts * 1000));
|
||||
}
|
||||
|
@@ -1,79 +0,0 @@
|
||||
{
|
||||
"navbar": {
|
||||
"home": "home",
|
||||
"services": "services",
|
||||
"donate": "donate"
|
||||
},
|
||||
"home": {
|
||||
"title": "hello world!",
|
||||
"welcome": {
|
||||
"title": "about",
|
||||
"desc": "Welcome to my website, I'm ngn",
|
||||
"whoami": "I'm a security, privacy and freedom advocate 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",
|
||||
"fix": "fixing 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": "I highly prefer email, you can send encrypted emails using my PGP key"
|
||||
},
|
||||
"services": {
|
||||
"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 an 1 Gbit interface",
|
||||
"security": "All use SSL encrypted connection and they respect your privacy and freedom",
|
||||
"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!"
|
||||
},
|
||||
"projects": {
|
||||
"title": "projects",
|
||||
"desc": "I mostly work on free software projects, here are some of projects that you might find interesting"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"title": "service status",
|
||||
"none": "No services found",
|
||||
"search": "Search for a service",
|
||||
"feed": "News and updates",
|
||||
"last": "Last checked at {time}",
|
||||
"status": {
|
||||
"up": "Up",
|
||||
"down": "Down",
|
||||
"slow": "Slow"
|
||||
}
|
||||
},
|
||||
"donate": {
|
||||
"title": "donate!",
|
||||
"info": "I spend a lot of time and money on different projects and maintaining different services.",
|
||||
"price": "I mostly pay for hosting and electricity. Which when added up costs around 550₺ per month (~$15 at the time of writing).",
|
||||
"details": "So even a small donation would be useful. And it would help me keep everything up and running.",
|
||||
"thanks": "Also huge thanks to all of you who have donated so far!",
|
||||
"table": {
|
||||
"platform": "Platform",
|
||||
"address": "Adress/Link"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"title": "something went wrong!",
|
||||
"report": "Report this issue"
|
||||
},
|
||||
"footer": {
|
||||
"source": "Source",
|
||||
"license": "License",
|
||||
"privacy": "Privacy",
|
||||
"number": "Visited {total} times since {since}",
|
||||
"wow": "wow!!",
|
||||
"js": "Enable javascript to display all the elements"
|
||||
}
|
||||
}
|
@@ -1,79 +0,0 @@
|
||||
{
|
||||
"navbar": {
|
||||
"home": "anasayfa",
|
||||
"services": "servisler",
|
||||
"donate": "bağış"
|
||||
},
|
||||
"home": {
|
||||
"title": "merhaba dünya!",
|
||||
"welcome": {
|
||||
"title": "hakkımda",
|
||||
"desc": "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": "iş",
|
||||
"desc": "Şuan bir işim yok, o yüzden zamanımın çoğunu şunlarla geçiriyorum:",
|
||||
"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": "iletişim",
|
||||
"desc": "Eğer benim ile iletişime geçmek istiyorsanız, işte bazı faydalı linkler",
|
||||
"prefer": "Email'i fazlasıyla tercih ediyorum, PGP anahtarım ile şifreli email'ler gönderebilirsiniz"
|
||||
},
|
||||
"services": {
|
||||
"title": "servisler",
|
||||
"desc": "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 1 Gbit ağ arayüzü üzerinden erişilebilir",
|
||||
"security": "Hepsi SSL şifreli bağlantı kullanıyor ve hepsi gizliliğinize ve özgürlüğünüze saygı gösteriyor",
|
||||
"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 ve diğer saçmalıklar yok",
|
||||
"link": "Tüm servisleri incele!"
|
||||
},
|
||||
"projects": {
|
||||
"title": "projeler",
|
||||
"desc": "Çoğunlukla özgür yazılım projeleri üzerinde çalışıyorum, işte ilginç bulabileceğiniz bazı projelerim"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"title": "servis durumu",
|
||||
"none": "Servis bulunamadı",
|
||||
"search": "Bir servisi ara",
|
||||
"feed": "Yenilikler ve güncellemeler",
|
||||
"last": "Son kontrol zamanı {time}",
|
||||
"status": {
|
||||
"up": "Çalışıyor",
|
||||
"down": "Kapalı",
|
||||
"slow": "Yavaş"
|
||||
}
|
||||
},
|
||||
"donate": {
|
||||
"title": "bağış yap!",
|
||||
"info": "Farklı projeler ve farklı servisleri yönetmek için oldukça zaman ve para harcıyorum.",
|
||||
"price": "Çoğunlukla hosting ve elektrik için ödeme yapıyorum. Bunlar eklendiği zaman aylık 550₺ civarı bir miktar oluyor (yazdığım sırada ~15$).",
|
||||
"details": "Bu sebepten küçük bir bağış bile oldukça faydalı olacaktır. Ve herşeyi açık ve çalışmakta tutmama yardımcı olacaktır.",
|
||||
"thanks": "Ayrıca şuana kadar bağışta bulunan herkese çok teşekkür ederim!",
|
||||
"table": {
|
||||
"platform": "Platform",
|
||||
"address": "Adres/Bağlantı"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"title": "birşeyler yanlış gitti!",
|
||||
"report": "Bu sorunu raporlayın"
|
||||
},
|
||||
"footer": {
|
||||
"source": "Kaynak",
|
||||
"license": "Lisans",
|
||||
"privacy": "Gizlilik",
|
||||
"number": "{since} tarihinden beri {total} kez ziyaret edildi",
|
||||
"wow": "vay be!!",
|
||||
"js": "Tüm elementleri görüntelemek için javascript'i açın"
|
||||
}
|
||||
}
|
@@ -1,8 +1,51 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
onMount(() => {
|
||||
goto("/");
|
||||
});
|
||||
import { page } from "$app/state";
|
||||
import { _ } from "$lib/locale.js";
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<h1>{$_("error.title")}</h1>
|
||||
<code>{page.error.message}</code>
|
||||
<a href={import.meta.env.WEBSITE_REPORT_URL}>{$_("error.report")}</a>
|
||||
<img src="/assets/sad.png" alt="" />
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
background: var(--transparent);
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: end;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
padding: 50px;
|
||||
font-size: var(--size-4);
|
||||
}
|
||||
|
||||
main h1 {
|
||||
font-size: var(--size-6);
|
||||
text-shadow: var(--text-shadow);
|
||||
color: var(--color);
|
||||
}
|
||||
|
||||
main code {
|
||||
font-size: var(--size-4);
|
||||
color: var(--white-2);
|
||||
}
|
||||
|
||||
main img {
|
||||
width: 250px;
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { locale_setup, locale_wait } from "$lib/locale.js";
|
||||
import localizer from "$lib/locale.js";
|
||||
|
||||
export async function load() {
|
||||
locale_setup();
|
||||
await locale_wait();
|
||||
export async function load({ data }) {
|
||||
await localizer.setup(data.locale);
|
||||
return data;
|
||||
}
|
||||
|
22
app/src/routes/+layout.server.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { color } from "../lib/util.js";
|
||||
|
||||
export async function load({ cookies, request }) {
|
||||
// attempt get the preferred locale from cookies
|
||||
let locale = cookies.get("locale");
|
||||
|
||||
/* if that doesn't work, try the accept-language header, and update the
|
||||
* cookies respectively */
|
||||
if (!locale) {
|
||||
locale = request.headers.get("accept-language")?.split(",")[0];
|
||||
cookies.set("locale", locale, { path: "/" });
|
||||
}
|
||||
|
||||
return {
|
||||
// the locale we detected
|
||||
locale: locale,
|
||||
|
||||
/* color is randomly picked on the server and passed via data to the client
|
||||
* so both the client and the server will have the same color */
|
||||
color: color(),
|
||||
};
|
||||
}
|
@@ -1,23 +1,22 @@
|
||||
<script>
|
||||
import Navbar from "$lib/navbar.svelte";
|
||||
import Footer from "$lib/footer.svelte";
|
||||
import { browser } from "$app/environment";
|
||||
|
||||
import { locale_select } from "$lib/locale.js";
|
||||
import { onMount } from "svelte";
|
||||
import { color } from "$lib/util.js";
|
||||
import api from "$lib/api.js";
|
||||
|
||||
let { children } = $props();
|
||||
let { data, children } = $props();
|
||||
|
||||
onMount(() => {
|
||||
locale_select();
|
||||
});
|
||||
if (browser) {
|
||||
// set the current app and the API version
|
||||
window._version = {
|
||||
app: pkg.version,
|
||||
api: api.version,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<Navbar />
|
||||
<div class="content">
|
||||
{@render children()}
|
||||
</div>
|
||||
<Footer />
|
||||
<main style="--color: var(--{data.color})">
|
||||
{@render children()}
|
||||
</main>
|
||||
|
||||
<style>
|
||||
@@ -28,9 +27,4 @@
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: var(--black-1);
|
||||
flex-grow: 1;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,15 +1,9 @@
|
||||
import { api_get_projects } from "$lib/api.js";
|
||||
import api from "$lib/api.js";
|
||||
|
||||
export async function load({ fetch }) {
|
||||
try {
|
||||
let projects = await api_get_projects(fetch);
|
||||
return {
|
||||
projects: null === projects ? [] : projects,
|
||||
error: "",
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
error: err.toString(),
|
||||
};
|
||||
}
|
||||
let projects = await api.projects(fetch);
|
||||
return {
|
||||
projects: null === projects ? [] : projects,
|
||||
error: "",
|
||||
};
|
||||
}
|
||||
|
@@ -1,133 +1,94 @@
|
||||
<script>
|
||||
import { api_version } from "$lib/api.js";
|
||||
import Header from "$lib/header.svelte";
|
||||
import Error from "$lib/error.svelte";
|
||||
import Head from "$lib/head.svelte";
|
||||
import Card from "$lib/card.svelte";
|
||||
import Link from "$lib/link.svelte";
|
||||
import Navbar from "$components/navbar.svelte";
|
||||
import Footer from "$components/footer.svelte";
|
||||
import Header from "$components/header.svelte";
|
||||
import Head from "$components/head.svelte";
|
||||
import Card from "$components/card.svelte";
|
||||
|
||||
import { browser } from "$app/environment";
|
||||
import { _, locale } from "svelte-i18n";
|
||||
import { color } from "$lib/util.js";
|
||||
import { locale, _ } from "$lib/locale.js";
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
if (browser) {
|
||||
window._version = {};
|
||||
window._version.app = pkg.version;
|
||||
window._version.api = api_version;
|
||||
// return list of projects that have decriptions for the given locale
|
||||
function projects() {
|
||||
return data.projects.filter(p => {
|
||||
return (
|
||||
p.desc[$locale.code] !== "" &&
|
||||
p.desc[$locale.code] !== null &&
|
||||
p.desc[$locale.code] !== undefined
|
||||
);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<Head title="home" desc="home page of my personal website" />
|
||||
<Navbar />
|
||||
<Header picture="tired" title={$_("home.title")} />
|
||||
<main>
|
||||
<!-- welcome -->
|
||||
<Card>
|
||||
<p>{@html $_("home.welcome.desc")}</p>
|
||||
<br />
|
||||
<p>{$_("home.welcome.thanks")}</p>
|
||||
</Card>
|
||||
|
||||
{#if data.error.length !== 0}
|
||||
<Error error={data.error} />
|
||||
{:else}
|
||||
<main>
|
||||
<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.fix")}</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>
|
||||
<!-- projects -->
|
||||
<Card title={$_("home.projects.title")} id="projects">
|
||||
<p>{$_("home.projects.desc")}:</p>
|
||||
<br />
|
||||
<ul>
|
||||
{#each projects() as project}
|
||||
<li class="project">
|
||||
<a href={project.url}>{project.name}</a>: {project.desc[$locale.code]}
|
||||
</li>
|
||||
<li>
|
||||
<Link icon="nf-md-email" link="mailto:ngn@ngn.tf">Email</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link icon="nf-md-mastodon" link="https://defcon.social/@ngn"
|
||||
>Mastodon</Link
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<span>
|
||||
{$_("home.links.prefer")}
|
||||
</span>
|
||||
</Card>
|
||||
<Card title={$_("home.services.title")}>
|
||||
<span>
|
||||
{$_("home.services.desc")}:
|
||||
</span>
|
||||
<ul>
|
||||
<li>
|
||||
<i style="color: var(--{color()});" class="nf nf-md-speedometer_slow"
|
||||
></i>
|
||||
{$_("home.services.speed")}
|
||||
</li>
|
||||
<li>
|
||||
<i style="color: var(--{color()});" class="nf nf-fa-lock"></i>
|
||||
{$_("home.services.security")}
|
||||
</li>
|
||||
<li>
|
||||
<i style="color: var(--{color()});" class="nf nf-fa-network_wired"
|
||||
></i>
|
||||
{$_("home.services.privacy")}
|
||||
</li>
|
||||
<li>
|
||||
<i style="color: var(--{color()});" class="nf nf-md-eye_off"></i>
|
||||
{$_("home.services.bullshit")}
|
||||
</li>
|
||||
</ul>
|
||||
<Link link="/services">{$_("home.services.link")}</Link>
|
||||
</Card>
|
||||
<Card title={$_("home.projects.title")}>
|
||||
<span>
|
||||
{$_("home.projects.desc")}:
|
||||
</span>
|
||||
{#if data.error.length === 0}
|
||||
<ul>
|
||||
{#each data.projects.filter((p) => {
|
||||
return p.desc[$locale] !== "" && p.desc[$locale] !== null && p.desc[$locale] !== undefined;
|
||||
}) as project}
|
||||
<li>
|
||||
<Link active={true} link={project.url}>{project.name}</Link>:
|
||||
{project.desc[$locale]}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</Card>
|
||||
</main>
|
||||
{/if}
|
||||
{/each}
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
<!-- services -->
|
||||
<Card title={$_("home.services.title")} id="services">
|
||||
<p>{$_("home.services.desc")}:</p>
|
||||
<br />
|
||||
<ul>
|
||||
<li>{@html $_("home.services.speed")}</li>
|
||||
<li>{@html $_("home.services.security")}</li>
|
||||
<li>{@html $_("home.services.blocks")}</li>
|
||||
<li>{@html $_("home.services.bullshit")}</li>
|
||||
</ul>
|
||||
<br />
|
||||
<a class="services" href="/services" data-sveltekit-preload-data>
|
||||
{$_("home.services.link")}
|
||||
</a>
|
||||
</Card>
|
||||
|
||||
<!-- contact -->
|
||||
<Card title={$_("home.contact.title")} id="contact">
|
||||
<p>{@html $_("home.contact.desc")}</p>
|
||||
<br />
|
||||
<p>{@html $_("home.contact.mastodon")}</p>
|
||||
</Card>
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
<style>
|
||||
main {
|
||||
background: var(--black-1);
|
||||
flex: 1;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
padding: 50px;
|
||||
gap: 28px;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 900px) {
|
||||
main {
|
||||
flex-direction: column;
|
||||
}
|
||||
.services {
|
||||
color: var(--color);
|
||||
text-decoration-color: var(--color);
|
||||
}
|
||||
|
||||
.project a {
|
||||
color: var(--color);
|
||||
text-decoration-color: var(--color);
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,12 +1,8 @@
|
||||
import { doc_get } from "$lib/doc";
|
||||
|
||||
export async function load({ fetch, params }) {
|
||||
try {
|
||||
return {
|
||||
doc: await doc_get(fetch, params.name),
|
||||
error: "",
|
||||
};
|
||||
} catch (err) {
|
||||
return { error: err.toString() };
|
||||
}
|
||||
return {
|
||||
doc: await doc_get(fetch, params.name),
|
||||
error: "",
|
||||
};
|
||||
}
|
||||
|
@@ -1,50 +1,41 @@
|
||||
<script>
|
||||
import Header from "$lib/header.svelte";
|
||||
import Error from "$lib/error.svelte";
|
||||
import Head from "$lib/head.svelte";
|
||||
import Navbar from "$components/navbar.svelte";
|
||||
import Footer from "$components/footer.svelte";
|
||||
import Header from "$components/header.svelte";
|
||||
import Head from "$components/head.svelte";
|
||||
|
||||
import { locale, _ } from "$lib/locale.js";
|
||||
|
||||
import { locale, _ } from "svelte-i18n";
|
||||
import { goto } from "$app/navigation";
|
||||
import { color } from "$lib/util.js";
|
||||
import DOMPurify from "dompurify";
|
||||
import { onMount } from "svelte";
|
||||
import { marked } from "marked";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let { data } = $props();
|
||||
marked.use({ breaks: true });
|
||||
marked.use();
|
||||
|
||||
onMount(async () => {
|
||||
for (let key in data.doc)
|
||||
onMount(() => {
|
||||
for (let key in data.doc) {
|
||||
data.doc[key]["content"] = DOMPurify.sanitize(data.doc[key]["content"]);
|
||||
|
||||
if (undefined !== data.error && data.error.includes("not found")) goto("/");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Head title="documentation" desc="website and API documentation" />
|
||||
<Header picture="reader" title={data.doc[$locale].title} />
|
||||
|
||||
{#if data.error.length !== 0}
|
||||
{#if !data.error.includes("not found")}
|
||||
<Error error={data.error} />
|
||||
{/if}
|
||||
{:else}
|
||||
<main>
|
||||
<div class="markdown-body" style="--link-color: var(--{color()})">
|
||||
{@html marked.parse(data.doc[$locale].content)}
|
||||
</div>
|
||||
</main>
|
||||
{/if}
|
||||
<Navbar />
|
||||
<Header picture="reader" title={data.doc[$locale.code].title} />
|
||||
<main>
|
||||
<div class="markdown-body">
|
||||
{@html marked.parse(data.doc[$locale.code].content)}
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
<style>
|
||||
@import "/css/markdown.css";
|
||||
|
||||
main {
|
||||
padding: 50px;
|
||||
background: var(--black-1);
|
||||
padding: 40px;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
main .markdown-body :global(a) {
|
||||
color: var(--link-color);
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,93 +1,43 @@
|
||||
<script>
|
||||
import Header from "$lib/header.svelte";
|
||||
import Head from "$lib/head.svelte";
|
||||
import Icon from "$lib/icon.svelte";
|
||||
import Navbar from "$components/navbar.svelte";
|
||||
import Footer from "$components/footer.svelte";
|
||||
import Header from "$components/header.svelte";
|
||||
import Head from "$components/head.svelte";
|
||||
import Card from "$components/card.svelte";
|
||||
|
||||
import { color } from "$lib/util.js";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { _ } from "$lib/locale.js";
|
||||
</script>
|
||||
|
||||
<Head title="donate" desc="give me all of your life savings" />
|
||||
<Navbar />
|
||||
<Header picture="money" title={$_("donate.title")} />
|
||||
|
||||
<main>
|
||||
<span> </span>
|
||||
<span>
|
||||
{$_("donate.info")}
|
||||
{$_("donate.price")}
|
||||
</span>
|
||||
<br />
|
||||
<br />
|
||||
<span>
|
||||
{$_("donate.details")}
|
||||
</span>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="color: var(--{color()})">{$_("donate.table.platform")}</th>
|
||||
<th style="color: var(--{color()})">{$_("donate.table.address")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Icon icon="nf-fa-monero" />
|
||||
Monero (XMR)
|
||||
</td>
|
||||
<td>
|
||||
<code>
|
||||
46q7G7u7cmASvJm7AmrhmNg6ctS77mYMmDAy1QxpDn5w57xV3GUY5za4ZPZHAjqaXdfS5YRWm4AVj5UArLDA1retRkJp47F
|
||||
</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<span>
|
||||
{$_("donate.thanks")}
|
||||
</span>
|
||||
<Card>
|
||||
<p>{@html $_("donate.desc")}</p>
|
||||
<br />
|
||||
<p>{@html $_("donate.info")}:</p>
|
||||
<div>
|
||||
<code>
|
||||
46q7G7u7cmASvJm7AmrhmNg6ctS77mYMmDAy1QxpDn5w57xV3GUY5za4ZPZHAjqaXdfS5YRWm4AVj5UArLDA1retRkJp47F
|
||||
</code>
|
||||
</div>
|
||||
<p>{$_("donate.thanks")}</p>
|
||||
</Card>
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
<style>
|
||||
main {
|
||||
padding: 50px;
|
||||
background: var(--black-1);
|
||||
flex: 1;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
main span {
|
||||
font-size: var(--size-4);
|
||||
color: var(--white-1);
|
||||
}
|
||||
|
||||
table {
|
||||
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,
|
||||
th,
|
||||
td {
|
||||
color: white;
|
||||
background: var(--dark-two);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
font-size: var(--size-4);
|
||||
border: solid 1px var(--black-4);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: 1000;
|
||||
}
|
||||
|
||||
td {
|
||||
color: var(--white-2);
|
||||
font-weight: 400;
|
||||
main div {
|
||||
background: var(--black-2);
|
||||
border: solid 1px var(--black-3);
|
||||
padding: 6px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
code {
|
||||
|
@@ -1,15 +1,9 @@
|
||||
import { api_get_services } from "$lib/api.js";
|
||||
import api from "$lib/api.js";
|
||||
|
||||
export async function load({ fetch }) {
|
||||
try {
|
||||
let services = await api_get_services(fetch);
|
||||
return {
|
||||
services: null === services ? [] : services,
|
||||
error: "",
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
error: err.toString(),
|
||||
};
|
||||
}
|
||||
let services = await api.services(fetch);
|
||||
return {
|
||||
services: null === services ? [] : services,
|
||||
error: "",
|
||||
};
|
||||
}
|
||||
|
@@ -1,110 +1,65 @@
|
||||
<script>
|
||||
import Service from "$lib/service.svelte";
|
||||
import Header from "$lib/header.svelte";
|
||||
import Error from "$lib/error.svelte";
|
||||
import Link from "$lib/link.svelte";
|
||||
import Head from "$lib/head.svelte";
|
||||
|
||||
import { api_urljoin } from "$lib/api.js";
|
||||
import { locale, _ } from "svelte-i18n";
|
||||
import { onMount } from "svelte";
|
||||
import Navbar from "$components/navbar.svelte";
|
||||
import Footer from "$components/footer.svelte";
|
||||
import Header from "$components/header.svelte";
|
||||
import Service from "$components/service.svelte";
|
||||
import Head from "$components/head.svelte";
|
||||
import Card from "$components/card.svelte";
|
||||
|
||||
import { locale, _ } from "$lib/locale.js";
|
||||
import api from "$lib/api.js";
|
||||
let { data } = $props();
|
||||
let services = $state(data.services);
|
||||
let show_input = $state(false);
|
||||
|
||||
function change(input) {
|
||||
let value = input.target.value.toLowerCase();
|
||||
services = [];
|
||||
|
||||
if (value === "") {
|
||||
services = data.services;
|
||||
return;
|
||||
}
|
||||
|
||||
data.services.forEach((s) => {
|
||||
if (s.name.toLowerCase().includes(value)) services.push(s);
|
||||
else if (s.desc[$locale].toLowerCase().includes(value)) services.push(s);
|
||||
});
|
||||
}
|
||||
|
||||
function get_services() {
|
||||
return services.filter((s) => {
|
||||
return (
|
||||
s.desc[$locale] !== "" &&
|
||||
s.desc[$locale] !== null &&
|
||||
s.desc[$locale] !== undefined
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
show_input = true;
|
||||
});
|
||||
// filtered list of services that have descriptions for the current locale
|
||||
let services = $derived(
|
||||
data.services.filter(
|
||||
s =>
|
||||
s.desc[$locale.code] !== "" &&
|
||||
s.desc[$locale.code] !== null &&
|
||||
s.desc[$locale.code] !== undefined
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
<Head title="services" desc="my self-hosted services and projects" />
|
||||
<Navbar />
|
||||
<Header picture="cool" title={$_("services.title")} />
|
||||
|
||||
{#if data.error.length !== 0}
|
||||
<Error error={data.error} />
|
||||
{:else}
|
||||
<main>
|
||||
<div class="title">
|
||||
{#if show_input}
|
||||
<input
|
||||
oninput={change}
|
||||
type="text"
|
||||
placeholder={$_("services.search")}
|
||||
/>
|
||||
{/if}
|
||||
<div>
|
||||
<Link icon="nf-fa-feed" link={api_urljoin("/news/" + $locale)}
|
||||
>{$_("services.feed")}</Link
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="services">
|
||||
{#if get_services().length == 0}
|
||||
<h3 class="none">{$_("services.none")}</h3>
|
||||
{:else}
|
||||
{#each get_services() as service}
|
||||
<Service {service} />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
{/if}
|
||||
<main>
|
||||
<Card>
|
||||
<p>{@html $_("services.desc")}</p>
|
||||
<br />
|
||||
<p>
|
||||
{@html $_("services.feed", {
|
||||
links: [api.join("/news/" + $locale.code)],
|
||||
})}
|
||||
</p>
|
||||
</Card>
|
||||
<div class="services">
|
||||
{#if services.length === 0}
|
||||
<h3>{$_("services.none")}</h3>
|
||||
{:else}
|
||||
{#each services as service}
|
||||
<Service {service} />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
<style>
|
||||
main {
|
||||
padding: 50px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
main .title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main .none {
|
||||
color: var(--white-3);
|
||||
background: var(--black-1);
|
||||
flex: 1;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
main .services {
|
||||
margin-top: 20px;
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
gap: 28px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
main .services {
|
||||
flex-direction: column;
|
||||
}
|
||||
color: var(--white-3);
|
||||
}
|
||||
</style>
|
||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
@@ -1,11 +0,0 @@
|
||||
@keyframes blink {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes cursor {
|
||||
to {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
@font-face {
|
||||
font-family: "Ubuntu";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("/assets/ubuntu.woff2") format("woff2");
|
||||
}
|
@@ -1,32 +1,26 @@
|
||||
/*
|
||||
|
||||
* animations.css: stuff like the cursor animation
|
||||
* webfont.css: webfont dumped from https://www.nerdfonts.com/assets/css/webfont.css
|
||||
* font.css: the main font (Ubuntu)
|
||||
|
||||
*/
|
||||
@import "./animations.css";
|
||||
@import "./nerdfonts.css";
|
||||
@import "./font.css";
|
||||
/* global CSS file, imported in every page, defines commonly used color, effects
|
||||
* etc. also defines styles for commonly used elements */
|
||||
|
||||
:root {
|
||||
/* randomly selected colors */
|
||||
--yellow: #d3b910;
|
||||
--cyan: #0dd2e8;
|
||||
--green: #06e00a;
|
||||
--pinkish: #d506e0;
|
||||
--red: #e8180d;
|
||||
--blue: #2036f9;
|
||||
--blue: #3768fc;
|
||||
|
||||
/* white tones */
|
||||
--white-1: #ffffff;
|
||||
--white-2: #bfbfbf;
|
||||
--white-3: #5f5f5f;
|
||||
--white-4: #0f0f0f;
|
||||
|
||||
/* black tones */
|
||||
--black-1: #000000;
|
||||
--black-2: #050505;
|
||||
--black-3: #111111;
|
||||
--black-4: #3a3b3c;
|
||||
--black-2: #111;
|
||||
--black-3: #3a3b3c;
|
||||
|
||||
/* different sizes */
|
||||
--size-1: 8px;
|
||||
--size-2: 16px;
|
||||
--size-3: 18px;
|
||||
@@ -34,17 +28,29 @@
|
||||
--size-5: 24px;
|
||||
--size-6: 30px;
|
||||
|
||||
--box-shadow-1:
|
||||
rgba(20, 20, 20, 0.19) 0px 10px 20px, rgba(30, 30, 30, 0.23) 0px 6px 6px;
|
||||
--box-shadow-2:
|
||||
rgba(0, 0, 0, 0.35) 0px 30px 60px -12px inset,
|
||||
rgba(20, 20, 20, 0.3) 0px 18px 36px -18px inset;
|
||||
/* shadows */
|
||||
--box-shadow: rgb(38, 57, 77) 0px 20px 30px -10px;
|
||||
--text-shadow: 0 1px 5px rgba(255, 255, 255, 0.15);
|
||||
|
||||
--text-shadow: 3px 2px 8px rgba(50, 50, 50, 0.8);
|
||||
--background:
|
||||
linear-gradient(rgba(11, 11, 11, 0.808), rgba(1, 1, 1, 0.96)),
|
||||
url("/assets/banner.png");
|
||||
--profile-size: 220px;
|
||||
/* backgrounds */
|
||||
--transparent: linear-gradient(rgba(11, 11, 11, 0.808), rgba(1, 1, 1, 0.96));
|
||||
--glass: rgba(1, 1, 1, 0.93);
|
||||
|
||||
/* fonts */
|
||||
--monospace:
|
||||
ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono,
|
||||
monospace;
|
||||
--sans-serif:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica,
|
||||
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||
--emoji: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
/* cursor blink animation */
|
||||
@keyframes blink {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -57,11 +63,14 @@ html {
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--black-1);
|
||||
font-family: "Ubuntu", sans-serif;
|
||||
font-family: var(--sans-serif);
|
||||
background-image: url("/assets/banner.png");
|
||||
overflow-x: hidden;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
|
||||
line-height: 1.5;
|
||||
font-size: var(--size-3);
|
||||
}
|
||||
|
||||
::selection {
|
||||
@@ -84,36 +93,24 @@ body {
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 900;
|
||||
color: var(--white-1);
|
||||
text-decoration: none;
|
||||
color: var(--white-2);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--white-2);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--white-1);
|
||||
text-shadow: var(--text-shadow);
|
||||
}
|
||||
|
||||
i .nf {
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: inside;
|
||||
margin: 12px 0 12px 0;
|
||||
}
|
||||
|
||||
li + li {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
background: var(--black-3);
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: var(--size-4);
|
||||
padding: 10px;
|
||||
border: solid 1px var(--black-4);
|
||||
color: var(--white-1);
|
||||
br {
|
||||
display: block;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
@@ -1,16 +1,21 @@
|
||||
/* modified version of github-markdown-css, the dark version
|
||||
* licensed under MIT, see: https://github.com/sindresorhus/github-markdown-css
|
||||
*/
|
||||
|
||||
.markdown-body {
|
||||
color-scheme: dark;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
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-size: 16px;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
color: var(--white-2);
|
||||
}
|
||||
|
||||
.markdown-body br {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown-body .octicon {
|
||||
@@ -61,10 +66,6 @@
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.markdown-body a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.markdown-body abbr[title] {
|
||||
border-bottom: none;
|
||||
text-decoration: underline dotted;
|
||||
@@ -82,9 +83,9 @@
|
||||
.markdown-body h1 {
|
||||
margin: 0.67em 0;
|
||||
font-weight: 600;
|
||||
padding-bottom: 0.3em;
|
||||
padding-bottom: 5px;
|
||||
font-size: 2em;
|
||||
border-bottom: 1px solid #21262d;
|
||||
border-bottom: 1px solid var(--black-3);
|
||||
}
|
||||
|
||||
.markdown-body mark {
|
||||
@@ -287,17 +288,18 @@
|
||||
.markdown-body h4,
|
||||
.markdown-body h5,
|
||||
.markdown-body h6 {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
color: var(--white-1);
|
||||
margin-top: 30px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
font-weight: 600;
|
||||
padding-bottom: 0.3em;
|
||||
padding-bottom: 5px;
|
||||
font-size: 1.5em;
|
||||
border-bottom: 1px solid #21262d;
|
||||
border-bottom: 1px solid var(--black-3);
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
@@ -359,28 +361,14 @@
|
||||
.markdown-body tt,
|
||||
.markdown-body code,
|
||||
.markdown-body samp {
|
||||
font-family:
|
||||
ui-monospace,
|
||||
SFMono-Regular,
|
||||
SF Mono,
|
||||
Menlo,
|
||||
Consolas,
|
||||
Liberation Mono,
|
||||
monospace;
|
||||
font-family: var(--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: var(--monospace);
|
||||
font-size: 12px;
|
||||
word-wrap: normal;
|
||||
}
|
||||
@@ -721,7 +709,7 @@
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
white-space: break-spaces;
|
||||
background: var(--black-3);
|
||||
background: var(--black-2);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@@ -762,11 +750,13 @@
|
||||
|
||||
.markdown-body .highlight pre,
|
||||
.markdown-body pre {
|
||||
padding: 16px;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
background-color: var(--black-3);
|
||||
|
||||
background-color: var(--black-2);
|
||||
border: solid 1px var(--black-3);
|
||||
}
|
||||
|
||||
.markdown-body pre code,
|
||||
@@ -980,7 +970,7 @@
|
||||
.markdown-body g-emoji {
|
||||
display: inline-block;
|
||||
min-width: 1ch;
|
||||
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-family: var(--emoji);
|
||||
font-size: 1em;
|
||||
font-style: normal !important;
|
||||
font-weight: 400;
|
||||
|
@@ -1,14 +1,10 @@
|
||||
//import adapter from '@sveltejs/adapter-auto';
|
||||
import adapter from "@sveltejs/adapter-node";
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
},
|
||||
onwarn: (warning, handler) => {
|
||||
if (warning.code === "a11y-click-events-have-key-events") return;
|
||||
handler(warning);
|
||||
alias: { $components: "src/components" },
|
||||
},
|
||||
};
|
||||
|
||||
|
@@ -22,7 +22,7 @@ function env_from(prefix, object) {
|
||||
|
||||
const default_env = {
|
||||
source_url: "https://git.ngn.tf/ngn/website",
|
||||
report_url: "https://git.ngn.tf/ngn/website/issues",
|
||||
report_url: "mailto:ngn@ngn.tf",
|
||||
doc_url: "http://localhost:7003",
|
||||
api: {
|
||||
url: "http://localhost:7002",
|
||||
|