This commit is contained in:
ngn
2023-11-12 17:43:23 +03:00
parent 8c1552d639
commit 498a54cd20
68 changed files with 1983 additions and 301 deletions

10
app/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

2
app/.npmrc Normal file
View File

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

10
app/Dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM node:20-alpine3.17 as build
RUN apk update && apk upgrade && adduser -D svelte
USER svelte
WORKDIR /app
COPY --chown=svelteuser:svelte . /app
RUN npm install && npm run build
CMD ["npm", "run", "preview"]

20
app/README.md Normal file
View File

@ -0,0 +1,20 @@
# [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
## development setup
```bash
git clone https://github.com/ngn13/ngn13.fun && cd ngn13.fun
npm i
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)

1762
app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

22
app/package.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "website",
"version": "4.0.0",
"private": true,
"scripts": {
"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": {
"@types/dompurify": "^3.0.2",
"dompurify": "^3.0.5",
"marked": "^7.0.4"
}
}

11
app/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
app/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>

View File

@ -0,0 +1,69 @@
<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);
box-shadow: var(--box-shadow);
border-radius: 7px;
cursor: pointer;
transition: .4s;
text-decoration: none;
}
a:hover > .title {
text-shadow: var(--text-shadow);
}
.title {
border: solid 1px var(--dark-two);
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
app/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.ngn.tf/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
app/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="/services">services</NavbarLink>
<NavbarLink link="/blog">blog</NavbarLink>
<NavbarLink type="icon" link="https://github.com/ngn13/website"></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,48 @@
<script>
import { page } from "$app/stores"
export let link
export let type
let audio
function epicSound() {
audio.play()
}
</script>
<div>
<audio bind:this={audio} preload="auto">
<source src="/click.wav" type="audio/mpeg" />
</audio>
{#if type==="icon"}
<a class="icon" data-sveltekit-preload-data on:click={epicSound} href="{link}"><slot></slot></a>
{:else}
<a data-sveltekit-preload-data on:click={epicSound} href="{link}"><slot></slot></a>
{/if}
</div>
<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;
}
.icon:hover {
text-decoration: none;
text-shadow: 3px 4px 7px rgba(81, 67, 21, 0.8);
animation-name: colorAnimation;
animation-duration: 5s;
animation-iteration-count: infinite;
}
</style>

View File

@ -0,0 +1,73 @@
<script>
export let desc
export let url
let icon = "󰅇"
let audio
function copy() {
audio.play()
navigator.clipboard.writeText(url)
icon = "󰅎"
setTimeout(()=>{
icon = "󰅇"
}, 500)
}
</script>
<main>
<audio bind:this={audio} preload="auto">
<source src="/click.wav" type="audio/mpeg" />
</audio>
<div>
<h1><slot></slot></h1>
<p>{desc}</p>
</div>
<div>
<a on:click={copy} href="#">{icon}</a>
<a href="{url}"></a>
</div>
</main>
<style>
main {
display: flex;
flex-direction: row;
padding: 30px 30px 30px 30px;
background: var(--dark-two);
border-radius: 7px 7px 0px 0px;
box-shadow: var(--box-shadow);
justify-content: space-between;
align-items: center;
border: none;
color: white;
gap: 100px;
transition: .4s;
}
div h1 {
animation-name: colorAnimation;
animation-duration: 10s;
animation-iteration-count: infinite;
font-size: 30px;
}
div p {
margin-top: 10px;
font-size: 20px;
}
a {
text-align: center;
font-size: 30px;
text-decoration: none;
color: white;
border: none;
}
a:hover {
animation-name: colorAnimation;
animation-duration: 5s;
animation-iteration-count: infinite;
}
</style>

View File

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

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>

139
app/src/routes/+page.svelte Normal file
View File

@ -0,0 +1,139 @@
<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, however I am currently running Arch</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> Backend API is written in Go and it's hosted on my server</li>
<li><c></c> All assets (images, fonts etc.) are hosted on my server as well</li>
<li><c></c> No cloudflare, no CAPTCHAs, no CDNs, this website is %100 privacy friendly</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, you should definitely check it out btw (it's very active)</li>
</ul>
</Card>
<Card title="wall">
Here are some of my socials:
<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>
</ul>
If you want to contact me directly, send me an email:
<ul>
<li><c></c> <a href="mailto:ngn13proton@proton.me">Personal email address</a></li>
<li><c>󰇮</c> <a href="mailto:ngn13proton@proton.me">Proton email address</a></li>
</ul>
Or you can add me on XMPP: <c>ngn@chat.ngn13.fun</c>
</Card>
</div>
<Card title="bash donate.sh">
If you want to do something stupid with your mone-
<br>
I mean if you want to support me and my silly little work, here is my monero wallet address:
<br>
<br>
<code>
46q7G7u7cmASvJm7AmrhmNg6ctS77mYMmDAy1QxpDn5w57xV3GUY5za4ZPZHAjqaXdfS5YRWm4AVj5UArLDA1retRkJp47F
</code>
</Card>
</main>
<div class="version">
<p>v4.0</p>
</div>
<style>
main{
display: flex;
flex-direction: column;
gap: 35px;
padding: 50px;
}
code {
background: var(--dark-two);
color: white;
border-radius: var(--radius);
text-shadow: var(--text-shadow);
word-wrap: break-word;
white-space: pre-wrap;
word-break: break-word;
}
.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>

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,58 @@
<script>
import Header from "../../lib/header.svelte";
import CardLink from "../../lib/card_link.svelte";
export let data
let posts = data.posts
</script>
<svelte:head>
<title>[ngn] | 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>
{#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;
}
@media only screen and (max-width: 1316px) {
main {
padding: 50px;
}
}
</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,165 @@
<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] | {data.title}</title>
<meta content="[ngn] | blog" property="og:title" />
<meta content="{data.content.substring(0, 100)}..." 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;
justify-content: center;
}
.content {
max-width: 80%;
padding: 40px;
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,41 @@
export async function load({ fetch }) {
const api = import.meta.env.VITE_API_URL_DEV
const res = await fetch(api+"/services/all")
const data = await res.json()
if (data["error"] != ""){
return {
error: data["error"]
}
}
// Some really bad code to convert
// [service1, service2, service3...]
// to
// [[service1, service2, service3], [service4, service5...]...]
// so i can render it in the UI easily
let all = data["result"]
let counter = 0
let currentlist = []
let services = []
for (let i = 0; i < all.length; i++){
currentlist.push(all[i])
counter += 1
if(i == all.length-1 && counter != 3){
services.push(currentlist)
}
if (counter == 3) {
services.push(currentlist)
currentlist = []
counter = 0
}
}
return {
services
}
}

View File

@ -0,0 +1,76 @@
<script>
import Header from "../../lib/header.svelte";
import Service from "../../lib/service.svelte";
export let data
</script>
<svelte:head>
<title>[ngn] | services</title>
<meta content="[ngn] | services" property="og:title" />
<meta content="Stuff that I host" property="og:description" />
<meta content="https://ngn.tf" property="og:url" />
<meta content="#000000" data-react-helmet="true" name="theme-color" />
</svelte:head>
<Header><c>ls -l</c> services</Header>
<main>
<div class="info">
<h1></h1>
<p>
These are free to use FOSS services that I host on my server.
If you want an account on any of these services, or if you are having issues with them,
please send an email to services@ngn.tf
</p>
</div>
{#each data.services as services_list}
<div class="flexrow">
{#each services_list as service}
<Service url="{service.url}" desc="{service.desc}">{service.name}</Service>
{/each}
</div>
{/each}
</main>
<style>
.info h1 {
margin-right: 40px;
animation-name: colorAnimation;
animation-duration: 5s;
animation-iteration-count: infinite;
}
.info {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 40px;
color: white;
border-radius: var(--radius);
font-size: 25px;
margin-bottom: 30px;
}
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
app/static/click.wav Normal file

Binary file not shown.

13
app/static/font.css Normal file
View File

@ -0,0 +1,13 @@
/*
* fonts are located under /static/fonts, they are from nerdfonts
* see nerdfonts.com
*
*/
@font-face {
font-family: "Ubuntu";
font-style: normal;
font-weight: 300;
font-display: swap;
src: url("/fonts/UbuntuNerdFont-Regular.ttf");
}

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.

1023
app/static/github.css Normal file

File diff suppressed because it is too large Load Diff

302
app/static/global.css Normal file
View File

@ -0,0 +1,302 @@
@import "./font.css";
@import "./github.css";
:root {
--white: white;
--dark-one: black;
--dark-two: #050505;
--dark-three: #121212;
--dark-four: #101010;
--dark-fife: #3a3b3c;
--border-rad: 30px;
/*
old shadow animation
--def-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px,
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;
*/
--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;
}
* {
padding: 0;
margin: 0;
}
body {
background: var(--dark-one);
font-family: "Ubuntu", sans-serif;
overflow-x: hidden;
}
::selection {
background: rgba(100, 100, 100, 0.5);
text-decoration: underline;
}
::-webkit-scrollbar {
border-radius: 10px;
width: 10px;
}
::-webkit-scrollbar-track {
border-radius: 10px;
background: #181818;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #282828;
}
@keyframes colorAnimation {
100%,
0% {
color: rgb(255, 0, 0);
}
8% {
color: rgb(255, 127, 0);
}
16% {
color: rgb(255, 255, 0);
}
25% {
color: rgb(127, 255, 0);
}
33% {
color: rgb(0, 255, 0);
}
41% {
color: rgb(0, 255, 127);
}
50% {
color: rgb(0, 255, 255);
}
58% {
color: rgb(0, 127, 255);
}
66% {
color: rgb(0, 0, 255);
}
75% {
color: rgb(127, 0, 255);
}
83% {
color: rgb(255, 0, 255);
}
91% {
color: rgb(255, 0, 127);
}
}
@keyframes borderAnimation {
100%,
0% {
border-bottom-color: rgb(255, 0, 0);
}
8% {
border-bottom-color: rgb(255, 127, 0);
}
16% {
border-bottom-color: rgb(255, 255, 0);
}
25% {
border-bottom-color: rgb(127, 255, 0);
}
33% {
border-bottom-color: rgb(0, 255, 0);
}
41% {
border-bottom-color: rgb(0, 255, 127);
}
50% {
border-bottom-color: rgb(0, 255, 255);
}
58% {
border-bottom-color: rgb(0, 127, 255);
}
66% {
border-bottom-color: rgb(0, 0, 255);
}
75% {
border-bottom-color: rgb(127, 0, 255);
}
83% {
border-bottom-color: rgb(255, 0, 255);
}
91% {
border-bottom-color: rgb(255, 0, 127);
}
}
@keyframes fullBorderAnimation {
100%,
0% {
border-color: rgb(255, 0, 0);
}
8% {
border-color: rgb(255, 127, 0);
}
16% {
border-color: rgb(255, 255, 0);
}
25% {
border-color: rgb(127, 255, 0);
}
33% {
border-color: rgb(0, 255, 0);
}
41% {
border-color: rgb(0, 255, 127);
}
50% {
border-color: rgb(0, 255, 255);
}
58% {
border-color: rgb(0, 127, 255);
}
66% {
border-color: rgb(0, 0, 255);
}
75% {
border-color: rgb(127, 0, 255);
}
83% {
border-color: rgb(255, 0, 255);
}
91% {
border-color: rgb(255, 0, 127);
}
}
@keyframes gayShadowAnimation {
100%,
0% {
box-shadow: rgba(255, 0, 0, 0.07) 0px 1px 2px,
rgba(255, 0, 0, 0.07) 0px 2px 4px, rgba(255, 0, 0, 0.07) 0px 4px 8px,
rgba(255, 0, 0, 0.07) 0px 8px 16px, rgba(255, 0, 0, 0.07) 0px 16px 32px,
rgba(255, 0, 0, 0.07) 0px 32px 64px;
}
8% {
box-shadow: rgba(255, 127, 0, 0.07) 0px 1px 2px,
rgba(255, 127, 0, 0.07) 0px 2px 4px, rgba(255, 127, 0, 0.07) 0px 4px 8px,
rgba(255, 127, 0, 0.07) 0px 8px 16px,
rgba(255, 127, 0, 0.07) 0px 16px 32px,
rgba(255, 127, 0, 0.07) 0px 32px 64px;
}
16% {
box-shadow: rgba(255, 255, 0, 0.07) 0px 1px 2px,
rgba(255, 255, 0, 0.07) 0px 2px 4px, rgba(255, 255, 0, 0.07) 0px 4px 8px,
rgba(255, 255, 0, 0.07) 0px 8px 16px,
rgba(255, 255, 0, 0.07) 0px 16px 32px,
rgba(255, 255, 0, 0.07) 0px 32px 64px;
}
25% {
box-shadow: rgba(127, 255, 0, 0.07) 0px 1px 2px,
rgba(127, 255, 0, 0.07) 0px 2px 4px, rgba(127, 255, 0, 0.07) 0px 4px 8px,
rgba(127, 255, 0, 0.07) 0px 8px 16px,
rgba(127, 255, 0, 0.07) 0px 16px 32px,
rgba(127, 255, 0, 0.07) 0px 32px 64px;
}
33% {
box-shadow: rgba(0, 255, 0, 0.07) 0px 1px 2px,
rgba(0, 255, 0, 0.07) 0px 2px 4px, rgba(0, 255, 0, 0.07) 0px 4px 8px,
rgba(0, 255, 0, 0.07) 0px 8px 16px, rgba(0, 255, 0, 0.07) 0px 16px 32px,
rgba(0, 255, 0, 0.07) 0px 32px 64px;
}
41% {
box-shadow: rgba(0, 255, 127, 0.07) 0px 1px 2px,
rgba(0, 255, 127, 0.07) 0px 2px 4px, rgba(0, 255, 127, 0.07) 0px 4px 8px,
rgba(0, 255, 127, 0.07) 0px 8px 16px,
rgba(0, 255, 127, 0.07) 0px 16px 32px,
rgba(0, 255, 127, 0.07) 0px 32px 64px;
}
50% {
box-shadow: rgba(0, 255, 255, 0.07) 0px 1px 2px,
rgba(0, 255, 255, 0.07) 0px 2px 4px, rgba(0, 255, 255, 0.07) 0px 4px 8px,
rgba(0, 255, 255, 0.07) 0px 8px 16px,
rgba(0, 255, 255, 0.07) 0px 16px 32px,
rgba(0, 255, 255, 0.07) 0px 32px 64px;
}
58% {
box-shadow: rgba(0, 127, 255, 0.07) 0px 1px 2px,
rgba(0, 127, 255, 0.07) 0px 2px 4px, rgba(0, 127, 255, 0.07) 0px 4px 8px,
rgba(0, 127, 255, 0.07) 0px 8px 16px,
rgba(0, 127, 255, 0.07) 0px 16px 32px,
rgba(0, 127, 255, 0.07) 0px 32px 64px;
}
66% {
box-shadow: rgba(0, 0, 255, 0.07) 0px 1px 2px,
rgba(0, 0, 255, 0.07) 0px 2px 4px, rgba(0, 0, 255, 0.07) 0px 4px 8px,
rgba(0, 0, 255, 0.07) 0px 8px 16px, rgba(0, 0, 255, 0.07) 0px 16px 32px,
rgba(0, 0, 255, 0.07) 0px 32px 64px;
}
75% {
box-shadow: rgba(127, 0, 255, 0.07) 0px 1px 2px,
rgba(127, 0, 255, 0.07) 0px 2px 4px, rgba(127, 0, 255, 0.07) 0px 4px 8px,
rgba(127, 0, 255, 0.07) 0px 8px 16px,
rgba(127, 0, 255, 0.07) 0px 16px 32px,
rgba(127, 0, 255, 0.07) 0px 32px 64px;
}
83% {
box-shadow: rgba(255, 0, 255, 0.07) 0px 1px 2px,
rgba(255, 0, 255, 0.07) 0px 2px 4px, rgba(255, 0, 255, 0.07) 0px 4px 8px,
rgba(255, 0, 255, 0.07) 0px 8px 16px,
rgba(255, 0, 255, 0.07) 0px 16px 32px,
rgba(255, 0, 255, 0.07) 0px 32px 64px;
}
91% {
box-shadow: rgba(255, 0, 127, 0.07) 0px 1px 2px,
rgba(255, 0, 127, 0.07) 0px 2px 4px, rgba(255, 0, 127, 0.07) 0px 4px 8px,
rgba(255, 0, 127, 0.07) 0px 8px 16px,
rgba(255, 0, 127, 0.07) 0px 16px 32px,
rgba(255, 0, 127, 0.07) 0px 32px 64px;
}
}
@keyframes underlineAnimation {
100%,
0% {
text-decoration-color: rgb(255, 0, 0);
}
8% {
text-decoration-color: rgb(255, 127, 0);
}
16% {
text-decoration-color: rgb(255, 255, 0);
}
25% {
text-decoration-color: rgb(127, 255, 0);
}
33% {
text-decoration-color: rgb(0, 255, 0);
}
41% {
text-decoration-color: rgb(0, 255, 127);
}
50% {
text-decoration-color: rgb(0, 255, 255);
}
58% {
text-decoration-color: rgb(0, 127, 255);
}
66% {
text-decoration-color: rgb(0, 0, 255);
}
75% {
text-decoration-color: rgb(127, 0, 255);
}
83% {
text-decoration-color: rgb(255, 0, 255);
}
91% {
text-decoration-color: rgb(255, 0, 127);
}
}
.c, c {
animation-name: colorAnimation;
animation-iteration-count: infinite;
animation-duration: 10s;
}

18
app/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
app/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()]
});