Author SHA1 Message Date
SquadleDude e0fc73df51 Merge pull request #30 from nexiumfit/squadledude-patch-13
Add files via upload
2026-08-06 10:16:44 +03:00
SquadleDude 92be439e27 Add files via upload 2026-08-06 10:16:07 +03:00
SquadleDude 65a7c846ab Add files via upload 2026-08-06 10:15:04 +03:00
SquadleDude de9aee85be Merge pull request #29 from nexiumfit/squadledude-patch-12
delete long png
2026-08-06 10:13:34 +03:00
SquadleDude fe7a904fa0 delete long png 2026-08-06 10:13:19 +03:00
copyrighttxt baf5a64d35 Update badwords.py 2026-08-05 17:25:08 -04:00
copyrighttxt f969daf1f7 Update badwords.py 2026-08-05 17:21:13 -04:00
copyrighttxt e79463ce20 ye 2026-08-04 18:24:32 -04:00
copyrighttxt cb2bcb56d4 FUcK NIGGERS 2026-08-04 18:05:17 -04:00
copyrighttxt a176948ecb Yeah 2026-08-04 17:57:44 -04:00
copyrighttxt 872a2ffe2b t 2026-08-04 17:52:46 -04:00
copyrighttxt 9d44d1d377 ttest 2026-08-04 17:50:26 -04:00
copyrighttxt 75d3babf27 Update downloads.html 2026-08-04 14:38:59 -04:00
copyrighttxt 2c7f6cc959 Update clientinfo.py 2026-08-04 14:35:15 -04:00
copyrighttxt bc37a1b87e it'll be a long time before you ever see me again 2026-08-03 21:12:29 -04:00
copyrighttxt bb49f6b943 Update start.sh 2026-08-03 18:41:10 -04:00
lx c226ed2da9 killing myself on live 2026-08-03 22:34:17 +00:00
copyrighttxt 296a0ad58a s 2026-08-03 17:51:55 -04:00
copyrighttxt e4c189aec0 s 2026-08-03 17:50:41 -04:00
copyrighttxt cec64d598f Word 2026-08-03 17:49:31 -04:00
copyrighttxt 7ddc6a3346 booty 2026-08-03 17:47:45 -04:00
copyrighttxt 9c8f38ab8c you been putting up with my shit just way too long 2026-08-03 17:44:28 -04:00
copyrighttxt c5a17b1d1b y 2026-08-03 17:43:44 -04:00
copyrighttxt 518ceb0af2 Word? 2026-08-03 17:42:43 -04:00
copyrighttxt 2d341a3029 a 2026-08-03 17:41:13 -04:00
copyrighttxt 9d7913bf21 i also had ai do this btw 2026-08-03 17:40:16 -04:00
copyrighttxt 7c7085c038 i had ai do this btw 2026-08-03 17:38:38 -04:00
copyrighttxt b8c141cde9 Word? 2026-08-03 17:32:42 -04:00
copyrighttxt d2a0d2ba88 ye 2026-08-03 17:26:50 -04:00
SquadleDude a1facc1247 Merge pull request #28 from nexiumfit/patch2/2
Rename NEXIUM.pn to NEXIUM.png
2026-08-03 23:23:09 +03:00
SquadleDude 3b76fd7431 Rename NEXIUM.pn to NEXIUM.png 2026-08-03 23:22:56 +03:00
SquadleDude 3c81d5adc0 Merge pull request #27 from nexiumfit/squadledude-patch-12
Rename NEXIUM.png to NEXIUM.pn
2026-08-03 23:22:27 +03:00
SquadleDude 71498b2dd4 Rename NEXIUM.png to NEXIUM.pn 2026-08-03 23:22:16 +03:00
SquadleDude de4f9d4200 Add files via upload 2026-08-03 23:20:56 +03:00
SquadleDude a782ace8a8 Merge pull request #26 from nexiumfit/patch1/2
Refactor layout by removing auth_page styles and updating logo
2026-08-03 23:20:31 +03:00
SquadleDude ac41a93352 Refactor layout by removing auth_page styles and updating logo
Removed conditional styles for auth_page and updated logo to use an image.
2026-08-03 23:20:21 +03:00
copyrighttxt 8495bd92a1 fix that 2026-08-03 16:18:53 -04:00
copyrighttxt 3447ebeefa h 2026-08-03 16:16:44 -04:00
SquadleDude 02c22a1d56 Merge pull request #25 from nexiumfit/squadledude-patch-12
rename
2026-08-03 23:15:40 +03:00
SquadleDude e549f64719 rename 2026-08-03 23:15:30 +03:00
SquadleDude d5e7a88b89 Add files via upload 2026-08-03 23:14:33 +03:00
SquadleDude 89026f015d Rename vortex.ico to not.ico 2026-08-03 23:13:17 +03:00
copyrighttxt e400630ee1 a 2026-08-03 16:12:32 -04:00
copyrighttxt b3cc52540d fuck nigga 2026-08-03 16:11:36 -04:00
copyrighttxt 9e47df577d . 2026-08-03 16:11:15 -04:00
copyrighttxt b4c9604137 a 2026-08-03 16:10:32 -04:00
copyrighttxt 01fe03e1ab Uh 2026-08-03 16:09:26 -04:00
copyrighttxt f3b3c2b168 Update global.css 2026-08-03 16:07:35 -04:00
copyrighttxt 95b52f4e3a Update twemoji.js 2026-08-03 16:05:33 -04:00
copyrighttxt 64091bfe65 Create header.css 2026-08-03 16:04:31 -04:00
copyrighttxt ae517249f0 Update __layout__.html 2026-08-03 16:03:58 -04:00
copyrighttxt 6652fa16b0 Update __layout__.html 2026-08-03 16:02:54 -04:00
SquadleDude a46681367b Merge pull request #24 from nexiumfit/fix
fix
2026-08-03 23:00:43 +03:00
SquadleDude 22377feed1 fix 2026-08-03 23:00:33 +03:00
SquadleDude 39be9cfe82 Merge pull request #23 from nexiumfit/squadledude-patch-12
settings fix
2026-08-03 22:54:59 +03:00
SquadleDude eebffbe578 settings fix 2026-08-03 22:54:37 +03:00
SquadleDude 5c246f5e8f Merge pull request #22 from nexiumfit/big-logo
bigger logo
2026-08-03 22:52:53 +03:00
SquadleDude de6ab19ce4 bigger logo 2026-08-03 22:52:36 +03:00
SquadleDude 1e240d4ae6 Merge pull request #21 from nexiumfit/sidebar
sidebar
2026-08-03 22:45:05 +03:00
SquadleDude da7ff41538 sidebar 2026-08-03 22:44:30 +03:00
SquadleDude d98132b42e Merge pull request #20 from nexiumfit/ui-tweakw
ui tweaks n stuff
2026-08-03 22:43:32 +03:00
SquadleDude a0236f9cdf ui tweaks n stuff
This file contains custom CSS tweaks for the UI, including adjustments for the navbar, login page, and left sidebar styles.
2026-08-03 22:43:21 +03:00
copyrighttxt 21f7106021 hello 2026-08-03 14:54:38 -04:00
copyrighttxt 9132bf971d gpt method #2 2026-08-03 14:46:46 -04:00
copyrighttxt e6bbf58937 Chatgpt method 2026 2026-08-03 14:44:50 -04:00
copyrighttxt 4da4a8572e Update signup.css 2026-08-03 14:42:24 -04:00
SquadleDude 2dee4e7f8c Merge pull request #19 from nexiumfit/squadledude-patch-11
BIG update
2026-08-03 20:49:05 +03:00
27 changed files with 1205 additions and 4301 deletions
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

+193 -177
View File
@@ -1,185 +1,201 @@
<!doctype html> <!doctype html>
<html lang="en" data-bs-theme="dark" data-theme="dark"> <html lang="en" data-bs-theme="dark">
<head>
<!--[if IE 6]>
© COPYRIGHT 2026 nexium.fit
DO NOT COPY, MODIFY, OR REDISTRIBUTE THIS CODE
WITHOUT PERMISSION FROM nexium.fit
<head>
PAGE GENERATED AT : {{ datetime_utcnow() }} UTC <!--[if IE 6]>
whenever you feel down,
remember that theres still a lot to live for.
nexium.fit, lx was here.
<![endif]--> <![endif]-->
<meta charset="utf-8"> <style>
<script>(function(){var t=localStorage.getItem('theme')||'dark';document.documentElement.setAttribute('data-theme',t);document.documentElement.setAttribute('data-bs-theme',t);})();</script> @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap');
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="/static/img/NEXIUM.ico" type="image/x-icon"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
<script async src="https://www.googletagmanager.com/gtag/js?id=G-7P5LY26BKG"></script>
<link href="https://fonts.googleapis.com/css2?family=Exo+2:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="/static/css/global.css" rel="stylesheet"/>
{% if currentuser: %}<script src="/static/js/presence.js"></script>{% endif %}
<style>
.subnav-bar a {
color: var(--subnav-link) !important;
font-size: 14px;
text-decoration: none;
margin-right: 15px;
margin-top: 0;
}
.subnav-bar a:hover {
color: var(--subnav-link-hover) !important;
}
.subnav-bar {
background-color: var(--subnav-bg);
padding: 3px;
}
@media screen and (max-width: 992px) {
.subnav-bar {
display: none;
}
.main-nav-dropdown {
display: block !important;
}
}
@media screen and (min-width: 992px) {
.main-nav-dropdown {
display: none;
}
}
</style>
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
<title>{% block title %}{% endblock %} - NEXIUM</title>
<meta name="robots" content="noindex">
{% block head %}{% endblock %}
</head>
<body {% if website_wide_message %}style="padding-top: 30px"{% endif %}>
<div class="fixed-top width-100">
{% if currentuser: %}
<nav class="navbar navbar-expand-lg">
<div class="container-fluid">
<a class="navbar-brand position-relative me-md-2 " href="/home">
<h1 class="title-logo">NEXIUM</h1>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/home">Home</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/games">Games</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/catalog">Catalog</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/develop">Develop</a>
</li>
<div class="nav-item dropdown d-flexr main-nav-dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false" style="height:28px;">
Others{% if currentuser.unread_messages > 0 or currentuser.inbound_trades > 0: %} 🔴{%endif%}
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/users/{{currentuser.id}}/friends">Friends{% if currentuser.friend_requests > 0: %}<span class="badge text-bg-danger ms-1">{{currentuser.friend_requests}}</span>{%endif%}</a></li>
<li><a class="dropdown-item" href="/groups">Groups</a></li>
<li><a class="dropdown-item" href="/users">Users</a></li>
<li><a class="dropdown-item" href="/avatar">Avatar</a></li>
<li><a class="dropdown-item" href="/messages">Messages{% if currentuser.unread_messages > 0: %}<span class="badge text-bg-danger ms-1">{{currentuser.unread_messages}}</span>{%endif%}</a></li>
<li><a class="dropdown-item" href="/trade">Trades{% if currentuser.inbound_trades > 0: %}<span class="badge text-bg-danger ms-1">{{currentuser.inbound_trades}}</span>{%endif%}</a></li>
<li><a class="dropdown-item" href="/download">Downloads</a></li>
<!-- <li><a class="dropdown-item" href="https://synmons.com">Synmons</a></li> -->
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="https://discord.gg/cBMp3Z52UM">Discord</a></li>
{% if currentuser.is_admin: %}
<li><a class="dropdown-item" href="/admin/">Admin Panel{% if currentuser.pending_asset_count > 0: %}<span class="badge text-bg-danger ms-1">{{currentuser.pending_asset_count}}</span>{%endif%}</a></li>
{% endif %}
</ul>
</div>
</ul>
<div class="d-flex" id="currencydiv">
<a style="margin-right:10px;font-weight: 600;text-decoration: none;" href="/users/{{currentuser.id}}/profile"><img class="rounded-5 overflow-hidden me-2" width="32" height="32" src="/Thumbs/Head.ashx?x=48&y=48&userId={{currentuser.id}}" alt="{{currentuser.username}}"/><span>{{currentuser.username}}</span></a>
<span class="rbx-icon-nav-robux me-1 text-body"></span>
<span class="" id="robux-amount">{{currentuser.robux}}</span>
<a href="/transactions/"><span class="rbx-icon-nav-tix"></span></a>
<span>{{currentuser.tix}}</span>
<div class="nav-item dropdown d-flex ms-3"> * {
<a class="nav-link" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false" style="height:28px;"> font-family: "Outfit", sans-serif;
<span class="rbx-icon-nav-settings"></span> font-optical-sizing: auto;
</a> font-style: normal;
<ul class="dropdown-menu dropdown-menu-end"> }
<li><a class="dropdown-item" href="/settings">Settings</a></li>
<li><a class="dropdown-item" href="/invite-keys">Invite Keys</a></li> img.emoji {
<li><a class="dropdown-item" href="/membership">Membership</a></li> height: 1em;
<li><a class="dropdown-item" href="/giftcard-redeem">Redeem Codes</a></li> width: 1em;
<li><hr class="dropdown-divider"></li> margin: 0 .05em 0 .1em;
<li><form action="/logout" method="post"><input type="hidden" name="csrf_token" value="{{csrf_token()}}"><button class="dropdown-item" style="color:rgb(255, 6, 60);font-weight:600;" type="submit">Logout</button></form></li> vertical-align: -0.1em;
</ul> }
</div>
</div> .main-content {
</div> margin-top: 0 !important;
</div> }
</nav>
<div class="w-100 border-bottom subnav-bar ps-3 pe-3">
<a href="/users/{{currentuser.id}}/profile">Profile</a> </style>
<a href="/users/{{currentuser.id}}/friends">Friends{% if currentuser.friend_requests > 0: %}<span class="badge text-bg-danger ms-1">{{currentuser.friend_requests}}</span>{%endif%}</a> <meta charset="utf-8">
<a href="/users">Users</a> <meta name="viewport" content="width=device-width, initial-scale=1">
<a href="/avatar">Avatar</a> <link rel="icon" href="/static/img/vortex.ico" type="image/x-icon" />
<a href="/messages">Messages{% if currentuser.unread_messages > 0: %}<span class="badge text-bg-danger ms-1">{{currentuser.unread_messages}}</span>{%endif%}</a> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet"
<a href="/groups">Groups</a> integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
<a href="/trade">Trades{% if currentuser.inbound_trades > 0: %}<span class="badge text-bg-danger ms-1">{{currentuser.inbound_trades}}</span>{%endif%}</a> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"
<a href="/download">Downloads</a> integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe"
<!-- <a href="https://synmons.com">Synmons</a> --> crossorigin="anonymous"></script>
<a href="https://discord.gg/cBMp3Z52UM">Discord</a> <script src="https://unpkg.com/twemoji@latest/dist/twemoji.min.js" crossorigin="anonymous"></script>
{% if currentuser.is_admin: %}
<a href="/admin/">Admin Panel{% if currentuser.pending_asset_count > 0: %}<span class="badge text-bg-danger ms-1">{{currentuser.pending_asset_count}}</span>{%endif%}</a> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css">
{% endif %} <link href="/static/css/global.css" rel="stylesheet" />
</div> <link href="/static/css/header.css?random={{ BuildVersionDontGetThisMixedUpCuh }}" rel="stylesheet" />
{% if currentuser: %}
<script src="/static/js/presence.js"></script>{% endif %}
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
<title>{% block title %}{% endblock %} - NEXIUM</title>
<meta name="robots" content="noindex">
{% block head %}{% endblock %}
</head>
<body {% if website_wide_message %}class="has-alert" {% endif %}>
<div class="width-100">
{% if currentuser: %}
<button class="toggle-sidebar btn btn-link text-white d-lg-none position-fixed top-0 start-0 m-2 z-3">
<i class="bi bi-list fs-4"></i>
</button>
<div class="sidebar">
<div class="sidebar-nav">
<a href="/home" class="nav-link"><i class="bi bi-house-fill"></i> Home</a>
<a href="/users/{{currentuser.id}}/profile" class="nav-link"><i class="bi bi-person-fill"></i> Profile</a>
<a href="/games" class="nav-link"><i class="bi bi-controller"></i> Games</a>
<a href="/catalog" class="nav-link"><i class="bi bi-shop"></i> Catalog</a>
<a href="/develop" class="nav-link"><i class="bi bi-code-square"></i> Develop</a>
<a href="/users/{{currentuser.id}}/friends" class="nav-link">
<i class="bi bi-people-fill"></i> Friends
{% if currentuser.friend_requests > 0: %}
<span class="badge text-bg-danger ms-1">{{currentuser.friend_requests}}</span>
{% endif %}
</a>
<a href="/groups" class="nav-link"><i class="bi bi-collection"></i> Groups</a>
<a href="/users" class="nav-link"><i class="bi bi-person-fill"></i> Users</a>
<a href="/avatar" class="nav-link"><i class="bi bi-person-badge"></i> Avatar</a>
<a href="/messages" class="nav-link">
<i class="bi bi-envelope-fill"></i> Messages
{% if currentuser.unread_messages > 0: %}
<span class="badge text-bg-danger ms-1">{{currentuser.unread_messages}}</span>
{% endif %}
</a>
<a href="/trade" class="nav-link">
<i class="bi bi-arrow-left-right"></i> Trades
{% if currentuser.inbound_trades > 0: %}
<span class="badge text-bg-danger ms-1">{{currentuser.inbound_trades}}</span>
{% endif %}
</a>
<a href="/download" class="nav-link"><i class="bi bi-download"></i> Downloads</a>
{% if InviteKeysEnabled %}
<a href="/invite-keys" class="nav-link"><i class="bi bi-key-fill"></i> Invite Keys</a>
{% endif %} {% endif %}
{% if website_wide_message: %} <a href="https://discord.gg/REPLACEME" class="nav-link"><i class="bi bi-discord"></i> Discord</a>
<div id="websitewidemessage" class="alert rounded-0 text-body p-1 position-relative" role="alert" data-announcement="{{ website_wide_message | e }}"> {% if currentuser.is_admin: %}
<button type="button" id="websitewidemessage-close" aria-label="Close">&times;</button> <a href="/admin/" class="nav-link">
<p>{{ website_wide_message }}</p> <i class="bi bi-shield-fill"></i> Admin Panel
</div> {% if currentuser.pending_asset_count > 0: %}
{% endif %} <span class="badge text-bg-danger ms-1">{{currentuser.pending_asset_count}}</span>
</div> {% endif %}
{% block content %}{% endblock %} </a>
{% if currentuser: %}
<div id="footer">
<div class="container pt-4 pb-4">
<div class="row">
<div class="col-4">
<b>©2026 nexium.fit</b>
<p>nexium.fit is not affiliated or related to the Roblox Corporation or any of its subsidaries.</p>
</div>
<div class="col" id="footerlist">
<a href="/terms">Terms of Service</a>
<a href="/privacy">Privacy Policy</a>
<a href="https://discord.gg/cBMp3Z52UM">Discord</a>
</div>
<div class="col" id="footerlist">
<a href="/home">Home</a>
<a href="/games">Games</a>
<a href="/catalog">Catalog</a>
<a href="/develop">Develop</a>
</div>
<div class="col" id="footerlist">
<a href="/settings">Settings</a>
<a href="/currency-exchange">Currency Exchange</a>
<a href="/download">Downloads</a>
</div>
</div>
</div>
</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<script src="/static/js/global.js" crossorigin="anonymous"></script> <nav class="navbar">
<script src="/static/js/theme.js" crossorigin="anonymous"></script> <div class="container-fluid">
</body> <div class="d-flex align-items-center">
</html> <button class="toggle-sidebar btn btn-link text-white me-2" type="button">
<i class="bi bi-list fs-4"></i>
</button>
<a class="navbar-brand" href="/home">
<h1 class="title-logo"><img src="/static/img/NEXIUM.png" alt="NEXIUM" /></h1>
</a>
</div>
<div class="navbar-actions d-flex align-items-center gap-2 flex-nowrap">
<div
class="navbar-currency d-none d-md-inline-flex align-items-center bg-dark text-white rounded-pill px-3 py-1 border shadow-sm gap-3">
<div class="d-flex align-items-center">
<span class="rbx-icon-nav-robux me-1 text-white"></span>
<span class="fw-semibold" id="robux-amount">{{currentuser.robux}}</span>
</div>
<div class=" d-flex align-items-center">
<span class="rbx-icon-nav-tix me-1 text-white"></span>
<span class="fw-semibold" id="tix-amount">{{currentuser.tix}}</span>
</div>
</div>
<div
class="navbar-profile nav-item dropdown d-inline-flex align-items-center rounded-pill px-3 py-1 bg-dark border shadow-sm">
<a class="nav-link dropdown-toggle d-flex align-items-center p-0" href="#" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
<img class="rounded-circle me-2" width="32" height="32"
src="/Thumbs/Head.ashx?x=48&y=48&userId={{currentuser.id}}" alt="{{currentuser.username}}" />
<b><span class="d-none d-md-inline">{{currentuser.username}}</span></b>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li class="d-md-none px-3 py-2">
<div class="d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center">
<span class="rbx-icon-nav-robux me-1 text-dark"></span>
<span class="fw-semibold" id="mobile-robux-amount">{{currentuser.robux}}</span>
</div>
<div class="d-flex align-items-center">
<span class="rbx-icon-nav-tix me-1 text-dark"></span>
<span class="fw-semibold" id="mobile-tix-amount">{{currentuser.tix}}</span>
</div>
</div>
</li>
<li><a class="dropdown-item" href="/settings">Settings</a></li>
<li><a class="dropdown-item" href="/membership">Membership</a></li>
<li><a class="dropdown-item" href="/giftcard-redeem">Redeem Codes</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li>
<form action="/logout" method="post">
<input type="hidden" name="csrf_token" value="{{csrf_token()}}">
<button class="dropdown-item text-danger fw-semibold" type="submit">Logout</button>
</form>
</li>
</ul>
</div>
</div>
</div>
</nav>
<div class="w-100 border-bottom subnav-bar ps-3 pe-3">
<a href="/users/{{currentuser.id}}/profile">Profile</a>
<a href="/users/{{currentuser.id}}/friends">Friends{% if currentuser.friend_requests > 0: %}<span
class="badge text-bg-danger ms-1">{{currentuser.friend_requests}}</span>{%endif%}</a>
<a href="/users">Users</a>
<a href="/avatar">Avatar</a>
<a href="/messages">Messages{% if currentuser.unread_messages > 0: %}<span
class="badge text-bg-danger ms-1">{{currentuser.unread_messages}}</span>{%endif%}</a>
<a href="/groups">Groups</a>
<a href="/trade">Trades{% if currentuser.inbound_trades > 0: %}<span
class="badge text-bg-danger ms-1">{{currentuser.inbound_trades}}</span>{%endif%}</a>
<a href="/download">Downloads</a>
<a href="https://discord.gg/REPLACEME">Discord</a>
{% if currentuser.is_admin: %}
<a href="/admin/">Admin Panel{% if currentuser.pending_asset_count > 0: %}<span
class="badge text-bg-danger ms-1">{{currentuser.pending_asset_count}}</span>{%endif%}</a>
{% endif %}
</div>
{% endif %}
{% if website_wide_message: %}
<div id="websitewidemessage" class="alert text-white" role="alert">
<p>{{ website_wide_message | safe }}</p>
</div>
{% endif %}
</div>
<div class="main-content">
{% block content %}{% endblock %}
</div>
<script src="/static/js/global.js" crossorigin="anonymous"></script>
<script src="/static/js/twemoji.js" crossorigin="anonymous"></script>
</body>
</html>
+2 -20
View File
@@ -22,7 +22,7 @@ from config import Config
config = Config() config = Config()
from app.routes.asset import migrateAsset from app.routes.asset import migrateAsset
from app.util import auth, assetversion, discord, redislock, announcement from app.util import auth, assetversion, discord, redislock
from app.services import economy, gameserver_comm from app.services import economy, gameserver_comm
from app.models.admin_permissions import AdminPermissions from app.models.admin_permissions import AdminPermissions
from app.models.user import User from app.models.user import User
@@ -297,25 +297,7 @@ def websitemessage():
def websitemessage_post(): def websitemessage_post():
AdminPermissionRequired('UpdateWebsiteMessage') AdminPermissionRequired('UpdateWebsiteMessage')
raw_message = request.form.get('message', '').strip() redis_controller.set("website_wide_message", request.form['message'])
is_valid, error_msg = announcement.validate_announcement(raw_message)
if not is_valid:
flash(f"Invalid announcement: {error_msg}", "danger")
return redirect("/admin/websitemessage")
sanitized_message = announcement.sanitize_announcement(raw_message)
AuthenticatedUser: User = auth.GetCurrentUser()
announcement.log_announcement_to_discord(
username=AuthenticatedUser.username,
user_id=AuthenticatedUser.id,
message=sanitized_message,
action="updated"
)
redis_controller.set("website_wide_message", sanitized_message)
flash("Announcement updated successfully", "success")
return redirect("/admin/websitemessage") return redirect("/admin/websitemessage")
@AdminRoute.route('/fflags', methods=['GET']) @AdminRoute.route('/fflags', methods=['GET'])
+1 -1
View File
@@ -23,7 +23,7 @@ html[data-theme="dark"] .tux-icon {
</p> </p>
</div> </div>
<div class="ms-auto"> <div class="ms-auto">
<a href="https://clients.orcbin.com/NEXIUMPlayerLauncher.exe" target="_blank" class="text-decoration-none m-0 btn btn-outline-primary btn fw-bold"><i class="bi bi-download me-2"></i>Download</a> <a href="https://clients.orcbin.com/comingsoon.txt" target="_blank" class="text-decoration-none m-0 btn btn-outline-primary btn fw-bold"><i class="bi bi-download me-2"></i>Download</a>
</div> </div>
</div> </div>
<div class="p-3 border d-flex align-items-center mt-3 rounded"> <div class="p-3 border d-flex align-items-center mt-3 rounded">
+44 -32
View File
@@ -4,8 +4,8 @@
<link href="/static/css/home.css" rel="stylesheet"/> <!-- Its just easier to resuse this--> <link href="/static/css/home.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="main"> <div id="main" class="home-page-shell">
<div class="container"> <div class="home-page-container container">
<!-- {% if not isEmailVerified: %} <!-- {% if not isEmailVerified: %}
<div class="alert p-2 alert-warning d-flex align-items-center"> <div class="alert p-2 alert-warning d-flex align-items-center">
<i class="bi bi-envelope-plus ms-2" style="font-size: 30px;"></i> <i class="bi bi-envelope-plus ms-2" style="font-size: 30px;"></i>
@@ -24,30 +24,35 @@
<a href="/settings/discord_link" class="ms-auto text-decoration-none me-2"><button class="btn btn-primary btn-sm">Link Discord</button></a> <a href="/settings/discord_link" class="ms-auto text-decoration-none me-2"><button class="btn btn-primary btn-sm">Link Discord</button></a>
</div> </div>
{%endif%} {%endif%}
<div class="d-flex align-items-center"> <div class="home-hero-subtle d-flex align-items-center">
<img class="rounded-5 overflow-hidden me-4" src="/Thumbs/Head.ashx?x=150&y=150&userId={{currentuser.id}}" style="width:auto;max-height: 130px;"/> <img class="rounded-5 overflow-hidden me-4 home-hero-avatar" src="/Thumbs/Head.ashx?x=150&y=150&userId={{currentuser.id}}" alt="{{currentuser.username}}"/>
<div class="mt-2"> <div class="mt-2">
<h1> <p class="home-kicker mb-1">Welcome back</p>
<h1 class="home-greeting mb-2">
<span id="greetings"></span>, {{currentuser.username}}! <span id="greetings"></span>, {{currentuser.username}}!
</h1> </h1>
{% if membershipValue == 1: %} <div class="home-status-row">
<span class="rbx-icon-bc"></span> {% if membershipValue == 1: %}
{%endif%} <span class="rbx-icon-bc"></span>
{% if membershipValue == 2: %} {%endif%}
<span class="rbx-icon-tbc"></span> {% if membershipValue == 2: %}
{%endif%} <span class="rbx-icon-tbc"></span>
{% if membershipValue == 3: %} {%endif%}
<span class="rbx-icon-negative-obc"></span> {% if membershipValue == 3: %}
{%endif%} <span class="rbx-icon-negative-obc"></span>
{%endif%}
<span class="home-status-text">You should play something!</span>
</div>
</div> </div>
</div> </div>
<h4>Friends ( {{friendcount}} )</h4> <h4 class="home-section-title mt-4">Friends ( {{friendcount}} )</h4>
<div class="w-100 p-2 d-flex card-container" style="overflow-x: hidden;min-height: 100px;"> <p class="home-section-meta mb-2">People you have added.</p>
<div class="w-100 p-2 d-flex flex-nowrap card-container friends-scroll home-soft-strip" style="min-height: 100px;">
{% for friend in friends %} {% for friend in friends %}
<a href="/users/{{friend.id}}/profile"> <a href="/users/{{friend.id}}/profile">
<div style="height: fit-content; width:90px;" class="me-3"> <div style="height: fit-content; width:90px;" class="me-3 home-friend-card">
<div class="image-container"> <div class="image-container home-friend-avatar-frame">
<img class="rounded w-100 d-table" src="/Thumbs/Head.ashx?x=100&y=100&userId={{friend.id}}" style="margin: 0 auto;height: fit-content; max-width: 100px;"/> <img class="rounded w-100 d-table home-friend-avatar" src="/Thumbs/Head.ashx?x=100&y=100&userId={{friend.id}}" style="margin: 0 auto;height: fit-content; max-width: 100px;" alt="{{friend.username}}"/>
{% if friend.isonline: %}<div class="status-icon {% if friend.ingame: %}status-icon-green{% endif %}"></div>{% endif %} {% if friend.isonline: %}<div class="status-icon {% if friend.ingame: %}status-icon-green{% endif %}"></div>{% endif %}
</div> </div>
<p class="text-body w-100 text-center d-inline-block m-0 text-truncate" style="font-size: 12px;">{{friend.username}}</p> <p class="text-body w-100 text-center d-inline-block m-0 text-truncate" style="font-size: 12px;">{{friend.username}}</p>
@@ -55,27 +60,31 @@
</a> </a>
{% endfor %} {% endfor %}
{% if friendcount == 0: %} {% if friendcount == 0: %}
<p style="margin: auto auto;" class="d-table align-items-center d-flex">You have no friends <img width="21" height="21" src="https://media.tenor.com/jeYb8iK3YfsAAAAi/skull-skullgif.gif"></p> <div class="home-empty-state home-empty-inline">
<p class="mb-0">You have no friends yet.</p>
<a href="/games" class="btn btn-sm btn-primary mt-2">Explore games</a>
</div>
{% endif %} {% endif %}
</div> </div>
<h4 class="mt-2">Recently Played</h4> <h4 class="home-section-title mt-4">Recently Played</h4>
<div class="w-100 p-2 d-flex" style="overflow-x: hidden;min-height: 100px;overflow-x: auto;"> <p class="home-section-meta mb-2">Pick up where you left off or try something new.</p>
<div class="w-100 p-2 d-flex home-soft-strip" style="overflow-x: hidden;min-height: 100px;overflow-x: auto;">
{% for place in recentlyplayed %} {% for place in recentlyplayed %}
<a class="text-decoration-none" href="/games/{{place.id}}/"> <a class="text-decoration-none" href="/games/{{place.id}}/">
<div class="me-3 overflow-hidden rounded place-card"> <div class="me-3 overflow-hidden rounded place-card home-place-card">
<div class="position-relative"> <div class="position-relative home-place-thumb-wrap">
<img width="100%" style="aspect-ratio: 1/1;" src="/Thumbs/PlaceIcon.ashx?assetId={{place.id}}&x=150&y=150"> <img class="home-place-thumb" src="/Thumbs/PlaceIcon.ashx?assetId={{place.id}}&x=150&y=150" alt="{{place.name}}">
<div class="position-absolute" style="bottom: 0px;left: 0px;"> <div class="position-absolute" style="bottom: 0px;left: 0px;">
<div class="fw-bold bg-surface text-body" style="font-size: 12px;padding: 3px;border-top-right-radius: 4px;">{{place.placeyear.value}}</div> <div class="fw-bold bg-surface text-body home-place-year">{{place.placeyear.value}}</div>
</div> </div>
</div> </div>
<div class="p-1"> <div class="p-2 home-place-copy">
<h5 class="mb-0" style="font-size: 17px;text-overflow: ellipsis;">{{place.name}}</h5> <h5 class="mb-1 home-place-name" title="{{place.name}}">{{place.name}}</h5>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<p style="margin:0;font-size: 14px;" class="text-secondary"><i class="bi bi-people"></i> {{place.playercount}} <span style="font-size: 13px;">Playing</span></p> <p class="mb-2 text-secondary home-place-playing"><i class="bi bi-people"></i> {{place.playercount}} <span>Playing</span></p>
</div> </div>
<div class="w-100 votePercentageBackground"> <div class="w-100 votePercentageBackground home-place-votes">
<div class="votePercentageFill" style="width: {{place.likePercentage}}%;"></div> <div class="votePercentageFill home-place-vote-fill" data-like-percentage="{{place.likePercentage}}"></div>
<div> <div>
<div class="segment" style="left: 18%;"></div> <div class="segment" style="left: 18%;"></div>
<div class="segment" style="left: 38%;"></div> <div class="segment" style="left: 38%;"></div>
@@ -88,7 +97,10 @@
</a> </a>
{% endfor %} {% endfor %}
{% if recentlyplayedcount == 0: %} {% if recentlyplayedcount == 0: %}
<p style="margin: auto auto;" class="d-table">Looks like you havent played any games yet, maybe you should go <a class="text-decoration-none" href="/games">play</a> one right now!</p> <div class="home-empty-state home-empty-wide home-empty-inline">
<p class="mb-0">Looks like you havent played any games yet.</p>
<a class="btn btn-sm btn-primary mt-2" href="/games">Go and play something!</a>
</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
+3 -3
View File
@@ -6,12 +6,12 @@
{% block content %} {% block content %}
<div id="main"> <div id="main">
<div class="login-container d-flex align-items-center justify-content-center"> <div class="login-container d-flex align-items-center justify-content-center">
<div style="padding: 10px;min-width:330px;" class="ps-md-4 pe-md-4"> <div class="login-panel px-4 py-4">
<img src="/static/img/LoginLogo.png" alt="Nexium" class="login-logo" /> <img src="/static/img/LoginLogo.png" alt="Nexium" class="login-logo" />
<p class="login-subtitle">2016+ with freedom.</p> <p class="login-subtitle">2016+ with freedom.</p>
{% with messages = get_flashed_messages(with_categories=true) %} {% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %} {% if messages %}
<div> <div class="mb-2">
{% for category, message in messages %} {% for category, message in messages %}
{% if category == 'error': %} {% if category == 'error': %}
<div class="alert border p-2 text-center alert-danger border-danger"> <div class="alert border p-2 text-center alert-danger border-danger">
@@ -34,7 +34,7 @@
<button type="submit" class="btn btn-primary mb-3" {% if not loginEnabled: %}disabled{% endif %}>Login</button> <button type="submit" class="btn btn-primary mb-3" {% if not loginEnabled: %}disabled{% endif %}>Login</button>
</form> </form>
<div class="text-center mt-2"> <div class="text-center mt-2">
<p>Don't have an account? <a href="/signup">Signup</a> <p>Don't have an account? <a href="/signup">Signup</a></p>
</div> </div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -59,7 +59,7 @@ def login_page():
if not loginEnabled: if not loginEnabled:
flash("Logins are temporarily disabled", "error") flash("Logins are temporarily disabled", "error")
login_page_response = make_response(render_template("login/login.html", loginEnabled=loginEnabled)) login_page_response = make_response(render_template("login/login.html", loginEnabled=loginEnabled, auth_page=True))
if request.cookies.get( key = ".ROBLOSECURITY", default = None, type = str ) is not None: if request.cookies.get( key = ".ROBLOSECURITY", default = None, type = str ) is not None:
login_page_response.set_cookie( login_page_response.set_cookie(
key = ".ROBLOSECURITY", key = ".ROBLOSECURITY",
+7 -15
View File
@@ -6,17 +6,13 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="main"> <div id="main">
<div class="signup-wrapper" style="position:relative;"> <div class="signup-container d-flex align-items-center justify-content-center">
<!-- logo moved outside the signup card so it can overlap/escape the container --> <div class="signup-panel px-4 py-4">
<img src="/static/img/LoginLogo.png" alt="NEXIUM" class="logo-outside d-none d-lg-block" /> <img src="/static/img/LoginLogo.png" alt="NEXIUM" class="signup-logo" />
<div class="signup-row"> <p class="signup-subtitle">2016+ with freedom.</p>
<div class="signup-card">
<p class="signup-subtitle">2016+ with freedom.</p>
<div class="signup-container d-flex align-items-center justify-content-center">
<div style="padding: 10px;min-width:300px;" class="ps-md-4 pe-md-4">
{% with messages = get_flashed_messages() %} {% with messages = get_flashed_messages() %}
{% if messages %} {% if messages %}
<div> <div class="mb-2">
{% for message in messages %} {% for message in messages %}
<div class="alert border border-danger p-2 text-center messagealerts"> <div class="alert border border-danger p-2 text-center messagealerts">
{{ message }} {{ message }}
@@ -37,16 +33,12 @@
<p>I agree to the <a href="/terms">Terms of Service</a> and <a href="/privacy">Privacy Policy</a></p> <p>I agree to the <a href="/terms">Terms of Service</a> and <a href="/privacy">Privacy Policy</a></p>
</label> </label>
</div> </div>
<div class="cf-turnstile d-table mb-2" style="margin: 0 auto;" data-theme="dark" data-sitekey="{{turnstilekey}}"></div> <div class="cf-turnstile d-table mb-2 mx-auto" data-theme="dark" data-sitekey="{{turnstilekey}}"></div>
{% if SignupEnabled: %}<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>{%endif%} {% if SignupEnabled: %}<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>{%endif%}
<button type="submit" class="btn btn-primary mb-3" {% if not SignupEnabled: %}disabled{% endif %}>Signup</button> <button type="submit" class="btn btn-primary mb-3" {% if not SignupEnabled: %}disabled{% endif %}>Signup</button>
</form> </form>
<div class="text-center mt-2"> <div class="text-center mt-2">
<p>Already have an account? <a href="/login">Login</a> <p>Already have an account? <a href="/login">Login</a></p>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -34,7 +34,7 @@ def signup_page():
flash("Signups are temporarily disabled") flash("Signups are temporarily disabled")
SignupEnabled = False SignupEnabled = False
InviteKeysEnabled = websiteFeatures.GetWebsiteFeature("InviteKeys") InviteKeysEnabled = websiteFeatures.GetWebsiteFeature("InviteKeys")
return render_template("signup/signup.html", SignupEnabled=SignupEnabled, InviteKeysEnabled=InviteKeysEnabled) return render_template("signup/signup.html", SignupEnabled=SignupEnabled, InviteKeysEnabled=InviteKeysEnabled, auth_page=True)
def RateLimitReached(e): def RateLimitReached(e):
flash("You are being rate limited. Please try again later.") flash("You are being rate limited. Please try again later.")
+3 -2
View File
@@ -13,7 +13,8 @@ from app.pages.login.login import CreateLoginRecord
config = Config() config = Config()
AuthenticationRoute = Blueprint('authentication', __name__, url_prefix='/') AuthenticationRoute = Blueprint('authentication', __name__, url_prefix='/')
@AuthenticationRoute.route('/Login/Negotiate.ashx', methods=['GET']) @AuthenticationRoute.route('/Login/Negotiate.ashx', methods=['GET', 'POST'])
@csrf.exempt
def loginNegotiate(): def loginNegotiate():
AuthenticationTicket = request.args.get( AuthenticationTicket = request.args.get(
key = 'suggest', key = 'suggest',
@@ -94,7 +95,7 @@ def gameGetCurrentUser():
return "Bad Request", 200 return "Bad Request", 200
return str(AuthenticatedUser.id), 200 return str(AuthenticatedUser.id), 200
@AuthenticationRoute.route('/login/RequestAuth.ashx', methods=['GET']) @AuthenticationRoute.route('/login/RequestAuth.ashx', methods=['GET', 'POST'])
@limiter.limit("6/minute") @limiter.limit("6/minute")
def loginRequestAuth(): def loginRequestAuth():
AuthenticatedUser : User = auth.GetCurrentUser() AuthenticatedUser : User = auth.GetCurrentUser()
+3 -3
View File
@@ -42,17 +42,17 @@ def GetAllowedMD5Hashes():
"data": [ "data": [
"aa",# 2020 "aa",# 2020
"a", "a",
"89ae5307ca8bcd4059fa8b61399b6dba" # 2021 "04cb8a13a2d0eda3b7ae2b4e3984e424" # 2021
] ]
}), 200 }), 200
return jsonify({ return jsonify({
"data": [ "data": [
# 2016 # 2016
"7c4caa74baf2100a2e2b2ba34eb9d69b", "4004fb391db720edcdf520907f4cf663",
"aaa", "aaa",
# 2018, # 2018,
"cb625aa02e932bdeb11ac84001ed5475" "763fade9fabe4fcd64fc1676393c173e"
] ]
}) })
+12 -54
View File
@@ -22,12 +22,9 @@ from app.models.place import Place
from app.models.asset import Asset from app.models.asset import Asset
from app.models.login_records import LoginRecord from app.models.login_records import LoginRecord
from app.models.user_hwid_log import UserHWIDLog from app.models.user_hwid_log import UserHWIDLog
from app.models.user_ban import UserBan
from app.models.admin_permissions import AdminPermissions
from app.models.universe import Universe from app.models.universe import Universe
from app.models.asset_version import AssetVersion from app.models.asset_version import AssetVersion
from app.enums.AssetType import AssetType from app.enums.AssetType import AssetType
from app.enums.BanType import BanType
from app.enums.MembershipType import MembershipType from app.enums.MembershipType import MembershipType
from app.enums.PlaceYear import PlaceYear from app.enums.PlaceYear import PlaceYear
from app.services.gameserver_comm import perform_post from app.services.gameserver_comm import perform_post
@@ -319,48 +316,6 @@ def ReturnPlaceLauncher( message : str, status : int, authenticated_userid : int
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
return response return response
def BanUsersForSharedHwid(hwid: str, triggering_user_id: int | None = None) -> bool:
MatchingHWIDLogs : list[UserHWIDLog] = UserHWIDLog.query.filter_by(hwid=hwid).distinct(UserHWIDLog.user_id).all()
if len(MatchingHWIDLogs) < 2:
return False
MatchingUserIds = {log.user_id for log in MatchingHWIDLogs if log.user_id is not None}
if len(MatchingUserIds) < 2:
return False
for user_id in MatchingUserIds:
UserObj : User | None = User.query.filter_by(id=user_id).first()
if UserObj is None:
continue
if AdminPermissions.query.filter_by(userid=UserObj.id).first() is not None:
return False
for user_id in MatchingUserIds:
UserObj : User | None = User.query.filter_by(id=user_id).first()
if UserObj is None:
continue
if UserObj.accountstatus != 1:
continue
ExistingActiveBan : UserBan | None = UserBan.query.filter_by(userid=UserObj.id, acknowledged=False).order_by(UserBan.id.desc()).first()
if ExistingActiveBan is not None:
continue
BanObj = UserBan(
userid=UserObj.id,
author_userid=1,
reason="Alting is not permitted on NEXIUM, contact us if you believe this is unfair",
ban_type=BanType.Deleted,
moderator_note=f"Automatic HWID alt ban for shared HWID {hwid}",
expires_at=None
)
db.session.add(BanObj)
UserObj.accountstatus = 3
if MatchingUserIds:
db.session.commit()
return True
@GameJoinRoute.route('/game/PlaceLauncher.ashx', methods=['GET', 'POST']) @GameJoinRoute.route('/game/PlaceLauncher.ashx', methods=['GET', 'POST'])
@GameJoinRoute.route('/Game/PlaceLauncher.ashx', methods=['GET', 'POST']) @GameJoinRoute.route('/Game/PlaceLauncher.ashx', methods=['GET', 'POST'])
@GameJoinRoute.route('/game/placelauncher.ashx', methods=['GET', 'POST']) @GameJoinRoute.route('/game/placelauncher.ashx', methods=['GET', 'POST'])
@@ -368,7 +323,7 @@ def BanUsersForSharedHwid(hwid: str, triggering_user_id: int | None = None) -> b
@csrf.exempt @csrf.exempt
def placelauncher(): def placelauncher():
if not websiteFeatures.GetWebsiteFeature("GameJoinAPI"): if not websiteFeatures.GetWebsiteFeature("GameJoinAPI"):
logging.warning("opsec") return ReturnPlaceLauncher("GameJoinAPI is disabled", 12)
AuthenticatdUser = None AuthenticatdUser = None
placeid = request.args.get( key = 'placeId', default = None, type = int) or request.args.get( key = 'placeid', default = None, type = int) placeid = request.args.get( key = 'placeId', default = None, type = int) or request.args.get( key = 'placeid', default = None, type = int)
@@ -391,6 +346,12 @@ def placelauncher():
if authticketInfo is None and AuthenticatdUser is None: if authticketInfo is None and AuthenticatdUser is None:
return ReturnPlaceLauncher("Invalid authentication ticket", 12) return ReturnPlaceLauncher("Invalid authentication ticket", 12)
#UserIPHash = hashlib.md5(get_remote_address().encode("utf-8")).hexdigest()
#LoginRecords : list[LoginRecord] = LoginRecord.query.filter(LoginRecord.ip == UserIPHash).distinct(LoginRecord.userid).all()
#for record in LoginRecords:
# if record.User.accountstatus != 1:
# return ReturnPlaceLauncher("Invalid authentication ticket", 12)
userId = int(authticketInfo) if authticketInfo is not None else AuthenticatdUser.id userId = int(authticketInfo) if authticketInfo is not None else AuthenticatdUser.id
if PlaceServerPlayer.query.filter_by(userid=userId).first() is not None and not isTeleport: if PlaceServerPlayer.query.filter_by(userid=userId).first() is not None and not isTeleport:
CurrentPlaceServerPlayerObj : PlaceServerPlayer = PlaceServerPlayer.query.filter_by(userid=userId).first() CurrentPlaceServerPlayerObj : PlaceServerPlayer = PlaceServerPlayer.query.filter_by(userid=userId).first()
@@ -427,9 +388,6 @@ def placelauncher():
redis_controller.delete(f"hwid:{str(Tracking_Cookie)}") redis_controller.delete(f"hwid:{str(Tracking_Cookie)}")
if BanUsersForSharedHwid(UserHWIDHash, UserObj.id):
return ReturnPlaceLauncher("Invalid session", 12)
UserMembershipStatus : MembershipType = GetUserMembership(UserObj) UserMembershipStatus : MembershipType = GetUserMembership(UserObj)
if UniverseObj.bc_required and UserMembershipStatus == MembershipType.NonBuildersClub: if UniverseObj.bc_required and UserMembershipStatus == MembershipType.NonBuildersClub:
return ReturnPlaceLauncher("Builders Club required", 12) return ReturnPlaceLauncher("Builders Club required", 12)
@@ -461,10 +419,11 @@ def placelauncher():
if PlaceServerObj is False: if PlaceServerObj is False:
logging.info(f"Placelauncher.ashx : {str(placeid)} : {UserObj.username} [{UserObj.id}] : No available place servers found yet") logging.info(f"Placelauncher.ashx : {str(placeid)} : {UserObj.username} [{UserObj.id}] : No available place servers found yet")
return ReturnPlaceLauncher(None, 1, authenticated_userid=userId) return ReturnPlaceLauncher(None, 1, authenticated_userid=userId)
redis_controller.delete(f"authticket:{ticket}")
authenticatedTicketUUID = str(uuid.uuid4()) authenticatedTicketUUID = str(uuid.uuid4())
redis_controller.setex(f"place:{placeid}:ticket:{authenticatedTicketUUID}", 60, json.dumps({"id": userId, "jobid": str(PlaceServerObj.serveruuid)})) redis_controller.setex(f"place:{placeid}:ticket:{authenticatedTicketUUID}", 60, json.dumps({"id": userId, "jobid": str(PlaceServerObj.serveruuid)}))
authticket = ''.join(random.choices(string.ascii_uppercase + string.digits, k=256)) authticket = ''.join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(256))
redis_controller.set(f"authticket:{authticket}", userId, 60*10) redis_controller.set(f"authticket:{authticket}", userId, 60*10)
resp = make_response(jsonify({ resp = make_response(jsonify({
"jobId": PlaceServerObj.serveruuid, "jobId": PlaceServerObj.serveruuid,
@@ -612,7 +571,7 @@ def GenerateClientTicket( UserObj : User, JobId : str, CharacterURL : str = None
if TicketVersion == 2: if TicketVersion == 2:
CharacterURL = str(UserObj.id) CharacterURL = str(UserObj.id)
elif TicketVersion == 1: elif TicketVersion == 1:
CharacterURL = Config.BaseURL + "/Asset/CharacterFetch.ashx?userId=" + str(UserObj.id) # f"http://www.nexium.fit/v1.1/avatar-fetch?userId={str(UserObj.id)}&placeId={str(PlaceId)}" CharacterURL = Config.BaseURL + "/Asset/CharacterFetch.ashx?userId=" + str(UserObj.id) # f"http://www.vortexi.cc/v1.1/avatar-fetch?userId={str(UserObj.id)}&placeId={str(PlaceId)}"
elif TicketVersion == 4: elif TicketVersion == 4:
CharacterURL = f"http://www.nexium.fit/v1/avatar-fetch?userId={str(UserObj.id)}&placeId={str(PlaceId)}" CharacterURL = f"http://www.nexium.fit/v1/avatar-fetch?userId={str(UserObj.id)}&placeId={str(PlaceId)}"
@@ -664,8 +623,7 @@ def join():
PlaceObj : Place = Place.query.filter_by(placeid=placeid).first() PlaceObj : Place = Place.query.filter_by(placeid=placeid).first()
AssetObj : Asset = Asset.query.filter_by(id=placeid).first() AssetObj : Asset = Asset.query.filter_by(id=placeid).first()
UniverseObj : Universe = Universe.query.filter_by(id=PlaceObj.parent_universe_id).first() UniverseObj : Universe = Universe.query.filter_by(id=PlaceObj.parent_universe_id).first()
if UniverseObj.place_year == PlaceYear.Sixteen:
return 'Who is bro', 400
ClientTicket = GenerateClientTicket(UserObj, jobid, TicketVersion = 1 if UniverseObj.place_year in [PlaceYear.Sixteen, PlaceYear.Fourteen] else ( 2 if UniverseObj.place_year == PlaceYear.Eighteen else 4), PlaceId = placeid) ClientTicket = GenerateClientTicket(UserObj, jobid, TicketVersion = 1 if UniverseObj.place_year in [PlaceYear.Sixteen, PlaceYear.Fourteen] else ( 2 if UniverseObj.place_year == PlaceYear.Eighteen else 4), PlaceId = placeid)
AuthenticationTicket = auth.CreateToken(UserObj.id, get_remote_address() , (60*60*24) ) AuthenticationTicket = auth.CreateToken(UserObj.id, get_remote_address() , (60*60*24) )
@@ -764,4 +722,4 @@ def join():
expires = datetime.utcnow() + timedelta(days=3), expires = datetime.utcnow() + timedelta(days=3),
domain = f".{config.BaseDomain}" domain = f".{config.BaseDomain}"
) )
return joinResposne return joinResposne
+143
View File
@@ -0,0 +1,143 @@
/* Custom UI tweaks for demos: reposition/resize header and navbar elements */
:root{
--navbar-height: 64px;
}
/* Make navbar slightly taller and adjust brand positioning */
.navbar {
min-height: var(--navbar-height) !important;
height: var(--navbar-height) !important;
}
.navbar .navbar-brand .title-logo {
font-size: 1.25rem;
line-height: 1;
margin: 0;
transform: translateY(6px);
}
/* Resize avatar in navbar */
.d-flex #currencydiv img {
width: 28px !important;
height: 28px !important;
}
/* Slightly increase spacing for nav links */
.navbar-nav .nav-link {
padding-top: 10px;
padding-bottom: 10px;
}
/* Example utility: move nav to the right on larger screens */
@media (min-width: 992px) {
.navbar .container-fluid > .navbar-brand { margin-right: 24px; }
}
/* Use this file to experiment: change --navbar-height or transforms to reposition/resize elements */
/* ------------------------
Login page repositioning
- Moves the decorative image to the left of the form on wide screens
- Stacks the image above the form on small screens
- Adjust gap and max-height to taste
------------------------ */
.login-container {
display: flex;
align-items: center;
gap: 32px;
}
.login-container > div {
order: 2; /* form column */
flex: 1 1 0;
}
.login-container img {
order: 1; /* image column */
max-height: 420px;
width: auto;
display: block;
}
/* Mobile / small screens: stack vertically with image on top */
@media (max-width: 992px) {
.login-container {
flex-direction: column-reverse; /* keep form visible first on very small screens */
min-width: 280px;
gap: 16px;
}
.login-container img {
width: 100%;
max-height: 240px;
object-fit: cover;
}
.login-container > div {
order: 1;
width: 100%;
}
}
/* Utility: nudge the form horizontally if needed */
.login-container .form-nudge { transform: translateX(10px); }
/* Login logo styling: replace h1 text with an image */
.login-logo {
display: block;
margin: 0 auto 8px;
max-width: 220px;
height: auto;
}
.login-subtitle {
text-align: center;
margin-bottom: 12px;
font-size: small;
color: rgba(230,238,248,0.9);
}
/* Navbar brand logo styling */
.navbar .navbar-brand .brand-logo {
display: block;
max-height: 72px; /* increased size */
width: auto;
transform: translateY(2px);
}
@media (max-width: 576px) {
.navbar .navbar-brand .brand-logo { max-height: 48px; transform: translateY(4px); }
}
.has-left-sidebar .fixed-top.width-100 {
/* Keep the top bar visible when a left sidebar is present.
Shift it right so it doesn't overlap the sidebar and reduce its width. */
left: 220px !important;
width: calc(100% - 220px) !important;
box-sizing: border-box;
}
.left-sidebar {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: 220px;
background: #1f1f1f;
color: #fff;
padding-top: 18px;
z-index: 1040;
overflow: auto;
}
.left-sidebar .sidebar-brand { text-align: center; padding: 6px 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.04); }
.left-sidebar .sidebar-brand .brand-logo { max-height: 48px; }
.left-sidebar .sidebar-nav ul { list-style:none; margin:0; padding:8px 0; }
.left-sidebar .sidebar-nav li { margin:0; }
.left-sidebar .sidebar-nav a { display:block; padding:10px 16px; color: #ddd; text-decoration:none; border-bottom:1px solid rgba(255,255,255,0.02); }
.left-sidebar .sidebar-nav a:hover { background: rgba(255,255,255,0.03); color:#fff; }
.left-sidebar .sidebar-nav .badge { background:#e53935; color:#fff; font-size:11px; padding:2px 6px; border-radius:10px; margin-left:6px; }
/* push page content to the right so the sidebar doesn't overlap */
.has-left-sidebar body, .has-left-sidebar .width-100 { margin-left: 220px !important; }
@media (max-width: 992px) {
.left-sidebar { transform: translateX(-220px); transition: transform .2s ease; }
.left-sidebar.open { transform: translateX(0); }
.has-left-sidebar .width-100 { margin-left: 0 !important; }
}
+20 -119
View File
@@ -1,86 +1,22 @@
html[data-theme="dark"] {
color-scheme: dark;
--bs-body-bg: rgb(9, 9, 9) !important;
--bs-body-color: rgb(240, 240, 240) !important;
--bs-heading-color: #fff !important;
--page-bg: rgb(9, 9, 9);
--nav-bg: rgb(9, 9, 9);
--nav-border: rgb(90, 90, 90);
--subnav-bg: rgb(23, 23, 23);
--subnav-link: rgb(199, 199, 199);
--subnav-link-hover: rgb(255, 255, 255);
--footer-bg: rgb(15, 15, 15);
--footer-text: rgb(255, 255, 255);
--linebreak: rgb(60, 60, 60);
--card-bg: rgb(30, 30, 30);
--card-shadow: rgb(0, 0, 0);
--text-muted: rgb(170, 170, 170);
--nav-text: rgb(255, 255, 255);
--website-message-bg: rgb(65, 7, 7);
--surface-alt: rgb(27, 27, 27);
--surface-sunken: rgb(18, 18, 18);
--track-bg: rgb(49, 49, 49);
--track-fill: rgb(85, 85, 85);
--alert-bg: rgba(255, 0, 0, 0.2);
--alert-text: rgb(226, 226, 226);
--auth-bg: rgb(20, 20, 20);
--auth-dot: #333;
--auth-card-bg: rgba(25, 25, 25, 1);
--auth-card-shadow: rgba(0, 0, 0, 0.7);
}
html[data-theme="light"] {
color-scheme: light;
--bs-body-bg: rgb(245, 247, 250) !important;
--bs-body-color: rgb(31, 41, 55) !important;
--bs-heading-color: rgb(17, 24, 39) !important;
--page-bg: rgb(245, 247, 250);
--nav-bg: rgb(255, 255, 255);
--nav-border: rgb(210, 216, 224);
--subnav-bg: rgb(241, 243, 246);
--subnav-link: rgb(70, 80, 99);
--subnav-link-hover: rgb(17, 24, 39);
--footer-bg: rgb(233, 237, 245);
--footer-text: rgb(26, 35, 48);
--linebreak: rgb(208, 215, 226);
--card-bg: rgb(255, 255, 255);
--card-shadow: rgba(15, 23, 42, 0.12);
--text-muted: rgb(87, 96, 113);
--nav-text: rgb(31, 41, 55);
--website-message-bg: rgb(255, 243, 205);
--surface-alt: rgb(229, 231, 235);
--surface-sunken: rgb(214, 218, 225);
--track-bg: rgb(222, 226, 232);
--track-fill: rgb(178, 185, 196);
--alert-bg: rgba(220, 38, 38, 0.12);
--alert-text: rgb(153, 27, 27);
--auth-bg: rgb(238, 240, 244);
--auth-dot: rgba(15, 23, 42, 0.08);
--auth-card-bg: rgb(255, 255, 255);
--auth-card-shadow: rgba(15, 23, 42, 0.15);
}
body { body {
background-color: var(--bs-body-bg) !important; --bs-body-bg: rgb(9,9,9) !important;
color: var(--bs-body-color) !important; --bs-heading-color: #fff !important;
--bs-heading-color: var(--bs-heading-color) !important;
font-family: 'Exo 2', sans-serif;
} }
.linebreak { .linebreak {
width: 100%; width: 100%;
border-bottom: 1px solid var(--linebreak); border-bottom: 1px solid rgb(60,60,60);
margin: 10px 0; margin: 10px 0;
height: 1px; height: 1px;
} }
.place-card { .place-card {
background-color: var(--card-bg); background-color: rgb(30,30,30);
min-height: 220px; min-height: 220px;
transition: 0.2s; transition: 0.2s;
width: 150px; width: 150px;
border-radius: 3px; border-radius: 3px;
box-shadow: 0 0 5px var(--card-shadow); box-shadow: 0 0 5px rgb(0,0,0);
} }
.place-card h5 { .place-card h5 {
@@ -95,23 +31,23 @@ body {
} }
.place-card .votePercentageBackground { .place-card .votePercentageBackground {
background-color: var(--track-bg); background-color: rgb(49,49,49);
z-index: 1; z-index: 1;
height: 10px; height: 10px;
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
border: 1px solid var(--surface-alt); border: 1px solid rgb(27,27,27);
margin-top: 3px; margin-top: 3px;
} }
.place-card .votePercentageFill { .place-card .votePercentageFill {
background-color: var(--track-fill); background-color: rgb(85,85,85);
height: 10px; height: 10px;
} }
.place-card .segment { .place-card .segment {
background-color: var(--surface-alt); background-color: rgb(27, 27, 27);
height: 10px; height: 10px;
width: 2px; width: 2px;
position: absolute; position: absolute;
@@ -119,53 +55,18 @@ body {
z-index: 3; z-index: 3;
} }
.messagealerts {
background: var(--alert-bg);
color: var(--alert-text);
word-wrap: break-word;
}
.bg-surface {
background-color: var(--surface-alt) !important;
}
html[data-theme="light"] .rbx-icon-nav-robux,
html[data-theme="light"] .rbx-icon-nav-tix,
html[data-theme="light"] .rbx-icon-nav-settings {
filter: invert(1);
}
#websitewidemessage { #websitewidemessage {
background-color: var(--website-message-bg); background-color: rgb(4, 83, 73);
} }
#websitewidemessage p { #websitewidemessage p {
margin: 0; margin: 0;
padding: 0 24px; padding: 0;
font-weight: 500; font-weight: 500;
font-size: 1rem; font-size: 1rem;
text-align: center; text-align: center;
} }
#websitewidemessage-close {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
font-size: 1.25rem;
line-height: 1;
padding: 0 4px;
cursor: pointer;
color: inherit;
opacity: 0.7;
}
#websitewidemessage-close:hover {
opacity: 1;
}
#websitewidemessage a { #websitewidemessage a {
text-decoration: underline !important; text-decoration: underline !important;
color: rgb(0, 140, 255) !important; color: rgb(0, 140, 255) !important;
@@ -174,8 +75,8 @@ html[data-theme="light"] .rbx-icon-nav-settings {
} }
.navbar { .navbar {
background-color: var(--nav-bg); background-color: rgb(0, 0, 0);
border-bottom: 1px solid var(--nav-border); border-bottom: 1px solid rgb(0, 0, 0);
} }
.navbar-brand h1 { .navbar-brand h1 {
@@ -186,8 +87,8 @@ html[data-theme="light"] .rbx-icon-nav-settings {
} }
#footer { #footer {
border-top: 1px solid var(--nav-border); border-top: 1px solid rgb(20, 20, 20);
background-color: var(--footer-bg); background-color: rgb(17, 16, 16);
min-height: 100px; min-height: 100px;
} }
@@ -195,7 +96,7 @@ html[data-theme="light"] .rbx-icon-nav-settings {
display: flex; display: flex;
width: 100%; width: 100%;
text-decoration: none !important; text-decoration: none !important;
color: var(--footer-text) !important; color: rgb(255, 255, 255) !important;
font-weight: 400 !important; font-weight: 400 !important;
transition: 0.2s; transition: 0.2s;
} }
@@ -204,12 +105,12 @@ html[data-theme="light"] .rbx-icon-nav-settings {
margin-left: 4px; margin-left: 4px;
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 400; font-weight: 400;
color: var(--nav-text); color: rgb(255,255,255);
min-width: 28px; min-width: 28px;
} }
.text-secondary { .text-secondary {
color: var(--text-muted) !important; color: rgb(255, 255, 255) !important;
} }
.rbx-icon-default-navigation,.rbx-icon-nav-menu,.rbx-icon-nav-robux,.rbx-icon-nav-tix,.rbx-icon-nav-settings,.rbx-icon-nav-search,.rbx-icon-nav-home,.rbx-icon-nav-profile,.rbx-icon-nav-message,.rbx-icon-nav-friends,.rbx-icon-nav-charactercustomizer,.rbx-icon-nav-inventory,.rbx-icon-nav-trade,.rbx-icon-nav-group,.rbx-icon-nav-forum,.rbx-icon-nav-blog,.rbx-icon-nav-search-white,.rbx-icon-nav-shop { .rbx-icon-default-navigation,.rbx-icon-nav-menu,.rbx-icon-nav-robux,.rbx-icon-nav-tix,.rbx-icon-nav-settings,.rbx-icon-nav-search,.rbx-icon-nav-home,.rbx-icon-nav-profile,.rbx-icon-nav-message,.rbx-icon-nav-friends,.rbx-icon-nav-charactercustomizer,.rbx-icon-nav-inventory,.rbx-icon-nav-trade,.rbx-icon-nav-group,.rbx-icon-nav-forum,.rbx-icon-nav-blog,.rbx-icon-nav-search-white,.rbx-icon-nav-shop {
@@ -519,4 +420,4 @@ button:hover .rbx-icon-nav-shop,a:hover .rbx-icon-nav-shop {
.rbx-icon-limited-unique-obc-label { .rbx-icon-limited-unique-obc-label {
background-position: 0 -180px; background-position: 0 -180px;
} }
+397
View File
@@ -0,0 +1,397 @@
:root {
--sidebar-width: 250px;
--navbar-height: 60px;
--primary-color: #333333;
--sidebar-bg: #090909;
--transition-speed: 0.3s;
--transition-timing: cubic-bezier(0.4, 0, 0.2, 1);
--alert-color: #0e19aa;
--scrollbar-color: #333333;
}
::-webkit-scrollbar {
height: 12px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background-color: #666;
border-radius: 8px;
background-clip: padding-box;
border: 3px solid transparent;
}
::-webkit-scrollbar-thumb:hover {
background-color: #888;
}
::-webkit-scrollbar-button {
width: 0;
height: 0;
display: none;
}
/* ariez what the fuck? */
* {
scrollbar-width: thin;
scrollbar-color: #666 transparent;
}
.modal-content {
z-index: 1060 !important;
position: relative;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes scaleIn {
from {
transform: scale(0.95);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
body {
min-height: 100vh;
padding-top: var(--navbar-height) !important;
}
.sidebar {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: var(--sidebar-width);
background: var(--sidebar-bg);
border-right: 1px solid rgba(255,255,255,0.1);
z-index: 1030;
transition: transform var(--transition-speed) var(--transition-timing);
padding-top: var(--navbar-height);
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: var(--primary-color) var(--sidebar-bg);
}
.sidebar-nav a {
animation: slideIn 0.5s var(--transition-timing);
animation-fill-mode: both;
}
.sidebar-nav a:nth-child(1) { animation-delay: 0.1s; }
.sidebar-nav a:nth-child(2) { animation-delay: 0.15s; }
.sidebar-nav a:nth-child(3) { animation-delay: 0.2s; }
.sidebar-nav a:nth-child(4) { animation-delay: 0.25s; }
.sidebar-nav a:nth-child(5) { animation-delay: 0.3s; }
.sidebar-nav a:nth-child(6) { animation-delay: 0.35s; }
.sidebar-nav a:nth-child(7) { animation-delay: 0.4s; }
.sidebar-nav a:nth-child(8) { animation-delay: 0.45s; }
.sidebar-nav a:nth-child(9) { animation-delay: 0.5s; }
.sidebar-nav a:nth-child(10) { animation-delay: 0.55s; }
.sidebar-hidden .sidebar {
transform: translateX(-100%);
}
.main-content {
margin-left: var(--sidebar-width);
transition: margin var(--transition-speed) var(--transition-timing);
animation: fadeIn 0.5s var(--transition-timing);
padding: 1rem;
min-height: calc(100vh - var(--navbar-height));
}
.sidebar-hidden .main-content {
margin-left: 0;
}
.sidebar-nav {
padding: 1rem;
}
.sidebar-nav .nav-link {
color: rgb(255, 255, 255);
padding: 0.75rem 1rem;
border-radius: 8px;
margin-bottom: 0.5rem;
transition: all var(--transition-speed) var(--transition-timing);
display: flex;
align-items: center;
gap: 0.75rem;
position: relative;
}
.sidebar-nav .nav-link:hover {
background: rgba(255,255,255,0.1);
color: white;
transform: translateX(5px);
}
.sidebar-nav .nav-link::before {
content: '';
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 3px;
background: var(--primary-color);
transform: scaleY(0);
transition: transform var(--transition-speed) var(--transition-timing);
}
.sidebar-nav .nav-link:hover::before {
transform: scaleY(1);
}
.sidebar-nav .nav-link i {
font-size: 1.25rem;
transition: transform var(--transition-speed) var(--transition-timing);
}
.sidebar-nav .nav-link:hover i {
transform: scale(1.1);
}
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--navbar-height);
background: var(--sidebar-bg) !important;
border-bottom: 1px solid rgba(255,255,255,0.1);
animation: fadeIn 0.5s var(--transition-timing);
z-index: 1040;
padding: 0.5rem 1rem;
}
.navbar .container-fluid {
justify-content: space-between;
padding: 0;
flex-wrap: nowrap;
align-items: center;
gap: 0.75rem;
}
.navbar-brand {
display: flex;
align-items: center;
min-width: 0;
}
.navbar-actions {
margin-left: auto;
flex-shrink: 0;
}
.navbar-currency,
.navbar-profile {
flex-shrink: 0;
}
.navbar-brand:hover {
transform: scale(1.05);
}
.profile-section {
padding: 1rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
margin-top: auto;
animation: scaleIn 0.5s var(--transition-timing);
}
.profile-section img {
transition: transform var(--transition-speed) var(--transition-timing);
}
.profile-section:hover img {
transform: scale(1.1);
}
.toggle-sidebar {
cursor: pointer;
padding: 0.5rem;
display: none;
transition: transform var(--transition-speed) var(--transition-timing);
}
.toggle-sidebar:hover {
transform: scale(1.1);
}
.badge {
transition: all var(--transition-speed) var(--transition-timing);
}
.nav-link:hover .badge {
transform: scale(1.1) translateX(2px);
}
#footer {
margin-left: var(--sidebar-width);
transition: margin var(--transition-speed) var(--transition-timing);
animation: fadeIn 0.5s var(--transition-timing);
}
.sidebar-hidden #footer {
margin-left: 0;
}
.title-logo {
margin: 0;
display: inline-block;
height: 2.5rem;
}
.title-logo img {
height: 100%;
width: auto;
display: block;
}
.title-logo-lander {
margin: 0;
display: inline-block;
height: 2.9rem;
}
.title-logo-lander img {
height: 100%;
width: auto;
display: block;
}
#websitewidemessage {
position: fixed;
top: var(--navbar-height);
left: var(--sidebar-width);
right: 0;
z-index: 1030;
margin: 0;
border-radius: 0;
text-align: center;
background-color: var(--alert-color);
animation: slideIn 0.5s var(--transition-timing);
padding: 0.25rem;
font-size: 0.875rem;
min-height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.sidebar-hidden #websitewidemessage {
left: 0;
transition: left var(--transition-speed) var(--transition-timing);
}
#websitewidemessage p {
margin: 0;
}
body.has-alert .main-content {
padding-top: 30px;
}
@media (max-width: 992px) {
.sidebar {
transform: translateX(-100%);
}
.sidebar.show {
transform: translateX(0);
}
.main-content {
margin-left: 0;
}
.toggle-sidebar {
display: block;
}
#websitewidemessage {
left: 0;
}
body.has-alert .main-content {
padding-top: 30px;
}
}
@media (max-width: 767.98px) {
.navbar {
padding-inline: 0.75rem;
}
.navbar-brand {
flex: 1 1 auto;
}
.title-logo {
height: 2rem;
}
.navbar-profile {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.navbar-profile .nav-link {
white-space: nowrap;
}
}
.subnav-bar {
display: none;
}
.sidebar::-webkit-scrollbar {
width: 6px;
}
.sidebar::-webkit-scrollbar-track {
background: var(--scrollbar-color);
}
.sidebar::-webkit-scrollbar-thumb {
background-color: #4a4a4a;
border-radius: 3px;
}
.sidebar::-webkit-scrollbar-thumb:hover {
background-color: #555555;
}
.nav-item.settings-dropdown {
display: none !important;
}
#currencydiv {
display: none;
}
+216 -1
View File
@@ -1,7 +1,61 @@
#main { #main {
display: flex; display: flex;
min-height: 100vh; min-height: 100vh;
margin-top: 120px; }
.home-page-shell {
width: 100%;
padding: 1.75rem 1rem 1.75rem;
}
.home-page-container {
width: 100%;
max-width: 980px;
margin: 0 auto;
padding: 0 0.25rem;
}
.home-hero-subtle {
padding: 0.5rem 0 0.75rem;
}
.home-hero-avatar {
width: 76px;
height: 76px;
object-fit: cover;
}
.home-kicker {
color: var(--text-secondary, #9c9c9c);
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 0.68rem;
}
.home-greeting {
font-size: clamp(1.35rem, 2.1vw, 1.85rem);
font-weight: 700;
line-height: 1.1;
word-break: break-word;
}
.home-status-row {
display: flex;
align-items: center;
gap: 0.6rem;
flex-wrap: wrap;
}
.home-status-text,
.home-section-meta {
color: var(--text-secondary, #9c9c9c);
font-size: 0.85rem;
}
.home-section-title {
font-size: 1.05rem;
line-height: 1.1;
margin-bottom: 0;
} }
.card-container { .card-container {
@@ -10,6 +64,117 @@
border-radius: 5px; border-radius: 5px;
} }
.home-soft-strip {
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.06);
}
.home-friends-strip {
display: flex;
gap: 0.75rem;
align-items: flex-start;
min-height: 116px;
}
.home-friend-card {
width: 96px;
flex: 0 0 auto;
text-decoration: none;
}
.home-friend-avatar-frame {
width: 96px;
height: 96px;
}
.home-friend-avatar {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 16px;
}
.home-empty-state {
min-height: 116px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
color: var(--text-secondary, #9c9c9c);
border: 1px dashed rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 1rem;
flex: 1 1 auto;
}
.home-empty-wide {
width: 100%;
}
.home-empty-inline {
margin: 0.25rem 0;
}
.home-place-card {
width: 170px;
flex: 0 0 auto;
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.07);
}
.home-place-thumb-wrap {
overflow: hidden;
border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.home-place-thumb {
width: 100%;
aspect-ratio: 1 / 1;
object-fit: cover;
display: block;
}
.home-place-year {
font-size: 11px;
padding: 3px 6px;
border-top-right-radius: 6px;
}
.home-place-copy {
min-height: 90px;
}
.home-place-name {
font-size: 0.95rem;
line-height: 1.15;
line-clamp: 2;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.home-place-playing {
margin: 0;
font-size: 0.84rem;
}
.home-place-playing span {
font-size: 0.8rem;
}
.home-place-votes {
margin-top: auto;
}
.home-places-strip {
display: flex;
gap: 0.75rem;
overflow-x: auto;
padding-bottom: 0.25rem;
}
.status-icon { .status-icon {
position: absolute; position: absolute;
bottom : -8px; bottom : -8px;
@@ -30,4 +195,54 @@
display: table; display: table;
margin: 0 auto; margin: 0 auto;
}
.friends-scroll {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
white-space: nowrap;
}
.friends-scroll a {
flex: 0 0 auto;
}
.friends-scroll::-webkit-scrollbar { display: none; }
.friends-scroll { -ms-overflow-style: none; scrollbar-width: none; }
@media (max-width: 768px) {
.home-page-shell {
padding: 1rem 0.75rem 1.5rem;
}
.home-hero-subtle {
align-items: flex-start;
}
.home-hero-avatar {
width: 64px;
height: 64px;
}
.home-kicker {
font-size: 0.63rem;
}
.home-greeting {
font-size: 1.4rem;
}
.home-status-text,
.home-section-meta {
font-size: 0.8rem;
}
.home-section-title {
font-size: 1rem;
}
.home-place-card {
width: 160px;
}
} }
+52 -16
View File
@@ -1,28 +1,46 @@
body { body {
background: radial-gradient(circle, var(--auth-dot) 10%, transparent 11%),radial-gradient(circle at bottom left, var(--auth-dot) 5%, transparent 6%),radial-gradient(circle at bottom right, var(--auth-dot) 5%, transparent 6%),radial-gradient(circle at top left, var(--auth-dot) 5%, transparent 6%),radial-gradient(circle at top right, var(--auth-dot) 5%, transparent 6%); position: relative;
background-size: 3em 3em; min-height: 100vh;
background-color: var(--auth-bg); background-color: #050505;
overflow-x: hidden;
isolation: isolate;
}
body::before {
content: "";
position: fixed;
inset: 0;
background-image: linear-gradient(rgba(0, 0, 0, 0.68), rgba(0, 0, 0, 0.68)), url("/static/img/LoginPage.png");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
filter: blur(12px) brightness(0.7) saturate(0.85);
transform: scale(1.08);
z-index: 0;
pointer-events: none;
} }
#main { #main {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
min-height: 100vh; min-height: 100vh;
z-index: 1;
} }
.login-container { .login-container {
min-width: 330px; width: 100%;
display: inline-block; padding: 24px 16px;
background-color: var(--auth-card-bg);
box-shadow: 0 0 15px var(--auth-card-shadow);
} }
.login-container h1 { .login-panel {
text-align: center; width: min(100%, 420px);
margin-bottom: 0; background-color: var(--auth-card-bg);
font-size: 3.5rem; box-shadow: 0 0 15px var(--auth-card-shadow);
font-weight: 700; border-radius: 2px;
position: relative;
z-index: 1;
} }
.login-container p { .login-container p {
text-align: center; text-align: center;
padding: 0; padding: 0;
@@ -52,10 +70,28 @@ body {
margin-bottom: 10px; margin-bottom: 10px;
} }
.login-container img { .login-logo {
min-width: 500px; display: block;
margin: 0 auto 8px;
max-width: 320px;
width: 100%;
height: auto;
}
.login-subtitle {
text-align: center;
margin-bottom: 12px;
color: rgba(230, 238, 248, 0.9);
font-size: small;
}
.login-container button {
color: #fff;
}
.login-container .alert {
margin-bottom: 0.5rem;
} }
.form-text { .form-text {
/* wrap the text*/ /* wrap the text*/
@@ -91,7 +127,7 @@ body {
font-size: small; font-size: small;
} }
@media (max-width: 480px) { @media (max-width: 480px) {
.login-logo { max-width: 160px; } .login-logo { max-width: 240px; }
} }
.navbar .navbar-brand .brand-logo { .navbar .navbar-brand .brand-logo {
display: block; display: block;
+36 -1
View File
@@ -6,7 +6,42 @@
} }
.settings-container { .settings-container {
min-width: 800px; width: min(800px, 100%);
max-width: 800px;
min-width: 0;
padding: 10px; padding: 10px;
border-radius: 5px; border-radius: 5px;
} }
@media (max-width: 767.98px) {
#main {
align-items: flex-start;
min-height: auto;
padding-top: 0.5rem;
}
.settings-container {
width: 100%;
padding: 0.75rem;
}
.settings-container .d-flex.align-items-center.mb-2 {
align-items: flex-start !important;
flex-wrap: wrap;
gap: 0.5rem;
}
.settings-container .d-flex.align-items-center.mb-2 > p {
flex: 1 1 100%;
margin-right: 0 !important;
}
.settings-container .d-flex.align-items-center.mb-2 > a,
.settings-container .d-flex.align-items-center.mb-2 > form {
margin-left: 0 !important;
}
.settings-container h1 {
font-size: clamp(1.5rem, 6vw, 2rem);
}
}
+64 -209
View File
@@ -1,7 +1,23 @@
body { body {
background: radial-gradient(circle, #333 10%, transparent 11%),radial-gradient(circle at bottom left, #333 5%, transparent 6%),radial-gradient(circle at bottom right, #333 5%, transparent 6%),radial-gradient(circle at top left, #333 5%, transparent 6%),radial-gradient(circle at top right, #333 5%, transparent 6%); position: relative;
background-size: 3em 3em; min-height: 100vh;
background-color: rgb(20,20,20); background-color: #050505;
overflow-x: hidden;
isolation: isolate;
}
body::before {
content: "";
position: fixed;
inset: 0;
background-image: linear-gradient(rgba(0, 0, 0, 0.68), rgba(0, 0, 0, 0.68)), url("/static/img/LoginPage.png");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
filter: blur(12px) brightness(0.7) saturate(0.85);
transform: scale(1.08);
z-index: 0;
pointer-events: none;
} }
#main { #main {
@@ -9,75 +25,36 @@ body {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
min-height: 100vh; min-height: 100vh;
z-index: 1;
} }
.logo-outside { .signup-container {
position: absolute;
top: 50%;
transform: translateY(-50%);
max-width: 500px;
width: 500px;
height: auto;
z-index: 0;
pointer-events: none;
margin-top: -100px;
margin-right: 120px;
}
.form-control {
display: block;
width: 100%; width: 100%;
padding: .375rem .75rem; padding: 24px 16px;
font-size: 1rem; }
font-weight: 400;
line-height: 1.5; .signup-panel {
color: var(--bs-body-color); width: min(100%, 420px);
background-color: var(--bs-body-bg); background-color: var(--auth-card-bg);
background-clip: padding-box; box-shadow: 0 0 15px var(--auth-card-shadow);
border: var(--bs-border-width) solid var(--bs-border-color); border-radius: 2px;
-webkit-appearance: none; position: relative;
-moz-appearance: none; z-index: 1;
appearance: none; }
border-radius: var(--bs-border-radius);
max-width: 300px; .signup-logo {
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out; display: block;
margin: 0 auto 8px;
max-width: 320px;
width: 100%;
height: auto;
} }
.signup-subtitle { .signup-subtitle {
text-align: center; text-align: center;
margin-bottom: 5px;
color: rgb(255 255 255 / 90%);
font-size: larger;
margin-left: 800px;
}
.element.style {
min-width: 300px;
}
.signup-wrapper img {
min-width: 700px;
}
.signup-container {
min-width: 330px;
display: inline-block;
background-color: rgba(25,25,25, 1);
box-shadow: 0 0 15px rgba(0, 0, 0, 0.7);
}
/* only align h1 tags */
.signup-container h1 {
text-align: center;
margin-bottom: 0;
font-size: 3.5rem;
font-weight: 700;
}
.signup-container p {
text-align: center;
padding: 0;
margin-bottom: 15px;
font-size: small; font-size: small;
margin-bottom: 12px;
color: rgba(230, 238, 248, 0.9);
} }
.signup-container button { .signup-container button {
@@ -99,128 +76,35 @@ body {
} }
#password { #password {
min-width: 300px;
margin-top: 10px; margin-top: 10px;
margin-bottom: 10px; margin-bottom: 10px;
} }
.messagealerts { #username,
background: rgba(255, 0, 0, 0.2); #invite-key {
color: rgb(226, 226, 226); min-width: 0;
word-wrap: break-word;
} }
.form-text { #checkboxTerms {
/* wrap the text*/
word-wrap: break-word;
}
a {
text-decoration: none !important;
color: rgb(32, 147, 255) !important;
font-weight: 500 !important;
transition: 0.2s;
}
a:hover {
text-decoration: none !important;
color: rgb(3, 133, 255) !important;
transition: 0.2s;
}
/* Logo outside the signup card so it can overlap/escape the container */
.signup-wrapper { position: relative; }
.logo-outside {
position: absolute;
right: -40px; /* push beyond the card -- adjust to taste */
top: 50%;
transform: translateY(-50%);
max-width: 320px;
width: auto;
height: auto;
z-index: 0; /* behind the card; use higher if you want it above */
pointer-events: none;
}
.signup-container { position: relative; z-index: 1; }
.signup-subtitle { text-align: center; margin-bottom: 12px; color: rgba(230,238,248,0.9); font-size: small; }
/* Two-column layout for signup: left card + right visual */
.signup-row {
display: flex;
align-items: stretch;
gap: 0;
background: transparent;
box-shadow: none;
}
.signup-card { flex: 0 0 330px; }
.signup-visual { flex: 1 1 0; }
.signup-visual img { display:block; width:100%; height:100%; object-fit:cover; }
@media (max-width: 992px) {
.signup-row { display: block; }
.signup-card { width: 100%; }
.signup-visual { display: none; }
}body {
background: radial-gradient(circle, #333 10%, transparent 11%),radial-gradient(circle at bottom left, #333 5%, transparent 6%),radial-gradient(circle at bottom right, #333 5%, transparent 6%),radial-gradient(circle at top left, #333 5%, transparent 6%),radial-gradient(circle at top right, #333 5%, transparent 6%);
background-size: 3em 3em;
background-color: rgb(20,20,20);
}
#main {
display: flex; display: flex;
justify-content: center; align-items: flex-start;
align-items: center; gap: 0.5rem;
min-height: 100vh; margin: 8px 0 12px;
} }
.signup-container { #checkboxTerms p {
min-width: 330px; margin: 0;
display: inline-block; text-align: left;
background-color: rgba(25,25,25, 1);
box-shadow: 0 0 15px rgba(0, 0, 0, 0.7);
}
/* only align h1 tags */
.signup-container h1 {
text-align: center;
margin-bottom: 0;
font-size: 3.5rem;
font-weight: 700;
}
.signup-container p {
text-align: center;
padding: 0;
margin-bottom: 15px;
font-size: small; font-size: small;
} }
.signup-container button { .form-check-input {
width: 100%; margin-top: 0.2rem;
font-weight: 600;
font-size: larger;
padding: 3px;
margin-bottom: 0 !important;
color: #fff;
}
.signup-container a {
font-size: small;
font-weight: 400 !important;
}
.col {
padding: 0 !important;
}
#password {
min-width: 300px;
margin-top: 10px;
margin-bottom: 10px;
} }
.messagealerts { .messagealerts {
background: rgba(255, 0, 0, 0.2); background: var(--alert-bg);
color: rgb(226, 226, 226); color: var(--alert-text);
word-wrap: break-word; word-wrap: break-word;
} }
@@ -229,54 +113,25 @@ a:hover {
word-wrap: break-word; word-wrap: break-word;
} }
a { .signup-container a {
text-decoration: none !important; text-decoration: none !important;
color: rgb(32, 147, 255) !important; color: rgb(32, 147, 255) !important;
font-weight: 500 !important; font-weight: 500 !important;
transition: 0.2s; transition: 0.2s;
} }
a:hover { .signup-container a:hover {
text-decoration: none !important; text-decoration: none !important;
color: rgb(3, 133, 255) !important; color: rgb(3, 133, 255) !important;
transition: 0.2s; transition: 0.2s;
} }
/* Logo outside the signup card so it can overlap/escape the container */ @media (max-width: 480px) {
.signup-wrapper { position: relative; } .signup-panel {
.logo-outside { width: 100%;
position: absolute; }
right: -40px; /* push beyond the card -- adjust to taste */
top: 50%;
transform: translateY(-50%);
max-width: 320px;
width: auto;
height: auto;
z-index: 0; /* behind the card; use higher if you want it above */
pointer-events: none;
}
.signup-container { position: relative; z-index: 1; }
.signup-subtitle { text-align: center; margin-bottom: 12px; color: rgba(230,238,248,0.9); font-size: small; }
@media (max-width: 992px) { .signup-logo {
.logo-outside { display: none; } max-width: 240px;
.signup-container { min-width: 320px; } }
}
/* Two-column layout for signup: left card + right visual */
.signup-row {
display: flex;
align-items: stretch;
gap: 0;
background: transparent;
box-shadow: none;
}
.signup-card { flex: 0 0 330px; }
.signup-visual { flex: 1 1 0; }
.signup-visual img { display:block; width:100%; height:100%; object-fit:cover; }
@media (max-width: 992px) {
.signup-row { display: block; }
.signup-card { width: 100%; }
.signup-visual { display: none; }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

+1 -1
View File
@@ -8,7 +8,7 @@ console.log(
"color: #F5A9B8; font-size: 18px; font-weight: 500;" "color: #F5A9B8; font-size: 18px; font-weight: 500;"
); );
console.log("Remember, trans rights are human rights!") console.log("Trans rights are human rights. Never forget.")
twemoji.parse( twemoji.parse(
document.body, document.body,
{ base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/' } { base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/' }
-113
View File
@@ -1,113 +0,0 @@
import re
import logging
import requests
import threading
from datetime import datetime
from config import Config
config = Config()
def sanitize_announcement(message: str) -> str:
if not isinstance(message, str):
return ""
message = message.strip()
if not message:
return ""
message = re.sub(r'<[^>]+>', '', message)
message = re.sub(r'(javascript|data|vbscript):', '', message, flags=re.IGNORECASE)
message = re.sub(r'\s*on\w+\s*=', '', message, flags=re.IGNORECASE)
message = message[:500]
return message
def validate_announcement(message: str) -> tuple[bool, str]:
if not isinstance(message, str):
return False, "Message must be a string"
if len(message.strip()) == 0:
return False, "Message cannot be empty"
if len(message) > 500:
return False, "Message is too long (max 500 characters)"
suspicious_patterns = [
r'<script',
r'javascript:',
r'onerror\s*=',
r'onclick\s*=',
r'onload\s*=',
r'data:text/html',
r'vbscript:',
r'<iframe',
r'<object',
r'<embed',
]
for pattern in suspicious_patterns:
if re.search(pattern, message, re.IGNORECASE):
return False, "Message contains potentially dangerous content"
return True, ""
def log_announcement_to_discord(
username: str,
user_id: int,
message: str,
action: str = "updated",
avatar_url: str = None
) -> bool:
if not config.DISCORD_ADMIN_LOGS_WEBHOOK:
logging.warning("Discord webhook not configured for announcement logging")
return False
if avatar_url is None:
avatar_url = f"https://www.nexium.fit/Thumbs/Head.ashx?x=48&y=48&userId={str(user_id)}"
embed = {
"type": "rich",
"title": f"Website Announcement {action.capitalize()}",
"description": message if message else "(empty message)",
"color": 0x2196F3,
"author": {
"name": username,
"icon_url": avatar_url
},
"footer": {
"text": "NEXIUM - Announcement Logs"
},
"timestamp": datetime.utcnow().isoformat()
}
def send_webhook():
try:
response = requests.post(
url=config.DISCORD_ADMIN_LOGS_WEBHOOK,
json={
"username": "NEXIUM - Announcement Logs",
"embeds": [embed],
"avatar_url": avatar_url
},
timeout=15
)
if response.status_code not in [200, 204]:
logging.warning(
f"Failed to log announcement to Discord: "
f"status={response.status_code}, response={response.text}"
)
return False
return True
except Exception as e:
logging.error(f"Exception while logging announcement to Discord: {str(e)}")
return False
thread = threading.Thread(target=send_webhook)
thread.daemon = True
thread.start()
return True
+5 -3531
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -13,5 +13,5 @@ $$ \ /$$//$$$$$$ |/$$$$$$ |$$$$$$/ /$$$$$$ |$$ \/$$/ $$ |
EOF EOF
echo "Version - $(git rev-parse --short HEAD) (prod)" echo "Version - $(git rev-parse --short HEAD) (prod)"
echo "Running NEXIUM OSS at $(date)" echo "Running NEXIUM at $(date)"
gunicorn -b 0.0.0.0:3003 --preload --workers=8 --threads=20 "app:create_app()" gunicorn -b 0.0.0.0:3003 --preload --workers=8 --threads=20 "app:create_app()"