mirror of
https://git.suyu.dev/suyu/website.git
synced 2025-12-25 08:44:35 +01:00
fix fonts, colors, etc
This commit is contained in:
parent
69f7976702
commit
55edefd906
19 changed files with 242 additions and 45 deletions
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Jost:ital,wght@0,100..900;1,100..900&display=swap"
|
||||
href="https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<meta property="og:type" content="summary" />
|
||||
|
|
|
|||
10
src/app.pcss
10
src/app.pcss
|
|
@ -4,14 +4,6 @@
|
|||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@font-face {
|
||||
font-family: "Roc Grotesk";
|
||||
src: url(./assets/fonts/RocGroteskWideMedium.ttf) format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Consolas";
|
||||
src: url(./assets/fonts/Consolas.ttf) format("truetype");
|
||||
|
|
@ -37,7 +29,7 @@ html {
|
|||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
font-family: "Roc Grotesk", sans-serif;
|
||||
font-family: "Outfit", sans-serif;
|
||||
}
|
||||
|
||||
::selection {
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -213,6 +213,14 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
style="background: radial-gradient(50% 50%, rgba(255,0,0,0.05), transparent); z-index: -1; width: 800px ;height: 800px; position: fixed; top: -50%; left: calc(25% - 400px);"
|
||||
/>
|
||||
|
||||
<div
|
||||
style="background: radial-gradient(50% 50%, rgba(0,128,255,0.05), transparent); z-index: -1; width: 800px ;height: 800px; position: fixed; top: -50%; right: calc(25% - 400px);"
|
||||
/>
|
||||
|
||||
<main
|
||||
class={`min-h-full w-full ${dropdownOpen || !dropdownCloseFinished ? "overflow-hidden" : ""}`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
</svelte:head>
|
||||
|
||||
<div
|
||||
class="relative flex w-full flex-col gap-6 overflow-hidden rounded-[2.25rem] rounded-bl-none rounded-br-none bg-black p-8 md:p-12 lg:rounded-bl-none lg:rounded-br-[2.25rem]"
|
||||
class="relative flex w-full flex-col gap-6 overflow-hidden rounded-[2.25rem] rounded-bl-none rounded-br-none bg-[rgba(0,0,0,0.25)] p-8 backdrop-blur-xl md:p-12 lg:rounded-bl-none lg:rounded-br-[2.25rem]"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
@ -60,8 +60,9 @@
|
|||
stroke="white"
|
||||
/>
|
||||
</svg>
|
||||
<h1 class="text-[24px] leading-[1.41] md:text-[60px] md:leading-[1.1]">
|
||||
suyu is an open-source, non-profit Switch emulator
|
||||
<h1 class="text-[24px] leading-[1.41] md:text-[56px] md:leading-[1.1]">
|
||||
<span class="font-bold text-[#60c7e9]">suyu</span> is a fully open-source
|
||||
<span class="font-bold text-[#f94d4d]">Switch</span> emulator
|
||||
</h1>
|
||||
<p class="max-w-[36rem] text-lg leading-relaxed text-[#A6A5A7]">
|
||||
suyu is a familiar C++ based Switch emulator with a focus on compatibility. Completely free
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
const navItems: NavItem[] = [
|
||||
{
|
||||
name: "Online Services",
|
||||
name: "Multiplayer",
|
||||
href: "/account",
|
||||
},
|
||||
{
|
||||
|
|
@ -30,8 +30,8 @@
|
|||
href: "/account/lobbies",
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
href: "/account/settings",
|
||||
name: "Friends",
|
||||
href: "/account/friends",
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -54,7 +54,11 @@
|
|||
const pillBounds = indicator.getBoundingClientRect();
|
||||
indicator.style.transform = `translateX(${bounds.left - navBounds.left}px)`;
|
||||
indicator.style.width = `${bounds.width}px`;
|
||||
if ((selected !== 0 && selected !== navItems.length - 1) || $reducedMotion) return;
|
||||
if (
|
||||
// (selected !== 0 && selected !== navItems.length - 1) ||
|
||||
$reducedMotion
|
||||
)
|
||||
return;
|
||||
indicator.offsetHeight;
|
||||
const transformFactor = bounds.left - pillBounds.left;
|
||||
navBar.animate(
|
||||
|
|
@ -64,23 +68,18 @@
|
|||
easing: "ease-out",
|
||||
},
|
||||
{
|
||||
transform: `translateX(${transformFactor / 100}px)`,
|
||||
offset: 0.1,
|
||||
transform: `translateX(${transformFactor / 50}px)`,
|
||||
offset: 0.4,
|
||||
easing: "ease-out",
|
||||
},
|
||||
{
|
||||
transform: `translateX(${-transformFactor / 200}px)`,
|
||||
offset: 0.8,
|
||||
easing: "ease-in",
|
||||
},
|
||||
{
|
||||
transform: "translateX(0px)",
|
||||
easing: "ease-in",
|
||||
},
|
||||
],
|
||||
{
|
||||
duration: 500,
|
||||
delay: 170,
|
||||
duration: 360,
|
||||
delay: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,12 +52,14 @@
|
|||
stroke="white"
|
||||
/>
|
||||
</svg>
|
||||
<h1 class="text-[36px] leading-[1.41] md:text-[60px] md:leading-[1.1]">
|
||||
suyu Online Services
|
||||
</h1>
|
||||
<h2 class="text-[36px] leading-[1.41] md:text-[48px] md:leading-[1.1]">Multiplayer</h2>
|
||||
<p class="text-wrap text-lg leading-relaxed text-[#A6A5A7]">
|
||||
Your token should be kept private. If you believe it has been compromised, please
|
||||
contact us immediately.
|
||||
Hey, <span
|
||||
class="bg-gradient-to-r from-[#60c7e9] via-[#e06bb3] to-[#f94d4d] bg-clip-text font-bold text-transparent"
|
||||
>{data.user.username}</span
|
||||
>! This is your <i>token</i>, used to authenticate your identity within suyu. Your token
|
||||
should be kept private. If you believe it has been compromised, please contact us
|
||||
immediately.
|
||||
</p>
|
||||
<div class="flex gap-4">
|
||||
<div
|
||||
|
|
@ -72,9 +74,5 @@
|
|||
</div>
|
||||
<button class="button-sm" on:click={copyToken}>{copyText}</button>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<a href="/account/friends" class="button-sm">Manage Friends</a>
|
||||
<a href="/account/rooms" class="button-sm">Rooms</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
10
src/routes/account/friends/+page.svelte
Normal file
10
src/routes/account/friends/+page.svelte
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<div class="relative h-[calc(100vh-200px)] flex-col gap-6 overflow-hidden">
|
||||
<div
|
||||
class="relative flex w-full flex-col gap-6 overflow-hidden rounded-[2.25rem] bg-black p-8 md:p-12"
|
||||
>
|
||||
<h2 class=" text-[36px] leading-[1.41] md:text-[48px] md:leading-[1.1]">Friends</h2>
|
||||
<p class="text-md text-wrap leading-relaxed text-[#4e4e4e]">
|
||||
TBD: madeline has no friends :( ~maddie/null
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
7
src/routes/account/lobbies/+page.server.ts
Normal file
7
src/routes/account/lobbies/+page.server.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { RoomManager } from "$lib/server/class/Room.js";
|
||||
|
||||
export function load({ request }) {
|
||||
return {
|
||||
rooms: RoomManager.getRooms().map((r) => r.toJSON()),
|
||||
};
|
||||
}
|
||||
|
|
@ -1,7 +1,15 @@
|
|||
<script lang="ts">
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<div class="relative h-[calc(100vh-200px)] flex-col gap-6 overflow-hidden">
|
||||
<div
|
||||
class="relative flex w-full flex-col gap-6 overflow-hidden rounded-[2.25rem] bg-black p-8 md:p-12"
|
||||
>
|
||||
LOBBIES YEHAHH
|
||||
{#each data.rooms as room}
|
||||
<p>{room.name}</p>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
<div class="relative h-[calc(100vh-200px)] flex-col gap-6 overflow-hidden">
|
||||
<div
|
||||
class="relative flex w-full flex-col gap-6 overflow-hidden rounded-[2.25rem] bg-black p-8 md:p-12"
|
||||
>
|
||||
SETTTTTINGGGASSS WOOOOOOOOOOOO
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -39,7 +39,7 @@ export async function POST({ request, getClientAddress }) {
|
|||
error: "missing fields",
|
||||
});
|
||||
}
|
||||
if (body.username.length < 3 || body.username.length > 24) {
|
||||
if (body.username.length < 3 || body.username.length > 24 || body.username.trim() === "") {
|
||||
return json<CreateAccountResponse>({
|
||||
success: false,
|
||||
error: "invalid username",
|
||||
|
|
|
|||
|
|
@ -36,9 +36,12 @@ export async function load({ request }) {
|
|||
const user = await useModeratorAuth(request);
|
||||
return {
|
||||
posts,
|
||||
userInfo: JSON.parse(JSON.stringify(user)) as {
|
||||
userInfo: (JSON.parse(JSON.stringify(user)) as {
|
||||
user: SuyuUser | null;
|
||||
isModerator: boolean;
|
||||
}) || {
|
||||
user: null,
|
||||
isModerator: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
<script lang="ts">
|
||||
import SvelteMarkdown from "svelte-markdown";
|
||||
import CodeRenderer from "$components/CodeRenderer.svelte";
|
||||
import type { PageData } from "./$types";
|
||||
import { onMount } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
let content = "";
|
||||
|
||||
|
|
@ -8,6 +11,12 @@
|
|||
event.preventDefault();
|
||||
console.log(content);
|
||||
};
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
onMount(() => {
|
||||
if (!data.userInfo.isModerator) goto("/blog");
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex h-[calc(100vh-196px)] min-h-[32rem] w-full flex-row gap-8">
|
||||
|
|
|
|||
47
src/routes/jwt/external/key.pem/+server.ts
vendored
Normal file
47
src/routes/jwt/external/key.pem/+server.ts
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { json } from "$lib/server/util/index.js";
|
||||
|
||||
export function GET({ request }) {
|
||||
return new Response(
|
||||
`-----BEGIN CERTIFICATE-----
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
-----END CERTIFICATE-----`,
|
||||
{
|
||||
headers: {
|
||||
"content-type": "text/plain",
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function POST({ request }) {
|
||||
return new Response();
|
||||
}
|
||||
16
src/routes/jwt/internal/+server.ts
Normal file
16
src/routes/jwt/internal/+server.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { PRIVATE_KEY } from "$lib/server/secrets/secrets.json";
|
||||
import { useAuth } from "$lib/util/api/index.js";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
export async function POST({ request }) {
|
||||
const userKey = `${request.headers.get("x-username")}:${request.headers.get("x-token")}`;
|
||||
const user = await useAuth(userKey);
|
||||
const token = jwt.sign({ ...user, apiKey: userKey }, Buffer.from(PRIVATE_KEY), {
|
||||
algorithm: "RS256",
|
||||
});
|
||||
return new Response(token, {
|
||||
headers: {
|
||||
"content-type": "text/html",
|
||||
},
|
||||
});
|
||||
}
|
||||
65
src/routes/lobby/+server.ts
Normal file
65
src/routes/lobby/+server.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { Room, RoomManager } from "$lib/server/class/Room";
|
||||
import { userRepo } from "$lib/server/repo/index.js";
|
||||
import { SuyuUser } from "$lib/server/schema";
|
||||
import { PUBLIC_KEY } from "$lib/server/secrets/secrets.json";
|
||||
import { json } from "$lib/server/util";
|
||||
import { useAuth } from "$lib/util/api/index.js";
|
||||
import type { IJwtData } from "$types/auth.js";
|
||||
import type { IRoom, LobbyResponse } from "$types/rooms";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
export async function GET({ request }) {
|
||||
return json<LobbyResponse>({
|
||||
rooms: RoomManager.getRooms().map((r) => r.toJSON()),
|
||||
});
|
||||
}
|
||||
|
||||
/* credit to janeberru for showing the shape of this data */
|
||||
export async function POST({ request, getClientAddress }) {
|
||||
// TODO: per-ip room limit
|
||||
const body: IRoom = await request.json();
|
||||
/* description may contain "### END DESCRIPTION ###" on its own line. if it does, get all lines after that */
|
||||
const parsedDescription = body.description.split("### END DESCRIPTION ###");
|
||||
console.log(parsedDescription);
|
||||
const description = parsedDescription?.slice(1)?.join("### END DESCRIPTION ###") || "";
|
||||
const opts: { [key: string]: string } = {};
|
||||
description.split("\n").forEach((line) => {
|
||||
const [key, ...values] = line.split("=");
|
||||
const value = values.join("=").trim();
|
||||
if (!key || !value) return;
|
||||
opts[key] = value;
|
||||
});
|
||||
if (opts.ip) {
|
||||
if (
|
||||
!opts.ip.match(
|
||||
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
|
||||
)
|
||||
) {
|
||||
return new Response(null, { status: 400 });
|
||||
}
|
||||
}
|
||||
const token = request.headers.get("authorization");
|
||||
if (!token) return new Response(null, { status: 401 });
|
||||
// TODO: jwt utils which type and validate automatically
|
||||
const user = await useAuth(token);
|
||||
console.log(user);
|
||||
if (!user) return new Response(null, { status: 401 });
|
||||
const room = RoomManager.createRoom({
|
||||
name: body.name,
|
||||
description: parsedDescription[0] || "",
|
||||
gameName: body.preferredGameName,
|
||||
gameId: body.preferredGameId,
|
||||
players: [
|
||||
{
|
||||
gameId: 0,
|
||||
gameName: "",
|
||||
nickname: user.username,
|
||||
},
|
||||
],
|
||||
maxPlayers: body.maxPlayers,
|
||||
ip: `${opts.ip || getClientAddress().split(":").at(-1)}:${body.port}`,
|
||||
host: user,
|
||||
hasPassword: body.hasPassword || false,
|
||||
});
|
||||
return json(room.toJSON());
|
||||
}
|
||||
30
src/routes/lobby/[id]/+server.ts
Normal file
30
src/routes/lobby/[id]/+server.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { RoomManager } from "$lib/server/class/Room";
|
||||
import { json } from "$lib/server/util/index.js";
|
||||
import { useAuth } from "$lib/util/api/index.js";
|
||||
|
||||
/* thanks again janeberru for the shape of this data */
|
||||
export async function POST({ request, params }) {
|
||||
const body = await request.json();
|
||||
const { id } = params;
|
||||
const room = RoomManager.getRoom(id);
|
||||
if (!room) return new Response(null, { status: 500 });
|
||||
const user = await useAuth(request.headers.get("authorization") || "");
|
||||
if (!user) return new Response(null, { status: 401 });
|
||||
if (user.id !== room.host.id) return new Response(null, { status: 401 });
|
||||
if (body.players.length === 0 && room.roomInfo.owner) {
|
||||
console.log(room.roomInfo.players);
|
||||
room.setPlayerList([{ gameId: 0, gameName: "", nickname: room.roomInfo.owner }]);
|
||||
}
|
||||
return json({ message: "Lobby updated successfully" });
|
||||
}
|
||||
|
||||
export async function DELETE({ request, params }) {
|
||||
const { id } = params;
|
||||
const room = RoomManager.getRoom(id);
|
||||
if (!room) return new Response(null, { status: 500 });
|
||||
const user = await useAuth(request.headers.get("authorization") || "");
|
||||
if (!user) return new Response(null, { status: 401 });
|
||||
if (user.id !== room.host.id) return new Response(null, { status: 401 });
|
||||
room.delete();
|
||||
return json(room.toJSON());
|
||||
}
|
||||
11
src/routes/profile/+server.ts
Normal file
11
src/routes/profile/+server.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { json } from "$lib/server/util/index";
|
||||
import { useAuth } from "$lib/util/api/index.js";
|
||||
|
||||
export async function GET({ request }) {
|
||||
const user = await useAuth(request.headers.get("authorization") || "");
|
||||
console.log(user);
|
||||
if (!user) return new Response(null, { status: 401 });
|
||||
return json({
|
||||
username: user.username,
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue