Merge pull request #3 from ngn13/svelte

sveltekit rewrite
This commit is contained in:
ngn 2023-08-21 08:30:17 +03:00 committed by GitHub
commit c8c5c78912
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
85 changed files with 2644 additions and 28131 deletions

View File

@ -1,13 +0,0 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

98
.gitignore vendored
View File

@ -1,90 +1,10 @@
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# macOS
.DS_Store
# Vim swap files
*.swp
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

2
.npmrc Normal file
View File

@ -0,0 +1,2 @@
engine-strict=true
resolution-mode=highest

View File

@ -1 +0,0 @@
.nuxt

View File

@ -1,6 +0,0 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"singleQuote": false
}

View File

@ -1,17 +1,20 @@
# My Website | [ngn13.fun](https://ngn13.fun)
This repo contains the source code of my personal website.
It's written NuxtJS and supports full SSR. As database,
it uses mongodb. It's just a personal project that
I am working on.
# [ngn13.fun](https://ngn13.fun) | my personal website
This is the frontend application for my personal website, it's written in SvelteKit and vanilla CSS
## Setup
For some reason if you want to setup my website localy
install `nodejs` and `npm`, then run the following:
## development setup
```bash
git clone https://github.com/ngn13/ngn13.fun.git && cd ngn13.fun &&
git clone https://github.com/ngn13/ngn13.fun && cd ngn13.fun
npm i
echo "PASS=password" > .env
echo "DATABASE=mongodb://127.0.0.1" > .env
npm run build
npm run start
npm run dev
```
## build setup
```bash
git clone https://github.com/ngn13/ngn13.fun && cd ngn13.fun
npm i
npm run build
```
## license
you cannot publish my website or parts of it on any server/domain without my permission
if you want to do this (for some reason) [contact me](mailto:ngn13proton@proton.me)

View File

@ -1,46 +0,0 @@
const express = require("express")
const { MongoClient } = require("mongodb")
require("dotenv").config()
/*
* error: 0 -> no error
* error: 1 -> parameter error
* error: 2 -> auth error
* error: 3 -> not found error
*/
const db = new MongoClient(process.env.DATABASE)
const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(async (req, res, next) => {
await db.connect()
req.db = db
next()
})
const { auth, authware } = require("./routes/auth.js")
// anything starts with "add"
// requires admin privs
app.use("/*/a*", authware)
const resources = require("./routes/resources.js")
const projects = require("./routes/projects.js")
const blog = require("./routes/blog.js")
const routes = [resources, projects, blog, auth]
routes.forEach((route) => {
app.use(route.path, route)
})
async function pexit() {
await db.close()
process.exit()
}
process.on("SIGTERM", pexit)
process.on("SIGINT", pexit)
export default {
path: "/api",
handler: app
}

View File

@ -1,35 +0,0 @@
const express = require("express")
const { gimmeToken } = require("../util.js")
const auth = express.Router()
auth.path = "/auth"
const PASS = process.env.PASS
let TOKEN = gimmeToken()
function authware(req, res, next) {
const token = req.query.token ? req.query.token : req.body.token
if (typeof token !== "string") return res.json({ error: 1 })
if (token !== TOKEN) return res.json({ error: 2 })
next()
}
auth.use("/logout", authware)
auth.get("/login", async (req, res) => {
const pass = req.query.pass
if (typeof pass !== "string") return res.json({ error: 1 })
if (pass !== PASS) return res.json({ error: 2 })
res.json({ error: 0, token: TOKEN })
})
auth.get("/logout", async (req, res) => {
TOKEN = gimmeToken()
res.json({ error: 0 })
})
module.exports = { auth, authware }

View File

@ -1,87 +0,0 @@
const express = require("express")
const { makeID } = require("../util.js")
const blog = express.Router()
blog.path = "/blog"
blog.get("/sum", async (req, res) => {
const db = req.db.db("ngn13")
const col = db.collection("posts")
const results = await col.find({ priv: { $eq: false } }).toArray()
let posts = []
for (let i = 0; i < results.length; i++) {
posts.push({
title: results[i]["title"],
desc:
results[i]["content"]
.substring(0, 140) // a short desc
.replaceAll("#", "") // remove all the markdown stuff
.replaceAll("*", "")
.replaceAll("`", "")
.replaceAll("-", "") + "...", // add "..." to make it look like desc
info: `${results[i]["author"]} | ${results[i]["date"]}`
})
}
// reversing so we can get
// the latest posts on the top
res.json({ error: 0, posts: posts.reverse() })
})
blog.get("/get", async (req, res) => {
const id = req.query.id
const db = req.db.db("ngn13")
const col = db.collection("posts")
const results = await col.find().toArray()
for (let i = 0; i < results.length; i++) {
// id is basically the title of the post
// but ve remove the whitespace
// and make it lowerspace
// for example:
// Online Privacy Guide -> onlineprivacyguide
if (makeID(results[i]["title"]) === id) {
return res.json({
error: 0,
post: {
title: results[i]["title"],
// info is the subtitle, for example:
// ngn | 01/06/2023
info: `${results[i]["author"]} | ${results[i]["date"]}`,
content: results[i]["content"]
}
})
}
}
res.json({ error: 3 })
})
blog.post("/add", async (req, res) => {
const title = req.body.title
const author = req.body.author
const content = req.body.content
const priv = req.body.priv
if (
typeof title !== "string" ||
typeof author !== "string" ||
typeof content !== "string" ||
typeof priv !== "boolean"
)
return res.json({ error: 1 })
const db = req.db.db("ngn13")
const col = db.collection("posts")
await col.insertOne({
title: title,
author: author,
date: new Date().toLocaleDateString(),
content: content,
priv: priv
})
res.json({ error: 0 })
})
module.exports = blog

View File

@ -1,35 +0,0 @@
const express = require("express")
const projects = express.Router()
projects.path = "/projects"
projects.get("/get", async (req, res) => {
const db = req.db.db("ngn13")
const col = db.collection("projects")
const results = await col.find().toArray()
res.json({ error: 0, projects: results })
})
projects.get("/add", async (req, res) => {
let name = req.query.name
let desc = req.query.desc
let url = req.query.url
if (
typeof name !== "string" ||
typeof desc !== "string" ||
typeof url !== "string"
)
return res.json({ error: 1 })
const db = req.db.db("ngn13")
const col = db.collection("projects")
await col.insertOne({
name: name,
desc: desc,
url: url,
click: 0
})
res.json({ error: 0 })
})
module.exports = projects

View File

@ -1,32 +0,0 @@
const express = require("express")
const resources = express.Router()
resources.path = "/resources"
resources.get("/get", async (req, res) => {
const db = req.db.db("ngn13")
const col = db.collection("resources")
let results = []
if (req.query.sum) results = await col.find().limit(10).toArray()
else results = await col.find().toArray()
res.json({ error: 0, resources: results.reverse() })
})
resources.get("/add", async (req, res) => {
let name = req.query.name
let tags = req.query.tags
let url = req.query.url
if (
typeof name !== "string" ||
typeof tags !== "string" ||
typeof url !== "string"
)
return res.json({ error: 1 })
const db = req.db.db("ngn13")
const col = db.collection("resources")
await col.insertOne({ name: name, tags: tags.split(","), url: url })
res.json({ error: 0 })
})
module.exports = resources

View File

@ -1,22 +0,0 @@
function gimmeToken() {
var result = ""
var characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
var charactersLength = characters.length
for (var i = 0; i < 32; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
function makeID(title) {
// this is used in blog.js
// id is basically the title of the post
// but ve remove the whitespace
// and make it lowerspace
// for example:
// Online Privacy Guide -> onlineprivacyguide
return title.toLowerCase().replaceAll(" ", "")
}
module.exports = { gimmeToken, makeID }

View File

@ -1,29 +0,0 @@
<template>
<button @click="click"><slot></slot></button>
</template>
<script>
export default {
props: ["click"]
}
</script>
<style scoped>
button {
text-align: center;
width: 540px;
font-size: 25px;
padding: 20px;
border-radius: 20px;
background: var(--dark-two);
border: none;
color: white;
outline: none;
cursor: pointer;
transition: 0.4s;
}
button:hover {
box-shadow: var(--def-shadow);
}
</style>

View File

@ -1,54 +0,0 @@
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {}
</script>
<style scoped>
div {
box-shadow: var(--def-shadow);
background: var(--dark-two);
width: 100%;
height: auto;
padding: 50px;
color: white;
}
h1 {
text-shadow: 3px 4px 7px rgba(81, 67, 21, 0.8);
font-size: 50px;
margin-bottom: 30px;
text-decoration: underline;
}
i {
animation-name: colorAnimation;
animation-duration: 10s;
animation-iteration-count: infinite;
}
a:hover {
font-weight: 900;
}
i {
font-size: 30px;
margin-top: 3px;
}
h2 {
font-size: 40px;
line-height: 60px;
}
a {
font-size: 40px;
color: white;
line-height: 60px;
text-decoration: none;
}
</style>

View File

@ -1,30 +0,0 @@
<template>
<div class="header">
<h1>
<slot></slot>
</h1>
</div>
</template>
<script>
export default {}
</script>
<style scoped>
div {
background: linear-gradient(rgba(3, 3, 3, 0.706), rgba(22, 22, 22, 0.808)),
url("https://www.sketchappsources.com/resources/source-image/tv-glitch-sureshmurali29.png");
width: 100%;
height: 100%;
}
h1 {
font-weight: 900;
font-size: 7.5vw;
padding: 150px;
text-align: center;
color: white;
text-shadow: 3px 4px 7px rgba(81, 67, 21, 0.8);
text-size-adjust: 80%;
}
</style>

View File

@ -1,27 +0,0 @@
<template>
<input v-on:keyup="keyup" :placeholder="placeholder" :type="type" />
</template>
<script>
export default {
props: ["keyup", "placeholder", "type"]
}
</script>
<style scoped>
input {
width: 500px;
font-size: 25px;
padding: 20px;
border-radius: 10px;
background: var(--dark-two);
border: none;
color: white;
outline: none;
transition: 0.4s;
}
input:focus {
box-shadow: var(--def-shadow);
}
</style>

View File

@ -1,41 +0,0 @@
<template>
<div>
<h1>Currently logged in</h1>
<Button :click="click">Logout</Button>
</div>
</template>
<script>
import axios from "axios"
import Button from "./Button.vue"
export default {
methods: {
async click(e) {
await axios.get(`/api/auth/logout?token=${localStorage.getItem("token")}`)
localStorage.clear()
location.reload()
}
}
}
</script>
<style scoped>
h1 {
color: var(--white);
font-size: 50px;
margin-bottom: 20px;
text-align: center;
}
div {
background-color: var(--dark-three);
padding: 50px;
margin-top: 50px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
justify-content: center;
}
</style>

View File

@ -1,55 +0,0 @@
<template>
<nav>
<div>
<h3>[ngn]</h3>
</div>
<div>
<NavbarLink :out="false" url="/">Home</NavbarLink>
<NavbarLink :out="false" url="/projects">Projects</NavbarLink>
<NavbarLink :out="false" url="/resources">Resources</NavbarLink>
<NavbarLink :out="false" url="/blog">Blog</NavbarLink>
<NavbarLink :out="true" url="http://github.com/ngn13/ngn13.fun"
>Source</NavbarLink
>
</div>
</nav>
</template>
<script>
import NavbarLink from "./NavbarLink.vue"
export default {}
</script>
<style scoped>
nav {
background: var(--dark-two);
padding: 25px 35px 30px 35px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
border-bottom: solid 3px black;
animation-name: borderAnimation;
animation-duration: 10s;
animation-iteration-count: infinite;
box-shadow: var(--def-shadow);
}
div {
display: flex;
overflow: hidden;
align-items: center;
justify-content: center;
gap: 5px;
}
h3 {
font-weight: 900;
font-size: 30px;
color: red;
animation-name: colorAnimation;
animation-iteration-count: infinite;
animation-duration: 10s;
}
</style>

View File

@ -1,60 +0,0 @@
<template>
<div>
<nuxt-link v-if="!out" :class="cname" id="link" :to="url">
<slot></slot>
</nuxt-link>
<a v-if="out" :class="cname" id="link" :href="url">
<slot></slot>
</a>
</div>
</template>
<script>
export default {
props: ["url", "out"],
data() {
return {
cname: "notcurrent"
}
},
mounted() {
let url = window.location.pathname
if (url === this.url) {
this.cname = "current"
}
}
}
</script>
<style scoped>
.notcurrent {
margin-left: 20px;
font-weight: 700;
font-size: 25px;
text-decoration: none;
color: white;
cursor: pointer;
}
.notcurrent:hover {
text-decoration: underline;
animation-name: underlineAnimation;
animation-duration: 5s;
animation-iteration-count: infinite;
text-shadow: 3px 4px 7px rgba(81, 67, 21, 0.8);
}
.current {
margin-left: 20px;
font-weight: 700;
font-size: 25px;
text-decoration: none;
color: white;
cursor: pointer;
text-decoration: underline;
animation-name: underlineAnimation;
animation-duration: 10s;
animation-iteration-count: infinite;
text-shadow: 3px 4px 7px rgba(81, 67, 21, 0.8);
}
</style>

View File

@ -1,123 +0,0 @@
<template>
<main>
<h1>Add New Post</h1>
<div class="textareas">
<Input
:keyup="function () {}"
id="title"
placeholder="Post Title"
type="text"
/>
<Input
:keyup="function () {}"
id="author"
placeholder="Author"
type="text"
/>
<h2>
Make the post private
<input id="private" type="checkbox" />
</h2>
</div>
<textarea
name="content"
id="content"
cols="30"
rows="10"
placeholder="Content"
></textarea>
<Button :click="click">Post</Button>
</main>
</template>
<script>
import axios from "axios"
import Input from "./Input.vue"
import Button from "./Button.vue"
export default {
methods: {
async click(e) {
const title = document.getElementById("title").value
const author = document.getElementById("author").value
const content = document.getElementById("content").value
const priv = document.getElementById("private").value
const token = localStorage.getItem("token")
const res = await axios.post("/api/blog/add", {
token: token,
title: title,
author: author,
content: content,
priv: priv === "on"
})
if (res.data["error"] !== 0) return alert("Error!")
alert("Post added!")
location.reload()
}
}
}
</script>
<style scoped>
h1 {
color: var(--white);
font-size: 50px;
margin-bottom: 20px;
text-align: center;
}
h2 {
background: var(--dark-two);
font-size: 25px;
border-radius: 20px;
border: none;
padding: 20px;
color: var(--white);
display: flex;
justify-content: space-between;
}
input[type="checkbox"] {
-ms-transform: scale(2);
-moz-transform: scale(2);
-webkit-transform: scale(2);
-o-transform: scale(2);
transform: scale(2);
padding: 10px;
}
textarea {
width: 500px;
font-size: 20px;
padding: 20px;
border-radius: 20px;
background: var(--dark-two);
border: none;
color: white;
outline: none;
resize: vertical;
height: 200px;
transition: 0.4s;
}
.textareas {
flex-direction: column;
display: flex;
gap: 20px;
}
textarea:focus {
box-shadow: var(--def-shadow);
}
main {
background-color: var(--dark-three);
padding: 50px;
margin-top: 50px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
justify-content: center;
}
</style>

View File

@ -1,67 +0,0 @@
<template>
<div>
<h1>Add Project</h1>
<Input
:keyup="function () {}"
id="name"
placeholder="Project Name"
type="text"
/>
<Input
:keyup="function () {}"
id="desc"
placeholder="Project Desc"
type="text"
/>
<Input
:keyup="function () {}"
id="url"
placeholder="Project URL"
type="text"
/>
<Button :click="click">Post</Button>
</div>
</template>
<script>
import axios from "axios"
import Input from "./Input.vue"
import Button from "./Button.vue"
export default {
methods: {
async click(e) {
const name = document.getElementById("name").value
const desc = document.getElementById("desc").value
const url = document.getElementById("url").value
const token = localStorage.getItem("token")
const res = await axios.get(
`/api/projects/add?token=${token}&name=${name}&desc=${desc}&url=${url}`
)
if (res.data["error"] !== 0) return alert("Error!")
alert("Project added!")
location.reload()
}
}
}
</script>
<style scoped>
h1 {
color: var(--white);
font-size: 50px;
margin-bottom: 20px;
text-align: center;
}
div {
background-color: var(--dark-three);
padding: 50px;
margin-top: 50px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
justify-content: center;
}
</style>

View File

@ -1,67 +0,0 @@
<template>
<div>
<h1>Add Resource</h1>
<Input
:keyup="function () {}"
id="name"
placeholder="Resource Name"
type="text"
/>
<Input
:keyup="function () {}"
id="tags"
placeholder="Resource Tags (comma seperated)"
type="text"
/>
<Input
:keyup="function () {}"
id="url"
placeholder="Resource URL"
type="text"
/>
<Button :click="click">Post</Button>
</div>
</template>
<script>
import Input from "./Input.vue"
import Button from "./Button.vue"
import axios from "axios"
export default {
methods: {
async click(e) {
const name = document.getElementById("name").value
const tags = document.getElementById("tags").value
const url = document.getElementById("url").value
const token = localStorage.getItem("token")
const res = await axios.get(
`/api/resources/add?token=${token}&name=${name}&tags=${tags}&url=${url}`
)
if (res.data["error"] !== 0) return alert("Error!")
alert("Resource added!")
location.reload()
}
}
}
</script>
<style scoped>
h1 {
color: var(--white);
font-size: 50px;
margin-bottom: 20px;
text-align: center;
}
div {
background-color: var(--dark-three);
padding: 50px;
margin-top: 50px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
justify-content: center;
}
</style>

View File

@ -1,42 +0,0 @@
<template>
<nuxt-link :to="url">
<h1>{{ title }}</h1>
<p>{{ info }}</p>
<h2>{{ desc }}</h2>
</nuxt-link>
</template>
<script>
export default {
props: ["title", "desc", "info", "url"]
}
</script>
<style scoped>
a {
padding: 30px;
color: white;
background: var(--dark-two);
cursor: pointer;
text-decoration: none;
transition: 0.4s;
width: 70%;
}
a:hover {
box-shadow: var(--def-shadow);
}
h1 {
font-size: 40px;
margin-bottom: 5px;
animation-name: colorAnimation;
animation-iteration-count: infinite;
animation-duration: 10s;
text-shadow: none;
}
h2 {
margin-top: 10px;
}
</style>

View File

@ -1,62 +0,0 @@
<template>
<main @click="redirect()">
<i class="bx bx-code-alt"></i>
<div>
<h1>{{ name }}</h1>
<h2>{{ desc }}</h2>
</div>
</main>
</template>
<script>
export default {
props: ["name", "desc", "url"],
methods: {
redirect(e) {
location.href = this.url
}
}
}
</script>
<style scoped>
main {
color: var(--white);
background: var(--dark-two);
display: flex;
flex-direction: row;
align-items: center;
gap: 20px;
padding: 40px;
cursor: pointer;
height: 100px;
width: 450px;
transition: .4s;
}
main:hover {
box-shadow: var(--def-shadow);
}
i {
font-size: 65px;
animation-name: colorAnimation;
animation-duration: 10s;
animation-iteration-count: infinite;
}
h1 {
font-size: 35px;
margin-bottom: 10px;
}
h2 {
font-size: 25px;
}
@media only screen and (max-width: 1416px) {
main {
width: 80%;
}
}
</style>

View File

@ -1,26 +0,0 @@
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {}
</script>
<style scoped>
div {
display: flex;
flex-direction: row;
padding: 15px;
align-items: center;
justify-content: center;
gap: 30px;
}
@media only screen and (max-width: 1416px) {
div {
flex-direction: column;
}
}
</style>

View File

@ -1,66 +0,0 @@
<template>
<main @click="redirect()" class="mn">
<div class="resource">
<h1>{{ name }}</h1>
<div class="tags">
<Tag v-for="tag in tags" :key="tag">{{ tag }}</Tag>
</div>
</div>
<i class="bx bx-right-arrow-alt"></i>
</main>
</template>
<script>
import Tag from "./Tag.vue"
export default {
props: ["tags", "name", "url"],
methods: {
redirect(e) {
location.href = this.url
}
}
}
</script>
<style scoped>
main {
background: var(--dark-two);
padding: 30px 40px 30px 40px;
display: flex;
flex-direction: row;
color: var(--white);
justify-content: space-between;
align-items: center;
width: 80%;
cursor: pointer;
transition: .4s;
}
main:hover {
box-shadow: var(--def-shadow);
}
.mn:hover i {
color: white;
}
.resource {
display: flex;
flex-direction: column;
gap: 10px;
}
.tags {
display: flex;
flex-direction: row;
gap: 10px;
}
i {
font-size: 70px;
cursor: pointer;
color: var(--dark-two);
transition: 0.4s;
}
</style>

View File

@ -1,20 +0,0 @@
<template>
<p>#<slot></slot></p>
</template>
<script>
export default {}
</script>
<style scoped>
p {
background: var(--dark-three);
color: white;
text-shadow: 1px 1px 2px white;
padding: 5px 10px 5px 10px;
font-size: 25px;
border-radius: 7px;
margin-top: 10px;
transition: 0.4s;
}
</style>

View File

@ -1,12 +0,0 @@
// https://v2.nuxt.com/deployments/pm2/
module.exports = {
apps: [
{
name: "ngn13.fun website",
exec_mode: "cluster",
instances: "max",
script: "./node_modules/nuxt/bin/nuxt.js",
args: "start"
}
]
}

View File

@ -1,12 +0,0 @@
<template>
</template>
<script>
export default {
props: ["error"],
layout: "error",
mounted() {
this.$router.push({ path: "/" })
}
}
</script>

View File

@ -1,46 +0,0 @@
const express = require("express")
const { MongoClient } = require("mongodb")
const { makeID } = require("../api/util.js")
require("dotenv").config()
const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
const client = new MongoClient(process.env.DATABASE)
app.get("/:id", async (req, res) => {
const id = req.params.id
if (typeof id !== "string") return res.redirect("/projects")
await client.connect()
const db = client.db("ngn13")
const col = db.collection("projects")
const projects = await col.find().toArray()
for (let i = 0; i < projects.length; i++) {
if (makeID(projects[i]["name"]) === id) {
res.redirect(projects[i]["url"])
await col.updateOne(
{ name: projects[i]["name"] },
{ $set: { click: projects[i]["click"] + 1 } }
)
}
}
return res.redirect("/projects")
})
async function pexit() {
await client.close()
process.exit()
}
process.on("SIGTERM", pexit)
process.on("SIGINT", pexit)
export default {
path: "/l",
handler: app
}

View File

@ -1,41 +0,0 @@
export default {
head: {
title: "[ngn]",
htmlAttrs: {
lang: "en"
},
meta: [
{ charset: "utf-8" },
{ name: "viewport", content: "width=1200" },
{ hid: "description", name: "description", content: "" },
{ name: "format-detection", content: "telephone=no" },
{ hid: "og:title", content: "[ngn]" },
{
hid: "og:description",
content:
"personal website of ngn | read my blogs, check out my projects, discover cool resources"
},
{ hid: "og:url", content: "https://ngn13.fun" },
{ name: "theme-color", content: "#141414", "data-react-helmet": "true" }
],
link: [
{
rel: "stylesheet",
href: "https://files.ngn13.fun/boxicons.min.css",
},
]
},
css: ["@/static/global.css"],
plugins: [],
components: true,
buildModules: [],
modules: ["@nuxtjs/axios"],
axios: {
baseURL: "/"
},
build: {},
serverMiddleware: {
"/api": "~/api",
"/l": "~/links"
}
}

26915
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,22 @@
{
"name": "my-website",
"version": "2.5.0",
"name": "website",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate"
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.20.4",
"svelte": "^4.0.5",
"vite": "^4.4.2"
},
"type": "module",
"dependencies": {
"@nuxtjs/axios": "^5.13.6",
"core-js": "^3.25.3",
"dompurify": "^3.0.3",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"marked": "^5.0.3",
"mongodb": "^5.5.0",
"nuxt": "^2.15.8",
"vue": "^2.7.10",
"vue-server-renderer": "^2.7.10",
"vue-template-compiler": "^2.7.10"
"@types/dompurify": "^3.0.2",
"dompurify": "^3.0.5",
"marked": "^7.0.4"
}
}

View File

@ -1,111 +0,0 @@
<template>
<div class="all">
<Navbar />
<Header>
<label class="glitch title">{{ post.title }}</label>
<p>{{ post.info }}</p>
</Header>
<div class="postcontain">
<main class="markdown-body" v-html="content"></main>
</div>
</div>
</template>
<script>
import Navbar from "../../../components/Navbar.vue"
import Header from "../../../components/Header.vue"
import axios from "axios"
import * as DOMPurify from "dompurify"
import marked from "marked"
export default {
head() {
return {
title: "[ngn] | blog",
meta: [
{
hid: "description",
name: "description",
content: "read my blog posts"
}
]
}
},
data() {
return {
post: {},
lang: "",
content: ""
}
},
async created() {
const res = await axios.get(`/api/blog/get?id=${this.$route.params.id}`)
if (res.data["error"] === 3) return this.$router.push({ path: "/blog" })
this.post = res.data["post"]
this.post["content"] = this.post["content"].replaceAll(
"\n<br>\n<br>\n",
"\n\n"
)
this.content = DOMPurify.sanitize(
marked.parse(this.post["content"], { breaks: true }),
{
ADD_TAGS: ["iframe"],
ADD_ATTR: ["allow", "allowfullscreen", "frameborder", "scrolling"]
}
)
}
}
</script>
<style scoped>
glitch {
font-size: 80px;
}
p {
font-size: 30px;
}
.info {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.postcontain {
padding: 50px;
}
.markdown-body {
font-size: 25px;
padding: 50px;
border-radius: 10px;
background-color: var(--dark-two);
box-shadow: var(--def-shadow);
}
</style>
<style>
.markdown-body {
font-family: "Ubuntu", sans-serif;
}
.markdown-body h1 {
border-bottom: 1px solid #505050;
}
.markdown-body iframe {
display: block;
margin: 20px 0px;
}
.markdown-body a {
animation-name: colorAnimation;
animation-iteration-count: infinite;
animation-duration: 10s;
text-shadow: none;
}
</style>

View File

@ -1,92 +0,0 @@
<template>
<div>
<Navbar />
<Header> <label class="glitch">/dev/</label>blog </Header>
<div class="blogs">
<Input :keyup="keyup" placeholder="Search post" type="text" />
<PostPreview
v-for="post in posts"
:key="post.title"
:title="post.title"
:desc="post.desc"
:info="post.info"
:url="post.url"
>
{{ post.desc }}
</PostPreview>
</div>
<NewPost v-if="logged" />
</div>
</template>
<script>
import Navbar from "../../components/Navbar.vue"
import Header from "../../components/Header.vue"
import NewPost from "../../components/NewPost.vue"
import PostPreview from "../../components/PostPreview.vue"
import axios from "axios"
export default {
head() {
return {
title: "[ngn] | blog",
meta: [
{
hid: "description",
name: "description",
content: "read my blog posts"
}
]
}
},
data() {
return {
logged: false,
posts: [],
all: []
}
},
mounted: async function () {
if (localStorage.getItem("token")) this.logged = true
const res = await axios.get("/api/blog/sum")
let posts = []
res.data["posts"].forEach((post) => {
posts.push({
title: post.title,
desc: post.desc,
info: post.info,
url: `/blog/${post.title.toLowerCase().replaceAll(" ", "")}`
})
})
this.posts = posts
this.all = posts
},
methods: {
keyup(e) {
let val = e.target.value
// search looks at name and info
this.posts = []
for (let i = 0; i < this.all.length; i++) {
if (this.all[i].title.toLowerCase().includes(val.toLowerCase()))
this.posts.push(this.all[i])
else if (this.all[i].info.toLowerCase().includes(val.toLowerCase()))
this.posts.push(this.all[i])
}
}
}
}
</script>
<style scoped>
.blogs {
padding: 50px;
gap: 35px;
display: flex;
flex-direction: column;
gap: 30px;
align-items: center;
}
</style>

View File

@ -1,109 +0,0 @@
<template>
<div>
<Navbar />
<Header> <label class="glitch">echo</label> hello world! </Header>
<div class="info">
<Card>
<h1>👋 Welcome to my website!</h1>
<h2>
I am a high school student who is interested in
<br />
cyber security
<br />
coding
<br />
electronics
<br />
gaming
<br />
simply: everything about computers!
</h2>
</Card>
<Card>
<h1>👉 Contact me</h1>
<h2>You can contact me on the following platforms:</h2>
<a href="https://discord.com/users/568131907368386565"
><i class="bx bxl-discord-alt"></i> Discord</a
>
<br />
<a href="https://github.com/ngn13"
><i class="bx bxl-github"></i> Github</a
>
<br />
<a href="https://mastodon.social/@ngn"
><i class="bx bxl-mastodon"></i> Mastodon</a
>
<br />
<a href="mailto:ngn13proton@proton.me"
><i class="bx bxs-envelope"></i> Mail</a
>
<br />
<h2>or private message me on matrix:</h2>
<a><i>[matrix]</i> @ngn:matrix.ngn13.fun</a>
</Card>
</div>
<Logout v-if="logged" />
<div class="version">
<p>v2.6</p>
</div>
</div>
</template>
<script>
import Navbar from "../components/Navbar.vue"
import Header from "../components/Header.vue"
import Card from "../components/Card.vue"
import Logout from "../components/Logout.vue"
export default {
head() {
return {
title: "[ngn]",
meta: [
{
hid: "description",
name: "description",
content: "homepage of my website"
}
]
}
},
data() {
return {
logged: false
}
},
mounted() {
if (localStorage.getItem("token")) this.logged = true
}
}
</script>
<style scoped>
.info {
padding: 50px;
gap: 30px;
display: flex;
}
.version {
color: var(--dark-fife);
position: fixed;
bottom: 10px;
right: 10px;
font-size: 15px;
}
i {
font-style: normal;
}
@media only screen and (max-width: 1076px) {
.info {
flex-direction: column;
}
.info div {
width: auto;
}
}
</style>

View File

@ -1,56 +0,0 @@
<template>
<div>
<h1>Login Page</h1>
<Input
:keyup="function () {}"
placeholder="Password"
type="password"
id="pass"
/>
<Button :click="click">Login</Button>
</div>
</template>
<script>
import Input from "../components/Input.vue"
import Button from "../components/Button.vue"
import axios from "axios"
export default {
methods: {
async click(e) {
const pass = document.getElementById("pass").value
const res = await axios.get(`/api/auth/login?pass=${pass}`)
if (res.data["error"] === 0) {
localStorage.setItem("token", res.data["token"])
return (location.href = "/")
}
alert("Incorrect password!")
}
}
}
</script>
<style scoped>
div {
padding: 50px;
background: var(--dark-three);
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
display: flex;
flex-direction: column;
gap: 20px;
color: var(--white);
align-items: center;
justify-content: center;
}
h1 {
font-size: 70px;
margin-bottom: 20px;
}
</style>

View File

@ -1,105 +0,0 @@
<template>
<div>
<Navbar />
<Header> <label class="glitch">ls -la</label> projects </Header>
<div class="projects">
<ProjectList v-for="project in projects" :key="project.id">
<Project
v-if="logged"
v-for="p in project.list"
:key="p.name"
:name="`${p.name} (${p.click})`"
:desc="p.desc"
:url="p.url"
/>
<Project
v-if="!logged"
v-for="p in project.list"
:key="p.desc"
:name="p.name"
:desc="p.desc"
:url="p.url"
/>
</ProjectList>
</div>
<NewProject v-if="logged" />
</div>
</template>
<script>
import ProjectList from "../components/ProjectList.vue"
import Project from "../components/Project.vue"
import NewProject from "../components/NewProject.vue"
import axios from "axios"
export default {
head() {
return {
title: "[ngn] | projects",
meta: [
{
hid: "description",
name: "description",
content: "check out my projects"
}
]
}
},
data() {
return {
logged: false,
projects: []
}
},
mounted: async function () {
if (localStorage.getItem("token")) this.logged = true
const res = await axios.get("/api/projects/get")
let all = res.data["projects"]
let pcounter = 0
let projects = []
let project = {
id: pcounter,
list: []
}
for (let i = 0; i < all.length; i++) {
if (project["list"].length === 3) {
projects.push(project)
pcounter += 1
project = {
id: pcounter,
list: []
}
}
project["list"].push({
name: all[i]["name"],
desc: all[i]["desc"],
click: all[i]["click"],
url: `/l/${all[i]["name"].toLowerCase().replaceAll(" ", "")}`
})
if (i === all.length - 1) {
projects.push(project)
}
}
this.projects = projects
}
}
</script>
<style>
.projects {
padding: 50px;
display: flex;
flex-direction: column;
}
@media only screen and (max-width: 1121px) {
.projects {
padding: 50px;
}
}
</style>

View File

@ -1,108 +0,0 @@
<template>
<main>
<Navbar />
<Header> <label class="glitch">cat</label> {{ header }} </Header>
<div class="resources">
<Input :keyup="keyup" placeholder="Search resource" type="text" />
<Resource
v-for="res in show_resources"
:key="res.name"
:name="res.name"
:tags="res.tags"
:url="res.url"
/>
</div>
<NewResource v-if="logged" />
</main>
</template>
<script>
import axios from "axios"
import Resource from "../components/Resource.vue"
import Input from "../components/Input.vue"
import NewResource from "../components/NewResource.vue"
export default {
head() {
return {
title: "[ngn] | resources",
meta: [
{
hid: "description",
name: "description",
content: "discover new resources"
}
]
}
},
data() {
return {
header: "resources",
logged: false,
sum_resources: [],
all_resources: [],
show_resources: []
}
},
methods: {
keyup(e) {
let search = e.target.value
if (e.key === "Backspace" && search === "") {
this.header = "resources"
this.show_resources = this.sum_resources
return
}
if (e.key === "OS") return
this.header = `resources | grep ${search}`
// dirty asf search alg
this.show_resources = []
for (let i = 0; i < this.all_resources.length; i++) {
if (
this.all_resources[i].name
.toLowerCase()
.includes(search.toLowerCase())
) {
this.show_resources.push(this.all_resources[i])
continue
}
for (let e = 0; e < this.all_resources[i].tags.length; e++) {
if (
this.all_resources[i].tags[e]
.toLowerCase()
.includes(search.toLowerCase())
) {
this.show_resources.push(this.all_resources[i])
break
}
}
}
}
},
mounted: async function () {
if (localStorage.getItem("token")) this.logged = true
// request top 10 resources so we can
// render the DOM as fast as possible
let res = await axios.get("/api/resources/get?sum=1")
this.sum_resources = res.data["resources"]
this.show_resources = this.sum_resources
// then we can load all the resources
res = await axios.get("/api/resources/get")
this.all_resources = res.data["resources"]
}
}
</script>
<style scoped>
.resources {
padding: 50px;
padding-bottom: 60px;
display: flex;
flex-direction: column;
align-items: center;
gap: 40px;
}
</style>

11
src/app.html Normal file
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=1024">
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

52
src/lib/card.svelte Normal file
View File

@ -0,0 +1,52 @@
<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@ngn13.fun:~# {current}
</div>
<div class="content">
<slot></slot>
</div>
</div>
<style>
.main {
display: flex;
flex-direction: column;
width: 100%;
background: var(--dark-three);
box-shadow: var(--box-shadow);
border-radius: 7px;
}
.title {
background: var(--dark-two);
padding: 30px;
border-radius: 7px 7px 0px 0px;
font-size: 20px;
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
color: white;
}
.content {
background: var(--dark-three);
padding: 40px;
padding-top: 30px;
color: white;
border-radius: 5px;
font-size: 25px;
}
</style>

67
src/lib/card_link.svelte Normal file
View File

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

28
src/lib/header.svelte Normal file
View File

@ -0,0 +1,28 @@
<script></script>
<header>
<h1>
<slot></slot>
</h1>
</header>
<style>
header {
background:
linear-gradient(rgba(11, 11, 11, 0.808), rgba(1, 1, 1, 0.96)),
url("https://files.ngn13.fun/banner.png");
background-size: 50%;
width: 100%;
height: 100%;
}
h1 {
font-weight: 900;
font-size: 5.5vw;
padding: 120px;
text-align: center;
color: white;
text-shadow: var(--text-shadow);
text-size-adjust: 80%;
}
</style>

50
src/lib/navbar.svelte Normal file
View File

@ -0,0 +1,50 @@
<script>
import NavbarLink from "./navbar_link.svelte";
</script>
<nav>
<div>
<h3>[ngn]</h3>
</div>
<div>
<NavbarLink link="/">home</NavbarLink>
<NavbarLink link="/projects">projects</NavbarLink>
<NavbarLink link="/blog">blog</NavbarLink>
<NavbarLink link="https://github.com/ngn13/ngn13.fun">source</NavbarLink>
</div>
</nav>
<style>
nav {
background: var(--dark-one);
padding: 25px 30px 27px 25px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
border-bottom: solid 1.5px black;
animation-name: borderAnimation;
animation-duration: 10s;
animation-iteration-count: infinite;
box-shadow: var(--def-shadow);
}
div {
display: flex;
overflow: hidden;
align-items: center;
justify-content: center;
gap: 1px;
}
h3 {
font-weight: 900;
font-size: 25px;
color: red;
animation-name: colorAnimation;
animation-iteration-count: infinite;
animation-duration: 10s;
}
</style>

View File

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

57
src/lib/project.svelte Normal file
View File

@ -0,0 +1,57 @@
<script>
import { goto } from "$app/navigation";
export let desc
export let url
let audio
function redirect() {
audio.play()
goto(url);
}
</script>
<main on:click={redirect}>
<audio bind:this={audio} preload="auto">
<source src="/click.wav" type="audio/mpeg" />
</audio>
<h1 class="c"></h1>
<div>
<h1><slot></slot></h1>
<p>{desc}</p>
</div>
</main>
<style>
main {
display: flex;
padding: 40px 60px 40px 60px;
background: var(--dark-two);
border-radius: 7px 7px 0px 0px;
justify-content: center;
align-content: center;
flex-direction: row;
cursor: pointer;
transition: .4s;
border: none;
color: white;
gap: 20px;
}
main:hover {
box-shadow: var(--box-shadow);
}
h1 {
font-size: 70px;
}
div h1 {
font-size: 30px;
}
div p {
margin-top: 10px;
font-size: 20px;
}
</style>

8
src/routes/+error.svelte Normal file
View File

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

12
src/routes/+layout.svelte Normal file
View File

@ -0,0 +1,12 @@
<script>
import Navbar from "../lib/navbar.svelte";
</script>
<main>
<Navbar />
<slot></slot>
</main>
<style>
@import "../../static/global.css";
</style>

119
src/routes/+page.svelte Normal file
View File

@ -0,0 +1,119 @@
<script>
import Header from "../lib/header.svelte";
import Card from "../lib/card.svelte";
</script>
<svelte:head>
<title>[ngn] | homepage</title>
<meta content="[ngn] | homepage" property="og:title" />
<meta content="Homepage of my personal website" property="og:description" />
<meta content="https://ngn13.fun" property="og:url" />
<meta content="#000000" data-react-helmet="true" name="theme-color" />
</svelte:head>
<Header>
<c>echo</c>
hello world!
</Header>
<main>
<div class="flexbox">
<Card title="whoami">
👋 Hello! I'm ngn!
<ul>
<li>🇹🇷 I'm a high school student from Turkey</li>
<li>🖥️ I'm interested in cyber security, programming and electronics</li>
<li>❤️ I love and support Free/Libre and Open Source Software (FLOSS)</li>
<li>🐧 My GNU/Linux distribution of choice is Artix</li>
<li>😋 In my free time, I like playing Minecraft</li>
</ul>
</Card>
<Card title="pwd">
You are currently on my personal website, for all you nerds, here is some
techinal details you may want to know:
<ul>
<li><c></c> I built the frontend app using SvelteKit</li>
<li><c></c> I deploy my app to vercel using Github actions</li>
<li><c>󰒋 </c> Backend server is written in Go and it's hosted on my server</li>
<li><c></c> And yup, I also have other services hosted on the same server</li>
</ul>
</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, which you should definitely check out (it's very active)</li>
</ul>
</Card>
<Card title="wall">
If you want to have a chat, you can find me on the following platforms:
<ul>
<li><c></c> <a href="https://github.com/ngn13">Github</a></li>
<li><c>󰫑 </c> <a href="https://mastodon.social/@ngn">Mastodon</a></li>
<li><c>󰙯 </c> <a href="https://discord.com/users/568131907368386565">Discord</a></li>
<li><c></c> <a href="mailto:ngn13proton@proton.me">Email</a></li>
</ul>
Or you can DM me on matrix:
<br>
<c>@ngn:matrix.ngn13.fun</c>
</Card>
</div>
</main>
<div class="version">
<p>v3.0</p>
</div>
<style>
main{
display: flex;
flex-direction: column;
gap: 35px;
padding: 50px;
}
.flexbox {
display: flex;
gap: 30px;
}
ul {
padding-left: 30px;
margin-bottom: 20px;
}
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: 1076px) {
.flexbox {
flex-direction: column;
}
}
</style>

9
src/routes/blog/+page.js Normal file
View File

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

View File

@ -0,0 +1,87 @@
<script>
import Header from "../../lib/header.svelte";
import CardLink from "../../lib/card_link.svelte";
import { onMount } from "svelte";
export let data
let inpt
let all = data.posts
let posts = all
let inputcls = "c"
function show(term){
posts = []
for(let i = 0; i < all.length; i++) {
if (all[i].title.toLowerCase().includes(term.toLowerCase())){
posts.push(all[i])
}
}
if (posts.length == 0){
inputcls = "nf"
return
}
inputcls = "c"
}
onMount(() => {
inpt.focus()
show(inpt.value)
})
function search(){
let term = inpt.value
show(term)
}
</script>
<svelte:head>
<title>[ngn] | blog</title>
<meta content="[ngn] | blog" property="og:title" />
<meta content="View my blog posts" property="og:description" />
<meta content="https://ngn13.fun" property="og:url" />
<meta content="#000000" data-react-helmet="true" name="theme-color" />
</svelte:head>
<Header>
<c>/dev/</c>blog
</Header>
<main>
<input on:keyup={search} bind:this={inpt} class="{inputcls}" placeholder="Search for a post">
{#each posts as post}
<CardLink url="/blog/{post.id}" title="{post.title}">
<p>{post.author} | {post.date}</p>
<br>
{post.content}...
</CardLink>
{/each}
</main>
<style>
main{
display: flex;
flex-direction: column;
gap: 35px;
padding: 15%;
padding-top: 50px;
}
input {
text-align: center;
background: var(--dark-two);
background: none;
border: none;
outline: none;
font-size: 40px;
}
p {
font-size: 20px;
}
.nf {
color: rgb(120, 120, 120);
}
</style>

View File

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

View File

@ -0,0 +1,163 @@
<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/status?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] | blog</title>
<meta content="[ngn] | blog" property="og:title" />
<meta content="View my blog posts" property="og:description" />
<meta content="https://ngn13.fun" property="og:url" />
<meta content="#000000" data-react-helmet="true" name="theme-color" />
</svelte:head>
<Header>
<c>{data.title}</c>
<p>{data.author} | {data.date}</p>
</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">
<h3 on:click={async ()=>{upvote()}} class="{upvote_status}">󰜷</h3>
<p>{data.vote}</p>
<h3 on:click={async ()=>{downvote()}} class="{downvote_status}">󰜮</h3>
</div>
</main>
<style>
p {
font-size: 30px;
}
main {
padding: 50px;
color: white;
display: flex;
flex-direction: row;
gap: 20px;
}
.content {
padding: 30px;
background: var(--dark-two);
box-shadow: var(--box-shadow);
border-radius: 7px;
}
.votes {
display: flex;
flex-direction: column;
text-align: center;
text-shadow: var(--text-shadow);
}
.votes h3{
font-size: 40px;
cursor: pointer;
}
.votes h3: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

@ -0,0 +1,35 @@
export async function load({ fetch }) {
const api = import.meta.env.VITE_API_URL_DEV
const res = await fetch(api+"/projects/get")
const data = await res.json()
if (data["error"] != ""){
return {
error: data["error"]
}
}
let all = data["result"]
let counter = 0
let currentlist = []
let projects = []
for (let i = 0; i < all.length; i++){
currentlist.push(all[i])
counter += 1
if(i == all.length-1 && counter != 3){
projects.push(currentlist)
}
if (counter == 3) {
projects.push(currentlist)
currentlist = []
counter = 0
}
}
return {
projects
}
}

View File

@ -0,0 +1,50 @@
<script>
import Header from "../../lib/header.svelte";
import Project from "../../lib/project.svelte";
export let data
console.log(data)
</script>
<svelte:head>
<title>[ngn] | projects</title>
<meta content="[ngn] | projects" property="og:title" />
<meta content="Projects that I work on" property="og:description" />
<meta content="https://ngn13.fun" property="og:url" />
<meta content="#000000" data-react-helmet="true" name="theme-color" />
</svelte:head>
<Header><c>ls -l</c> projects</Header>
<main>
{#each data.projects as projectlist}
<div class="flexrow">
{#each projectlist as project}
<Project url="{project.link}" desc="{project.desc}">{project.name}</Project>
{/each}
</div>
{/each}
</main>
<style>
main {
display: flex;
flex-direction: column;
align-content: center;
justify-content: center;
padding: 50px;
gap: 25px;
}
.flexrow {
display: flex;
flex-direction: row;
align-content: center;
justify-content: center;
gap: 25px;
}
@media only screen and (max-width: 1316px) {
.flexrow {
flex-direction: column;
}
}
</style>

BIN
static/click.wav Normal file

Binary file not shown.

View File

@ -1,64 +1,13 @@
/*
* this what you would get from google fonts, i downloaded all the fonts and
* placed them in static/fonts because google fonts made my website load
* slower lol
* fonts are located under /static/fonts, they are from nerdfonts
* see nerdfonts.com
*
* you can get the original css at:
* https://fonts.googleapis.com/css2?family=Ubuntu:wght@300&display=swap
*/
/* cyrillic-ext */
@font-face {
font-family: 'Ubuntu';
font-family: "Ubuntu";
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(./fonts/cyrillic-ext.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
src: url("/fonts/UbuntuNerdFont-Regular.ttf");
}
/* cyrillic */
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(./fonts/cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(./fonts/greek-ext.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(./fonts/greek.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* latin-ext */
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(./fonts/latin-ext.woff2) format('woff2');
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(./fonts/latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -47,6 +47,9 @@
}
.markdown-body a {
animation-name: colorAnimation;
animation-iteration-count: infinite;
animation-duration: 10s;
background-color: transparent;
color: #58a6ff;
text-decoration: none;
@ -666,7 +669,7 @@
.markdown-body span.float-left span {
margin: 13px 0 0;
}
https://github.com/ngn13/ngn13.fun/raw/main/static/fonts/latin.woff2
.markdown-body span.float-right {
display: block;
float: right;

View File

@ -1,12 +1,12 @@
@import url("./font.css");
@import url("./github.css");
@import "./font.css";
@import "./github.css";
:root {
--white: white;
--dark-one: #141414;
--dark-two: #18191a;
--dark-three: #282828;
--dark-four: #242526;
--dark-one: black;
--dark-two: #050505;
--dark-three: #121212;
--dark-four: #101010;
--dark-fife: #3a3b3c;
--border-rad: 30px;
/*
@ -15,7 +15,8 @@
rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px,
rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
*/
--def-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
--text-shadow: 0px 10px 20px rgba(90, 90, 90, 0.8);
--box-shadow: rgba(20, 20, 20, 0.19) 0px 10px 20px, rgba(30, 30, 30, 0.23) 0px 6px 6px;
}
* {
@ -24,17 +25,15 @@
}
body {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background: #141414;
background: var(--dark-one);
font-family: "Ubuntu", sans-serif;
overflow-x: hidden;
}
::selection {
background: rgba(0, 0, 0, 0.7);
}
::-webkit-scrollbar {
border-radius: 10px;
width: 10px;
@ -295,9 +294,8 @@ body {
}
}
.glitch {
.c, c {
animation-name: colorAnimation;
animation-iteration-count: infinite;
animation-duration: 10s;
text-shadow: none;
}

18
svelte.config.js Normal file
View File

@ -0,0 +1,18 @@
import adapter from '@sveltejs/adapter-auto';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
},
onwarn: (warning, handler) => {
if (warning.code === "a11y-click-events-have-key-events") return
handler(warning)
},
};
export default config;

6
vite.config.js Normal file
View File

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