Compare commits

...

54 commits
main ... main

Author SHA1 Message Date
blurryface 31ab838baa
Merge pull request 'Update the Minecraft version in NavBar.tsx' (#25) from MiniStumpy/Website-fixver:main into main
All checks were successful
Deploy Website / deploy (push) Successful in 59s
Lint Codebase / lint (push) Successful in 1m13s
Reviewed-on: CRSS/Website#25
2024-09-16 16:43:35 +00:00
MiniStumpy a4a348e2da
Update the Minecraft version
Some checks failed
Lint Codebase / lint (pull_request) Has been cancelled
2024-09-16 16:34:31 +00:00
worldwidepixel 0c3bc195fb Add CRSS Premium+ and EnterpriseX
All checks were successful
Deploy Website / deploy (push) Successful in 57s
Lint Codebase / lint (push) Successful in 1m9s
2024-09-04 13:33:52 -07:00
worldwidepixel 39e87cffdc feat: Formatting
All checks were successful
Deploy Website / deploy (push) Successful in 1m28s
Lint Codebase / lint (push) Successful in 1m5s
2024-09-04 10:57:01 -07:00
blryface 70a173d02b Rules page
Some checks failed
Deploy Website / deploy (push) Failing after 20s
Lint Codebase / lint (push) Failing after 1m9s
2024-09-03 20:43:14 -03:00
WorldWidePixel 30af0b0cf6
feat: privacy
All checks were successful
Deploy Website / deploy (push) Successful in 1m14s
Lint Codebase / lint (push) Successful in 55s
bye bye guild scope
2024-08-30 19:35:54 +00:00
TheClashFruit d1c80e2acc
Merge branch 'main' of https://git.theclashfruit.me/CRSS/Website
All checks were successful
Deploy Website / deploy (push) Successful in 49s
Lint Codebase / lint (push) Successful in 55s
2024-08-30 19:09:41 +02:00
TheClashFruit d3a8d01824
feat: edit people on the admin dashboard 2024-08-30 19:09:39 +02:00
TheClashFruit a65bb1bb5d
chore: some naming changes 2024-08-30 18:27:10 +02:00
TheClashFruit 4af5a5e4b5
feat: comunicate with the server 🔥 2024-08-30 18:26:49 +02:00
WorldWidePixel 22135381ec
feat: 1.15.2
All checks were successful
Deploy Website / deploy (push) Successful in 46s
Lint Codebase / lint (push) Successful in 57s
Quick and lazy change.
2024-08-30 15:15:15 +00:00
TheClashFruit b478e46055
feat: better session display
All checks were successful
Deploy Website / deploy (push) Successful in 1m21s
Lint Codebase / lint (push) Successful in 57s
2024-08-30 16:05:54 +02:00
TheClashFruit 9bb6362a68
fix: discord global_name are optional 😶
All checks were successful
Deploy Website / deploy (push) Successful in 44s
Lint Codebase / lint (push) Successful in 56s
2024-08-30 15:04:27 +02:00
TheClashFruit 9e38c87a5a
feat: add error loging to the auth api route
All checks were successful
Deploy Website / deploy (push) Successful in 46s
Lint Codebase / lint (push) Successful in 1m1s
2024-08-30 14:50:04 +02:00
TheClashFruit 020bbd7a43
fix(ci): fix deploy
All checks were successful
Deploy Website / deploy (push) Successful in 1m32s
Lint Codebase / lint (push) Successful in 1m5s
2024-08-30 13:58:16 +02:00
TheClashFruit 26160c87d0
feat: merge?
Some checks failed
Deploy Website / deploy (push) Failing after 6s
Lint Codebase / lint (push) Successful in 54s
2024-08-30 13:53:42 +02:00
TheClashFruit 8e8334981e
feat: induvidual nation pages
All checks were successful
Lint Codebase / lint (push) Successful in 57s
Lint Codebase / lint (pull_request) Successful in 54s
2024-08-30 13:40:02 +02:00
TheClashFruit cb11246c1e
feat(ci): add deploy action
All checks were successful
Lint Codebase / lint (push) Successful in 1m1s
Lint Codebase / lint (pull_request) Successful in 59s
2024-08-30 13:08:38 +02:00
TheClashFruit 24ff2d39b7
fix: fix font-weight for nations list
All checks were successful
Lint Codebase / lint (push) Successful in 1m3s
Lint Codebase / lint (pull_request) Successful in 53s
2024-08-29 23:59:56 +02:00
TheClashFruit 981d85b254
feat: remove ! from the title in the header
All checks were successful
Lint Codebase / lint (push) Successful in 57s
2024-08-29 23:55:40 +02:00
TheClashFruit 6ef13ca090
fix: explicitly set the locale for toLocaleDateString() 2024-08-29 23:55:24 +02:00
TheClashFruit ba5a9ff29c
feat: nations list
All checks were successful
Lint Codebase / lint (push) Successful in 58s
2024-08-29 23:46:56 +02:00
TheClashFruit b7bcf5eddd
feat: add notice to map page 2024-08-29 23:15:02 +02:00
TheClashFruit 32919ca405
fix: trim = off the user id base64 too in the token 2024-08-29 23:00:20 +02:00
TheClashFruit 2c998c8302
feat: terminal thing on the admin panel
All checks were successful
Lint Codebase / lint (push) Successful in 57s
2024-08-29 18:39:37 +02:00
TheClashFruit 60a53467b2
feat: some website <-> server communications "proposal"
All checks were successful
Lint Codebase / lint (push) Successful in 1m5s
2024-08-29 17:29:43 +02:00
TheClashFruit 0daff3ecfa
feat: add tos and privacy 2024-08-29 17:06:43 +02:00
TheClashFruit 004734ea24
feat: add state to discord ouath 2024-08-29 16:29:23 +02:00
TheClashFruit 4880cfe613
fix: remove session cookie if session is not found
All checks were successful
Lint Codebase / lint (push) Successful in 1m0s
2024-08-29 16:12:31 +02:00
TheClashFruit 77234531c5
feat: logout + better token generation 2024-08-29 16:06:07 +02:00
TheClashFruit 7991ce7ee0
feat: git data 2024-08-29 15:33:37 +02:00
TheClashFruit 305e507407
fix: add license back 2024-08-29 15:25:04 +02:00
TheClashFruit ea9c5ff5bd
docs: readme
All checks were successful
Lint Codebase / lint (push) Successful in 1m4s
2024-08-29 15:24:20 +02:00
TheClashFruit 844a9060de
feat: Next.js rewrite
All checks were successful
Lint Codebase / lint (push) Successful in 1m10s
2024-08-29 15:20:28 +02:00
blurryface e0dcd9a335
fix: Make website use js and react
All checks were successful
Upload Website / sftp (push) Successful in 19s
2024-07-25 06:11:18 +02:00
blurryface c7e681dee1
refactor: rewrite whole site
All checks were successful
Upload Website / sftp (push) Successful in 18s
2024-07-19 19:56:15 +02:00
WorldWidePixel 0965288448
TOA -> toa
All checks were successful
Upload Website / sftp (push) Successful in 16s
CAPITALISATION matters
2024-06-23 06:20:35 +02:00
Lupancham 1906432e1f
TTK -> TOA (#22)
All checks were successful
Upload Website / sftp (push) Successful in 25s
Reviewed-on: CRSS/Website#22
Reviewed-by: blurryface <blurryface@git.theclashfruit.me>
Co-authored-by: Lupancham <lupancham@gmail.com>
Co-committed-by: Lupancham <lupancham@gmail.com>
2024-06-23 06:18:45 +02:00
TheClashFruit 36853a4723
feat: add id to team.json, remove debug info from 404
All checks were successful
Lint PHP Code / lint (push) Successful in 1m1s
2024-06-17 20:13:36 +02:00
TheClashFruit 0717d94521
feat: a few improvements to the team grid 2024-06-17 13:47:37 +02:00
TheClashFruit 8593a58645
feat: about tab 2024-06-17 13:29:21 +02:00
TheClashFruit 48528ecde4
fix: missing comma and indentation
All checks were successful
Upload Website / sftp (push) Successful in 17s
2024-06-11 14:50:08 +02:00
polycord 16ba01ab37
adding deforest and updating info text on homepage (#20)
All checks were successful
Upload Website / sftp (push) Successful in 21s
Reviewed-on: CRSS/Website#20
Reviewed-by: blurryface <blurryface@git.theclashfruit.me>
Co-authored-by: polycord <polycord@git.theclashfruit.me>
Co-committed-by: polycord <polycord@git.theclashfruit.me>
2024-06-11 14:15:50 +02:00
TheClashFruit c427cb4829
feat: homepage content
Some checks failed
Lint PHP Code / lint (push) Failing after 56s
2024-06-10 21:38:49 +02:00
TheClashFruit e1c569e0e7
fix: put the modpack link in an anchor tag
All checks were successful
Upload Website / sftp (push) Successful in 17s
why wasn't it in one 🗿
2024-06-10 18:51:57 +02:00
TheClashFruit 572df6b14b
fix: use p instead of ol and close it at the modpack part
All checks were successful
Upload Website / sftp (push) Successful in 16s
2024-06-10 18:50:17 +02:00
Lupancham 056bb4f1c2
CI added to website (#18)
All checks were successful
Upload Website / sftp (push) Successful in 17s
Reviewed-on: CRSS/Website#18
Reviewed-by: blurryface <blurryface@git.theclashfruit.me>
Co-authored-by: Lupancham <lupancham@gmail.com>
Co-committed-by: Lupancham <lupancham@gmail.com>
2024-06-10 17:48:20 +02:00
blurryface 4bda691a8d
Merge pull request 'Add modpack link into main website page' (#19) from KTrain5169/CRSSWebsite:main into main
All checks were successful
Upload Website / sftp (push) Successful in 18s
Reviewed-on: CRSS/Website#19
2024-06-10 14:35:12 +02:00
KTrain5169 9bd26dca3c
Add modpack link
Signed-off-by: Forgejo <>
2024-06-10 13:58:04 +02:00
blurryface 16bedf6ba7
New IP, 1.11
All checks were successful
Upload Website / sftp (push) Successful in 16s
2024-06-09 22:21:56 +02:00
blurryface d13db9280e
New sponsors
All checks were successful
Upload Website / sftp (push) Successful in 19s
someone pr more pls
2024-06-09 22:21:01 +02:00
TheClashFruit aa1ece3db8
feat: separate stuff 2024-06-09 16:52:29 +02:00
TheClashFruit 427b643fc9
feat: start v2 of the website
All checks were successful
Lint PHP Code / lint (push) Successful in 1m9s
2024-06-09 15:47:59 +02:00
blurryface efcb438b81
Merge pull request 'Add TCG' (#16) from blurryface/Website:main into main
All checks were successful
Upload Website / sftp (push) Successful in 17s
Reviewed-on: CRSS/Website#16
2024-06-08 09:48:56 +02:00
124 changed files with 9013 additions and 3010 deletions

View file

@ -1,9 +1,15 @@
DISCORD_CLIENT=
DISCORD_SECRET=
DISCORD_REDIRECT=
DISCORD_OAUTH=
DISCORD_REDIRECT="http://localhost:3000/api/v1/auth"
MYSQL_HOST=
MYSQL_DB=
MYSQL_USER=
MYSQL_PASS=
DISCORD_API="https://discord.com/api/v10"
DB_NAME=
DB_USER=
DB_PASS=
DB_HOST=
DB_PORT=
AUTH_SECRET=
MC_API=

17
.eslintrc.json Normal file
View file

@ -0,0 +1,17 @@
{
"extends": "next/core-web-vitals",
"rules": {
"indent": [
"error",
2
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
}

View file

@ -0,0 +1,23 @@
name: Deploy Website
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy Over SSH
uses: https://github.com/nekiro/ssh-job@main
with:
host: ${{ secrets.HOST }}
user: crss
password: ${{ secrets.DEPLOY_PASSWORD }}
command: |
cd ~/crss
git pull
~/.local/share/pnpm/pnpm install
~/.local/share/pnpm/pnpm build
sudo systemctl restart crss.service

View file

@ -0,0 +1,24 @@
name: Lint Codebase
on:
push:
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout The Repository
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install PNPM
uses: https://github.com/pnpm/action-setup@v3.0.0
with:
version: 9
- name: Lint
run: |
pnpm install
pnpm lint

View file

@ -1,34 +0,0 @@
name: Upload Website
on:
push:
branches:
- main
jobs:
sftp:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Upload files via SFTP
uses: https://github.com/wangyucode/sftp-upload-action@v2.0.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USER }}
password: ${{ secrets.PASSWORD }}
forceUpload: false
localDir: '.'
remoteDir: '/var/www/crss'
exclude: '.*,.git*,.gitea*,LICENSE,README.md'
- name: Install dependencies over SSH
uses: https://github.com/nekiro/ssh-job@main
with:
host: ${{ secrets.HOST }}
user: ${{ secrets.USER }}
password: ${{ secrets.PASSWORD }}
command: |
cd /var/www/crss
composer install --no-dev --optimize-autoloader

121
.gitignore vendored
View file

@ -1,103 +1,36 @@
# Created by https://www.toptal.com/developers/gitignore/api/phpstorm+all,composer,dotenv
# Edit at https://www.toptal.com/developers/gitignore?templates=phpstorm+all,composer,dotenv
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
### Composer ###
composer.phar
/vendor/
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
# composer.lock
# testing
/coverage
### dotenv ###
.env
# next.js
/.next/
/out/
### PhpStorm+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# production
/build
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# misc
.DS_Store
*.pem
# AWS User-specific
.idea/**/aws.xml
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Generated files
.idea/**/contentModel.xml
# local env files
.env*.local
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# vercel
.vercel
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### PhpStorm+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
.idea/*
!.idea/codeStyles
!.idea/runConfigurations
# End of https://www.toptal.com/developers/gitignore/api/phpstorm+all,composer,dotenv
# typescript
*.tsbuildinfo
next-env.d.ts

5
.prettierrc Normal file
View file

@ -0,0 +1,5 @@
{
"tabWidth": 2,
"singleQuote": true,
"semi": true
}

15
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
}
]
}

6
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,6 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

View file

@ -1,5 +1,17 @@
# Website
# CRSS Server's Website
> Newer version of the CRSS site written with PHP!
This is the official website for the CRSS Server.
# why did I choose php help me
## Documentation
### Contributing
tba
## License
```
This work is licensed under CC BY 4.0.
```
See the [LICENSE](LICENSE) file for more information.

View file

@ -1,32 +0,0 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/util/Database.php';
require __DIR__ . '/util/Discord.php';
require __DIR__ . '/util/Admin.php';
use Twig\Loader\FilesystemLoader;
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
$loader = new FilesystemLoader('template');
$twig = new Twig\Environment($loader);
$twig->addGlobal('discord_auth', $_ENV['DISCORD_OAUTH']);
$discord = new Discord(
$_ENV['DISCORD_CLIENT'],
$_ENV['DISCORD_SECRET'],
$_ENV['DISCORD_REDIRECT']
);
$mysql = new Database(
$_ENV['MYSQL_HOST'],
$_ENV['MYSQL_USER'],
$_ENV['MYSQL_PASS'],
$_ENV['MYSQL_DB']
);

View file

@ -1,49 +0,0 @@
<?php
global $discord, $mysql;
require_once "_config.php";
session_start();
if(isset($_GET['code'])) {
$res = $discord->validateCode($_GET['code']);
if(!$res['error']) {
$_SESSION['access_token'] = $res['access_token'];
$_SESSION['refresh_token'] = $res['refresh_token'];
$_SESSION['expires_in'] = $res['expires_in'];
$guilds = $discord->getGuilds($res['access_token']);
$guildIds = array();
foreach ($guilds as $guild) {
$guildIds[] = $guild['id'];
}
if(!in_array('1127731341283307520', $guildIds) || !in_array('1195393418151596032', $guildIds)) {
echo json_encode(array(
'error' => true,
'error_description' => 'You are not in any of CRSS\'s guilds.'
));
} else {
$_SESSION['user'] = $discord->getUser($res['access_token']);
$mysql->createUserRecord($_SESSION['user']);
if (isset($_GET['state'])) {
header('Location: ' . $_GET['state']);
} else {
header('Location: /');
}
}
} else {
echo json_encode($res);
}
} else {
echo json_encode(array(
'error' => true,
'error_description' => 'No code provided.'
));
}

29
components/AdBanner.tsx Normal file
View file

@ -0,0 +1,29 @@
'use client';
import styles from '@/styles/AdBanner.module.scss';
import { Info } from 'lucide-react';
export default function AdBanner() {
return (
<div className={styles.container} style={{ display: 'flex', justifyContent: 'center', marginBottom: '1.5rem' }}>
<iframe id='a4962f15' name='a4962f15' src='https://ads.theclashfruit.me/www/delivery/afr.php?zoneid=3&amp;cb=75234' frameBorder='0' scrolling='no' width='468' height='60' allow='autoplay'></iframe>
</div>
);
/*
return (
<div className={styles.adBanner}>
<div className={styles.adContent}>
</div>
<div className={styles.adRevive}>
<iframe id='a4962f15' name='a4962f15' src='https://ads.theclashfruit.me/www/delivery/afr.php?zoneid=3&amp;cb=75234' frameBorder='0' scrolling='no' width='468' height='60' allow='autoplay'></iframe>
</div>
<button className={styles.adInfo}>
<Info />
</button>
</div>
);
*/
}

10
components/Card.tsx Normal file
View file

@ -0,0 +1,10 @@
import styles from '@/styles/Card.module.scss';
export default function Card({ children, className }: { children: React.ReactNode, className?: string }) {
return (
<div className={`${styles.card} ${className}`}>
{ children }
</div>
);
}

51
components/Dropdown.tsx Normal file
View file

@ -0,0 +1,51 @@
import styles from '@/styles/Dropdown.module.scss';
import Link from 'next/link';
import { MouseEventHandler, useRef } from 'react';
export default function Dropdown({ children, items, className }: { children: React.ReactNode, items: { divider?: boolean, icon?: any, label?: string, href?: string, onClick?: MouseEventHandler<HTMLAnchorElement> }[], className: string }) {
const dropDownRef = useRef<HTMLDivElement>(null);
const handleClick = () => {
dropDownRef.current?.classList.toggle(styles.open);
};
return (
<div className={`${styles.dropDown} ${className}`} ref={dropDownRef}>
<label onClick={handleClick}>
{children}
</label>
<div className={styles.dropDownMenu}>
<ul>
{items.map((item, i) => (
(item.divider &&
<li key={i}>
<div className={styles.divider}></div>
</li>
) || (
<li key={i}>
{item.href ? (
<Link href={item.href!}>
{item.icon && <item.icon />}
{item.label}
</Link>
) : (
<a onClick={item.onClick}>
{item.icon && <item.icon />}
{item.label}
</a>
)}
</li>
)
))}
</ul>
</div>
<div className={styles.mobileOverlay} onClick={handleClick} />
</div>
);
}

70
components/Footer.tsx Normal file
View file

@ -0,0 +1,70 @@
import {
SiModrinth,
SiForgejo,
SiYoutube,
SiDiscord
} from '@icons-pack/react-simple-icons';
import Link from 'next/link';
import styles from '@/styles/Footer.module.scss';
import AdBanner from './AdBanner';
import getConfig from 'next/config';
export default function Footer() {
const { publicRuntimeConfig } = getConfig();
const git = publicRuntimeConfig.git;
return (
<>
<AdBanner />
<footer className={styles.pageFooter}>
<div className={styles.container}>
<div>
<p>
Copyright &copy; {new Date().getFullYear()} CRSS
</p>
<p>
Website originally designed by Myadeleines, heavily modified and rewritten by TheClashFruit.
</p>
</div>
<div>
<ul>
<li>
<Link href="https://modrinth.com/organization/crss" target="_blank">
<SiModrinth />
</Link>
</li>
<li>
<Link href="https://git.theclashfruit.me/crss" target="_blank">
<SiForgejo />
</Link>
</li>
<li>
<Link href="https://youtube.com/@CRSS666" target="_blank">
<SiYoutube />
</Link>
</li>
<li>
<Link href="https://discord.gg/rGjCKawPkS" target="_blank">
<SiDiscord />
</Link>
</li>
</ul>
<p>
CRSS/Website
<br />
{git.branch}@<a href={`https://git.theclashfruit.me/CRSS/Website/commit/${git.commit.sha}`}>{git.commit.sha.slice(0, 7)}</a>
</p>
</div>
</div>
</footer>
</>
);
}

61
components/Meta.tsx Normal file
View file

@ -0,0 +1,61 @@
import Head from 'next/head';
import { useRouter } from 'next/router';
export default function Meta({ page }: { page: { title: string, user?: any } }) {
const router = useRouter();
let ldJson: any = {
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'Clyde\'s Real Survival SMP',
alternateName: [ 'CRSS' ],
url: 'https://crss.cc'
};
if (page.user) {
ldJson = {
'@context': 'https://schema.org',
'@type': 'ProfilePage',
mainEntity: {
'@type': 'Person',
name: page.user.global_name,
alternateName: page.user.username,
identifier: page.user.did,
image: `https://cdn.discordapp.com/avatars/${page.user.did}/${page.user.avatar}.png`,
}
};
}
return (
<Head>
<title>{page.title} &bull; Clyde&apos;s Real Survival SMP</title>
<meta name="name" content={`${page.title} &bull; Clyde's Real Survival SMP`} />
<meta name="description" content="A very cool minecraft SMP that updates to every version starting from b1.0." />
<meta name="keywords" content="crss, minecraft, beta, alpha, release, new, 1.0, version" />
<meta name="theme-color" content="#537F53" />
<meta property="og:site_name" content="Clyde's Real Survival SMP" />
<meta property="og:title" content={page.title} />
<meta property="og:url" content={`https://crss.cc${router.asPath}`} />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_GB" />
<meta property="og:image" content="https://crss.fra1.cdn.digitaloceanspaces.com/img/social_image.png" />
<meta property="og:description" content="A very cool minecraft SMP that updates to every version starting from b1.0." />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={`${page.title} &bull; Clyde's Real Survival SMP`} />
<meta name="twitter:description" content="A very cool minecraft SMP that updates to every version starting from b1.0." />
<meta name="twitter:image" content="https://crss.fra1.cdn.digitaloceanspaces.com/img/social_image.png" />
<meta property="twitter:domain" content="crss.cc" />
<meta property="twitter:url" content={`https://crss.cc${router.asPath}`} />
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ldJson) }} />
<link rel="canonical" href={`https://crss.cc${router.asPath}`} />
<link rel="icon" href="/favicon.ico" />
</Head>
);
}

273
components/NavBar.tsx Normal file
View file

@ -0,0 +1,273 @@
import {
Menu,
Home,
Scale,
AtSign,
Images,
Map,
Gamepad,
User,
Settings,
LayoutDashboard,
LogOut,
LogIn,
Earth,
X,
PlusIcon,
Plus,
} from 'lucide-react';
import Link from 'next/link';
import styles from '@/styles/NavBar.module.scss';
import { getCookie } from '@/utils/cookies';
import Logo from '@/public/logo.svg';
import { useRouter } from 'next/router';
import getConfig from 'next/config';
import { useEffect, useRef, useState } from 'react';
import Dropdown from './Dropdown';
import { useUser } from '@/context/UserContext';
import { Permission, hasPermission } from '@/utils/permissions';
export default function NavBar({ currentPage }: { currentPage: string }) {
const { user, isLoggedIn } = useUser();
const [navOpen, setNavOpen] = useState(false);
const { publicRuntimeConfig } = getConfig();
const router = useRouter();
const server = {
version: '1.16.5',
};
const buildDiscordUrl = (): string => {
const url = new URL('https://discord.com/api/oauth2/authorize');
url.searchParams.append('client_id', publicRuntimeConfig.discord.clientId);
url.searchParams.append('response_type', 'code');
url.searchParams.append(
'redirect_uri',
publicRuntimeConfig.discord.redirectUri
);
url.searchParams.append(
'scope',
publicRuntimeConfig.discord.scopes.join(' ')
);
url.searchParams.append('state', router.asPath);
return url.toString();
};
return (
<>
<header className={styles.pageHero}>
<div className={styles.heroOverlay}>
<div className={styles.container}>
<div>
<Logo />
<h1>Clyde&apos;s Real Survival SMP</h1>
</div>
<div>
<label htmlFor="ip">Server Address:</label>
<input
type="text"
value="play.crss.cc"
id="ip"
readOnly
size={8}
/>
<label htmlFor="ip">Version: {server.version}</label>
</div>
</div>
</div>
</header>
<nav className={`${styles.navBar} ${navOpen ? styles.navOpen : ''}`}>
<div className={styles.container}>
<div className={styles.navMobileContainer}>
<button
className={styles.navToggle}
onClick={() => {
setNavOpen(!navOpen);
}}
>
{!navOpen ? <Menu /> : <X />}
</button>
</div>
<div className={styles.navCollapse}>
<ul>
<li>
<Link
href={currentPage == 'home' ? '#' : '/'}
className={currentPage == 'home' ? styles.active : ''}
>
<Home />
Home
</Link>
</li>
<li>
<Link
href={currentPage == 'rules' ? '#' : '/rules'}
className={currentPage == 'rules' ? styles.active : ''}
>
<Scale />
Rules
</Link>
</li>
<li>
<Link
href={currentPage == 'about' ? '#' : '/about'}
className={currentPage == 'about' ? styles.active : ''}
>
<AtSign />
About
</Link>
</li>
<li>
<Link
href={currentPage == 'gallery' ? '#' : '/gallery'}
className={currentPage == 'gallery' ? styles.active : ''}
>
<Images />
Gallery
</Link>
</li>
<li>
<Link
href={currentPage == 'map' ? '#' : '/map'}
className={currentPage == 'map' ? styles.active : ''}
>
<Map />
Map
</Link>
</li>
<li>
<Link
href={currentPage == 'nations' ? '#' : '/nations'}
className={currentPage == 'nations' ? styles.active : ''}
>
<Earth />
Nations
</Link>
</li>
</ul>
<ul>
{isLoggedIn && user && (
<li>
{(hasPermission(user.permissions, Permission.Admin) && (
<Dropdown
items={[
{
icon: User,
label: 'Profile',
href: `/u/${
user ? user.names.username : 'Loading...'
}`,
},
{
icon: Settings,
label: 'Settings',
href: '/settings',
},
{
icon: Plus,
label: 'Premium+',
href: '/pp',
},
{
divider: true,
},
{
icon: LayoutDashboard,
label: 'Admin',
href: '/admin',
},
{
icon: LogOut,
label: 'Logout',
onClick: async (e) => {
e.preventDefault();
await fetch('/api/v1/session', {
method: 'DELETE',
});
router.reload();
},
},
]}
className={styles.dropDown}
>
<User />
{user ? user.names.global_name : 'Loading...'}
</Dropdown>
)) || (
<Dropdown
items={[
{
icon: User,
label: 'Profile',
href: `/u/${
user ? user.names.username : 'Loading...'
}`,
},
{
icon: Settings,
label: 'Settings',
href: '/settings',
},
{
divider: true,
},
{
icon: LogOut,
label: 'Logout',
onClick: async (e) => {
e.preventDefault();
await fetch('/api/v1/session', {
method: 'DELETE',
});
router.reload();
},
},
]}
className={styles.dropDown}
>
<User />
{user ? user.names.global_name : 'Loading...'}
</Dropdown>
)}
</li>
)}
{!isLoggedIn && !user && (
<li>
<Link href={buildDiscordUrl()}>
<LogIn />
Login
</Link>
</li>
)}
</ul>
</div>
</div>
</nav>
</>
);
}

View file

@ -0,0 +1,21 @@
import { motion } from 'framer-motion';
import styles from '@/styles/global.module.scss';
export default function PageContent({ children }: { children: React.ReactNode }) {
return (
<motion.main
className={styles.pageContent}
initial={{ scale: 0.96, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.96, opacity: 0 }}
transition={{ duration: 0.24, ease: 'easeInOut', bounce: 0.45, type: 'spring', stiffness: 100, opacity: { bounce: 0 } }}
>
<div className={styles.container}>
{children}
</div>
</motion.main>
);
}

View file

@ -1,8 +0,0 @@
{
"require": {
"twig/twig": "^3.0",
"anlutro/curl": "^1.5",
"vlucas/phpdotenv": "^5.5",
"bramus/router": "1.6"
}
}

654
composer.lock generated
View file

@ -1,654 +0,0 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "37fb0f4a910e4ba4add92cd9abbdf84a",
"packages": [
{
"name": "anlutro/curl",
"version": "1.5.2",
"source": {
"type": "git",
"url": "https://github.com/anlutro/php-curl.git",
"reference": "1c569768a106dd40f5160ebebb703f09c6b58c8b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/anlutro/php-curl/zipball/1c569768a106dd40f5160ebebb703f09c6b58c8b",
"reference": "1c569768a106dd40f5160ebebb703f09c6b58c8b",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"mockery/mockery": "0.9.*",
"phpunit/phpunit": "4.*"
},
"type": "library",
"autoload": {
"psr-4": {
"anlutro\\cURL\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Andreas Lutro",
"email": "anlutro@gmail.com"
}
],
"description": "Simple OOP cURL wrapper.",
"support": {
"issues": "https://github.com/anlutro/php-curl/issues",
"source": "https://github.com/anlutro/php-curl/tree/1.5.2"
},
"time": "2023-06-27T07:39:41+00:00"
},
{
"name": "bramus/router",
"version": "1.6",
"source": {
"type": "git",
"url": "https://github.com/bramus/router.git",
"reference": "d2cf97d5c471e272ac5a2a88b652bc75089c8ae3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bramus/router/zipball/d2cf97d5c471e272ac5a2a88b652bc75089c8ae3",
"reference": "d2cf97d5c471e272ac5a2a88b652bc75089c8ae3",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.14",
"phpunit/php-code-coverage": "~2.0",
"phpunit/phpunit": "~4.8"
},
"type": "library",
"autoload": {
"psr-0": {
"Bramus": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bram(us) Van Damme",
"email": "bramus@bram.us",
"homepage": "http://www.bram.us"
}
],
"description": "A lightweight and simple object oriented PHP Router",
"homepage": "https://github.com/bramus/router",
"keywords": [
"router",
"routing"
],
"support": {
"issues": "https://github.com/bramus/router/issues",
"source": "https://github.com/bramus/router/tree/1.6"
},
"time": "2021-07-23T09:48:14+00:00"
},
{
"name": "graham-campbell/result-type",
"version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/GrahamCampbell/Result-Type.git",
"reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831",
"reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
"phpoption/phpoption": "^1.9.1"
},
"require-dev": {
"phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12"
},
"type": "library",
"autoload": {
"psr-4": {
"GrahamCampbell\\ResultType\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
}
],
"description": "An Implementation Of The Result Type",
"keywords": [
"Graham Campbell",
"GrahamCampbell",
"Result Type",
"Result-Type",
"result"
],
"support": {
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
"type": "tidelift"
}
],
"time": "2023-02-25T20:23:15+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.9.1",
"source": {
"type": "git",
"url": "https://github.com/schmittjoh/php-option.git",
"reference": "dd3a383e599f49777d8b628dadbb90cae435b87e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e",
"reference": "dd3a383e599f49777d8b628dadbb90cae435b87e",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": true
},
"branch-alias": {
"dev-master": "1.9-dev"
}
},
"autoload": {
"psr-4": {
"PhpOption\\": "src/PhpOption/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Johannes M. Schmitt",
"email": "schmittjoh@gmail.com",
"homepage": "https://github.com/schmittjoh"
},
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
}
],
"description": "Option Type for PHP",
"keywords": [
"language",
"option",
"php",
"type"
],
"support": {
"issues": "https://github.com/schmittjoh/php-option/issues",
"source": "https://github.com/schmittjoh/php-option/tree/1.9.1"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
"type": "tidelift"
}
],
"time": "2023-02-25T19:38:58+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "42292d99c55abe617799667f454222c54c60e229"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
"reference": "42292d99c55abe617799667f454222c54c60e229",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-07-28T09:04:16+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php80\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ion Bazan",
"email": "ion.bazan@gmail.com"
},
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-01-26T09:26:14+00:00"
},
{
"name": "twig/twig",
"version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554",
"reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3"
},
"require-dev": {
"psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^5.4.9|^6.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Twig\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Twig Team",
"role": "Contributors"
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"role": "Project Founder"
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "https://twig.symfony.com",
"keywords": [
"templating"
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.7.1"
},
"funding": [
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
"type": "tidelift"
}
],
"time": "2023-08-28T11:09:02+00:00"
},
{
"name": "vlucas/phpdotenv",
"version": "v5.5.0",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
"reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7",
"reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7",
"shasum": ""
},
"require": {
"ext-pcre": "*",
"graham-campbell/result-type": "^1.0.2",
"php": "^7.1.3 || ^8.0",
"phpoption/phpoption": "^1.8",
"symfony/polyfill-ctype": "^1.23",
"symfony/polyfill-mbstring": "^1.23.1",
"symfony/polyfill-php80": "^1.23.1"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1",
"ext-filter": "*",
"phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25"
},
"suggest": {
"ext-filter": "Required to use the boolean validator."
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": true
},
"branch-alias": {
"dev-master": "5.5-dev"
}
},
"autoload": {
"psr-4": {
"Dotenv\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Vance Lucas",
"email": "vance@vancelucas.com",
"homepage": "https://github.com/vlucas"
}
],
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
"keywords": [
"dotenv",
"env",
"environment"
],
"support": {
"issues": "https://github.com/vlucas/phpdotenv/issues",
"source": "https://github.com/vlucas/phpdotenv/tree/v5.5.0"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
"type": "tidelift"
}
],
"time": "2022-10-16T01:01:54+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.6.0"
}

50
context/UserContext.tsx Normal file
View file

@ -0,0 +1,50 @@
import { getCookie } from '@/utils/cookies';
import getConfig from 'next/config';
import { createContext, useContext, useEffect, useState } from 'react';
interface UserContextType {
user: any;
isLoggedIn: boolean;
}
const UserContext = createContext<UserContextType>({ user: null, isLoggedIn: false });
export const UserProvider = ({ children }: { children: React.ReactNode }) => {
const [ user, setUser ] = useState(null);
const [ isLoggedIn, setIsLoggedIn ] = useState(false);
const { publicRuntimeConfig } = getConfig();
useEffect(() => {
const fetchUser = async () => {
const sessionCookie = getCookie('session');
if (sessionCookie) {
try {
const res = await fetch('/api/v1/user/@me');
if (res.ok) {
const userData = await res.json();
setIsLoggedIn(true);
setUser(userData);
} else {
document.cookie = 'session=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
}
} catch (error) {
console.error('Error fetching user data:', error);
}
}
};
fetchUser();
}, []);
return (
<UserContext.Provider value={{ user, isLoggedIn }}>
{children}
</UserContext.Provider>
);
};
export const useUser = () => useContext(UserContext);

1
css/map.min.css vendored
View file

@ -1 +0,0 @@
*{padding:0;margin:0;box-sizing:border-box}#map{height:100vh}#homeLink{position:absolute;bottom:10px;left:10px;padding:5px 10px;z-index:400;background-color:#fff;background-clip:padding-box;border:2px solid rgba(0,0,0,.2);border-radius:5px;text-decoration:none;color:#000}img.leaflet-tile,img.leaflet-marker-icon{image-rendering:pixelated}.leaflet-control{color:#333}.leaflet-control-mouseposition{background:rgba(255,255,255,.8);margin:0 !important;padding:6px;width:100%;text-align:end;border-top-left-radius:5px}.leaflet-marker-icon{transition:300ms}/*# sourceMappingURL=map.min.css.map */

View file

@ -1 +0,0 @@
{"version":3,"sourceRoot":"","sources":["src/map.scss"],"names":[],"mappings":"AAAA,EACE,UACA,SAEA,sBAKF,kBAEA,UACE,kBACA,YACA,UACA,iBACA,YAEA,sBACA,4BAEA,gCACA,kBAEA,qBACA,WAGF,yCACE,0BAGF,iBACE,WAGF,+BACE,gCAEA,oBACA,YAEA,WAEA,eAEA,2BAGF,qBACE","file":"map.min.css"}

View file

@ -1,18 +0,0 @@
// @import url('https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Outfit:wght@100;200;300;400;500;600;700;800;900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Comic+Neue:wght@700&family=Noto+Sans:ital,wght@0,400;0,700;1,400;1,700&family=Outfit:wght@400;500;700&display=swap');
body, input {
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
}
h1, h2, h3, h4, h5, h6 {
font-family: "Outfit", "Noto Color Emoji", sans-serif;
font-weight: 500;
//margin-bottom: 6px;
}
.pageHero {
font-family: "Comic Neue", "Comic Sans MS", "Noto Color Emoji", sans-serif;
//margin-bottom: 6px;
}

View file

@ -1,28 +0,0 @@
// Dark Theme
$pageBG: #202120;
$pageFG: white;
$accent: #527D52;
$linkColor: #d0dfd0;
$btnNormalBG: $accent;
$btnNormalFG: white;
$btnActiveBG: $accent;
$btnActiveFG: white;
$navBG: #272f27;
$navBorder: #575f57;
$cardNormalBG: #272727;
$cardNormalFG: $pageFG;
$cardNormalBorder: #575757;
$cardActiveBG: $navBG;
$cardActiveFG: $linkColor;
$cardActiveBorder: $navBorder;
$navLinkOutlineColor: #707f70;
$navLinkNormalColor: #7E9E7E;
$navLinkActiveColor: #DEFEDE;
$headerOverlay: #101610;

View file

@ -1,32 +0,0 @@
// Light Theme
$pageBG: white;
$pageFG: black;
$accent: #527D52;
$linkColor: $accent;
$btnNormalBG: $accent;
$btnNormalFG: white;
$btnActiveBG: $accent;
$btnActiveFG: white;
$navBG: #f9fff9;
$navBorder: #d0dfd0;
$cardNormalBG: #f9f9f9;
$cardNormalFG: $pageFG;
$cardNormalBorder: #d0d0d0;
$cardActiveBG: $navBG;
$cardActiveFG: $linkColor;
$cardActiveBorder: $navBorder;
/*$cardBG: $navBG;
$cardFG: $pageFG;
$cardBorder: $navBorder;*/
$navLinkOutlineColor: #D0DFD0;
$navLinkNormalColor: #7E9E7E;
$navLinkActiveColor: $accent;
$headerOverlay: white;

View file

@ -1,52 +0,0 @@
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
/// old
#map { height: 100vh; }
#homeLink {
position: absolute;
bottom: 10px;
left: 10px;
padding: 5px 10px;
z-index: 400;
background-color: #fff;
background-clip: padding-box;
border: 2px solid rgba(0,0,0,0.2);
border-radius: 5px;
text-decoration: none;
color: #000;
}
img.leaflet-tile, img.leaflet-marker-icon {
image-rendering: pixelated;
}
.leaflet-control {
color: #333;
}
.leaflet-control-mouseposition {
background: rgba(255, 255, 255, 0.8);
margin: 0 !important;
padding: 6px;
width: 100%;
text-align: end;
border-top-left-radius: 5px;
}
.leaflet-marker-icon {
transition: 300ms;
}

View file

@ -1,457 +0,0 @@
@use "fonts";
@use "colors/light" as light;
@use "colors/dark" as dark;
* {
-webkit-tap-highlight-color: transparent; // fuck you (L)
}
html,
body {
overflow-x: hidden;
}
body {
margin: 0;
background: light.$pageBG;
color: light.$pageFG;
@media (prefers-color-scheme: dark) {
background: dark.$pageBG;
color: dark.$pageFG;
}
}
main {
transition: 0.24s;
&.buffering {
transform: scale(1.01);
transition: 0.16s;
opacity: 0.4;
pointer-events: none;
user-select: none;
}
&.transition {
transform: scale(0.96);
transition: 0.16s;
opacity: 0;
}
}
.container {
max-width: 960px;
width: 100%;
margin: 0 auto;
padding: 24px;
box-sizing: border-box;
}
ul,
ol {
list-style-position: inside;
}
p {
line-height: 1.6;
opacity: 0.88;
}
img {
max-width: 100%;
}
.cards {
display: flex;
flex-wrap: wrap;
gap: 8px;
width: 100%;
@media (max-width: 600px) {
gap: 12px;
}
}
.card {
position: relative;
display: flex;
flex-direction: column;
box-sizing: border-box;
max-width: 100%;
height: 150px;
text-decoration: none;
border-radius: 4px;
padding: 8px 12px;
cursor: pointer;
overflow: hidden;
outline: 1px solid light.$cardNormalBorder;
background: light.$cardNormalBG;
color: light.$cardNormalFG;
@media (prefers-color-scheme: dark) {
outline-color: dark.$cardNormalBorder;
background: dark.$cardNormalBG;
color: dark.$cardNormalFG;
}
transition: 0.24s;
&:hover {
outline-width: 2px;
outline-color: light.$cardActiveBorder;
background: light.$cardActiveBG;
color: light.$cardActiveFG;
@media (prefers-color-scheme: dark) {
outline-color: dark.$cardActiveBorder;
background: dark.$cardActiveBG;
color: dark.$cardActiveFG;
}
transition: 0.08s;
.icon {
transition: 0.48s;
transform: scale(1.2);
}
}
width: calc(100% / 3 - 6px);
@media (max-width: 800px) {
width: calc(50% - 4px);
}
@media (max-width: 600px) {
border-radius: 8px;
width: 100%;
}
h1 {
margin: 0;
font-size: 1.2em;
font-weight: 400;
}
.icon {
position: absolute;
top: 0;
right: 0;
height: 100%;
aspect-ratio: 1/1;
object-fit: cover;
mask-image: linear-gradient(to right, transparent, rgba(red, 0.3));
@media (prefers-color-scheme: dark) {
mask-image: linear-gradient(to right, transparent, rgba(red, 0.1));
}
transition: 0.64s;
}
p {
margin: 8px 0;
}
}
a {
color: light.$linkColor;
@media (prefers-color-scheme: dark) {
color: dark.$linkColor;
}
&:visited {
opacity: 0.9;
}
}
.pageHero {
background-image: linear-gradient(rgba(light.$headerOverlay, 0.9), rgba(light.$headerOverlay, 0.9)), // white overlay. yeah that's a bit of an ugly hack?
url('/img/Panorama-Lens-Blur.png');
@media (prefers-color-scheme: dark) {
background-image: linear-gradient(rgba(dark.$headerOverlay, 0.9), rgba(dark.$headerOverlay, 0.9)),
url('/img/Panorama-Lens-Blur.png');
}
background-position: center;
background-size: cover;
height: 220px;
>.container {
height: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
@media (max-width: 600px) {
justify-content: center;
}
>div {
display: flex;
flex-direction: column;
gap: 12px;
&.Branding {
align-items: flex-start;
@media (max-width: 600px) {
align-items: center;
}
@media (prefers-color-scheme: dark) {
img {
filter: invert(1)
}
}
span {
font-size: 24px;
opacity: 0.75;
}
}
&.Server-Information {
@media (max-width: 600px) {
display: none;
}
align-items: center;
gap: 4px;
input {
text-align: center;
font-size: 24px;
&:not(:hover, :focus) {
color: inherit;
border-color: transparent;
background: transparent;
}
}
}
}
}
}
.navToggle {
@media (min-width: 601px) {
display: none;
}
z-index: 10;
position: fixed;
top: 8px;
right: 8px;
font-size: 18px;
background: light.$btnNormalBG;
color: light.$btnNormalFG;
@media (prefers-color-scheme: dark) {
background: dark.$btnNormalBG;
color: dark.$btnNormalFG;
}
padding: 8px 24px;
border-radius: 32px;
font-weight: bold;
user-select: none;
cursor: pointer;
}
.pageNav {
width: 100%;
border: solid light.$navBorder;
border-width: 0 0 1px 0;
background: light.$navBG;
@media (prefers-color-scheme: dark) {
border-color: dark.$navBorder;
background: dark.$navBG;
}
>.container {
padding: 16px 24px;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
@media (max-width: 600px) {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9;
backdrop-filter: blur(15px);
background: rgba(light.$navBG, 0.86);
@media (prefers-color-scheme: dark) {
background: rgba(dark.$navBG, 0.9);
}
justify-content: center;
flex-direction: column;
gap: 64px;
&,
>* {
transition: 0.16s
}
&:not(.opened) {
opacity: 0;
pointer-events: none;
//&, > * { transition: 0.12s }
>* {
transform: translateX(32px);
}
}
}
>div {
margin: 0;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
gap: 6px;
@media (max-width: 600px) {
width: 100%;
flex-direction: column;
}
>a {
font-family: Outfit;
user-select: none;
font-size: 16px;
box-sizing: border-box;
color: light.$navLinkNormalColor;
@media (prefers-color-scheme: dark) {
color: dark.$navLinkNormalColor;
}
@media (max-width: 600px) {
font-size: 28px;
outline: none;
padding: 8px 32px;
text-align: end;
&.active::before {
content: ""; // i know..
}
}
text-decoration: none;
padding: 6px 20px;
border-radius: 32px;
outline: solid transparent 2px;
transition: 0.24s;
&.active {
pointer-events: none;
}
&:hover,
&.active {
//background: rgba(15, 23, 42, 0.1);
color: light.$navLinkActiveColor;
outline-color: light.$navLinkOutlineColor;
@media (prefers-color-scheme: dark) {
color: dark.$navLinkActiveColor;
outline-color: dark.$navLinkOutlineColor;
}
transition: 0.08s;
}
@media (min-width: 601px) {
&.buttonPrimary {
background: light.$btnNormalBG;
color: light.$btnNormalFG;
@media (prefers-color-scheme: dark) {
background: dark.$btnNormalBG;
color: dark.$btnNormalFG;
}
font-weight: 700 !important;
}
}
}
}
}
}
.pageContent {
//
}
.pageFooter {
opacity: 0.8;
>.SNS-Links {
display: flex;
flex-wrap: wrap;
gap: 16px;
>a {
display: flex;
gap: 8px;
font-size: 18px;
color: inherit;
img {
width: 1em;
@media (prefers-color-scheme: dark) {
filter: invert(1);
}
}
&:not(:hover, :focus) {
text-decoration: none;
}
}
}
}

1
css/style.min.css vendored

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
{"version":3,"sourceRoot":"","sources":["src/_fonts.scss","src/style.scss","src/colors/_light.scss","src/colors/_dark.scss"],"names":[],"mappings":"CAEQ,qKAER,WACE,sDAGF,kBACE,mDACA,gBAIF,UACE,uECXF,EACE,0CAGF,UACE,kBAGF,KACE,SAEA,WCbO,KDcP,MCbO,KDeP,mCANF,KAOI,WEjBK,QFkBL,MEjBK,MFqBT,KACE,gBAEA,eACE,sBACA,gBACA,WACA,oBACA,iBAGF,gBACE,sBACA,gBACA,UAIJ,WACE,gBAEA,WAEA,cAEA,aACA,sBAGF,MACE,2BAGF,EACE,gBACA,YAGF,IACE,eAGF,OACE,aACA,eACA,QACA,WAEA,yBANF,OAOI,UAIJ,MACE,kBACA,aACA,sBACA,sBACA,eACA,aACA,qBACA,kBACA,iBACA,eACA,gBAEA,0BACA,WC3Ea,QD4Eb,MCzFO,KDiGP,gBAsBA,iCA5BA,mCAjBF,MAkBI,cE7Ee,QF8Ef,WEhFW,QFiFX,ME9FK,MFmGP,YACE,kBACA,cC1FQ,QD2FR,WC5FI,QD6FJ,MCrGK,QD6GL,gBANA,mCANF,YAOI,cE/FM,QFgGN,WEjGE,QFkGF,MEzGM,SF8GR,kBACE,gBACA,qBAMJ,yBA/CF,MAgDI,uBAGF,yBAnDF,MAoDI,kBACA,YAGF,SACE,SACA,gBACA,gBAGF,YACE,kBACA,MACA,QACA,YACA,iBACA,iBAEA,wEAKA,gBAJA,mCATF,YAUI,yEAMJ,QACE,aAIJ,EACE,MC5JO,QD8JP,mCAHF,EAII,ME9JQ,SFiKV,UACE,WAIJ,UACE,wHAQA,2BACA,sBAEA,aARA,mCAJF,UAKI,mHASF,qBACE,YAEA,aACA,mBAEA,mBACA,8BAEA,yBATF,qBAUI,wBAGF,yBACE,aACA,sBAEA,SAEA,kCACE,uBAEA,yBAHF,kCAII,oBAGF,mCACE,sCACE,kBAIJ,uCACE,eACA,YAIJ,4CAKE,mBACA,QALA,yBADF,4CAEI,cAMF,kDACE,kBACA,eAEA,qEACE,cACA,2BACA,yBAQZ,WAIE,WAEA,eACA,QACA,UACA,eAEA,WC7PO,QD8PP,MC1PY,KDiQZ,iBAEA,mBAEA,iBACA,iBAEA,eAzBA,yBADF,WAEI,cAYF,mCAdF,WAeI,WEjQK,QFkQL,ME9PU,MF2Qd,SACE,WAEA,qBACA,uBAEA,WC7QM,QD+QN,mCARF,SASI,aE/QQ,QFgRR,WEjRI,SFoRN,oBACE,kBAEA,aACA,eAEA,mBACA,8BAEA,yBATF,oBAUI,eACA,MACA,OACA,WACA,YACA,UAEA,2BACA,iCAKA,uBACA,sBACA,UANA,yDAnBJ,oBAoBM,8BAXJ,yBAkBE,0CACE,gBAGF,iCACE,UACA,oBAGA,mCACE,4BAKN,wBACE,SAEA,aACA,eACA,qBAEA,QAEA,yBATF,wBAUI,WACA,uBAGF,0BACE,mBACA,iBAEA,eACA,sBACA,MCnUa,QDoVb,qBAEA,iBAEA,mBAEA,gCAEA,gBAxBA,mCAPF,0BAQI,MEzUW,SF4Ub,yBAXF,0BAYI,eACA,aACA,iBACA,eAEA,yCACE,aAeJ,iCACE,oBAGF,iEAEE,MC3XD,QD4XC,cCtWY,QD8WZ,gBANA,mCALF,iEAMI,ME3WS,QF4WT,cE9WU,SFqXd,yBACE,wCACE,WCzYH,QD0YG,MCtYE,KD6YF,4BALA,yDAJF,wCAKI,WE7YL,QF8YK,ME1YA,MFyZd,YACE,WAEA,uBACE,aACA,eACA,SAEA,yBACE,aACA,QAEA,eAEA,cAEA,6BACE,UAEA,mCAHF,6BAII,kBAIJ,4CACE","file":"style.min.css"}

28
docs/crss_server_api.md Normal file
View file

@ -0,0 +1,28 @@
# The CRSS Mod <-> CRSS Website Communication API
A layer bellow the website api to retrieve and send data to the CRSS server. For example a user requests `https://crss.cc/api/v1/server/players`, the website api will call the crss mod api to get the data from the server.
It will use not use http for reasons.
## Packets
### Get Server Info
#### Client -> Server
| Identifier | Lenght |
|------------|-----------------------|
| `0x00` | `0x00 0x00 0x00 0x00` |
* Identifier (1 Byte) - `0x00`
* Length (4 Bytes) - `UInt32`
#### Server -> Client
| Identifier | Lenght | Data |
|------------|-----------------------|-----------|
| `0x00` | `0x00 0x00 0x00 0x00` | `{ ... }` |
* Identifier (1 Byte) - `0x00`
* Length (4 Bytes) - `UInt32`
* Data (*Lenght) - `String` - JSON Object

View file

@ -1,3 +0,0 @@
module.exports = {
php: "/usr/bin/php"
}

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="146px" height="96px" viewBox="0 0 146 96" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
<defs>
<path d="M146 0L146 0L146 96L0 96L0 0L146 0Z" id="path_1" />
<clipPath id="clip_1">
<use xlink:href="#path_1" clip-rule="evenodd" fill-rule="evenodd" />
</clipPath>
</defs>
<g id="Frame" clip-path="url(#clip_1)">
<path d="M146 0L146 0L146 96L0 96L0 0L146 0Z" id="Frame" fill="none" stroke="none" />
<path d="M83.7827 12.6087C83.7827 10.6157 85.3984 9 87.3914 9C89.3844 9 91.0001 10.6157 91.0001 12.6087C91.0001 14.6017 89.3844 16.2174 87.3914 16.2174C85.3984 16.2174 83.7827 14.6017 83.7827 12.6087Z" id="Oval-5" fill="#000000" fill-rule="evenodd" stroke="none" />
<path d="M32.6594 16.4528L41.9008 32.5134L50.8527 17.4646L54.8404 29.75L55.9553 28.8478L66.5686 10.2029" id="Vector-3" fill="none" fill-rule="evenodd" stroke="#000000" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" />
<path d="M8 19.8261C8 17.8331 9.61567 16.2174 11.6087 16.2174C13.6017 16.2174 15.2174 17.8331 15.2174 19.8261C15.2174 21.8191 13.6017 23.4348 11.6087 23.4348C9.61567 23.4348 8 21.8191 8 19.8261Z" id="Oval-6" fill="#000000" fill-rule="evenodd" stroke="none" />
<g id="CRSS" transform="translate(0 32)">
<g id="CRSS">
<path d="M21.25 57.9906C27.4531 57.9906 33.0938 55.3656 36.6094 50.3656C37.0625 49.725 37.1875 49.0844 37.1875 48.4438C37.1875 46.3969 35.6406 45.2406 33.9844 45.2406C33.0312 45.2406 32.25 45.5063 31.4844 46.5219C29.4375 49.2094 26.2969 51.8344 21.25 51.8344C14.5938 51.8344 8.95312 47.0375 8.95312 38.725C8.95312 29.4438 15.4844 20.5375 22.0781 20.5375C25.5312 20.5375 27.5156 21.6313 28.8594 23.0375L28.8594 23.4906C28.8594 24.8344 30.1406 26.0531 31.875 26.0531C33.6562 26.0531 35.0781 24.5688 35.0781 22.85L35.0781 18.7563C35.0781 17.1469 33.8594 15.8031 32.125 15.8031C31.2344 15.8031 30.5938 16.1938 30.2031 16.5688C28.2812 15.4281 25.4062 14.3344 22.5312 14.3344C12.7344 14.3344 2.5625 25.9125 2.5625 38.725C2.5625 50.8813 11.0781 57.9906 21.25 57.9906ZM46.4015 57.9906C48.0578 57.9906 49.5421 56.6469 49.5421 54.85L49.5421 43.2563C51.0734 43.0063 52.6046 42.8813 53.8234 42.8813C56.0578 42.8813 60.9953 43.6469 68.9328 56.3188C69.5734 57.35 70.5265 57.9906 71.7453 57.9906C73.6671 57.9906 74.8859 56.4438 74.8859 54.85C74.8859 54.3969 74.8234 53.8813 74.5578 53.3813C71.3546 46.975 66.9484 42.7563 63.9328 40.3813C68.9953 38.1469 73.4015 34.4281 73.4015 27.975C73.4015 19.3344 65.339 14.3344 51.0109 14.3344L48.3859 14.3344C43.964 14.3344 43.2609 14.9125 43.2609 18.1781L43.2609 54.85C43.2609 56.6469 44.6046 57.9906 46.4015 57.9906ZM49.5421 37.3813L49.5421 20.475L50.8234 20.475C61.0578 20.475 67.0734 23.0375 67.0734 27.975C67.0734 34.1156 57.339 36.8656 49.5421 37.3813ZM93.0573 57.9906C101.37 57.9906 108.479 52.475 108.479 45.1156C108.479 38.85 103.807 34.8188 95.3541 32.3813C88.4479 30.3969 85.5104 28.5375 85.5104 25.5375C85.5104 22.3344 88.4479 20.225 94.276 20.225C99.0104 20.225 101.885 22.0844 102.214 24.3813C102.401 25.85 103.62 27.0688 105.151 27.0688C106.948 27.0688 108.292 25.6625 108.292 23.7406C108.292 18.1156 103.104 14.3344 94.276 14.3344C84.9948 14.3344 79.2916 19.1313 79.2916 25.9906C79.2916 33.2875 85.8854 36.4125 93.1823 38.4594C100.229 40.4438 102.339 42.8188 102.339 45.4438C102.339 49.0844 98.5573 51.975 93.1823 51.975C89.3385 51.975 84.9323 50.1781 83.526 46.2719C83.0104 44.8656 81.9166 43.8969 80.3229 43.8969C78.4635 43.8969 77.0573 45.3813 77.0573 47.1C77.0573 47.8031 77.3073 48.6469 77.7604 49.6C79.8073 53.9594 85.2448 57.9906 93.0573 57.9906ZM128.001 57.9906C136.314 57.9906 143.423 52.475 143.423 45.1156C143.423 38.85 138.751 34.8188 130.298 32.3813C123.392 30.3969 120.454 28.5375 120.454 25.5375C120.454 22.3344 123.392 20.225 129.22 20.225C133.954 20.225 136.829 22.0844 137.158 24.3813C137.345 25.85 138.564 27.0688 140.095 27.0688C141.892 27.0688 143.236 25.6625 143.236 23.7406C143.236 18.1156 138.048 14.3344 129.22 14.3344C119.939 14.3344 114.236 19.1313 114.236 25.9906C114.236 33.2875 120.829 36.4125 128.126 38.4594C135.173 40.4438 137.283 42.8188 137.283 45.4438C137.283 49.0844 133.501 51.975 128.126 51.975C124.283 51.975 119.876 50.1781 118.47 46.2719C117.954 44.8656 116.861 43.8969 115.267 43.8969C113.408 43.8969 112.001 45.3813 112.001 47.1C112.001 47.8031 112.251 48.6469 112.704 49.6C114.751 53.9594 120.189 57.9906 128.001 57.9906Z" />
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"><path d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/></svg>

Before

Width:  |  Height:  |  Size: 749 B

View file

@ -1,3 +0,0 @@
<svg width="74" height="79" viewBox="0 0 74 79" fill="black" xmlns="http://www.w3.org/2000/svg">
<path d="M73.7014 17.4323C72.5616 9.05152 65.1774 2.4469 56.424 1.1671C54.9472 0.950843 49.3518 0.163818 36.3901 0.163818H36.2933C23.3281 0.163818 20.5465 0.950843 19.0697 1.1671C10.56 2.41145 2.78877 8.34604 0.903306 16.826C-0.00357854 21.0022 -0.100361 25.6322 0.068112 29.8793C0.308275 35.9699 0.354874 42.0498 0.91406 48.1156C1.30064 52.1448 1.97502 56.1419 2.93215 60.0769C4.72441 67.3445 11.9795 73.3925 19.0876 75.86C26.6979 78.4332 34.8821 78.8603 42.724 77.0937C43.5866 76.8952 44.4398 76.6647 45.2833 76.4024C47.1867 75.8033 49.4199 75.1332 51.0616 73.9562C51.0841 73.9397 51.1026 73.9184 51.1156 73.8938C51.1286 73.8693 51.1359 73.8421 51.1368 73.8144V67.9366C51.1364 67.9107 51.1302 67.8852 51.1186 67.862C51.1069 67.8388 51.0902 67.8184 51.0695 67.8025C51.0489 67.7865 51.0249 67.7753 50.9994 67.7696C50.9738 67.764 50.9473 67.7641 50.9218 67.7699C45.8976 68.9569 40.7491 69.5519 35.5836 69.5425C26.694 69.5425 24.3031 65.3699 23.6184 63.6327C23.0681 62.1314 22.7186 60.5654 22.5789 58.9744C22.5775 58.9477 22.5825 58.921 22.5934 58.8965C22.6043 58.8721 22.621 58.8505 22.6419 58.8336C22.6629 58.8167 22.6876 58.8049 22.714 58.7992C22.7404 58.7934 22.7678 58.794 22.794 58.8007C27.7345 59.9796 32.799 60.5746 37.8813 60.5733C39.1036 60.5733 40.3223 60.5733 41.5447 60.5414C46.6562 60.3996 52.0437 60.1408 57.0728 59.1694C57.1983 59.1446 57.3237 59.1233 57.4313 59.0914C65.3638 57.5847 72.9128 52.8555 73.6799 40.8799C73.7086 40.4084 73.7803 35.9415 73.7803 35.4523C73.7839 33.7896 74.3216 23.6576 73.7014 17.4323ZM61.4925 47.3144H53.1514V27.107C53.1514 22.8528 51.3591 20.6832 47.7136 20.6832C43.7061 20.6832 41.6988 23.2499 41.6988 28.3194V39.3803H33.4078V28.3194C33.4078 23.2499 31.3969 20.6832 27.3894 20.6832C23.7654 20.6832 21.9552 22.8528 21.9516 27.107V47.3144H13.6176V26.4937C13.6176 22.2395 14.7157 18.8598 16.9118 16.3545C19.1772 13.8552 22.1488 12.5719 25.8373 12.5719C30.1064 12.5719 33.3325 14.1955 35.4832 17.4394L37.5587 20.8853L39.6377 17.4394C41.7884 14.1955 45.0145 12.5719 49.2765 12.5719C52.9614 12.5719 55.9329 13.8552 58.2055 16.3545C60.4017 18.8574 61.4997 22.2371 61.4997 26.4937L61.4925 47.3144Z" fill="inherit"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 819 KiB

View file

@ -1,3 +0,0 @@
<svg width="72" height="52" viewBox="0 0 72 52" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28.8125 36.7812L47.464 26L28.8125 15.2188V36.7812ZM70.3562 8.64225C70.8235 10.3313 71.147 12.5952 71.3625 15.4702C71.614 18.3452 71.722 20.825 71.722 22.9813L71.9375 26C71.9375 33.8703 71.3625 39.6562 70.3562 43.3577C69.4577 46.5923 67.3735 48.6765 64.139 49.575C62.45 50.0422 59.3595 50.3658 54.6158 50.5813C49.9438 50.8328 45.6672 50.9408 41.714 50.9408L36 51.1562C20.9422 51.1562 11.5625 50.5812 7.861 49.575C4.6265 48.6765 2.54225 46.5923 1.64375 43.3577C1.1765 41.6688 0.853251 39.4047 0.637501 36.5297C0.386001 33.6547 0.278 31.175 0.278 29.0188L0.0625 26C0.0625 18.1297 0.637499 12.3438 1.64375 8.64225C2.54225 5.40775 4.6265 3.3235 7.861 2.425C9.55 1.95775 12.6407 1.63425 17.3845 1.41875C22.0562 1.16725 26.3327 1.05925 30.286 1.05925L36 0.84375C51.0578 0.84375 60.4375 1.41875 64.139 2.425C67.3735 3.3235 69.4577 5.40775 70.3562 8.64225Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 KiB

301
index.php
View file

@ -1,301 +0,0 @@
<?php
global $twig, $mysql;
require_once '_config.php';
use Bramus\Router\Router;
$curl = new anlutro\cURL\cURL;
$router = new Router();
session_start();
$nations = array(
'psf' => array(
'name' => 'Panorama Socialist Federation',
'flag' => 'https://raw.theclashfruit.me/CRSS/CRSS/main/Nations/Republic%20of%20Panorama/Flag.svg',
'short' => 'psf',
'short_description' => 'The first nation, prev. known as ROP.',
'description' => 'The first nation on CRSS, previously known as ROP.',
'leader' => 'iforgotaname',
'leader_term' => 'Prime Minister',
),
'cnk' => array(
'name' => 'Chunkia',
'flag' => 'https://raw.theclashfruit.me/CRSS/CRSS/main/Nations/Chunkia/Chunkia.svg',
'short' => 'cnk',
'short_description' => 'Chunkia is based in a chaotic landscape',
'description' => 'In the chaos of Minecraft, chunk errors are inevitable. Chunkia is based in one.',
'leader' => 'WorldWidePixel',
'leader_term' => 'Leader',
),
'ttk' => array(
'name' => 'The Toaster-Königreich',
'flag' => 'https://raw.theclashfruit.me/CRSS/CRSS/main/Nations/The%20Toaster-K%C3%B6nigreich/The%20Toaster-K%C3%B6nigreich%20Flag.svg',
'short' => 'ttk',
'short_description' => 'The Toaster-Königreich is the Industrialized Nation of CRSS',
'description' => 'In the vast landsacpe of CRSS, The Toaster-Königreich is one of the most industrialized Marxist district of CRSS. With Charge Industries as one of the main government controlled company in the nation.',
'leader' => 'Lupancham',
'leader_term' => 'Chancellor',
),
'rob' => array(
'name' => 'Republic of Budapest',
'flag' => 'https://raw.theclashfruit.me/CRSS/CRSS/main/Nations/Republic%20of%20Budapest/Assets/Flag.svg',
'short' => 'rob',
'short_description' => 'Republic of Budapest is a country located to the west of the map.',
'description' => 'Republic of Budapest is a country located to the west of the map.',
'leader' => 'TheClashFruit',
'leader_term' => 'Leader',
),
'rfm' => array(
'name' => 'Romanian Federation of Minecraft',
'flag' => 'https://raw.theclashfruit.me/CRSS/CRSS/main/Nations/Romanian%20Federation%20of%20Minecraft/FMC%20flag.svg',
'short' => 'rfm',
'short_description' => 'Helping modernise CRSS',
'description' => 'RFM is a nation helping in the modernisation of CRSS, not only by practices but by livelihood.',
'leader' => 'polycord',
'leader_term' => 'President',
),
'tcg' => array(
'name' => 'Toasteric Colony of Grapetopia',
'flag' => 'https://raw.theclashfruit.me/CRSS/CRSS/main/Nations/Grapetopia/TCG.svg',
'short' => 'tcg',
'short_description' => 'A colony of TTK, ~10K blocks out of 0,0',
'description' => 'This is a colony of TTK, an island with an area of ~382m², located at ~7.5k X and -2.8k Z',
'leader' => 'MrLagSwitcha',
'leader_term' => 'Leader in charge',
),
);
$companies = array(
'fbk' => array(
'name' => 'FedBank',
'logo' => 'https://raw.theclashfruit.me/CRSS/CRSS/main/Nations/Republic%20of%20Panorama/Flag.svg',
'short' => 'fbk',
'short_description' => 'An International Bank, owned by the PSF government',
'description' => 'FedBank is an International bank owned and controlled by the Panorama Socialist Federation\'s Government. The main building is in PSF, but there is a FedBank in TTK too.'
),
'ntn' => array(
'name' => 'Northern',
'logo' => 'https://raw.theclashfruit.me/CRSS/CRSS/main/Companies/Chunkia/northern.svg',
'short' => 'ntn',
'short_description' => 'The Northern Company.',
'description' => 'Creators of the H1 and the Northern Complex, Northern is dedicated to improving the CRSS experience.'
)
);
if(isset($_SESSION['user'])) {
$dbUser = $mysql->getUserRecordFromId($_SESSION['user']['id']);
$user = $_SESSION['user'];
$user['is_admin'] = $dbUser['is_admin'];
$twig->addGlobal('user', $user);
}
$res = $curl->get('https://crss.blurryface.xyz/api/v1/players');
$json = json_decode($res->body, true);
if($json != null)
$twig->addGlobal('playerCount', count($json));
else
$twig->addGlobal('playerCount', $json);
$twig->addGlobal('nations', $nations);
$twig->addGlobal('companies', $companies);
$twig->addGlobal('dc_uri', 'https://discord.com/api/oauth2/authorize?client_id=1144248396467683338&redirect_uri=' . urlencode($_ENV['DISCORD_REDIRECT']) . '&response_type=code&scope=identify%20guilds&state=' . urlencode($_SERVER['REQUEST_URI']));
$twig->addGlobal('reduced', isset($_GET['reduced']));
$router->get('/', function() {
global $twig;
$twig->addGlobal('pageUri', '/');
echo $twig->render('index.twig');
});
$router->get('/nations', function() {
global $twig;
$twig->addGlobal('pageUri', '/nations');
echo $twig->render('nations.twig');
});
$router->get('/companies', function() {
global $twig;
$twig->addGlobal('pageUri', '/companies');
echo $twig->render('companies.twig');
});
$router->get('/gallery', function() {
global $twig;
$twig->addGlobal('pageUri', '/gallery');
echo $twig->render('gallery.twig');
});
$router->get('/map', function() {
global $twig, $mysql;
$twig->addGlobal('pageUri', '/map');
$twig->addGlobal('markers', json_encode($mysql->getMarkers()));
if(isset($_GET['center']))
$twig->addGlobal('center', $_GET['center']);
else
$twig->addGlobal('center', '0;0');
echo $twig->render('map.twig');
});
$router->get('/profile', function() {
global $twig, $mysql;
$twig->addGlobal('pageUri', '/profile');
if (isset($_SESSION['user'])) {
$user = $mysql->getUserRecordFromId($_SESSION['user']['id']);
if ($user == null && $user['admin'] == 0) {
http_response_code(404);
echo $twig->render('404.twig');
} else {
echo $twig->render('profile.twig', array('db_data' => $user));
}
} else {
http_response_code(404);
echo $twig->render('404.twig');
}
});
$router->get('/nation/([a-z]+)', function ($nation) {
global $twig, $mysql, $nations;
$twig->addGlobal('pageUri', '/nation/' . $nation);
if(!$nations[$nation]) {
http_response_code(404);
echo $twig->render('404.twig');
} else {
echo $twig->render('nation.twig', array('nation' => $nations[$nation]));
}
});
$router->get('/company/([a-z]+)', function ($company) {
global $twig, $mysql, $companies;
$twig->addGlobal('pageUri', '/company/' . $company);
if(!$companies[$company]) {
http_response_code(404);
echo $twig->render('404.twig');
} else {
echo $twig->render('company.twig', array('company' => $companies[$company]));
}
});
$router->get('/u/([a-z0-9_\.]+)', function($name) {
global $twig, $mysql, $discord;
$twig->addGlobal('pageUri', '/u/' . $name);
$user = $mysql->getUserRecordFromUsername($name);
if($user == null) {
http_response_code(404);
echo $twig->render('404.twig');
} else {
echo $twig->render('user.twig', array('db_user' => $user));
}
});
// ---------------- Admin ---------------- //
$router->get('/admin', function() {
global $twig, $mysql;
$twig->addGlobal('pageUri', '/admin');
if (isset($_SESSION['user'])) {
$user = $mysql->getUserRecordFromId($_SESSION['user']['id']);
$users = $mysql->getUsers();
$markers = $mysql->getMarkers();
if ($user == null && $user['admin'] == 0) {
http_response_code(401);
echo '<style>body { overflow: hidden; height: 100svh; background: black; display: flex; justify-content: center; align-items: center; }</style><img src="https://http.cat/401" alt="401 Unauthorized" />';
} else {
echo $twig->render('admin/index.twig', array('users' => $users, 'markers' => $markers));
}
} else {
http_response_code(401);
echo '<style>body { overflow: hidden; height: 100svh; background: black; display: flex; justify-content: center; align-items: center; }</style><img src="https://http.cat/401" alt="401 Unauthorized" />';
}
});
$router->get('/admin/__data/page/([a-z]+)', function($page) {
global $twig, $mysql;
if (isset($_SESSION['user'])) {
$user = $mysql->getUserRecordFromId($_SESSION['user']['id']);
$users = $mysql->getUsers();
$markers = $mysql->getMarkers();
if ($user == null && $user['admin'] == 0) {
http_response_code(401);
echo '<style>body { overflow: hidden; height: 100svh; background: black; display: flex; justify-content: center; align-items: center; }</style><img src="https://http.cat/401" alt="401 Unauthorized" />';
} else {
try {
echo $twig->render('admin/pages/' . urlencode($page) . '.twig', array('users' => $users, 'markers' => $markers));
} catch (Exception $e) {
http_response_code(404);
echo $twig->render('admin/pages/404.twig');
}
}
} else {
http_response_code(401);
echo '<style>body { overflow: hidden; height: 100svh; background: black; display: flex; justify-content: center; align-items: center; }</style><img src="https://http.cat/401" alt="401 Unauthorized" />';
}
});
// ---------------- Admin API ---------------- //
$adminApi = new Admin($router);
$adminApi->registerApiRoutes();
// ----------------- 404 ----------------- //
$router->set404(function() {
global $twig;
$twig->addGlobal('pageUri', '404');
http_response_code(404);
echo $twig->render('404.twig');
});
$router->run();

View file

@ -1,50 +0,0 @@
const pageContainer = document.querySelector('.pageContainer');
window.history.pushState({}, '', '#/');
window.addEventListener('hashchange', () => {
let uri = window.location.href.split('#')[1];
if (!window.location.href.includes('#'))
uri = '/admin#/'
const allActiveLinks = document.querySelectorAll('.nav a.active');
const allLinksWithThisUrl = document.querySelectorAll(`.nav a[href="#${uri}"]`);
allActiveLinks.forEach(activeLink => {
activeLink.classList.remove('active');
activeLink.classList.add('link-body-emphasis');
});
allLinksWithThisUrl.forEach(link => {
link.classList.add('active');
link.classList.remove('link-body-emphasis');
});
changePage(window.location.href.split('#')[1].replace('/', ''));
});
const changePage = (url) => {
if (!url)
url = 'dashboard';
pageContainer.innerHTML = `<i class="loader" data-lucide="loader-circle"></i>`;
pageContainer.classList.add('d-flex');
pageContainer.classList.add('align-items-center');
pageContainer.classList.add('justify-content-center');
lucide.createIcons();
fetch(`/admin/__data/page/${url}`)
.then(res => res.text())
.then(html => {
pageContainer.innerHTML = html;
pageContainer.classList.remove('d-flex');
pageContainer.classList.remove('align-items-center');
pageContainer.classList.remove('justify-content-center');
lucide.createIcons();
});
};

View file

@ -1,13 +0,0 @@
import { enableTransition } from "/js/trans.js";
function enableLinks() {
document.querySelectorAll(".transitionEnabled").forEach( hyperlinkElement => {
enableTransition(hyperlinkElement);
hyperlinkElement.classList.remove("transitionEnabled");
});
}
enableLinks();
window.addEventListener("transitionEnd", enableLinks);

176
js/map.js
View file

@ -1,176 +0,0 @@
L.TileLayer.CRSSLayer = L.TileLayer.extend({
getTileUrl: function(coordinate) {
const tileX = coordinate.x;
const tileY = coordinate.y;
const tileZ = coordinate.z;
const url = ('https://cdn-new.theclashfruit.me/crss/tiles/zoom.{z}/{xd}/{yd}/tile.{x}.{y}.png')
.replace('{yd}', Math.floor(tileY / 10))
.replace('{xd}', Math.floor(tileX / 10))
.replace('{y}', tileY)
.replace('{x}', tileX)
.replace('{z}', tileZ);
return url;
}
});
L.tileLayer.crssLayer = function(templateUrl, options) {
return new L.TileLayer.CRSSLayer(templateUrl, options);
}
let mapLayer = L.tileLayer.crssLayer('https://cdn-new.theclashfruit.me/crss/tiles/zoom.{z}/{xd}/{yd}/tile.{x}.{y}.png', {
attribution: '&copy; <a href="https://crss.blurryface.xyz/">CRSS</a> Players | Tiles With <a href="https://unmined.net/">uNmINeD</a>.',
tileSize: 256,
noWrap: true,
maxNativeZoom: 0,
minNativeZoom: -4,
minZoom: -4,
maxZoom: -4 + 8,
zoomOffset: -8
});
let ropMarkers = L.layerGroup([]);
let drrMarkers = L.layerGroup([]);
let miscMarkers = L.layerGroup([]);
markers.forEach(marker => {
const coords = marker.data.split(';');
switch (marker.category) {
case 'rop':
ropMarkers
.addLayer(
L.marker([
parseFloat(coords[0]), parseFloat(coords[1])
]).bindPopup(marker.name)
);
break;
case 'drr':
drrMarkers.addLayer(
L.marker([
parseFloat(coords[0]), parseFloat(coords[1])
]).bindPopup(marker.name)
);
break;
default:
miscMarkers.addLayer(
L.marker([
parseFloat(coords[0]), parseFloat(coords[1])
]).bindPopup(marker.name)
);
break;
}
});
let playerMarkers = L.layerGroup([
]);
console.log(L.CRS.Simple.infinite)
let map = L.map('map', {
layers: [
mapLayer,
miscMarkers,
ropMarkers,
drrMarkers,
playerMarkers
],
preferCanvas: true,
crs: L.Util.extend(L.CRS.Simple, {
transformation: new L.Transformation(1, 0, 1, 0),
projection: L.Projection.LonLat
}),
}).setView([
parseFloat(center.split(';')[1]),
parseFloat(center.split(';')[0])
], 2);
let baseMaps = {
"Overworld": mapLayer
};
let overlayMaps = {
"Players": playerMarkers,
"Miscellaneous Markers": miscMarkers,
"Markers in RoP": ropMarkers,
"Markers in DRR": drrMarkers,
};
let layerControl = L.control.layers(baseMaps, overlayMaps).addTo(map);
L.control.mousePosition({
position: 'bottomright',
separator: '; ',
lngFormatter: (x) => {
return Math.floor(x)
},
latFormatter: (y) => {
return Math.floor(y)
},
wrapLng: false,
lngFirst: true
}).addTo(map);
const mappedPlayers = {}
const updatePlayerPos = (players) =>{
for (const player of players) {
const playerMarker = mappedPlayers[player.uniqueId];
if(playerMarker) {
playerMarker.setLatLng([player.location.z, player.location.x]);
playerMarker.setPopupContent(`${player.displayName} (${Math.floor(player.location.x)}; ${Math.floor(player.location.y)}; ${Math.floor(player.location.z)})`);
mappedPlayers[player.uniqueId] = playerMarker;
} else {
const playerIcon = L.icon({
iconUrl: `https://mc-heads.net/avatar/${player.displayName}/16`,
iconSize: [28, 28],
iconAnchor: [14, 14],
popupAnchor: [0, -14]
});
const marker = L.marker([player.location.z, player.location.x], { icon: playerIcon, alt: player.displayName })
.bindPopup(`${player.displayName} (${Math.floor(player.location.x)}; ${Math.floor(player.location.y)}; ${Math.floor(player.location.z)})`);
playerMarkers.addLayer(marker);
mappedPlayers[player.uniqueId] = marker;
}
}
for (const [uniqueId, playerMarker] of Object.entries(mappedPlayers)) {
if(!players.find(p => p.uniqueId === uniqueId)) {
playerMarkers.removeLayer(playerMarker);
delete mappedPlayers[uniqueId];
}
}
}
fetch('https://crss.blurryface.xyz/api/v1/players')
.then(r => r.json())
.then(p => {
updatePlayerPos(p);
});
setInterval(() => {
fetch('https://crss.blurryface.xyz/api/v1/players')
.then(r => r.json())
.then(p => {
updatePlayerPos(p);
});
}, 1000);

View file

@ -1,18 +0,0 @@
const $ = selector => document.querySelector(selector);
const navToggle = $(".navToggle");
const menu = $(".pageNav > .container");
navToggle.onclick = () => {
const menuToggled = menu.classList.contains("opened");
if (menuToggled) {
menu.classList.remove("opened");
} else {
menu.classList.add("opened");
}
navToggle.innerHTML = menuToggled ? "Menu" : "Close";
}
window.addEventListener("transitionBuffering", () => menu.classList.remove("opened"));

View file

@ -1,66 +0,0 @@
/*
* Myadeleines' Simple Page Transition Script. "Trans" for short. :trol:
* Making CSS-powered animated transitions between pages possible.
*
* Licensed under the Apache License. Please refer to the LICENSE file.
*/
const transitionBufferingEvent = new Event("transitionBuffering");
const transitionStartEvent = new Event("transitionStart");
const transitionEndEvent = new Event("transitionEnd");
const $ = selector => document.querySelector(selector);
const parser = new DOMParser();
const mainElement = $("main");
export function enableTransition( hyperlinkElement ) {
hyperlinkElement.addEventListener("click", event => {
event.preventDefault(); // Browser won't load the page when the hyperlink is pressed.
loadURL(hyperlinkElement.href, true, hyperlinkElement);
});
}
function loadURL( targetedURL, updateURL, hyperlinkElement ) {
const activeHyperlink = $(".pageNav .active");
mainElement.classList.add("buffering");
window.dispatchEvent(transitionBufferingEvent);
fetch(targetedURL + "?reduced")
.catch(error => {
console.log(error);
alert(error);
mainElement.classList.remove("buffering");
})
.then(response => response.text().then( fetchedPage => {
fetchedPage = parser.parseFromString(fetchedPage, "text/html");
fetchedPage = {
content: fetchedPage.querySelector("main").innerHTML,
title: fetchedPage.querySelector("title").innerHTML,
}
if (activeHyperlink) activeHyperlink.classList.remove("active");
if (hyperlinkElement) hyperlinkElement.classList.add("active");
if (updateURL) history.pushState({}, fetchedPage.title, targetedURL);
$("title").innerHTML = fetchedPage.title;
mainElement.classList.remove("buffering");
mainElement.classList.add("transition");
let transitionDuration = getComputedStyle(mainElement).transitionDuration;
transitionDuration = parseFloat(transitionDuration) * 1000;
window.dispatchEvent(transitionStartEvent);
setTimeout(() => {
mainElement.innerHTML = fetchedPage.content;
mainElement.classList.remove("transition");
window.dispatchEvent(transitionEndEvent);
}, transitionDuration);
}));
}
window.addEventListener("popstate", Event => {
loadURL(window.location.href, false)
});

224
lib/Database.ts Normal file
View file

@ -0,0 +1,224 @@
import { Role } from '@/utils/permissions';
import mysql, { Pool, QueryResult } from 'mysql2/promise';
import crypto from 'node:crypto';
interface DiscordTokenData {
token_type: string;
access_token: string;
expires_in: number;
refresh_token: string;
scope: string;
id_token: string;
}
interface UserTable {
id: number;
did: string;
username: string;
global_name: string;
email: string;
avatar?: string;
banner?: string;
accent_color?: number;
permissions: number;
}
interface UserTableWithoutEmail {
id: number;
did: string;
username: string;
global_name: string;
avatar?: string;
banner?: string;
accent_color?: number;
permissions: number;
}
interface TeamMember {
id: number;
uid: number;
did: string;
username: string;
global_name: string;
email: string;
avatar: string;
banner: string;
accent_color: number;
permissions: number;
role: Role;
}
class Database {
static instance: (Database | null) = null;
mysqlPool: (Pool | null) = null;
constructor() {
if(Database.instance)
return Database.instance;
this.mysqlPool = mysql.createPool({
database: process.env.DB_NAME,
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
waitForConnections: true,
supportBigNumbers: true,
connectionLimit: 10,
queueLimit: 0
});
Database.instance = this;
}
// Auth Stuff
async createUser(user: any): Promise<number> {
const [ result ] = await this.mysqlPool!.execute('SELECT * FROM users WHERE did = ?', [ user.id ]);
if ((result as any).length > 0) {
return (result as any)[0].id;
}
const [ res ] = await this.mysqlPool!.execute('INSERT INTO users (did, username, global_name, email, avatar, banner, accent_color) VALUES (?, ?, ?, ?, ?, ?, ?)', [
user.id,
user.username,
(user.global_name || user.username),
user.email,
user.avatar,
user.banner,
user.accent_color
]);
return (res as any).insertId;
}
/**
* Creates a session for the user.
*
* Should only be called by backend!
*
* @param userData The data returned from the `/oauth2/token` endpoint.
* @param userAgent The user agent of the user.
*
* @returns `string` The session token.
*/
async createSession(userData: DiscordTokenData, userAgent?: string): Promise<string | null> {
const res = await fetch(`${process.env.DISCORD_API}/users/@me`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${userData.access_token}`,
},
});
if (res.ok) {
const user = await res.json();
const uid = await this.createUser(user);
const sum = crypto.createHmac('sha256', process.env.AUTH_SECRET!);
const base = Buffer.from(user.id).toString('base64').replaceAll('=', '');
const date = Buffer.from((Date.now() - 1688940000000).toString()).toString('base64').replaceAll('=', '');
sum.update(userData.access_token);
const sid = base + '.' + date + '.' + sum.digest('hex');
const [ result ] = await this.mysqlPool!.execute('INSERT INTO sessions (sid, uid, access_token, refresh_token, id_token, user_agent, expires) VALUES (?, ?, ?, ?, ?, ?, ?)', [
sid,
uid,
userData.access_token,
userData.refresh_token,
userData.id_token,
userAgent,
new Date(Date.now() + (userData.expires_in * 1000)),
]);
return sid;
} else {
throw new Error('Error Fetching Discord User Data');
}
}
async deleteSession(sid: string): Promise<void> {
await this.mysqlPool!.execute('DELETE FROM sessions WHERE sid = ?', [ sid ]);
}
async getSession(sid: string): Promise<any> {
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM sessions WHERE sid = ?', [ sid ]);
return (rows as any)[0];
}
// End of Auth Stuff
async getUser(id: number): Promise<UserTable> {
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM users WHERE id = ?', [ id ]);
return (rows as UserTable[])[0];
}
async getUsers(): Promise<UserTableWithoutEmail[]> {
const [ rows ] = await this.mysqlPool!.execute('SELECT id, did, username, global_name, avatar, banner, accent_color, permissions FROM users');
return rows as UserTableWithoutEmail[];
}
async getUserUsername(username: string): Promise<UserTable> {
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM users WHERE username = ?', [ username ]);
return (rows as UserTable[])[0];
}
async getUserSessions(uid: number): Promise<any> {
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM sessions WHERE uid = ?', [ uid ]);
return rows;
}
async getTeam(): Promise<TeamMember[]> {
const [ rows ] = await this.mysqlPool!.execute('SELECT team_members.id AS tid, team_members.uid AS uid, users.did, users.username, users.global_name, users.email, users.avatar, users.banner, users.accent_color, users.permissions, team_members.role FROM team_members JOIN users ON team_members.uid = users.id;');
return (rows as any).map((row: any) => {
return {
id: row.tid,
uid: row.uid,
did: row.did,
username: row.username,
global_name: row.global_name,
email: row.email,
avatar: row.avatar,
banner: row.banner,
accent_color: row.accent_color,
permissions: row.permissions,
role: (row.role as Role),
};
});
}
async getNations(): Promise<any> {
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM nations');
return rows;
}
async getNationCode(code: string): Promise<any> {
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM nations WHERE code = ?', [ code ]);
return (rows as any)[0];
}
async getCompaies(nid: number): Promise<any> {
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM companies WHERE nid = ?', [ nid ]);
return rows;
}
async updateUserPermissions(id: number, permissions: any) {
await this.mysqlPool!.execute('UPDATE users SET permissions = ? WHERE id = ?', [ permissions, id ]);
return;
}
}
export default Database;

79
next.config.js Normal file
View file

@ -0,0 +1,79 @@
const path = require('path');
const childProcess = require('child_process');
const gitBranch = childProcess.execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
const gitCommit = childProcess.execSync('git rev-parse HEAD').toString().trim();
const gitCommitTime = childProcess.execSync('git show -s --format=%cI').toString().trim();
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
sassOptions: {
includePaths: [ path.join(__dirname, 'styles') ]
},
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'cdn.discordapp.com',
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'crss.fra1.cdn.digitaloceanspaces.com',
port: '',
pathname: '/**',
}
]
},
publicRuntimeConfig: {
modifiedDate: new Date().getTime(),
discord: {
clientId: process.env.DISCORD_CLIENT,
redirectUri: process.env.DISCORD_REDIRECT,
api: process.env.DISCORD_API,
scopes: [
'identify',
'email',
'openid'
]
},
git: {
branch: gitBranch,
commit: {
sha: gitCommit,
created: new Date(gitCommitTime)
}
}
},
generateBuildId: async () => {
return childProcess.execSync('git rev-parse HEAD').toString().trim();
},
headers: async () => {
return [
{
source: '/api/:path*',
headers: [
{
key: 'Access-Control-Allow-Origin',
value: '*'
}
]
}
];
},
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: [ '@svgr/webpack' ]
});
return config;
},
experimental: {
optimizePackageImports: [ '@icons-pack/react-simple-icons' ]
}
};
module.exports = nextConfig;

37
package.json Normal file
View file

@ -0,0 +1,37 @@
{
"name": "crss-website",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@icons-pack/react-simple-icons": "^10.0.0",
"@svgr/webpack": "^8.1.0",
"bcrypt": "^5.1.1",
"cookie": "^0.6.0",
"framer-motion": "^11.3.31",
"jose": "^5.8.0",
"lucide-react": "^0.429.0",
"mysql2": "^3.11.0",
"next": "14.2.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"showdown": "^2.1.0",
"ua-parser-js": "^1.0.38"
},
"devDependencies": {
"@types/cookie": "^0.6.0",
"@types/node": "^20.16.2",
"@types/react": "^18.3.4",
"@types/react-dom": "^18.3.0",
"@types/ua-parser-js": "^0.7.39",
"eslint": "^8.57.0",
"eslint-config-next": "14.2.6",
"sass": "^1.77.8",
"typescript": "^5.5.4"
}
}

25
pages/404.tsx Normal file
View file

@ -0,0 +1,25 @@
import Footer from '@/components/Footer';
import Meta from '@/components/Meta';
import NavBar from '@/components/NavBar';
import PageContent from '@/components/PageContent';
export default function NotFound() {
return (
<>
<Meta page={{ title: '404' }} />
<NavBar currentPage="404" />
<PageContent>
<div className="container">
<h1 id="404-not-found">404 Not Found</h1>
<p>
We couldn&apos;t find this page :(
</p>
</div>
</PageContent>
<Footer />
</>
);
}

16
pages/_app.tsx Normal file
View file

@ -0,0 +1,16 @@
import '@/styles/globals.scss';
import { UserProvider } from '@/context/UserContext';
import { AnimatePresence } from 'framer-motion';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps, router }: AppProps) {
return (
<UserProvider>
<AnimatePresence mode="wait" initial={false} onExitComplete={() => window.scrollTo(0, 0)}>
<Component {...pageProps} key={router.asPath} />
</AnimatePresence>
</UserProvider>
);
}

13
pages/_document.tsx Normal file
View file

@ -0,0 +1,13 @@
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}

78
pages/about.tsx Normal file
View file

@ -0,0 +1,78 @@
import Card from '@/components/Card';
import Footer from '@/components/Footer';
import NavBar from '@/components/NavBar';
import PageContent from '@/components/PageContent';
import Database from '@/lib/Database';
import Image from 'next/image';
import Link from 'next/link';
import { Role } from '@/utils/permissions';
import styles from '@/styles/About.module.scss';
import { Globe } from 'lucide-react';
import Meta from '@/components/Meta';
export default function About({ teamMembers }: { teamMembers: any[] }) {
return (
<>
<Meta page={{ title: 'About' }} />
<NavBar currentPage="about" />
<PageContent>
<h1>About</h1>
<p>
We are a small team running this server.
</p>
<h2>Team</h2>
<div className={styles.teamList}>
{teamMembers.map((member, i) => (
<Card key={i} className={styles.teamCard}>
<div className={styles.memberBanner}>
<Image src={`https://cdn.discordapp.com/avatars/${member.did}/a_${member.avatar}.png`} alt={member.global_name} width={128} height={128} />
</div>
<div className={styles.memberContent}>
<ul className={styles.memberLinks}>
<li>
<Link href="#" title="Website">
<Globe />
</Link>
</li>
</ul>
<h3>{member.global_name}</h3>
{member.role === Role.Owner && <label>Owner</label>}
{member.role === Role.Admin && <label>Admin</label>}
</div>
</Card>
))}
</div>
</PageContent>
<Footer />
</>
);
}
export async function getServerSideProps(context: any) {
const db = new Database();
const teamMembers = await db.getTeam();
if (!teamMembers)
return {
notFound: true
};
return {
props: {
teamMembers
}
};
}

134
pages/admin/index.tsx Normal file
View file

@ -0,0 +1,134 @@
import PageContent from '@/components/PageContent';
import { useUser } from '@/context/UserContext';
import { getCookieFromContext } from '@/utils/cookies';
import { use, useEffect, useState } from 'react';
import Image from 'next/image';
export default function Admin() {
const { user, isLoggedIn } = useUser();
const [ users, setUsers ] = useState<any[]>([]);
useEffect(() => {
fetch('/api/v1/users')
.then((response) => response.json())
.then((data) => {
setUsers(data);
});
}, [ ]);
const addTeamMember = (userId: number) => {
alert(userId);
};
const editPermissions = (user: any) => {
const perms = prompt('Enter new permissions for user (1 == Admin; There is nothing more.):', user.permissions);
if (perms === null) {
return;
}
fetch(`/api/v1/user/${user.username}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
permissions: perms,
})
})
.then((response) => {
if (response.status === 204) {
alert('Permissions updated.');
} else {
alert('Failed to update permissions.');
}
});
};
if (!isLoggedIn) {
return null;
}
return (
<>
<PageContent>
<h1>Admin</h1>
<p>Welcome, {user.names.global_name}!</p>
<p>What are we doin&apos; today?</p>
<h2>Users</h2>
<table>
<thead>
<tr>
<th>Avatar</th>
<th>Username</th>
<th>Global Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{users && (
users.map((u) => (
<tr key={u.id}>
<td>
<Image src={`https://cdn.discordapp.com/avatars/${u.did}/${u.avatar}.png`} alt={user.global_name} width={32} height={32} />
</td>
<td>{u.username}</td>
<td>{u.global_name}</td>
<td>
<button onClick={() => { addTeamMember(u.id); }}>
Add As Team Member (On the /about page.)
</button>
<button onClick={() => { editPermissions(u); }}>
Edit Permissions
</button>
<button onClick={() => { alert('GDPR? Yeah no, will implement later.'); }}>
Delete
</button>
</td>
</tr>
))
)}
</tbody>
</table>
<h2>Your User in JSON Format</h2>
<p>In case you need it for some reason.</p>
<pre>
{JSON.stringify(user, null, 2)}
</pre>
</PageContent>
</>
);
}
export async function getServerSideProps(context: any) {
const { req } = context;
const cookie = req.headers.cookie;
let isLoggedIn = false;
if (cookie) {
const sessionCookie = getCookieFromContext('session', cookie);
if (sessionCookie) {
isLoggedIn = true;
}
}
if (!isLoggedIn) {
return {
notFound: true,
};
}
return {
props: {},
};
}

13
pages/api/hello.ts Normal file
View file

@ -0,0 +1,13 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
type Data = {
name: string;
};
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>,
) {
res.status(200).json({ name: 'John Doe' });
}

71
pages/api/v1/auth.ts Normal file
View file

@ -0,0 +1,71 @@
import Database from '@/lib/Database';
import { serialize } from 'cookie';
import type { NextApiRequest, NextApiResponse } from 'next';
type Data = {
sid: string | null;
};
type Error = {
error: string;
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data | Error>,
) {
const db = new Database();
const { code, state } = req.query;
console.log(code, state);
const discordApi = process.env.DISCORD_API!;
try {
if (typeof code === 'string') {
const data = await fetch(`${discordApi}/oauth2/token`, {
method: 'POST',
body: new URLSearchParams({
client_id: process.env.DISCORD_CLIENT!,
client_secret: process.env.DISCORD_SECRET!,
grant_type: 'authorization_code',
code,
redirect_uri: process.env.DISCORD_REDIRECT!,
}),
});
const json = await data.json();
const sid = await db.createSession(json, req.headers['user-agent']);
if (sid) {
const cookie = serialize('session', sid, {
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
path: '/',
expires: new Date(Date.now() + json.expires_in * 1000),
});
res.setHeader('Set-Cookie', cookie);
if ((state as string).startsWith('/'))
res.status(302).redirect(state as string);
else
res.status(400).json({ error: 'Invalid redirect uri in state!' });
}
return;
}
} catch (error) {
console.error(error);
res.status(500).json(
{ error: 'Internal Server Error' }
);
}
res.status(400).json(
{ error: 'Invalid code' }
);
}

View file

@ -0,0 +1,61 @@
import type { NextApiRequest, NextApiResponse } from 'next';
type Error = {
message: string;
};
interface ServerInfo {
version: string;
online: number;
worlds: string[];
}
import * as net from 'node:net';
export default function handler(
req: NextApiRequest,
res: NextApiResponse<ServerInfo | Error>,
) {
try {
const mc_api = process.env.MC_API!.split(':');
const socket = net.createConnection({
host: mc_api[0],
port: parseInt(mc_api[1]),
}, async () => {
const reqData = Buffer.alloc(1 + 4);
reqData.writeInt8(0x00, 0);
reqData.writeUint32BE(0, 1);
socket.write(reqData);
socket.on('data', (data) => {
const packetId = data[0];
const length = data.readUInt32BE(1); // unused but in case someone wants to verify the lenght :3
if (packetId !== 0x00) {
socket.end();
return res.status(500).json({ message: 'There was an error with the server.' });
}
const jsonData = data.toString('utf-8', 5);
socket.end();
res.status(200).json(JSON.parse(jsonData));
});
socket.on('error', (err) => {
console.error(err);
socket.end();
res.status(500).json({ message: 'There was an error with the server.' });
});
});
} catch (e) {
res.status(500).json({ message: 'There was an error with the server.' });
}
}

48
pages/api/v1/session.ts Normal file
View file

@ -0,0 +1,48 @@
import Database from '@/lib/Database';
import type { NextApiRequest, NextApiResponse } from 'next';
import { serialize } from 'cookie';
type Data = {
success: string;
};
interface Error {
error: string;
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data | Error>,
) {
const db = new Database();
const sid = req.cookies.session;
if (!sid)
return res.status(401).json({ error: 'Unauthorized' });
const session = await db.getSession(sid!);
if (!session)
return res.status(401).json({ error: 'Unauthorized' });
const user = await db.getUser(session.uid);
if (!user)
return res.status(404).json({ error: 'Not Found' });
if (req.method === 'DELETE') {
db.deleteSession(sid!);
res.setHeader('Set-Cookie', serialize('session', '', {
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
path: '/',
expires: new Date(0),
}));
return res.status(200).json({ success: 'Delete Session for ' + user.username });
}
res.status(405).json({ error: 'Method Not Allowed' });
}

52
pages/api/v1/user/@me.ts Normal file
View file

@ -0,0 +1,52 @@
import Database from '@/lib/Database';
import type { NextApiRequest, NextApiResponse } from 'next';
type Data = {
id: number;
discord_id: string;
names: {
username: string;
global_name: string;
};
email: string;
avatar?: string;
banner?: string;
accent_color?: number;
permissions: number;
};
interface Error {
error: string;
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data | Error>,
) {
const db = new Database();
const sid = req.cookies.session;
if (!sid)
return res.status(401).json({ error: 'Unauthorized' });
const session = await db.getSession(sid!);
if (!session)
return res.status(401).json({ error: 'Unauthorized' });
const user = await db.getUser(session.uid);
res.status(200).json({
id: user.id,
discord_id: user.did,
names: {
username: user.username,
global_name: user.global_name
},
email: user.email,
avatar: user.avatar,
banner: user.banner,
accent_color: user.accent_color,
permissions: user.permissions
});
}

View file

@ -0,0 +1,69 @@
import Database from '@/lib/Database';
import { isUserAdmin } from '@/utils/auth_util';
import type { NextApiRequest, NextApiResponse } from 'next';
type Data = {
id: number;
discord_id: string;
names: {
username: string;
global_name: string;
};
avatar?: string;
banner?: string;
accent_color?: number;
permissions: number;
};
interface Error {
error: string;
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data | Error>,
) {
const db = new Database();
const user = await db.getUserUsername(req.query.username as string);
if (!user) {
return res.status(404).json({ error: 'Not Found' });
}
// hehe only admins update users :trolley:
// also validation yeah uh... didn't have budget for that
// tech debt for the win
if (req.method === 'PATCH') {
const sid = req.cookies.session;
const isAdmin = isUserAdmin(sid);
if (!isAdmin) {
return res.status(401).json({ error: 'Unauthorized' });
}
const { permissions } = req.body;
if (permissions) {
await db.updateUserPermissions(user.id, permissions);
}
res.status(204).end();
return;
}
res.status(200).json({
id: user.id,
discord_id: user.did,
names: {
username: user.username,
global_name: user.global_name
},
avatar: user.avatar,
banner: user.banner,
accent_color: user.accent_color,
permissions: user.permissions
});
}

24
pages/api/v1/users.ts Normal file
View file

@ -0,0 +1,24 @@
import Database from '@/lib/Database';
import type { NextApiRequest, NextApiResponse } from 'next';
interface Response {
id: number;
did: string;
username: string;
global_name: string;
avatar?: string;
banner?: string;
accent_color?: number;
permissions: number;
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Response[]>,
) {
const db = new Database();
const users = await db.getUsers();
res.status(200).json(users);
}

25
pages/gallery.tsx Normal file
View file

@ -0,0 +1,25 @@
import Footer from '@/components/Footer';
import Meta from '@/components/Meta';
import NavBar from '@/components/NavBar';
import PageContent from '@/components/PageContent';
export default function Gallery() {
return (
<>
<Meta page={{ title: 'Gallery' }} />
<NavBar currentPage="gallery" />
<PageContent>
<div className="container">
<h1>Gallery</h1>
<p>
Under Construction
</p>
</div>
</PageContent>
<Footer />
</>
);
}

41
pages/index.tsx Normal file
View file

@ -0,0 +1,41 @@
import AdBanner from '@/components/AdBanner';
import Footer from '@/components/Footer';
import Meta from '@/components/Meta';
import NavBar from '@/components/NavBar';
import PageContent from '@/components/PageContent';
export default function Home() {
const nations = [ ];
return (
<>
<Meta page={{ title: 'Home' }} />
<NavBar currentPage="home" />
<PageContent>
<h1>Home</h1>
<p>
Welcome to Clyde&apos;s Real Survival SMP, CRSS for short. We are a small SMP server that updates to every version starting from b1.0 on the 1st of every month. We have a small community of players that are very friendly and welcoming to new players. We have a few rules that you should follow to make the server a better place for everyone, you can find them at the bottom of the page.
</p>
<p>
The server is built on the idea of nations, featuring { nations.length } nations so far, with the oldest being Panorama Socialist Federation, which was originally known as Republic of Panorama. These nations are scattered around the map, with some being more active than others. You can be sure to find a nation that fits your playstyle, if not you can just start your own!
</p>
<h2 id="modpacl">Modpack</h2>
<p>
We have a client-side modpack to make your experince on CRSS better, the mods included are intended to be small, simple, and follow the rules of CRSS.
</p>
<p>
You can download it from Modrinth: <a href="https://modrinth.com/modpack/crsspack">https://modrinth.com/modpack/crsspack</a>.
</p>
</PageContent>
<Footer />
</>
);
}

137
pages/legal/privacy.tsx Normal file
View file

@ -0,0 +1,137 @@
import Footer from '@/components/Footer';
import Meta from '@/components/Meta';
import NavBar from '@/components/NavBar';
import PageContent from '@/components/PageContent';
import Link from 'next/link';
export default function PrivacyPolicy() {
return (
<>
<Meta page={{ title: 'Privacy Policy' }} />
<NavBar currentPage="privacy" />
<PageContent>
<h1>Privacy Policy</h1>
<p>
<strong>Effective Date</strong>: {new Date('2024-08-29T02:00:00.000Z').toLocaleDateString('en-GB')}
</p>
<h2 id="introduction">1. Introduction</h2>
<p>
Welcome to Clyde&apos;s Real Survival SMP (&quot;CRSS&quot;). We are
committed to protecting your privacy and ensuring that your personal
information is handled in a safe and responsible manner. This Privacy
Policy outlines the types of information we collect from our users,
how we use that information, and the measures we take to protect it.
</p>
<h2 id="information-we-collect">2. Information We Collect</h2>
<p>CRSS collects the following personal information from users:</p>
<ul>
<li>
<strong>Discord User IDs</strong>: A unique identifier provided by Discord for each user.
</li>
<li>
<strong>Discord Username</strong>: The username you use on Discord.
</li>
<li>
<strong>Discord Global Display Names</strong>: The display name that is visible to other users on Discord.
</li>
<li>
<strong>Discord Emails</strong>: The email address associated with your Discord account.
</li>
<li>
<strong>User Agents</strong>: Information regarding the device, browser, and operating system you use to access CRSS.
</li>
</ul>
<h2 id="how-we-use-your-information">3. How We Use Your Information</h2>
<p>The information we collect is used for the following purposes:</p>
<ul>
<li>
<strong>Account Management</strong>: To verify your identity and manage your account on CRSS.
</li>
<li>
<strong>Communication</strong>: To send notifications, updates, and other relevant communications related to CRSS.
</li>
<li>
<strong>Security and Moderation</strong>: To ensure the safety and security of our community, including the detection and prevention of fraudulent or unauthorized activities.
</li>
<li>
<strong>Improvement of Services</strong>: To enhance and improve the user experience on CRSS.
</li>
</ul>
<h2 id="sharing-and-disclosure-of-information">4. Sharing and Disclosure of Information</h2>
<p>CRSS does not sell, trade, or otherwise transfer your personal information to outside parties except under the following circumstances:</p>
<ul>
<li>
<strong>Legal Compliance</strong>: We may disclose your information if required to do so by law or in response to a valid request from a law enforcement authority.
</li>
<li>
<strong>Protection of Rights</strong>: We may share information when we believe it is necessary to protect the rights, property, or safety of CRSS, our users, or others.
</li>
<li>
<strong>Service Providers</strong>: We may engage third-party service providers to perform functions on our behalf, such as server hosting and maintenance. These providers have access to the necessary information only to perform their functions and are obligated to maintain confidentiality.
</li>
</ul>
<h2 id="data-security">5. Data Security</h2>
<p>We implement reasonable and appropriate security measures to protect your personal information from unauthorized access, alteration, disclosure, or destruction. However, no method of transmission over the internet or electronic storage is 100% secure, and we cannot guarantee absolute security.</p>
<h2 id="data-retention">6. Data Retention</h2>
<p>We will retain your personal information only for as long as necessary to fulfill the purposes for which it was collected or as required by applicable laws. Once your information is no longer needed, we will delete or anonymize it in a secure manner.</p>
<h2 id="your-rights">7. Your Rights</h2>
<p>You have the right to:</p>
<ul>
<li>
Access the personal information we hold about you.
</li>
<li>
Request corrections to any inaccuracies in your personal information.
</li>
<li>
Request the deletion of your personal information.
</li>
<li>
Withdraw your consent to the processing of your information.
</li>
</ul>
<p>To exercise these rights, please contact us using the information provided below.</p>
<h2 id="changes-to-this-privacy-policy">8. Changes to This Privacy Policy</h2>
<p>CRSS reserves the right to update or modify this Privacy Policy at any time. Any changes will be effective immediately upon posting the revised policy. We will notify you of any significant changes through the Discord server or by other means. Your continued use of CRSS after any modifications to this Privacy Policy constitutes your acceptance of the updated terms.</p>
<h2 id="contact-us">9. Contact Us</h2>
<p>If you have any questions or concerns about this Privacy Policy or our data practices, please contact us at:</p>
<p>
<strong>Email:</strong> <Link href="mailto:admin@theclashfruit.me">admin@theclashfruit.me</Link> <br />
<strong>Discord Server:</strong> <Link href="https://discord.gg/rGjCKawPkS">https://discord.gg/rGjCKawPkS</Link>
</p>
<hr />
<p>By using Clyde&apos;s Real Survival SMP, you acknowledge that you have read and understood this Privacy Policy and agree to the collection, use, and sharing of your information as described herein.</p>
</PageContent>
<Footer />
</>
);
}

108
pages/legal/tos.tsx Normal file
View file

@ -0,0 +1,108 @@
import Footer from '@/components/Footer';
import Meta from '@/components/Meta';
import NavBar from '@/components/NavBar';
import PageContent from '@/components/PageContent';
import Link from 'next/link';
export default function TermsOfService() {
return (
<>
<Meta page={{ title: 'Terms of Service' }} />
<NavBar currentPage="terms" />
<PageContent>
<h1>Terms of Service</h1>
<p>
<strong>Effective Date</strong>: {new Date('2024-08-29T02:00:00.000Z').toLocaleDateString('en-GB')}
</p>
<h2 id="acceptance-of-terms">1. Acceptance of Terms</h2>
<p>
By accessing or using Clyde&apos;s Real Survival SMP (&quot;CRSS&quot;), you agree to comply with and be bound by these Terms of Service. If you do not agree with any part of these terms, you must not use CRSS.
</p>
<h2 id="eligibility">2. Eligibility</h2>
<p>
To participate in CRSS, you must have a valid Discord account and be at least 13 years old. By using CRSS, you represent and warrant that you meet these eligibility requirements.
</p>
<h2 id="user-conduct">3. User Conduct</h2>
<p>While using CRSS, you agree to:</p>
<ul>
<li>
<strong>Respect Other Users</strong>: Do not engage in harassment, hate speech, discrimination, or any behavior that harms or threatens other users.
</li>
<li>
<strong>Follow the Rules</strong>: Adhere to all posted rules and guidelines specific to CRSS, including in-game rules and server rules.
</li>
<li>
<strong>No Cheating or Exploiting</strong>: Do not use cheats, hacks, or exploits to gain an unfair advantage in the game.
</li>
<li>
<strong>No Unauthorized Activities</strong>: Do not engage in activities that violate any laws or regulations or that could harm CRSS or its users.
</li>
</ul>
<h2 id="account-responsibility">4. Account Responsibility</h2>
<p>
You are responsible for maintaining the confidentiality of your Discord account credentials and for all activities that occur under your account. If you suspect any unauthorized use of your account, you must notify CRSS administrators immediately.
</p>
<h2 id="content-and-intellectual-property">5. Content and Intellectual Property</h2>
<p>
All content within CRSS, including but not limited to text, graphics, logos, and software, is the property of CRSS or its licensors. You may not use, reproduce, distribute, or create derivative works based on this content without explicit permission.
</p>
<h2 id="termination">6. Termination</h2>
<p>
CRSS reserves the right to terminate or suspend your access to the server at any time, without notice, for violating these Terms of Service or for any other reason deemed necessary by the administrators.
</p>
<h2 id="disclaimers">7. Disclaimers</h2>
<p>
CRSS is provided &quot;as is&quot; without warranties of any kind, either express or implied. We do not guarantee that the server will be available at all times or that it will be free of errors or interruptions.
</p>
<h2 id="limitation-of-liability">8. Limitation of Liability</h2>
<p>
In no event shall CRSS or its administrators be liable for any direct, indirect, incidental, special, or consequential damages arising from your use or inability to use the server.
</p>
<h2 id="changes-to-these-terms">9. Changes to These Terms</h2>
<p>
CRSS reserves the right to update or modify these Terms of Service at any time. Any changes will be effective immediately upon posting the revised terms. Your continued use of CRSS after any modifications constitutes your acceptance of the updated terms.
</p>
<h2 id="contact-information">10. Contact Information</h2>
<p>
If you have any questions or concerns about these Terms of Service, please contact us at:
</p>
<p>
<strong>Email:</strong> <Link href="mailto:admin@theclashfruit.me">admin@theclashfruit.me</Link> <br />
<strong>Discord Server:</strong> <Link href="https://discord.gg/rGjCKawPkS">https://discord.gg/rGjCKawPkS</Link>
</p>
<hr />
<p>
By using Clyde&apos;s Real Survival SMP, you acknowledge that you have read, understood, and agree to these Terms of Service.
</p>
</PageContent>
<Footer />
</>
);
}

36
pages/map.tsx Normal file
View file

@ -0,0 +1,36 @@
import Footer from '@/components/Footer';
import Meta from '@/components/Meta';
import NavBar from '@/components/NavBar';
import PageContent from '@/components/PageContent';
import { ExternalLink } from 'lucide-react';
import Link from 'next/link';
export default function Map() {
return (
<>
<Meta page={{ title: 'Map' }} />
<NavBar currentPage="map" />
<PageContent>
<h1>Map</h1>
<p>
The orginal map&apos;s v2 version will be coming eventually. Till then enjoy the BlueMap:
</p>
<iframe src="https://map.crss.cc" width="100%" style={{ aspectRatio: '16/9' }} />
<p>
<Link href="https://map.crss.cc" target="_blank">
Open in New Tab
<ExternalLink />
</Link>
</p>
</PageContent>
<Footer />
</>
);
}

61
pages/nation/[nation].tsx Normal file
View file

@ -0,0 +1,61 @@
import Footer from '@/components/Footer';
import Meta from '@/components/Meta';
import NavBar from '@/components/NavBar';
import PageContent from '@/components/PageContent';
import Database from '@/lib/Database';
export default function Nation({ nation, companies }: { nation: any, companies: any[] }) {
return (
<>
<Meta page={{ title: nation.name }} />
<NavBar currentPage={nation.code} />
<PageContent>
<div className="container">
<h1>{nation.name}</h1>
<p>{nation.description}</p>
<h2>Companies</h2>
{companies.length > 0 ? (
<ul>
{companies.map((company) => {
return (
<li key={company.id}>
{company.name}
</li>
);
})}
</ul>
) : (
<p>This nation has no companies.</p>
)}
</div>
</PageContent>
<Footer />
</>
);
}
export async function getServerSideProps(context: any) {
const db = new Database();
const nation = await db.getNationCode(context.query.nation as string);
if (!nation) {
return {
notFound: true
};
}
const companies = await db.getCompaies(nation.id);
return {
props: {
nation,
companies
}
};
}

48
pages/nations.tsx Normal file
View file

@ -0,0 +1,48 @@
import Footer from '@/components/Footer';
import Meta from '@/components/Meta';
import NavBar from '@/components/NavBar';
import PageContent from '@/components/PageContent';
import Database from '@/lib/Database';
import Link from 'next/link';
import Image from 'next/image';
import styles from '@/styles/Nations.module.scss';
export default function Nations({ nations }: { nations: any[] }) {
return (
<>
<Meta page={{ title: 'Nations' }} />
<NavBar currentPage="nations" />
<PageContent>
<h1>Nations</h1>
<div className={styles.nationGrid}>
{nations.map((nation, i) => (
<Link key={i} href={`/nation/${nation.code}`} className={styles.nationCard}>
<h2>{nation.name}</h2>
<p>{nation.short_description}</p>
<Image src={`https://crss.fra1.cdn.digitaloceanspaces.com/nation/${nation.code}/flag.svg`} alt={nation.name} width={128} height={64} className={styles.icon} />
</Link>
))}
</div>
</PageContent>
<Footer />
</>
);
}
export async function getServerSideProps(context: any) {
const db = new Database();
const nations = await db.getNations();
return {
props: {
nations
}
};
}

138
pages/pp.tsx Normal file
View file

@ -0,0 +1,138 @@
import PageContent from '@/components/PageContent';
import NavBar from '@/components/NavBar';
import Meta from '@/components/Meta';
import styles from '../styles/PremiumPlus.module.scss';
import Link from 'next/link';
import Footer from '@/components/Footer';
export default function PremiumPlus() {
return (
<>
<Meta page={{ title: 'Premium Plus' }} />
<NavBar currentPage="pp" />
<PageContent>
<div className={styles.cardContainer}>
<div className={styles.card}>
<h1>CRSS Premium+ Subscription Plan</h1>
<span>
Unlock the Ultimate Minecraft Experience with CRSS Premium
</span>
<h2>$6.90/month</h2>
<span>or save 2% with annual billing!</span>
</div>
<span>
Join the elite ranks of CRSS Premium+ members and elevate your
Minecraft gameplay to new heights.
</span>
<span>
For just $6.90 per month, our subscription plan offers you exclusive
perks, priority access, and a superior gaming environment. With CRSS
Premium+, you&apos;ll enjoy premium features that enhance your
experience and set you apart from the rest.
</span>
<span>
<h3>Exclusive Benefits of CRSS Premium+:</h3>
<ul>
<li>
<strong>Priority Queue Access:</strong> Skip the lines and jump
straight into the game with priority server access, ensuring
you&apos;re always where the action is.
</li>
<li>
<strong>Custom Kits & Items:</strong> Receive unique in-game
kits and items available only to CRSS Premium+ members, giving
you an edge in every match.
</li>
<li>
<strong>Enhanced Gameplay Features:</strong> Access premium
plugins and gameplay enhancements, including advanced commands
and special game modes, designed to take your Minecraft
experience to the next level.
</li>
<li>
<strong>Monthly Loot Crates:</strong> Get monthly loot crates
filled with rare items, unique cosmetics, and special bonuses,
keeping your inventory well-stocked with top-tier gear.
</li>
<li>
<strong>Dedicated Support:</strong> Enjoy priority assistance
from our premium support team, ensuring any issues or questions
are resolved quickly so you can focus on the game.
</li>
<li>
<strong>VIP Events & Contests:</strong> Participate in exclusive
events and contests with bigger and better rewards, open only to
CRSS Premium+ members.
</li>
</ul>
<h3>Subscription Details:</h3>
<ul>
<li>
<strong>Subscription Fee:</strong> $6.90 per month, offering
exceptional value for a premium gaming experience.
</li>
<li>
<strong>Contract Term:</strong> Flexible monthly subscription
with the freedom to cancel anytime.
</li>
<li>
<strong>Instant Activation:</strong> Your CRSS Premium+ benefits
activate immediately upon subscription, so you can start
enjoying all the perks right away.
</li>
</ul>
<h3>Join CRSS Premium+ Today</h3>
<span>
For just $6.90 per month, you can access the ultimate Minecraft
experience with CRSS Premium+. Don&apos;t miss out on the
exclusive benefits that come with being a premium member of our
community. For more information or to subscribe, visit our website
at <Link href="/">crss.cc</Link> or contact our support team
at&nbsp;
<a href="mailto:admin@theclashfruit.me">admin@theclashfruit.me</a>
.
</span>
</span>
<center>
{' '}
<h2>Compare our plans</h2>
</center>
<div className={styles.subLayout}>
<div className={styles.card}>
<h1>CRSS Free</h1>
<ul>
<li>9 inventory slots</li>
<li>Full access to inventory</li>
<li>Unrestricted use of chat</li>
</ul>
<h3>Current Plan</h3>
</div>
<div className={styles.card}>
<h1>CRSS Premium+</h1>
<ul>
<li>Ad-free browsing</li>
<li>Access to special kits and gamemodes</li>
<li>Browse the web ingame</li>
<li>Exclusive capes</li>
</ul>
<h3>$6.90</h3>
<strong>Best Value</strong>
</div>
<div className={styles.card}>
<h1>CRSS EnterpriseX</h1>
<ul>
<li>Free flight</li>
<li>Creative mode</li>
<li>Add your own custom cape</li>
<li>Create your own items</li>
<li>Enterprise-grade management solutions</li>
</ul>
<h3>Contact Us</h3>
</div>
</div>
</div>
</PageContent>
<Footer />
</>
);
}

84
pages/rules.tsx Normal file
View file

@ -0,0 +1,84 @@
import AdBanner from '@/components/AdBanner';
import Footer from '@/components/Footer';
import Meta from '@/components/Meta';
import NavBar from '@/components/NavBar';
import PageContent from '@/components/PageContent';
export default function Rules() {
return (
<>
<Meta page={{ title: 'Rules' }} />
<NavBar currentPage="rules" />
<PageContent>
<h1 id="rules">Rules</h1>
<ol>
<li>
The use of modified clients that give an unfair advantage to
players, such as hacked clients, is not permitted.
<ul>
<li>
You are not allowed to use them even for their legitimate
features, such as a &quot;fullbright&quot; option.
</li>
<li>
If admins suspect you are hacking you will be immediately
banned.
</li>
</ul>
</li>
<li>
Do not modify or destroy (grief) other player&apos;s constructions
without their consent, or steal any of their items.
<ul>
<li>
You are allowed to visit any build, as long as you don&apos;t
take anything, and if you do you pay them back.
</li>
<li>
You should ask permission in the discord or the in-game chat
before modifying builds.
</li>
</ul>
</li>
<li>
Follow the laws of the nations you are in to avoid issues with other
players and making the server not fun to play.
<ul>
<li>
If you feel the laws are too vague, feel free to ask the people
in charge of them what they mean with something, and feel free
to contribute to them. Complaining that they don&apos;t make
sense won&apos;t get you anywhere.
</li>
<li>
Breaking laws won&apos;t necessarily get you banned, the nation
you are in will take measures and punish you for your actions as
they see fit.
</li>
</ul>
</li>
<li>
Do not attempt to make nations where the territory is already owned
by another nation.
<ul>
<li>
You can make it near the borders of a nation but never inside
one, you can&apos;t just take existing territory as your own.
</li>
<li>
Other nations are free to claim more territory whenever they
feel like it, as long as it doesn&apos;t take other
nations&apos; territory with it.
</li>
</ul>
</li>
</ol>
</PageContent>
<Footer />
</>
);
}

167
pages/settings.tsx Normal file
View file

@ -0,0 +1,167 @@
import Footer from '@/components/Footer';
import NavBar from '@/components/NavBar';
import PageContent from '@/components/PageContent';
import Database from '@/lib/Database';
import { notFound } from 'next/navigation';
import { useEffect, useState } from 'react';
import cookie from 'cookie';
import Meta from '@/components/Meta';
import { UAParser } from 'ua-parser-js';
import styles from '@/styles/Settings.module.scss';
import { X } from 'lucide-react';
interface SettingsType {
animations: boolean;
ads: boolean;
}
export default function Settings({ sessions }: { sessions: any[] }) {
const [ settings, setSettings ] = useState<SettingsType>({
animations: true,
ads: true
});
useEffect(() => {
const settings = localStorage.getItem('crss_settings');
if (settings !== null) {
setSettings(JSON.parse(settings));
}
}, []);
useEffect(() => {
localStorage.setItem('crss_settings', JSON.stringify(settings));
}, [ settings ]);
const currentSession = sessions.find(s => s.current);
const otherSessions = sessions.filter(s => !s.current);
return (
<>
<Meta page={{ title: 'Settings' }} />
<NavBar currentPage="settings" />
<PageContent>
<h1>Settings</h1>
{ /* TODO: Move it to the css 😒 */ }
<div style={{ display: 'flex', flexDirection: 'column', gap: '.5rem', marginBottom: '1rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<label htmlFor="ani">Animations</label>
<input id="ani" type="checkbox" checked={settings.animations} onChange={e => setSettings({ ...settings, animations: e.target.checked })} />
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<label htmlFor="ads">Ads</label>
<input id="ads" type="checkbox" checked={settings.ads} onChange={e => setSettings({ ...settings, ads: e.target.checked })} />
</div>
</div>
<h2>Sessions</h2>
<p>
View and manage your active sessions.
</p>
<h3>Current Session</h3>
<div className={styles.sessions}>
<div className={styles.sessionCard}>
<div>
<p>{currentSession.user_agent.os.name} {currentSession.user_agent.os.version} &bull; {currentSession.user_agent.browser.name} {currentSession.user_agent.browser.version}</p>
<p>{new Date(currentSession.created).toLocaleString('en-GB')}</p>
</div>
</div>
</div>
<h3>Other Sessions</h3>
<div className={styles.sessions}>
{otherSessions.map(session => (
<div key={session.id} className={styles.sessionCard}>
<div>
<p>{session.user_agent.os.name} {session.user_agent.os.version} &bull; {session.user_agent.browser.name} {session.user_agent.browser.version}</p>
<p>{new Date(session.created).toLocaleString('en-GB')}</p>
</div>
<button onClick={() => { alert('Not yet implemented, yeah...'); }}>
<X />
</button>
</div>
))}
</div>
</PageContent>
<Footer />
</>
);
}
export async function getServerSideProps(context: any) {
const db = new Database();
const cookies = cookie.parse(context.req.headers.cookie);
if (!cookies.session)
return {
notFound: true
};
const session = await db.getSession(cookies.session);
if (!session)
return {
notFound: true
};
const sessions = await db.getUserSessions(session.uid);
if (!sessions)
return {
notFound: true
};
sessions.forEach((s: any) => {
s.current = session.id === s.id;
s.created = s.created.toISOString();
s.expires = s.expires.toISOString();
const ua = new UAParser(s.user_agent).getResult();
s.user_agent = {
ua: ua.ua,
browser: {
name: ua.browser.name || null,
version: ua.browser.version || null,
major: ua.browser.major || null
},
engine: {
name: ua.engine.name || null,
version: ua.engine.version || null
},
os: {
name: ua.os.name || null,
version: ua.os.version || null
},
device: {
vendor: ua.device.vendor || null,
model: ua.device.model || null,
type: ua.device.type || null
},
cpu: {
architecture: ua.cpu.architecture || null
}
};
});
return {
props: {
sessions
}
};
}

64
pages/u/[username].tsx Normal file
View file

@ -0,0 +1,64 @@
import Footer from '@/components/Footer';
import Meta from '@/components/Meta';
import NavBar from '@/components/NavBar';
import PageContent from '@/components/PageContent';
import Database from '@/lib/Database';
import {
Permission,
hasPermission
} from '@/utils/permissions';
import Image from 'next/image';
export default function User({ user }: { user: any }) {
return (
<>
<Meta page={{ title: user.global_name, user }} />
<NavBar currentPage="user" />
<PageContent>
<div className="container">
<h1>{user.global_name}</h1>
<Image src={`https://cdn.discordapp.com/avatars/${user.did}/a_${user.avatar}.png`} alt={user.global_name} width={128} height={128} />
<ul>
<li>Admin: {hasPermission(user.permissions, Permission.Admin) ? 'Yes' : 'No'}</li>
</ul>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Deserunt perferendis exercitationem aliquid? Corrupti, veniam nam quis, quas, reprehenderit similique perspiciatis veritatis consectetur quidem omnis iste placeat quod! Dolore, labore est.</p>
</div>
</PageContent>
<Footer />
</>
);
}
export async function getServerSideProps(context: any) {
const db = new Database();
const user = await db.getUserUsername(context.query.username as string);
if (!user) {
return {
notFound: true
};
}
return {
props: {
user: {
id: user.id,
did: user.did,
username: user.username,
global_name: user.global_name,
avatar: user.avatar,
banner: user.banner,
accent_color: user.accent_color,
permissions: user.permissions
}
}
};
}

5002
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

View file

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

1
public/logo.svg Normal file
View file

@ -0,0 +1 @@
<svg width="146" height="96" viewBox="0 0 146 96" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M83.7827 12.6087C83.7827 10.6157 85.3984 9 87.3914 9C89.3844 9 91.0001 10.6157 91.0001 12.6087C91.0001 14.6017 89.3844 16.2174 87.3914 16.2174C85.3984 16.2174 83.7827 14.6017 83.7827 12.6087Z" fill="currentColor"/><path d="M32.6594 16.4528L41.9008 32.5134L50.8527 17.4646L54.8404 29.75L55.9553 28.8478L66.5686 10.2029" stroke="currentColor" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/><path fill-rule="evenodd" clip-rule="evenodd" d="M8 19.8261C8 17.8331 9.61567 16.2174 11.6087 16.2174C13.6017 16.2174 15.2174 17.8331 15.2174 19.8261C15.2174 21.8191 13.6017 23.4348 11.6087 23.4348C9.61567 23.4348 8 21.8191 8 19.8261Z" fill="currentColor"/><path d="M21.25 89.9906C27.4531 89.9906 33.0938 87.3656 36.6094 82.3656C37.0625 81.725 37.1875 81.0844 37.1875 80.4438C37.1875 78.3969 35.6406 77.2406 33.9844 77.2406C33.0312 77.2406 32.25 77.5063 31.4844 78.5219C29.4375 81.2094 26.2969 83.8344 21.25 83.8344C14.5938 83.8344 8.95312 79.0375 8.95312 70.725C8.95312 61.4438 15.4844 52.5375 22.0781 52.5375C25.5312 52.5375 27.5156 53.6313 28.8594 55.0375V55.4906C28.8594 56.8344 30.1406 58.0531 31.875 58.0531C33.6562 58.0531 35.0781 56.5688 35.0781 54.85V50.7563C35.0781 49.1469 33.8594 47.8031 32.125 47.8031C31.2344 47.8031 30.5938 48.1938 30.2031 48.5688C28.2812 47.4281 25.4062 46.3344 22.5312 46.3344C12.7344 46.3344 2.5625 57.9125 2.5625 70.725C2.5625 82.8813 11.0781 89.9906 21.25 89.9906ZM46.4015 89.9906C48.0578 89.9906 49.5421 88.6469 49.5421 86.85V75.2563C51.0734 75.0063 52.6046 74.8813 53.8234 74.8813C56.0578 74.8813 60.9953 75.6469 68.9328 88.3188C69.5734 89.35 70.5265 89.9906 71.7453 89.9906C73.6671 89.9906 74.8859 88.4438 74.8859 86.85C74.8859 86.3969 74.8234 85.8813 74.5578 85.3813C71.3546 78.975 66.9484 74.7563 63.9328 72.3813C68.9953 70.1469 73.4015 66.4281 73.4015 59.975C73.4015 51.3344 65.339 46.3344 51.0109 46.3344H48.3859C43.964 46.3344 43.2609 46.9125 43.2609 50.1781V86.85C43.2609 88.6469 44.6046 89.9906 46.4015 89.9906ZM49.5421 69.3813V52.475H50.8234C61.0578 52.475 67.0734 55.0375 67.0734 59.975C67.0734 66.1156 57.339 68.8656 49.5421 69.3813ZM93.0573 89.9906C101.37 89.9906 108.479 84.475 108.479 77.1156C108.479 70.85 103.807 66.8188 95.3541 64.3813C88.4479 62.3969 85.5104 60.5375 85.5104 57.5375C85.5104 54.3344 88.4479 52.225 94.276 52.225C99.0104 52.225 101.885 54.0844 102.214 56.3813C102.401 57.85 103.62 59.0688 105.151 59.0688C106.948 59.0688 108.292 57.6625 108.292 55.7406C108.292 50.1156 103.104 46.3344 94.276 46.3344C84.9948 46.3344 79.2916 51.1313 79.2916 57.9906C79.2916 65.2875 85.8854 68.4125 93.1823 70.4594C100.229 72.4438 102.339 74.8188 102.339 77.4438C102.339 81.0844 98.5573 83.975 93.1823 83.975C89.3385 83.975 84.9323 82.1781 83.526 78.2719C83.0104 76.8656 81.9166 75.8969 80.3229 75.8969C78.4635 75.8969 77.0573 77.3813 77.0573 79.1C77.0573 79.8031 77.3073 80.6469 77.7604 81.6C79.8073 85.9594 85.2448 89.9906 93.0573 89.9906ZM128.001 89.9906C136.314 89.9906 143.423 84.475 143.423 77.1156C143.423 70.85 138.751 66.8188 130.298 64.3813C123.392 62.3969 120.454 60.5375 120.454 57.5375C120.454 54.3344 123.392 52.225 129.22 52.225C133.954 52.225 136.829 54.0844 137.158 56.3813C137.345 57.85 138.564 59.0688 140.095 59.0688C141.892 59.0688 143.236 57.6625 143.236 55.7406C143.236 50.1156 138.048 46.3344 129.22 46.3344C119.939 46.3344 114.236 51.1313 114.236 57.9906C114.236 65.2875 120.829 68.4125 128.126 70.4594C135.173 72.4438 137.283 74.8188 137.283 77.4438C137.283 81.0844 133.501 83.975 128.126 83.975C124.283 83.975 119.876 82.1781 118.47 78.2719C117.954 76.8656 116.861 75.8969 115.267 75.8969C113.408 75.8969 112.001 77.3813 112.001 79.1C112.001 79.8031 112.251 80.6469 112.704 81.6C114.751 85.9594 120.189 89.9906 128.001 89.9906Z" fill="currentColor"/></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

94
styles/About.module.scss Normal file
View file

@ -0,0 +1,94 @@
@import 'variables.module';
@import 'global.module';
.teamList {
display: grid;
gap: 1rem;
grid-template-columns: repeat(3, minmax(200px, 1fr));
> .teamCard {
padding: 0;
width: 100%;
> .memberBanner {
position: relative;
height: 150px;
background: #537f53;
border-top-left-radius: calc(1rem - 1px);
border-top-right-radius: calc(1rem - 1px);
> img {
position: absolute;
bottom: -52px;
left: 16px;
border: 4px solid $colorSurfaceLight2;
border-radius: 1rem;
@media (prefers-color-scheme: dark) {
border-color: $colorSurfaceDark2;
}
}
&[data-has-background] {
background: var(--background);
}
}
> .memberContent {
padding: 1rem;
> .memberLinks {
display: flex;
justify-content: end;
align-items: end;
gap: .5rem;
list-style: none;
margin: 0 0 .5rem;
> li {
> a {
display: flex;
align-items: center;
padding: 4px;
color: $colorBorderLight3;
&:hover {
color: $colorPrimary;
}
@media (prefers-color-scheme: dark) {
color: $colorBorderDark3;
}
}
}
}
> h3 {
margin: 0;
}
}
}
@media (max-width: 992px) {
grid-template-columns: repeat(2, minmax(200px, 1fr));
}
@media (max-width: 768px) {
grid-template-columns: 1fr;
}
}

111
styles/AdBanner.module.scss Normal file
View file

@ -0,0 +1,111 @@
@import 'variables.module';
@import 'global.module';
.adBanner {
width: 100%;
height: 106px;
padding: 8px;
background: $colorSurfaceLight3;
border: 1px solid $colorBorderLight1;
border-radius: 1rem;
position: relative;
> .adContent {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
> .adRevive {
position: absolute;
top: 8px;
bottom: 8px;
left: 8px;
right: 8px;
display: flex;
align-items: center;
justify-content: center;
scale: 1.3;
@media (max-width: 768px) {
scale: 1;
height: 60px;
}
}
> .adInfo {
position: absolute;
top: 8px;
right: 8px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 8px;
background: transparent;
border: none;
border-radius: 100%;
color: $colorTextLight1;
transition: 150ms;
&:hover {
background: rgba($colorPrimary, 0.25);
}
@media (max-width: 768px) {
display: none;
}
@media (prefers-color-scheme: dark) {
color: $colorTextDark1;
}
}
@media (max-width: 768px) {
border-color: transparent !important;
background: transparent !important;
height: calc(60px);
padding: 0;
> .adContent {
display: none;
}
> .adRevive {
position: initial;
}
}
@media (prefers-color-scheme: dark) {
background: $colorSurfaceDark3;
border-color: $colorBorderDark1;
}
}

17
styles/Card.module.scss Normal file
View file

@ -0,0 +1,17 @@
@import 'variables.module';
@import 'global.module';
.card {
background: $colorSurfaceLight2;
border: 1px solid $colorBorderLight1;
border-radius: 1rem;
padding: 1rem;
@media (prefers-color-scheme: dark) {
background: $colorSurfaceDark2;
border-color: $colorBorderDark1;
}
}

153
styles/Dropdown.module.scss Normal file
View file

@ -0,0 +1,153 @@
@import 'variables.module';
@import 'global.module';
.dropDown {
position: relative;
> label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 8px 24px;
}
> .dropDownMenu {
position: absolute;
top: calc(100% + 4px);
right: 0;
background: $colorSurfaceLight4;
border: 1px solid $colorBorderLight1;
border-radius: .5rem;
overflow: hidden;
display: none;
min-width: max(100%, 200px);
> ul {
list-style: none;
display: flex;
flex-direction: column;
margin: 0;
> li {
> a {
padding: 11px 24px;
display: flex;
flex-direction: row;
align-items: center;
gap: 24px;
text-decoration: none;
transition: 150ms;
> .icon {
width: 20px;
height: 20px;
}
&:hover {
background: rgba($colorPrimary, 0.65);
color: $colorSurfaceLight1;
}
}
> .divider {
border-bottom: 1px solid $colorBorderLight1;
width: 100%;
@media (prefers-color-scheme: dark) {
border-color: $colorBorderDark1;
}
}
}
}
@media (prefers-color-scheme: dark) {
border-color: $colorBorderDark1;
background: $colorSurfaceDark4;
}
}
> .mobileOverlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba($colorSurfaceLight1, 0.65);
display: none;
@media (prefers-color-scheme: dark) {
background: rgba($colorSurfaceDark1, 0.65);
}
}
&.open {
> label {
outline: 2px solid rgba($colorPrimary, 0.65);
color: $colorPrimary;
}
> .dropDownMenu {
display: block;
z-index: 1000000;
}
@media (max-width: 768px) {
> .mobileOverlay {
display: block;
z-index: 100000;
}
> .dropDownMenu {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
min-width: fit-content;
min-height: fit-content;
height: fit-content;
width: 80vw;
overflow-y: auto;
border-radius: 1rem;
padding: 8px 0;
> ul {
align-items: initial;
}
}
}
}
}

102
styles/Footer.module.scss Normal file
View file

@ -0,0 +1,102 @@
@import 'variables.module';
@import 'global.module';
.pageFooter {
background: $colorSurfaceLight3;
border-top: 1px solid $colorBorderLight1;
> .container {
display: flex;
justify-content: space-between;
align-items: start;
padding: 1rem 0;
p {
margin-bottom: .5rem;
}
div:first-child {
width: 420px;
}
div:last-child {
text-align: right;
> ul {
display: flex;
gap: 1rem;
list-style: none;
> li {
> a {
display: flex;
align-items: center;
justify-content: center;
width: 31px;
height: 31px;
border-radius: 50%;
padding: 4px;
transition: 150ms;
color: $colorTextLight1;
&:hover {
color: $colorPrimary;
}
@media (prefers-color-scheme: dark) {
color: $colorTextDark1;
}
}
}
}
}
@media (max-width: 768px) {
flex-direction: column;
div:first-child {
width: 100%;
text-align: center;
}
div:last-child {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
margin-top: 1rem;
}
ul {
margin: 0;
margin-bottom: 1.5rem;
}
}
}
@media (prefers-color-scheme: dark) {
background: $colorSurfaceDark3;
border-color: $colorBorderDark1;
}
}

View file

@ -0,0 +1,97 @@
@import 'variables.module';
@import 'global.module';
.nationGrid {
display: grid;
grid-template-columns: repeat(3, minmax(200px, 1fr));
gap: 1rem;
> .nationCard {
position: relative;
display: flex;
flex-direction: column;
width: 100%;
height: 150px;
text-decoration: none;
padding: 1rem;
cursor: pointer;
overflow: hidden;
outline: 1px solid $colorBorderLight1;
border-radius: 1rem;
background: $colorSurfaceLight2;
color: $colorTextLight1;
transition: 0.24s;
> h2 {
margin: 0;
margin-bottom: .1rem;
font-size: 1.2em;
}
> p {
margin: 0;
}
> .icon {
position: absolute;
top: 0;
right: 0;
height: 100%;
object-fit: cover;
mask-image: linear-gradient(to right, transparent, rgba(red, 0.3));
aspect-ratio: 1/1;
transition: 0.64s;
@media (prefers-color-scheme: dark) {
mask-image: linear-gradient(to right, transparent, rgba(red, 0.1));
}
}
&:hover {
outline-width: 2px;
outline-color: $colorPrimary;
transition: 0.08s;
> .icon {
transition: 0.48s;
transform: scale(1.2);
}
}
@media (prefers-color-scheme: dark) {
outline-color: $colorBorderDark1;
background: $colorSurfaceDark2;
color: $colorTextDark1;
}
}
@media (max-width: 992px) {
grid-template-columns: repeat(2, minmax(200px, 1fr));
}
@media (max-width: 768px) {
grid-template-columns: 1fr;
}
}

333
styles/NavBar.module.scss Normal file
View file

@ -0,0 +1,333 @@
@import 'variables.module';
@import 'global.module';
.pageHero {
height: 220px;
background-image: url("https://crss.fra1.cdn.digitaloceanspaces.com/img/2024-06-08_14.19.52.png");
background-size: cover;
background-repeat: no-repeat;
background-position: center;
font-family: 'Comic Neue', 'Comic Sans MS', 'Noto Color Emoji', 'Noto Emoji', sans-serif;
font-weight: 600;
> .heroOverlay {
background: rgba($colorSurfaceLight2, 0.9);
backdrop-filter: blur(8px);
height: 100%;
width: 100%;
> .container {
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
> div:first-child {
display: flex;
flex-direction: column;
gap: 0.5rem;
> h1 {
font-family: 'Comic Neue', 'Comic Sans MS', 'Noto Color Emoji', 'Noto Emoji', sans-serif;
font-weight: 600;
font-size: 1.5rem;
margin: 0;
}
@media (max-width: 768px) {
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
}
> div:last-child {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
> input {
background: rgba(0,0,0,0);
width: fit-content;
padding: 4px 16px;
font-size: 1.5rem;
text-align: center;
color: inherit;
border: 1px solid rgba(0, 0, 0, 0);
border-radius: 1rem;
transition: 150ms;
&:focus, &:hover {
outline: none;
background: rgba($colorSurfaceLight4, .65);
border: 1px solid $colorBorderLight3;
}
@media (prefers-color-scheme: dark) {
color: inherit;
&:focus, &:hover {
background: rgba($colorSurfaceDark4, .65);
border: 1px solid $colorBorderDark3;
}
}
}
@media (max-width: 768px) {
display: none;
}
}
}
@media (prefers-color-scheme: dark) {
background: rgba($colorSurfaceDark2, 0.9);
}
}
@media (max-width: 768px) {
border-bottom: 1px solid $colorBorderLight1;
@media (prefers-color-scheme: dark) {
border-color: $colorBorderDark1;
}
}
}
.navBar {
height: 64px;
position: sticky;
top: 0;
left: 0;
right: 0;
background: $colorSurfaceLight3;
border-bottom: 1px solid $colorBorderLight1;
z-index: 999999999;
> .container {
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
@media (max-width: 768px) {
justify-content: space-between;
}
> .navMobileContainer {
display: none;
width: 100%;
height: 100%;
justify-content: end;
align-items: center;
z-index: 10000;
> .navToggle {
padding: 0.5rem;
background: none;
border: none;
border-radius: 50%;
display: flex;
color: $colorTextLight1;
transition: 150ms;
&:hover, &:focus {
background: rgba($colorPrimary, 0.5);
}
@media (prefers-color-scheme: dark) {
color: $colorTextDark1;
}
}
@media (max-width: 768px) {
display: flex;
}
}
> .navCollapse {
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
width: 100%;
z-index: 5000;
> ul {
list-style: none;
display: flex;
align-items: center;
gap: .5rem;
margin: 0;
> li {
> a, .dropDown > label {
padding: 8px 16px;
display: flex;
flex-direction: row;
align-items: center;
gap: 1rem;
text-decoration: none;
transition: 150ms;
border-radius: 2rem;
color: $colorTextLight2;
outline: 2px solid rgba(0, 0, 0, 0);
> .icon {
width: 20px;
height: 20px;
}
&:hover, &:focus, &.active {
outline: 2px solid rgba($colorPrimary, 0.65);
color: $colorPrimary;
}
@media (prefers-color-scheme: dark) {
color: $colorTextDark2;
&:hover, &:focus, &.active {
outline-color: rgba($colorPrimary, 0.65);
}
}
}
}
}
@media (max-width: 768px) {
opacity: 1;
transition: 300ms;
position: fixed;
height: 100vh;
width: 100vw;
top: 0;
right: -110%;
display: flex;
flex: 1;
flex-direction: column;
align-items: end;
justify-content: center;
gap: 2rem;
padding: 1rem;
background: rgba($colorSurfaceLight3, 0.95);
backdrop-filter: blur(8px);
ul {
flex-direction: column;
align-items: end;
gap: 1rem;
li {
a {
padding: 8px 24px;
}
}
}
@media (prefers-color-scheme: dark) {
background: rgba($colorSurfaceDark3, 0.95);
}
}
}
}
@media (prefers-color-scheme: dark) {
background: $colorSurfaceDark3;
border-color: $colorBorderDark1;
}
&.navOpen {
> .container {
flex-direction: column;
> .navMobileContainer {
height: 64px;
}
> .navCollapse {
opacity: 1;
right: 0;
}
}
}
@media (max-width: 768px) {
background: none;
position: fixed;
top: 0;
border: none;
}
}

View file

@ -0,0 +1,57 @@
@import 'variables.module';
.card {
display: flex;
flex-direction: column;
align-items: center;
border-radius: 1.5rem;
padding: 1rem;
gap: 0.25rem;
h1 {
margin: 0px;
}
h2 {
margin: 0px;
}
* {
text-align: center;
}
}
.cardContainer {
display: flex;
flex-direction: column;
gap: 1rem;
}
div.card {
border: 1px solid $colorBorderLight1;
background: linear-gradient(
180deg,
$colorSurfaceLight1 0%,
$colorBorderLight1 200%
);
}
@media (prefers-color-scheme: dark) {
div.card {
border: 1px solid $colorBorderDark1;
background: linear-gradient(
180deg,
$colorSurfaceDark2 0%,
$colorBorderDark1 200%
);
}
}
.subLayout {
display: grid;
gap: 1rem;
}
@media (min-width: 1000px) {
div.subLayout {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}

View file

@ -0,0 +1,55 @@
@import 'variables.module';
@import 'global.module';
.sessions {
display: flex;
flex-direction: column;
gap: .5rem;
> .sessionCard {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
p {
margin: 0;
}
button {
display: flex;
justify-content: center;
align-items: center;
background: transparent;
color: $colorTextLight1;
border: none;
border-radius: 100%;
padding: 8px;
cursor: pointer;
transition: 150ms;
&:hover {
background: rgba($colorPrimary, 0.35);
color: $colorPrimary;
}
@media (prefers-color-scheme: dark) {
color: $colorTextDark1;
}
}
}
&:not(:last-child) {
margin-bottom: 1rem;
}
}

43
styles/fonts.module.scss Normal file
View file

@ -0,0 +1,43 @@
@import url('https://fonts.googleapis.com/css2?family=Comic+Neue:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Noto+Color+Emoji&family=Noto+Emoji:wght@300..700&family=Noto+Sans+Mono:wght@100..900&family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Outfit:wght@100..900&display=swap');
body, input, textarea, select, button {
font-family: 'Noto Sans', 'Noto Color Emoji', 'Noto Emoji', sans-serif;
}
code, pre, kbd {
font-family: 'Noto Sans Mono', 'Noto Color Emoji', 'Noto Emoji', sans-serif;
}
$headers: (
h1: 1.95rem,
h2: 1.5rem,
h3: 1.25rem,
h4: 140%,
h5: 120%,
h6: 110%
);
h1, h2, h3, h4, h5, h6 {
font-family: 'Outfit', 'Noto Color Emoji', 'Noto Color Emoji', 'Noto Emoji', sans-serif;
font-weight: 500;
line-height: 1.7;
}
@each $tag, $size in $headers {
#{$tag} {
font-size: $size;
margin-bottom: 1rem;
}
}
p, ol, ul, label, a {
font-size: 1rem;
line-height: 1.7;
}
p:not(:last-child), ol:not(li > ol, li > ul, :last-child), ul:not(li > ol, li > ul, :last-child) {
margin-bottom: 1rem;
}

13
styles/global.module.scss Normal file
View file

@ -0,0 +1,13 @@
.container {
max-width: 1100px;
margin: 0 auto;
@media (max-width: 1100px) {
margin: 0 1rem;
}
}
.pageContent {
// trolley
}

237
styles/globals.scss Normal file
View file

@ -0,0 +1,237 @@
@import 'variables.module';
@import 'fonts.module';
@import 'global.module';
* {
padding: 0;
margin: 0;
box-sizing: border-box;
scrollbar-color: $colorPrimary $colorSurfaceLight1;
@media (prefers-color-scheme: dark) {
scrollbar-color: $colorPrimary $colorSurfaceDark1;
}
}
*::selection {
background: rgba($colorPrimary, 0.75);
@media (prefers-color-scheme: dark) {
background: rgba($colorPrimary, 0.65);
}
}
body {
background: $colorSurfaceLight1;
color: $colorTextLight1;
overflow-x: hidden;
@media (prefers-color-scheme: dark) {
background: $colorSurfaceDark1;
color: $colorTextDark1;
}
}
#__next {
display: flex;
flex-direction: column;
min-height: 100svh;
}
main {
padding: 1.5rem 0;
flex: 1 1;
}
a {
color: $colorPrimary;
text-decoration: none;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
button {
cursor: pointer;
}
ol, ul {
list-style-position: inside;
margin-left: 1rem;
}
pre {
overflow-x: scroll;
max-width: 100%;
display: block;
padding: 1rem;
background: $colorSurfaceLight2;
border: 1px solid $colorBorderLight1;
border-radius: .5rem;
@media (prefers-color-scheme: dark) {
background: $colorSurfaceDark2;
border-color: $colorBorderDark1;
}
}
iframe {
background: $colorSurfaceLight2;
border: 1px solid $colorBorderLight1;
border-radius: 1rem;
@media (prefers-color-scheme: dark) {
background: $colorSurfaceDark2;
border-color: $colorBorderDark1;
}
}
hr {
border: none;
height: 1px;
background: $colorBorderLight1;
margin: 1rem 0;
@media (prefers-color-scheme: dark) {
background: $colorBorderDark1;
}
}
// inputs
input {
&[type='checkbox'] {
appearance: none;
width: 52px;
height: 2rem;
background: $colorSurfaceLight2;
border: 1px solid $colorBorderLight1;
border-radius: 1rem;
position: relative;
cursor: pointer;
transition: 150ms;
&::before {
content: "";
position: absolute;
height: 1rem;
width: 1rem;
border-radius: 50%;
background: $colorPrimary;
top: 50%;
left: .5rem;
transform: translateY(-50%);
transition: 150ms;
}
&:checked {
background: $colorPrimary;
&::before {
background: $colorSurfaceLight2;
transform: translate(20px, -50%);
animation: thumbAnimation 150ms;
}
}
@media (prefers-color-scheme: dark) {
background: $colorSurfaceDark2;
border-color: $colorBorderDark1;
&::before {
background: $colorPrimary;
}
&:checked {
background: $colorPrimary;
&::before {
background: $colorSurfaceDark2;
transform: translate(20px, -50%);
animation: thumbAnimation 150ms;
}
}
}
}
}
select {
appearance: none;
padding: .5rem 1rem;
background: $colorSurfaceLight2;
color: $colorTextLight1;
border: 1px solid $colorBorderLight1;
border-radius: .5rem;
cursor: pointer;
transition: 150ms;
&:hover {
border-color: $colorPrimary;
}
&:focus {
outline: none;
border-color: $colorPrimary;
}
@media (prefers-color-scheme: dark) {
background: $colorSurfaceDark2;
color: $colorTextDark1;
border-color: $colorBorderDark1;
}
}
@keyframes thumbAnimation {
from {
transform: translateY(-50%);
}
to {
transform: translate(20px, -50%);
}
}

View file

@ -0,0 +1,27 @@
$colorPrimary: hsl(120, 21%, 41%);
$colorTextLight1: hsl(120, 40%, 8%);
$colorTextLight2: hsl(120, 5%, 45%);
$colorSurfaceLight1: hsl(120, 25%, 97%);
$colorSurfaceLight2: hsl(120, 20%, 95%);
$colorSurfaceLight3: hsl(120, 20%, 92%);
$colorSurfaceLight4: hsl(120, 20%, 85%);
$colorBorderLight1: hsl(120, 20%, 80%);
$colorBorderLight2: hsl(120, 20%, 70%);
$colorBorderLight3: hsl(120, 20%, 60%);
$colorBorderLight4: hsl(120, 20%, 50%);
$colorTextDark1: hsl(120, 15%, 85%);
$colorTextDark2: hsl(120, 5%, 65%);
$colorSurfaceDark1: hsl(120, 5%, 10%);
$colorSurfaceDark2: hsl(120, 10%, 15%);
$colorSurfaceDark3: hsl(120, 5%, 20%);
$colorSurfaceDark4: hsl(120, 5%, 25%);
$colorBorderDark1: hsl(120, 5%, 30%);
$colorBorderDark2: hsl(120, 5%, 40%);
$colorBorderDark3: hsl(120, 5%, 50%);
$colorBorderDark4: hsl(120, 5%, 60%);

View file

@ -1,16 +0,0 @@
{% include 'includes/head.twig' with {'pageTitle': '404'} %}
{% include 'includes/hero.twig' %}
{% include 'includes/nav.twig' with {'page': '404',} %}
<main class="pageContent container">
<h1>Not found! :<</h1>
<p>
This page is nowhere to be found!<br>
Go on Discord and complain about it to Clash or Mya!!
</p>
</main>
{% include 'includes/footer.twig' %}
{% include 'includes/foot.twig' %}

View file

@ -1,12 +0,0 @@
<script src="/js/admin/nav.js"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
lucide.createIcons();
changePage();
</script>
</body>
</html>

View file

@ -1,35 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>{{ pageTitle }} - Admin &bull; Clyde's Real Survival SMP</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" />
<style>
body {
min-height: 100svh;
}
.lucide {
vertical-align: -10%;
}
/* Loading Animation */
.loader {
animation: spin 1s linear infinite;
}
@keyframes spin {
100% {
-webkit-transform: rotate(360deg);
transform:rotate(360deg);
}
}
</style>
</head>
<body class="d-flex flex-column">

View file

@ -1,52 +0,0 @@
<div class="d-flex flex-column flex-shrink-0 p-3 bg-body-tertiary" style="width: 280px;">
<a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto link-body-emphasis text-decoration-none">
<i class="pe-none me-2" data-lucide="gauge" width="40" height="32"></i>
<span class="fs-4">Admin Panel</span>
</a>
<hr>
<ul class="nav nav-pills flex-column mb-auto">
<li class="nav-item">
<a href="#/" class="nav-link active" aria-current="page">
<i class="pe-none me-2" data-lucide="home" width="16" height="16"></i>
Home
</a>
</li>
<li class="nav-item">
<a href="#/pages" class="nav-link link-body-emphasis">
<i class="pe-none me-2" data-lucide="notebook-text" width="16" height="16"></i>
Pages
</a>
</li>
<li class="nav-item">
<a href="#/users" class="nav-link link-body-emphasis">
<i class="pe-none me-2" data-lucide="users" width="16" height="16"></i>
Users
</a>
</li>
<li class="nav-item">
<a href="#/markers" class="nav-link link-body-emphasis">
<i class="pe-none me-2" data-lucide="map-pin" width="16" height="16"></i>
Markers
</a>
</li>
</ul>
<hr>
<div class="dropdown">
<a href="#" class="d-flex align-items-center link-body-emphasis text-decoration-none dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<img src="https://cdn.discordapp.com/avatars/{{ user.id }}/{{ user.avatar }}.png" alt="" width="32" height="32" class="rounded-circle me-2">
<span>{{ user.global_name }}</span>
</a>
<ul class="dropdown-menu text-small shadow">
<li><a class="dropdown-item" href="#">New project...</a></li>
<li><a class="dropdown-item" href="#">Settings</a></li>
<li><a class="dropdown-item" href="#">Profile</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="/">Exit Admin</a></li>
</ul>
</div>
</div>

View file

@ -1,11 +0,0 @@
{% include 'admin/includes/head.twig' with {'pageTitle': 'Dashboard'} %}
<div class="d-flex flex-grow-1">
{% include 'admin/includes/sidebar.twig' %}
<div class="flex-grow-1 pageContainer">
</div>
</div>
{% include 'admin/includes/foot.twig' %}

View file

@ -1,3 +0,0 @@
<div class="d-flex align-items-center justify-content-center h-100">
<h1>Not Found :(</h1>
</div>

View file

@ -1,3 +0,0 @@
<div class="d-flex align-items-center justify-content-center h-100">
<h1>Welcome {{ user.global_name }} to the admin panel!</h1>
</div>

View file

@ -1,24 +0,0 @@
<table class="table">
<thead>
<tr>
<th scope="col">id</th>
<th scope="col">name</th>
<th scope="col">category</th>
<th scope="col">data</th>
<th scope="col">actions</th>
</tr>
</thead>
<tbody>
{% for marker in markers %}
<tr>
<th scope="row">{{ marker.id }}</th>
<td>{{ marker.name }}</td>
<td>{{ marker.category }}</td>
<td>{{ marker.data }}</td>
<td>
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>

Some files were not shown because too many files have changed in this diff Show more