Added user dashboard

Co-authored-by: Leander <henceiusegentoo@users.noreply.github.com>
This commit is contained in:
nsde 2023-09-21 23:59:10 +02:00
parent 0019ee9821
commit 569d40161b
24 changed files with 243 additions and 87 deletions

View file

@ -18,7 +18,8 @@
"web/static/css/footer.css": true, "web/static/css/footer.css": true,
"web/static/css/home.css": true, "web/static/css/home.css": true,
"web/static/css/input.css": true, "web/static/css/input.css": true,
"web/static/css/navbar.css": true "web/static/css/navbar.css": true,
"web/static/css/profile.css": true
}, },
"hide-files.files": [ "hide-files.files": [
"static/css/base.css", "static/css/base.css",
@ -30,6 +31,7 @@
"web/static/css/footer.css", "web/static/css/footer.css",
"web/static/css/home.css", "web/static/css/home.css",
"web/static/css/input.css", "web/static/css/input.css",
"web/static/css/navbar.css" "web/static/css/navbar.css",
"web/static/css/profile.css"
] ]
} }

0
web/__main__.py Normal file → Executable file
View file

39
web/account.py Normal file → Executable file
View file

@ -1,6 +1,7 @@
import os import os
import flask import flask
import requests import requests
import functools
from dotenv import load_dotenv from dotenv import load_dotenv
from requests_oauthlib import OAuth2Session from requests_oauthlib import OAuth2Session
@ -12,20 +13,23 @@ os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '0' # change this to '1' if you're r
API_URL = 'https://discordapp.com/api' API_URL = 'https://discordapp.com/api'
CLIENT_ID = str(os.environ['DISCORD_CLIENT_ID']) CLIENT_ID = str(os.environ['DISCORD_CLIENT_ID'])
CLIENT_SECRET = os.environ['DISCORD_CLIENT_SECRET'] CLIENT_SECRET = os.environ['DISCORD_CLIENT_SECRET']
# REDIRECT_URI = 'https://nova-oss.com/callback/discord' REDIRECT_URI = 'https://nova-oss.com/callback/discord'
REDIRECT_URI = 'http://localhost:2211/callback/discord' # REDIRECT_URI = 'http://localhost:2211/callback/discord'
SCOPES = ['identify'] SCOPES = ['identify']
@functools.lru_cache(maxsize=128)
def get_user(discord_id: int) -> dict: def get_user(discord_id: int) -> dict:
return requests.get( res = requests.get(
url=f'http://localhost:2333/users?discord_id={discord_id}', url=f'http://localhost:2333/users?discord_id={discord_id}',
timeout=3, timeout=3,
headers={ headers={
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': os.getenv('CORE_API_KEY') 'Authorization': os.environ['CORE_API_KEY']
} }
).json() ).json()
return res
def register(app): def register(app):
@app.route('/logout') @app.route('/logout')
def logout(): def logout():
@ -34,28 +38,32 @@ def register(app):
@app.route('/account') @app.route('/account')
def account_view(): def account_view():
discord_id = flask.session.get('discord_id') try:
discord_id = flask.session['discord_id']
if not discord_id: except KeyError:
return flask.redirect('/login') return flask.redirect('/login')
discord_account = OAuth2Session(CLIENT_ID, token=flask.session['discord_token']) discord_account = OAuth2Session(CLIENT_ID, token=flask.session['discord_token'])
profile = discord_account.get(f'{API_URL}/users/@me').json() discord_user = discord_account.get(f'{API_URL}/users/@me').json()
db_user = get_user(discord_id) db_user = get_user(discord_id)
user = { user = {
'name': profile['username'], 'username': discord_user.get('username'),
'avatar': profile['avatar'], 'display_name': discord_user.get('global_name'),
'role': db_user['role'], 'avatar': f'https://cdn.discordapp.com/avatars/{discord_user.get("id")}/{discord_user.get("avatar")}.png',
'credits': db_user['credits'], 'role': db_user.get('role'),
'api_key': db_user['api_key'] 'credits': db_user.get('credits'),
'api_key': db_user.get('api_key')
} }
return flask.render_template('account.html', user=user) return flask.render_template('account.html', user=user)
@app.route('/login') @app.route('/login')
def login_view(): def login_view():
if flask.session.get('discord_token'):
return flask.redirect('/account')
return flask.render_template('login.html') return flask.render_template('login.html')
@app.route('/auth/discord', methods=['POST', 'GET']) @app.route('/auth/discord', methods=['POST', 'GET'])
@ -78,9 +86,10 @@ def register(app):
client_secret=CLIENT_SECRET, client_secret=CLIENT_SECRET,
authorization_response=flask.request.url, authorization_response=flask.request.url,
) )
flask.session['discord_token'] = token
print('Token:', token) flask.session.permanent = True
flask.session['discord_token'] = token
flask.session['discord_id'] = discord_account.get(f'{API_URL}/users/@me').json()['id']
return flask.redirect('/account') return flask.redirect('/account')

2
web/app.py Normal file → Executable file
View file

@ -14,7 +14,7 @@ log.disabled = True
def create_app() -> flask.Flask: def create_app() -> flask.Flask:
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1) app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1)
app.secret_key = secrets.token_hex(16) app.secret_key = os.environ['FLASK_SECRET_KEY']
app.jinja_env.trim_blocks = True app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True app.jinja_env.lstrip_blocks = True

View file

@ -5,10 +5,10 @@ $secondary-dark: #0e0e0e
$border: #161d2bb0 $border: #161d2bb0
$soft: #fcf7f824 $soft: #fcf7f824
$primary: #b53d61 $primary: #5f44b9
$primary-light: #f45a88 $primary-light: #b88df0
$primary-glow: #f45a8861 $primary-glow: #683a9638
$primary-soft: #f79ab624 $primary-soft: #cc9af724
$accent: #28aaf0 $accent: #28aaf0
$error: #ef3628 $error: #ef3628

View file

@ -38,14 +38,14 @@ h3, h3 * {
main a { main a {
text-decoration: underline; text-decoration: underline;
text-decoration-color: #b53d61; text-decoration-color: #5f44b9;
text-underline-offset: 2px; text-underline-offset: 2px;
text-decoration-thickness: 5px; text-decoration-thickness: 5px;
transition: 0.2s; transition: 0.2s;
} }
main a:hover { main a:hover {
text-decoration-thickness: 0; text-decoration-thickness: 0;
background: rgba(244, 90, 136, 0.3803921569); background: rgba(104, 58, 150, 0.2196078431);
} }
main p { main p {
@ -59,7 +59,7 @@ img {
mark { mark {
color: #f3f3f3; color: #f3f3f3;
background: #b53d61; background: #5f44b9;
padding: 5px 7px; padding: 5px 7px;
border-radius: 5px; border-radius: 5px;
} }
@ -118,11 +118,18 @@ h6 {
} }
.special-gradient { .special-gradient {
background: linear-gradient(83deg, rgb(244, 90, 136) 7%, rgb(40, 170, 240) 100%); background: linear-gradient(45deg, #be89ec 7%, #2867f0 100%);
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
} }
.box {
background: rgba(204, 154, 247, 0.1411764706);
border: 2px solid rgba(204, 154, 247, 0.1411764706);
border-radius: 5px;
padding: 1rem 2rem;
}
@media (max-width: 900px) { @media (max-width: 900px) {
body { body {
margin: 0 2rem; margin: 0 2rem;

File diff suppressed because one or more lines are too long

View file

@ -105,10 +105,16 @@ h6
outline: none outline: none
.special-gradient .special-gradient
background: linear-gradient(83deg, rgba(244,90,136,1) 7%, rgba(40,170,240,1) 100%) background: linear-gradient(45deg, #be89ec 7%, #2867f0 100%)
-webkit-background-clip: text -webkit-background-clip: text
-webkit-text-fill-color: transparent -webkit-text-fill-color: transparent
.box
background: $primary-soft
border: 2px solid $primary-soft
border-radius: $edge
padding: 1rem 2rem
@media (max-width: 900px) @media (max-width: 900px)
body body
margin: 0 2rem margin: 0 2rem

View file

@ -1 +1 @@
{"version":3,"sources":["footer.sass","_vars.sass","footer.css"],"names":[],"mappings":"AAEA;EACI,iBAAA;EACA,iBAAA;EACA,gBCcS;ACfb;AFGI;EACI,iBCYK;ACbb;AFGI;EACI,aAAA;EACA,sBAAA;EAEA,mBAAA;AEFR;AFIQ;EACI,oBAAA;EACA,qBAAA;AEFZ;AFIY;EACI,eAAA;EACA,gBAAA;AEFhB;AFIY;EACI,UAAA;EACA,eAAA;EACA,gBAAA;EACA,cAAA;EACA,mBAAA;AEFhB;AFIY;EACI,uBAAA;EAAA,kBAAA;EACA,eAAA;EACA,YAAA;EACA,cAAA;EACA,qBAAA;EACA,mBAAA;AEFhB;AFIgB;EACI,YAAA;AEFpB;AFII;EACI,YAAA;AEFR;;AFIA;EAEQ;IACI,mBAAA;IACA,qCAAA;EEFV;EFIU;IACI,mBAAA;EEFd;AACF;AFGA;EAEQ;IACI,0BAAA;EEFV;AACF","file":"footer.css","sourcesContent":["@import '_vars'\n\nfooter\n padding-top: 4rem\n margin: 4rem auto\n max-width: $main-width\n\n &.wide\n max-width: $more-width\n\n div.rows\n display: grid\n grid-auto-flow: column\n // justify-content: space-between\n margin-bottom: 2rem\n\n div.row\n margin-right: 1.5rem\n margin-bottom: 0.5rem\n\n &:last-child\n margin-right: 0\n margin-bottom: 0\n\n h2\n opacity: 1\n font-size: 1rem\n font-weight: 500\n margin: 1rem 0\n line-height: normal\n\n a\n width: fit-content\n font-size: 14px\n opacity: 0.6\n display: block\n text-decoration: none\n margin-bottom: 1rem\n\n &:hover\n opacity: 0.8\n\n p\n opacity: 0.7\n\n@media (max-width: 1000px)\n footer\n div.rows\n grid-auto-flow: row\n grid-template-columns: repeat(2, 1fr)\n\n div.row\n margin-bottom: 1rem\n\n@media (max-width: 600px)\n footer\n div.rows\n grid-template-columns: 1fr\n","$text: #f3f3f3\n$background: #03060d\n$secondary: #252a38\n$secondary-dark: #0e0e0e\n$border: #161d2bb0\n$soft: #fcf7f824\n\n$primary: #b53d61\n$primary-light: #f45a88\n$primary-glow: #f45a8861\n$primary-soft: #f79ab624\n$accent: #28aaf0\n\n$error: #ef3628\n$warn: #f09928\n$success: #28ef6b\n\n$edge: 5px\n\n$main-width: 800px\n$more-width: 1000px\n","footer {\n padding-top: 4rem;\n margin: 4rem auto;\n max-width: 800px;\n}\nfooter.wide {\n max-width: 1000px;\n}\nfooter div.rows {\n display: grid;\n grid-auto-flow: column;\n margin-bottom: 2rem;\n}\nfooter div.rows div.row {\n margin-right: 1.5rem;\n margin-bottom: 0.5rem;\n}\nfooter div.rows div.row:last-child {\n margin-right: 0;\n margin-bottom: 0;\n}\nfooter div.rows div.row h2 {\n opacity: 1;\n font-size: 1rem;\n font-weight: 500;\n margin: 1rem 0;\n line-height: normal;\n}\nfooter div.rows div.row a {\n width: fit-content;\n font-size: 14px;\n opacity: 0.6;\n display: block;\n text-decoration: none;\n margin-bottom: 1rem;\n}\nfooter div.rows div.row a:hover {\n opacity: 0.8;\n}\nfooter p {\n opacity: 0.7;\n}\n\n@media (max-width: 1000px) {\n footer div.rows {\n grid-auto-flow: row;\n grid-template-columns: repeat(2, 1fr);\n }\n footer div.rows div.row {\n margin-bottom: 1rem;\n }\n}\n@media (max-width: 600px) {\n footer div.rows {\n grid-template-columns: 1fr;\n }\n}"]} {"version":3,"sources":["footer.sass","_vars.sass","footer.css"],"names":[],"mappings":"AAEA;EACI,iBAAA;EACA,iBAAA;EACA,gBCcS;ACfb;AFGI;EACI,iBCYK;ACbb;AFGI;EACI,aAAA;EACA,sBAAA;EAEA,mBAAA;AEFR;AFIQ;EACI,oBAAA;EACA,qBAAA;AEFZ;AFIY;EACI,eAAA;EACA,gBAAA;AEFhB;AFIY;EACI,UAAA;EACA,eAAA;EACA,gBAAA;EACA,cAAA;EACA,mBAAA;AEFhB;AFIY;EACI,uBAAA;EAAA,kBAAA;EACA,eAAA;EACA,YAAA;EACA,cAAA;EACA,qBAAA;EACA,mBAAA;AEFhB;AFIgB;EACI,YAAA;AEFpB;AFII;EACI,YAAA;AEFR;;AFIA;EAEQ;IACI,mBAAA;IACA,qCAAA;EEFV;EFIU;IACI,mBAAA;EEFd;AACF;AFGA;EAEQ;IACI,0BAAA;EEFV;AACF","file":"footer.css","sourcesContent":["@import '_vars'\n\nfooter\n padding-top: 4rem\n margin: 4rem auto\n max-width: $main-width\n\n &.wide\n max-width: $more-width\n\n div.rows\n display: grid\n grid-auto-flow: column\n // justify-content: space-between\n margin-bottom: 2rem\n\n div.row\n margin-right: 1.5rem\n margin-bottom: 0.5rem\n\n &:last-child\n margin-right: 0\n margin-bottom: 0\n\n h2\n opacity: 1\n font-size: 1rem\n font-weight: 500\n margin: 1rem 0\n line-height: normal\n\n a\n width: fit-content\n font-size: 14px\n opacity: 0.6\n display: block\n text-decoration: none\n margin-bottom: 1rem\n\n &:hover\n opacity: 0.8\n\n p\n opacity: 0.7\n\n@media (max-width: 1000px)\n footer\n div.rows\n grid-auto-flow: row\n grid-template-columns: repeat(2, 1fr)\n\n div.row\n margin-bottom: 1rem\n\n@media (max-width: 600px)\n footer\n div.rows\n grid-template-columns: 1fr\n","$text: #f3f3f3\n$background: #03060d\n$secondary: #252a38\n$secondary-dark: #0e0e0e\n$border: #161d2bb0\n$soft: #fcf7f824\n\n$primary: #5f44b9\n$primary-light: #b88df0\n$primary-glow: #683a9638\n$primary-soft: #cc9af724\n$accent: #28aaf0\n\n$error: #ef3628\n$warn: #f09928\n$success: #28ef6b\n\n$edge: 5px\n\n$main-width: 800px\n$more-width: 1000px\n","footer {\n padding-top: 4rem;\n margin: 4rem auto;\n max-width: 800px;\n}\nfooter.wide {\n max-width: 1000px;\n}\nfooter div.rows {\n display: grid;\n grid-auto-flow: column;\n margin-bottom: 2rem;\n}\nfooter div.rows div.row {\n margin-right: 1.5rem;\n margin-bottom: 0.5rem;\n}\nfooter div.rows div.row:last-child {\n margin-right: 0;\n margin-bottom: 0;\n}\nfooter div.rows div.row h2 {\n opacity: 1;\n font-size: 1rem;\n font-weight: 500;\n margin: 1rem 0;\n line-height: normal;\n}\nfooter div.rows div.row a {\n width: fit-content;\n font-size: 14px;\n opacity: 0.6;\n display: block;\n text-decoration: none;\n margin-bottom: 1rem;\n}\nfooter div.rows div.row a:hover {\n opacity: 0.8;\n}\nfooter p {\n opacity: 0.7;\n}\n\n@media (max-width: 1000px) {\n footer div.rows {\n grid-auto-flow: row;\n grid-template-columns: repeat(2, 1fr);\n }\n footer div.rows div.row {\n margin-bottom: 1rem;\n }\n}\n@media (max-width: 600px) {\n footer div.rows {\n grid-template-columns: 1fr;\n }\n}"]}

View file

@ -9,42 +9,38 @@ header h1 {
line-height: 4rem; line-height: 4rem;
} }
div.featured-box { div.featured__facts {
background: rgba(247, 154, 182, 0.1411764706);
border: 2px solid rgba(244, 90, 136, 0.3803921569);
border-radius: 5px;
padding: 2rem;
margin-block: 2rem; margin-block: 2rem;
display: flex; display: flex;
vertical-align: middle; vertical-align: middle;
transition: all 200ms; transition: all 200ms;
} }
div.featured-box:hover { div.featured__facts:hover {
scale: 1.025; scale: 1.025;
filter: brightness(1.1) saturate(1.1); filter: brightness(1.1) saturate(1.1);
box-shadow: 0 0 30px rgba(244, 90, 136, 0.3803921569); box-shadow: 0 0 30px rgba(104, 58, 150, 0.2196078431);
} }
div.featured-box img { div.featured__facts img, div.featured__facts svg {
width: 50px;
height: 50px; height: 50px;
-o-object-fit: cover; -o-object-fit: cover;
object-fit: cover; object-fit: cover;
} }
div.featured-box h2 { div.featured__facts h2 {
vertical-align: middle; vertical-align: middle;
margin-block: auto; margin-block: auto;
margin-left: 1rem; margin-left: 1rem;
font-size: 1.5rem; font-size: 1.5rem;
} }
div.featured-box h2 span { div.featured__facts h2 span {
color: #f45a88; color: #b88df0;
} }
div.user-quotes div.user-quote__field { div.user-quotes div.user-quote__field {
cursor: pointer;
margin-block: 2rem; margin-block: 2rem;
padding-inline: 2rem; background: rgba(204, 154, 247, 0.1411764706);
padding-block: 2rem; border: 2px solid rgba(104, 58, 150, 0.2196078431);
background: rgba(247, 154, 182, 0.1411764706);
border: 2px solid rgba(244, 90, 136, 0.3803921569);
border-radius: 5px; border-radius: 5px;
position: relative; position: relative;
transition: all 200ms; transition: all 200ms;
@ -54,13 +50,13 @@ div.user-quotes div.user-quote__field {
div.user-quotes div.user-quote__field:hover { div.user-quotes div.user-quote__field:hover {
scale: 1.025; scale: 1.025;
filter: brightness(1.1) saturate(1.1); filter: brightness(1.1) saturate(1.1);
box-shadow: 0 0 30px rgba(244, 90, 136, 0.3803921569); box-shadow: 0 0 30px rgba(104, 58, 150, 0.2196078431);
} }
div.user-quotes div.user-quote__field::before { div.user-quotes div.user-quote__field::before {
font-family: "Times New Roman", Times, serif; font-family: "Times New Roman", Times, serif;
content: "“"; content: "“";
font-size: 150px; font-size: 150px;
color: rgba(247, 154, 182, 0.1411764706); color: rgba(204, 154, 247, 0.1411764706);
position: absolute; position: absolute;
bottom: -1rem; bottom: -1rem;
left: 1rem; left: 1rem;
@ -71,7 +67,7 @@ div.user-quotes div.user-quote__field img {
width: 100px; width: 100px;
height: 100px; height: 100px;
border-radius: 50%; border-radius: 50%;
border: 3px solid rgba(244, 90, 136, 0.3803921569); border: 3px solid rgba(104, 58, 150, 0.2196078431);
position: absolute; position: absolute;
top: 1rem; top: 1rem;
right: 1rem; right: 1rem;
@ -81,7 +77,7 @@ div.user-quotes div.user-quote__field img {
div.user-quotes div.user-quote__field span.user-quote__username { div.user-quotes div.user-quote__field span.user-quote__username {
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
color: #b53d61; color: #b88df0;
} }
div.user-quotes div.user-quote__field p { div.user-quotes div.user-quote__field p {
margin-block: 0; margin-block: 0;

File diff suppressed because one or more lines are too long

View file

@ -9,11 +9,7 @@ header
h1 h1
line-height: 4rem line-height: 4rem
div.featured-box div.featured__facts
background: $primary-soft
border: 2px solid $primary-glow
border-radius: $edge
padding: 2rem
margin-block: 2rem margin-block: 2rem
display: flex display: flex
vertical-align: middle vertical-align: middle
@ -24,7 +20,8 @@ div.featured-box
filter: brightness(1.1) saturate(1.1) filter: brightness(1.1) saturate(1.1)
box-shadow: 0 0 30px $primary-glow box-shadow: 0 0 30px $primary-glow
img img, svg
width: 50px
height: 50px height: 50px
object-fit: cover object-fit: cover
@ -43,9 +40,8 @@ div.user-quotes
// grid-gap: 2rem // grid-gap: 2rem
div.user-quote__field div.user-quote__field
cursor: pointer
margin-block: 2rem margin-block: 2rem
padding-inline: 2rem
padding-block: 2rem
background: $primary-soft background: $primary-soft
border: 2px solid $primary-glow border: 2px solid $primary-glow
border-radius: $edge border-radius: $edge
@ -85,7 +81,7 @@ div.user-quotes
span.user-quote__username span.user-quote__username
font-size: 1rem font-size: 1rem
font-weight: 600 font-weight: 600
color: $primary color: $primary-light
p p
margin-block: 0 margin-block: 0

View file

@ -7,17 +7,17 @@ button {
font-weight: 600; font-weight: 600;
margin-bottom: 1rem; margin-bottom: 1rem;
color: #f3f3f3; color: #f3f3f3;
background: #b53d61; background: #5f44b9;
transition: box-shadow 0.2s; transition: box-shadow 0.2s;
} }
button:hover { button:hover {
filter: brightness(1.2); filter: brightness(1.2);
} }
button.special { button.special {
box-shadow: 0 0 20px rgba(244, 90, 136, 0.3803921569); box-shadow: 0 0 20px rgba(104, 58, 150, 0.2196078431);
} }
button.special:hover { button.special:hover {
box-shadow: 0 0 20px #b53d61; box-shadow: 0 0 20px #5f44b9;
} }
button.secondary { button.secondary {
color: #f3f3f3; color: #f3f3f3;
@ -41,7 +41,7 @@ input:not([type=checkbox]):not([type=radio]) {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
color: #f3f3f3; color: #f3f3f3;
background: #252a38; background: #252a38;
border: 1px solid #b53d61; border: 1px solid #5f44b9;
} }
input:not([type=checkbox]):not([type=radio]):focus { input:not([type=checkbox]):not([type=radio]):focus {
outline: none; outline: none;
@ -49,4 +49,30 @@ input:not([type=checkbox]):not([type=radio]):focus {
input[type=radio] { input[type=radio] {
filter: hue-rotate(50deg); filter: hue-rotate(50deg);
}
span.secret {
cursor: pointer;
font-size: 14px;
font-family: monospace;
padding: 2px 4px;
border-radius: 5px;
color: transparent;
background: rgba(252, 247, 248, 0.1411764706);
transition: color 200ms;
}
span.secret.revealed {
color: #f3f3f3;
background: rgba(252, 247, 248, 0.1411764706);
}
.copied::after {
content: "Copied!";
position: absolute;
padding: 2px 4px;
transform: translateX(10px);
border-radius: 5px;
background: rgba(252, 247, 248, 0.1411764706);
border: 1px solid #28ef6b;
color: #f3f3f3;
}/*# sourceMappingURL=input.css.map */ }/*# sourceMappingURL=input.css.map */

View file

@ -1 +1 @@
{"version":3,"sources":["input.sass","_vars.sass","input.css"],"names":[],"mappings":"AAEA;EACI,cAAA;EACA,eAAA;EACA,eAAA;EACA,kBCWG;EDVH,sBAAA;EACA,gBAAA;EACA,mBAAA;EAEA,cCXG;EDYH,mBCLM;EDON,2BAAA;AEHJ;AFKI;EACI,uBAAA;AEHR;AFKI;EACI,qDAAA;AEHR;AFKQ;EACI,4BAAA;AEHZ;AFKI;EACI,cC1BD;ED2BC,mBCzBI;ED0BJ,gDAAA;AEHR;AFKI;EACI,YAAA;EACA,qBAAA;EACA,sBAAA;EACA,mBAAA;AEHR;AFKI;EACI,6CChCD;AC6BP;;AFKA;EACI,WAAA;EACA,eAAA;EACA,kBCzBG;ED0BH,oBAAA;EAEA,cC7CG;ED8CH,mBC5CQ;ED6CR,yBAAA;AEHJ;AFKI;EACI,aAAA;AEHR;;AFKA;EACI,yBAAA;AEFJ","file":"input.css","sourcesContent":["@import '_vars'\n\nbutton\n line-height: 2\n cursor: pointer\n font-size: 16px\n border-radius: $edge\n padding: 0.7rem 1.5rem\n font-weight: 600\n margin-bottom: 1rem\n\n color: $text\n background: $primary\n\n transition: box-shadow 0.2s\n\n &:hover\n filter: brightness(1.2)\n\n &.special\n box-shadow: 0 0 20px $primary-glow\n\n &:hover\n box-shadow: 0 0 20px $primary\n\n &.secondary\n color: $text\n background: $secondary\n border: 1px solid $border\n\n svg, img\n height: 24px\n display: inline-block\n vertical-align: middle\n filter: invert(180)\n\n mark\n background: $soft\n\ninput:not([type=\"checkbox\"]):not([type=\"radio\"])\n width: 100%\n font-size: 18px\n border-radius: $edge\n padding: 0.5rem 1rem\n\n color: $text\n background: $secondary\n border: 1px solid $primary\n\n &:focus\n outline: none\n\ninput[type=\"radio\"]\n filter: hue-rotate(50deg)\n","$text: #f3f3f3\n$background: #03060d\n$secondary: #252a38\n$secondary-dark: #0e0e0e\n$border: #161d2bb0\n$soft: #fcf7f824\n\n$primary: #b53d61\n$primary-light: #f45a88\n$primary-glow: #f45a8861\n$primary-soft: #f79ab624\n$accent: #28aaf0\n\n$error: #ef3628\n$warn: #f09928\n$success: #28ef6b\n\n$edge: 5px\n\n$main-width: 800px\n$more-width: 1000px\n","button {\n line-height: 2;\n cursor: pointer;\n font-size: 16px;\n border-radius: 5px;\n padding: 0.7rem 1.5rem;\n font-weight: 600;\n margin-bottom: 1rem;\n color: #f3f3f3;\n background: #b53d61;\n transition: box-shadow 0.2s;\n}\nbutton:hover {\n filter: brightness(1.2);\n}\nbutton.special {\n box-shadow: 0 0 20px rgba(244, 90, 136, 0.3803921569);\n}\nbutton.special:hover {\n box-shadow: 0 0 20px #b53d61;\n}\nbutton.secondary {\n color: #f3f3f3;\n background: #252a38;\n border: 1px solid rgba(22, 29, 43, 0.6901960784);\n}\nbutton svg, button img {\n height: 24px;\n display: inline-block;\n vertical-align: middle;\n filter: invert(180);\n}\nbutton mark {\n background: rgba(252, 247, 248, 0.1411764706);\n}\n\ninput:not([type=checkbox]):not([type=radio]) {\n width: 100%;\n font-size: 18px;\n border-radius: 5px;\n padding: 0.5rem 1rem;\n color: #f3f3f3;\n background: #252a38;\n border: 1px solid #b53d61;\n}\ninput:not([type=checkbox]):not([type=radio]):focus {\n outline: none;\n}\n\ninput[type=radio] {\n filter: hue-rotate(50deg);\n}"]} {"version":3,"sources":["input.sass","_vars.sass","input.css"],"names":[],"mappings":"AAEA;EACI,cAAA;EACA,eAAA;EACA,eAAA;EACA,kBCWG;EDVH,sBAAA;EACA,gBAAA;EACA,mBAAA;EAEA,cCXG;EDYH,mBCLM;EDON,2BAAA;AEHJ;AFKI;EACI,uBAAA;AEHR;AFKI;EACI,qDAAA;AEHR;AFKQ;EACI,4BAAA;AEHZ;AFKI;EACI,cC1BD;ED2BC,mBCzBI;ED0BJ,gDAAA;AEHR;AFKI;EACI,YAAA;EACA,qBAAA;EACA,sBAAA;EACA,mBAAA;AEHR;AFKI;EACI,6CChCD;AC6BP;;AFKA;EACI,WAAA;EACA,eAAA;EACA,kBCzBG;ED0BH,oBAAA;EAEA,cC7CG;ED8CH,mBC5CQ;ED6CR,yBAAA;AEHJ;AFKI;EACI,aAAA;AEHR;;AFKA;EACI,yBAAA;AEFJ;;AFIA;EACI,eAAA;EACA,eAAA;EACA,sBAAA;EACA,gBAAA;EACA,kBC3CG;ED4CH,kBAAA;EACA,6CCzDG;ED2DH,uBAAA;AEFJ;AFII;EACI,cCnED;EDoEC,6CC/DD;AC6DP;;AFIA;EACI,kBAAA;EACA,kBAAA;EACA,gBAAA;EACA,2BAAA;EACA,kBC1DG;ED2DH,6CCvEG;EDwEH,yBAAA;EACA,cC9EG;AC6EP","file":"input.css","sourcesContent":["@import '_vars'\n\nbutton\n line-height: 2\n cursor: pointer\n font-size: 16px\n border-radius: $edge\n padding: 0.7rem 1.5rem\n font-weight: 600\n margin-bottom: 1rem\n\n color: $text\n background: $primary\n\n transition: box-shadow 0.2s\n\n &:hover\n filter: brightness(1.2)\n\n &.special\n box-shadow: 0 0 20px $primary-glow\n\n &:hover\n box-shadow: 0 0 20px $primary\n\n &.secondary\n color: $text\n background: $secondary\n border: 1px solid $border\n\n svg, img\n height: 24px\n display: inline-block\n vertical-align: middle\n filter: invert(180)\n\n mark\n background: $soft\n\ninput:not([type=\"checkbox\"]):not([type=\"radio\"])\n width: 100%\n font-size: 18px\n border-radius: $edge\n padding: 0.5rem 1rem\n\n color: $text\n background: $secondary\n border: 1px solid $primary\n\n &:focus\n outline: none\n\ninput[type=\"radio\"]\n filter: hue-rotate(50deg)\n\nspan.secret\n cursor: pointer\n font-size: 14px\n font-family: monospace\n padding: 2px 4px\n border-radius: $edge\n color: transparent\n background: $soft\n\n transition: color 200ms\n\n &.revealed\n color: $text\n background: $soft\n\n.copied::after\n content: 'Copied!'\n position: absolute\n padding: 2px 4px\n transform: translateX(10px)\n border-radius: $edge\n background: $soft\n border: 1px solid $success\n color: $text\n","$text: #f3f3f3\n$background: #03060d\n$secondary: #252a38\n$secondary-dark: #0e0e0e\n$border: #161d2bb0\n$soft: #fcf7f824\n\n$primary: #5f44b9\n$primary-light: #b88df0\n$primary-glow: #683a9638\n$primary-soft: #cc9af724\n$accent: #28aaf0\n\n$error: #ef3628\n$warn: #f09928\n$success: #28ef6b\n\n$edge: 5px\n\n$main-width: 800px\n$more-width: 1000px\n","button {\n line-height: 2;\n cursor: pointer;\n font-size: 16px;\n border-radius: 5px;\n padding: 0.7rem 1.5rem;\n font-weight: 600;\n margin-bottom: 1rem;\n color: #f3f3f3;\n background: #5f44b9;\n transition: box-shadow 0.2s;\n}\nbutton:hover {\n filter: brightness(1.2);\n}\nbutton.special {\n box-shadow: 0 0 20px rgba(104, 58, 150, 0.2196078431);\n}\nbutton.special:hover {\n box-shadow: 0 0 20px #5f44b9;\n}\nbutton.secondary {\n color: #f3f3f3;\n background: #252a38;\n border: 1px solid rgba(22, 29, 43, 0.6901960784);\n}\nbutton svg, button img {\n height: 24px;\n display: inline-block;\n vertical-align: middle;\n filter: invert(180);\n}\nbutton mark {\n background: rgba(252, 247, 248, 0.1411764706);\n}\n\ninput:not([type=checkbox]):not([type=radio]) {\n width: 100%;\n font-size: 18px;\n border-radius: 5px;\n padding: 0.5rem 1rem;\n color: #f3f3f3;\n background: #252a38;\n border: 1px solid #5f44b9;\n}\ninput:not([type=checkbox]):not([type=radio]):focus {\n outline: none;\n}\n\ninput[type=radio] {\n filter: hue-rotate(50deg);\n}\n\nspan.secret {\n cursor: pointer;\n font-size: 14px;\n font-family: monospace;\n padding: 2px 4px;\n border-radius: 5px;\n color: transparent;\n background: rgba(252, 247, 248, 0.1411764706);\n transition: color 200ms;\n}\nspan.secret.revealed {\n color: #f3f3f3;\n background: rgba(252, 247, 248, 0.1411764706);\n}\n\n.copied::after {\n content: \"Copied!\";\n position: absolute;\n padding: 2px 4px;\n transform: translateX(10px);\n border-radius: 5px;\n background: rgba(252, 247, 248, 0.1411764706);\n border: 1px solid #28ef6b;\n color: #f3f3f3;\n}"]}

View file

@ -52,3 +52,28 @@ input:not([type="checkbox"]):not([type="radio"])
input[type="radio"] input[type="radio"]
filter: hue-rotate(50deg) filter: hue-rotate(50deg)
span.secret
cursor: pointer
font-size: 14px
font-family: monospace
padding: 2px 4px
border-radius: $edge
color: transparent
background: $soft
transition: color 200ms
&.revealed
color: $text
background: $soft
.copied::after
content: 'Copied!'
position: absolute
padding: 2px 4px
transform: translateX(10px)
border-radius: $edge
background: $soft
border: 1px solid $success
color: $text

View file

@ -5,7 +5,7 @@ nav {
} }
nav > .logo { nav > .logo {
display: inline-block; display: inline-block;
stroke: #f45a88; stroke: #b88df0;
min-height: 40px; min-height: 40px;
min-width: 40px; min-width: 40px;
height: 40px; height: 40px;
@ -41,7 +41,7 @@ nav #menu {
transition: color 0.1s; transition: color 0.1s;
} }
.link-menu > a:hover { .link-menu > a:hover {
color: #f45a88; color: #b88df0;
} }
@media (max-width: 800px) { @media (max-width: 800px) {
@ -71,7 +71,7 @@ nav #menu {
line-height: 3rem; line-height: 3rem;
} }
nav .logo { nav .logo {
stroke: #f45a88; stroke: #b88df0;
} }
nav #menu { nav #menu {
display: block; display: block;

View file

@ -1 +1 @@
{"version":3,"sources":["navbar.sass","navbar.css","_vars.sass"],"names":[],"mappings":"AAEA;EACI,aAAA;EACA,mBAAA;EACA,kBAAA;ACDJ;ADGI;EACI,qBAAA;EACA,eEDQ;EFER,gBAAA;EACA,eAAA;EACA,YAAA;EACA,WAAA;EAEA,iBAAA;EACA,uBAAA;ACFR;ADII;EACI,gBAAA;EACA,eAAA;EACA,yBAAA;KAAA,sBAAA;UAAA,iBAAA;ACFR;ADIQ;EACI,eAAA;EACA,kBAAA;EACA,SAAA;ACFZ;ADKI;EACI,YAAA;ACHR;ADMI;EACI,aAAA;EACA,eAAA;ACJR;;ADOI;EACI,eAAA;EACA,gBAAA;EACA,iBAAA;EACA,qBAAA;EACA,sBAAA;ACJR;ADMQ;EACI,cEtCI;ADkChB;;ADMA;EACI;IACI,sCAAA;IACA,UAAA;IAEA,YAAA;IACA,YAAA;IACA,kBAAA;IACA,SAAA;IACA,WAAA;IACA,mBExDI;IFyDJ,eAAA;IACA,kBE3CD;IF4CC,kCAAA;YAAA,0BAAA;IACA,gDAAA;IAEA,wCAAA;ECLN;EDOM;IACI,UAAA;IACA,kBAAA;ECLV;EDOM;IACI,eAAA;IACA,cAAA;IACA,iBAAA;ECLV;EDQM;IACI,eErEI;ED+Dd;EDQM;IACI,cAAA;IACA,gBAAA;IACA,eAAA;IACA,YAAA;IACA,WAAA;IACA,eAAA;IACA,gBAAA;ECNV;EDQU;IACI,wBAAA;ECNd;AACF;ADOA;EAEQ;IACI,aAAA;ECNV;AACF","file":"navbar.css","sourcesContent":["@import '_vars'\n\nnav\n display: flex\n align-items: center\n position: relative\n\n >.logo\n display: inline-block\n stroke: $primary-light\n min-height: 40px\n min-width: 40px\n height: 40px\n width: 40px\n\n margin-right: 8px\n transition: stroke 0.2s\n\n >h1\n font-weight: 500\n font-size: 20px\n user-select: none\n\n mark\n font-size: 1rem\n position: relative\n top: -3px\n\n\n >span\n flex-grow: 1\n\n\n #menu\n display: none\n cursor: pointer\n\n.link-menu\n >a\n font-size: 14px\n font-weight: 600\n margin-left: 2rem\n text-decoration: none\n transition: color 0.1s\n\n &:hover\n color: $primary-light\n\n@media (max-width: 800px)\n .link-menu\n transform: scale(0.8) translateX(1rem)\n opacity: 0\n\n z-index: 100\n width: 200px\n position: absolute\n top: 5rem\n right: 2rem\n background: $secondary\n padding: 1rem 0\n border-radius: $edge\n backdrop-filter: blur(4px)\n border: 1px solid $border\n\n transition: transform 0.2s, opacity 0.2s\n\n &.open\n opacity: 1\n transform: inherit\n\n a\n font-size: 20px\n display: block\n line-height: 3rem\n\n nav\n .logo\n stroke: $primary-light\n\n #menu\n display: block\n min-height: 32px\n min-width: 32px\n height: 32px\n width: 32px\n cursor: pointer\n transition: 0.5s\n\n &.active\n transform: rotate(90deg)\n\n@media (max-width: 400px)\n nav\n h1\n display: none","nav {\n display: flex;\n align-items: center;\n position: relative;\n}\nnav > .logo {\n display: inline-block;\n stroke: #f45a88;\n min-height: 40px;\n min-width: 40px;\n height: 40px;\n width: 40px;\n margin-right: 8px;\n transition: stroke 0.2s;\n}\nnav > h1 {\n font-weight: 500;\n font-size: 20px;\n user-select: none;\n}\nnav > h1 mark {\n font-size: 1rem;\n position: relative;\n top: -3px;\n}\nnav > span {\n flex-grow: 1;\n}\nnav #menu {\n display: none;\n cursor: pointer;\n}\n\n.link-menu > a {\n font-size: 14px;\n font-weight: 600;\n margin-left: 2rem;\n text-decoration: none;\n transition: color 0.1s;\n}\n.link-menu > a:hover {\n color: #f45a88;\n}\n\n@media (max-width: 800px) {\n .link-menu {\n transform: scale(0.8) translateX(1rem);\n opacity: 0;\n z-index: 100;\n width: 200px;\n position: absolute;\n top: 5rem;\n right: 2rem;\n background: #252a38;\n padding: 1rem 0;\n border-radius: 5px;\n backdrop-filter: blur(4px);\n border: 1px solid rgba(22, 29, 43, 0.6901960784);\n transition: transform 0.2s, opacity 0.2s;\n }\n .link-menu.open {\n opacity: 1;\n transform: inherit;\n }\n .link-menu a {\n font-size: 20px;\n display: block;\n line-height: 3rem;\n }\n nav .logo {\n stroke: #f45a88;\n }\n nav #menu {\n display: block;\n min-height: 32px;\n min-width: 32px;\n height: 32px;\n width: 32px;\n cursor: pointer;\n transition: 0.5s;\n }\n nav #menu.active {\n transform: rotate(90deg);\n }\n}\n@media (max-width: 400px) {\n nav h1 {\n display: none;\n }\n}","$text: #f3f3f3\n$background: #03060d\n$secondary: #252a38\n$secondary-dark: #0e0e0e\n$border: #161d2bb0\n$soft: #fcf7f824\n\n$primary: #b53d61\n$primary-light: #f45a88\n$primary-glow: #f45a8861\n$primary-soft: #f79ab624\n$accent: #28aaf0\n\n$error: #ef3628\n$warn: #f09928\n$success: #28ef6b\n\n$edge: 5px\n\n$main-width: 800px\n$more-width: 1000px\n"]} {"version":3,"sources":["navbar.sass","navbar.css","_vars.sass"],"names":[],"mappings":"AAEA;EACI,aAAA;EACA,mBAAA;EACA,kBAAA;ACDJ;ADGI;EACI,qBAAA;EACA,eEDQ;EFER,gBAAA;EACA,eAAA;EACA,YAAA;EACA,WAAA;EAEA,iBAAA;EACA,uBAAA;ACFR;ADII;EACI,gBAAA;EACA,eAAA;EACA,yBAAA;KAAA,sBAAA;UAAA,iBAAA;ACFR;ADIQ;EACI,eAAA;EACA,kBAAA;EACA,SAAA;ACFZ;ADKI;EACI,YAAA;ACHR;ADMI;EACI,aAAA;EACA,eAAA;ACJR;;ADOI;EACI,eAAA;EACA,gBAAA;EACA,iBAAA;EACA,qBAAA;EACA,sBAAA;ACJR;ADMQ;EACI,cEtCI;ADkChB;;ADMA;EACI;IACI,sCAAA;IACA,UAAA;IAEA,YAAA;IACA,YAAA;IACA,kBAAA;IACA,SAAA;IACA,WAAA;IACA,mBExDI;IFyDJ,eAAA;IACA,kBE3CD;IF4CC,kCAAA;YAAA,0BAAA;IACA,gDAAA;IAEA,wCAAA;ECLN;EDOM;IACI,UAAA;IACA,kBAAA;ECLV;EDOM;IACI,eAAA;IACA,cAAA;IACA,iBAAA;ECLV;EDQM;IACI,eErEI;ED+Dd;EDQM;IACI,cAAA;IACA,gBAAA;IACA,eAAA;IACA,YAAA;IACA,WAAA;IACA,eAAA;IACA,gBAAA;ECNV;EDQU;IACI,wBAAA;ECNd;AACF;ADOA;EAEQ;IACI,aAAA;ECNV;AACF","file":"navbar.css","sourcesContent":["@import '_vars'\n\nnav\n display: flex\n align-items: center\n position: relative\n\n >.logo\n display: inline-block\n stroke: $primary-light\n min-height: 40px\n min-width: 40px\n height: 40px\n width: 40px\n\n margin-right: 8px\n transition: stroke 0.2s\n\n >h1\n font-weight: 500\n font-size: 20px\n user-select: none\n\n mark\n font-size: 1rem\n position: relative\n top: -3px\n\n\n >span\n flex-grow: 1\n\n\n #menu\n display: none\n cursor: pointer\n\n.link-menu\n >a\n font-size: 14px\n font-weight: 600\n margin-left: 2rem\n text-decoration: none\n transition: color 0.1s\n\n &:hover\n color: $primary-light\n\n@media (max-width: 800px)\n .link-menu\n transform: scale(0.8) translateX(1rem)\n opacity: 0\n\n z-index: 100\n width: 200px\n position: absolute\n top: 5rem\n right: 2rem\n background: $secondary\n padding: 1rem 0\n border-radius: $edge\n backdrop-filter: blur(4px)\n border: 1px solid $border\n\n transition: transform 0.2s, opacity 0.2s\n\n &.open\n opacity: 1\n transform: inherit\n\n a\n font-size: 20px\n display: block\n line-height: 3rem\n\n nav\n .logo\n stroke: $primary-light\n\n #menu\n display: block\n min-height: 32px\n min-width: 32px\n height: 32px\n width: 32px\n cursor: pointer\n transition: 0.5s\n\n &.active\n transform: rotate(90deg)\n\n@media (max-width: 400px)\n nav\n h1\n display: none","nav {\n display: flex;\n align-items: center;\n position: relative;\n}\nnav > .logo {\n display: inline-block;\n stroke: #b88df0;\n min-height: 40px;\n min-width: 40px;\n height: 40px;\n width: 40px;\n margin-right: 8px;\n transition: stroke 0.2s;\n}\nnav > h1 {\n font-weight: 500;\n font-size: 20px;\n user-select: none;\n}\nnav > h1 mark {\n font-size: 1rem;\n position: relative;\n top: -3px;\n}\nnav > span {\n flex-grow: 1;\n}\nnav #menu {\n display: none;\n cursor: pointer;\n}\n\n.link-menu > a {\n font-size: 14px;\n font-weight: 600;\n margin-left: 2rem;\n text-decoration: none;\n transition: color 0.1s;\n}\n.link-menu > a:hover {\n color: #b88df0;\n}\n\n@media (max-width: 800px) {\n .link-menu {\n transform: scale(0.8) translateX(1rem);\n opacity: 0;\n z-index: 100;\n width: 200px;\n position: absolute;\n top: 5rem;\n right: 2rem;\n background: #252a38;\n padding: 1rem 0;\n border-radius: 5px;\n backdrop-filter: blur(4px);\n border: 1px solid rgba(22, 29, 43, 0.6901960784);\n transition: transform 0.2s, opacity 0.2s;\n }\n .link-menu.open {\n opacity: 1;\n transform: inherit;\n }\n .link-menu a {\n font-size: 20px;\n display: block;\n line-height: 3rem;\n }\n nav .logo {\n stroke: #b88df0;\n }\n nav #menu {\n display: block;\n min-height: 32px;\n min-width: 32px;\n height: 32px;\n width: 32px;\n cursor: pointer;\n transition: 0.5s;\n }\n nav #menu.active {\n transform: rotate(90deg);\n }\n}\n@media (max-width: 400px) {\n nav h1 {\n display: none;\n }\n}","$text: #f3f3f3\n$background: #03060d\n$secondary: #252a38\n$secondary-dark: #0e0e0e\n$border: #161d2bb0\n$soft: #fcf7f824\n\n$primary: #5f44b9\n$primary-light: #b88df0\n$primary-glow: #683a9638\n$primary-soft: #cc9af724\n$accent: #28aaf0\n\n$error: #ef3628\n$warn: #f09928\n$success: #28ef6b\n\n$edge: 5px\n\n$main-width: 800px\n$more-width: 1000px\n"]}

View file

@ -0,0 +1,20 @@
div.profile-base div.profile-header {
display: flex;
}
div.profile-base div.profile-header img {
height: 100px;
-o-object-fit: cover;
object-fit: cover;
-o-object-position: center;
object-position: center;
border-radius: 50%;
}
div.profile-base div.profile-header h2 {
margin-block: auto;
margin-left: 1rem;
font-size: 2rem;
}
div.profile-base div.profile-info {
line-height: 1.5rem;
padding: 2rem;
}/*# sourceMappingURL=profile.css.map */

View file

@ -0,0 +1 @@
{"version":3,"sources":["profile.sass","profile.css"],"names":[],"mappings":"AAGI;EACI,aAAA;ACFR;ADIQ;EACI,aAAA;EACA,oBAAA;KAAA,iBAAA;EACA,0BAAA;KAAA,uBAAA;EACA,kBAAA;ACFZ;ADIQ;EACI,kBAAA;EACA,iBAAA;EACA,eAAA;ACFZ;ADII;EACI,mBAAA;EACA,aAAA;ACFR","file":"profile.css","sourcesContent":["@import '_vars'\n\ndiv.profile-base\n div.profile-header\n display: flex\n\n img\n height: 100px\n object-fit: cover\n object-position: center\n border-radius: 50%\n\n h2\n margin-block: auto\n margin-left: 1rem\n font-size: 2rem\n\n div.profile-info\n line-height: 1.5rem\n padding: 2rem\n","div.profile-base div.profile-header {\n display: flex;\n}\ndiv.profile-base div.profile-header img {\n height: 100px;\n object-fit: cover;\n object-position: center;\n border-radius: 50%;\n}\ndiv.profile-base div.profile-header h2 {\n margin-block: auto;\n margin-left: 1rem;\n font-size: 2rem;\n}\ndiv.profile-base div.profile-info {\n line-height: 1.5rem;\n padding: 2rem;\n}"]}

View file

@ -0,0 +1,20 @@
@import '_vars'
div.profile-base
div.profile-header
display: flex
img
height: 100px
object-fit: cover
object-position: center
border-radius: 50%
h2
margin-block: auto
margin-left: 1rem
font-size: 2rem
div.profile-info
line-height: 1.5rem
padding: 2rem

View file

@ -1,8 +1,43 @@
{% include 'common/begin.html' %} {% include 'common/begin.html' %}
<main> <main>
<h1>Welcome back, {{ user.username }}</h1> <h1>Welcome back, <a>{{ user.display_name or 'Guest' }}</a>!</h1>
<div class="profile-base">
<div class="profile-header">
<img src="{{ user.avatar }}" alt="Discord profile avatar">
<h2>{{ user.username or 'Guest' }}</h2>
</div>
<div class="profile-info">
<b>Your reward rank: </b> {{ user.role or 'default' }}<br>
<b>API credits: </b> {{ user.credits or 0 }}<br>
<b>Secret API key (click to reveal): </b>
<br>
<span class="secret">{{ user.api_key or '-' }}</span>
<br>(Click again to copy)
<br>
<script>
let clicks = 0;
// reveal secret API key on click by adding "revealed" class
document.querySelector('.secret').addEventListener('click', function() {
this.classList.add('revealed');
clicks++;
});
// copy on click
document.querySelector('.secret').addEventListener('click', function() {
if (clicks > 1) {
navigator.clipboard.writeText(this.textContent);
this.classList.add('copied')
}
});
</script>
<br>
<a href="/logout">Logout</a>
</div>
</div>
</main> </main>
{% include 'common/end.html' %} {% include 'common/end.html' %}

View file

@ -6,7 +6,7 @@
{% include 'common/seo.html' %} {% include 'common/seo.html' %}
{% for css in 'navbar base footer input fonts'.split() %} {% for css in 'navbar base footer input fonts profile'.split() %}
<link rel="stylesheet" href="/static/css/{{ css }}.css"> <link rel="stylesheet" href="/static/css/{{ css }}.css">
{% endfor %} {% endfor %}

View file

@ -3,18 +3,18 @@
<header> <header>
<h1>Free Generative AI for <span class="special-gradient">Everyone</span></h1> <h1>Free Generative AI for <span class="special-gradient">Everyone</span></h1>
<h3>Make AI Open Again</h3> <h3>Open source ・ no CC required</h3>
<a href="https://discord.nova-oss.com" target="_blank"> <a href="https://discord.nova-oss.com" target="_blank">
<button class="special"> <button class="special">
<i class="bi bi-discord"></i> <i class="bi bi-discord"></i>
Join 2,300+ members Join 3,400+ members
</button> </button>
</a> </a>
<!-- <a href="/login"> --> <a href="/login">
<button class="secondary" style="pointer-events: none; cursor: not-allowed; opacity: 0.5;"> <button class="secondary">
Log in <mark>coming soon</mark> Log in <mark>beta</mark>
</button> </button>
<!-- </a> --> </a>
<br> <br>
</header> </header>
<main> <main>
@ -28,15 +28,22 @@
</p> </p>
<h2>About Nova</h2> <h2>About Nova</h2>
<div class="featured-box"> <div class="featured__facts box">
<img src="https://icons.getbootstrap.com/assets/icons/cash-coin.svg" style="filter: invert(1);"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-cash-coin" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M11 15a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm5-4a5 5 0 1 1-10 0 5 5 0 0 1 10 0z"/>
<path d="M9.438 11.944c.047.596.518 1.06 1.363 1.116v.44h.375v-.443c.875-.061 1.386-.529 1.386-1.207 0-.618-.39-.936-1.09-1.1l-.296-.07v-1.2c.376.043.614.248.671.532h.658c-.047-.575-.54-1.024-1.329-1.073V8.5h-.375v.45c-.747.073-1.255.522-1.255 1.158 0 .562.378.92 1.007 1.066l.248.061v1.272c-.384-.058-.639-.27-.696-.563h-.668zm1.36-1.354c-.369-.085-.569-.26-.569-.522 0-.294.216-.514.572-.578v1.1h-.003zm.432.746c.449.104.655.272.655.569 0 .339-.257.571-.709.614v-1.195l.054.012z"/>
<path d="M1 0a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h4.083c.058-.344.145-.678.258-1H3a2 2 0 0 0-2-2V3a2 2 0 0 0 2-2h10a2 2 0 0 0 2 2v3.528c.38.34.717.728 1 1.154V1a1 1 0 0 0-1-1H1z"/>
<path d="M9.998 5.083 10 5a2 2 0 1 0-3.132 1.65 5.982 5.982 0 0 1 3.13-1.567z"/>
</svg>
<h2>Free <span>forever</span> - no credit card required</h2> <h2>Free <span>forever</span> - no credit card required</h2>
</div> </div>
<div class="featured-box" onclick="window.open('https://github.com/orgs/NovaOSS/repositories', '_blank')"> <div class="featured__facts box" onclick="window.open('https://github.com/orgs/NovaOSS/repositories', '_blank')">
<img src="https://icons.getbootstrap.com/assets/icons/github.svg" style="filter: invert(1);"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-github" viewBox="0 0 16 16">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
</svg>
<h2>Open source on <span>GitHub</span></h2> <h2>Open source on <span>GitHub</span></h2>
</div> </div>
<div class="featured-box" onclick="window.open('https://fmhy.pages.dev/ai', '_blank')"> <div class="featured__facts box" onclick="window.open('https://fmhy.pages.dev/ai', '_blank')">
<img src="https://fmhy.pages.dev/static/fmhy.ico"> <img src="https://fmhy.pages.dev/static/fmhy.ico">
<h2>Featured in <span>Free Media Heck Yeah</span></h2> <h2>Featured in <span>Free Media Heck Yeah</span></h2>
</div> </div>
@ -44,43 +51,49 @@
<h2>What users say</h2> <h2>What users say</h2>
<div class="user-quotes"> <div class="user-quotes">
<div class="user-quote__field" onclick="window.open('https://discord.com/channels/1120037287300976640/1120037729644855427/1144279505981153302', '_blank')"> <div class="user-quote__field box" onclick="window.open('https://discord.com/channels/1120037287300976640/1120037729644855427/1144279505981153302', '_blank')">
<img src="https://i.ibb.co/dfWkyB3/86b4f6da0e44bf8545be937d66017b71.png"> <img src="https://i.ibb.co/dfWkyB3/86b4f6da0e44bf8545be937d66017b71.png">
<span class="user-quote__username">Krit</span> <span class="user-quote__username">Krit</span>
<p>We can all agree that NovaAI is the best.</p> <p>We can all agree that NovaAI is the best.</p>
</div> </div>
<div class="user-quote__field" onclick="window.open('https://discord.com/channels/1120037287300976640/1120037729644855427/1148559579890864178', '_blank')"> <div class="user-quote__field box" onclick="window.open('https://discord.com/channels/1120037287300976640/1120037729644855427/1151770993837219871', '_blank')">
<img src="https://i.ibb.co/XbnHFQm/a2000af4431e49565a8f8d97455418b9.png">
<span class="user-quote__username">forrany</span>
<p>NovaAI responses are so fast. Probably the fastest API I've ever used, other [Discord servers] like FoxGPT and PurGPT are not as fast as NovaAI.</p>
</div>
<div class="user-quote__field box" onclick="window.open('https://discord.com/channels/1120037287300976640/1120037729644855427/1148559579890864178', '_blank')">
<img src="https://i.ibb.co/p4TB2nm/f44610c370eec2b28ccc926e61fb86fe.png"> <img src="https://i.ibb.co/p4TB2nm/f44610c370eec2b28ccc926e61fb86fe.png">
<span class="user-quote__username">Tech With Anirudh</span> <span class="user-quote__username">Tech With Anirudh</span>
<p>NovaOSS is really good, I mean really. The code's good. The website's good. The API's good.</p> <p>NovaOSS is really good, I mean really. The code's good. The website's good. The API's good.</p>
</div> </div>
<div class="user-quote__field" onclick="window.open('https://discord.com/channels/1120037287300976640/1120037729644855427/1145791262063075378', '_blank')"> <div class="user-quote__field box" onclick="window.open('https://discord.com/channels/1120037287300976640/1120037729644855427/1145791262063075378', '_blank')">
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Femoji.gg%2Fassets%2Femoji%2F1129-discord.png&f=1&nofb=1&ipt=c507c1ea551b33c06e1f2bc2baa5f1d3eb29bca0d721db852876a86de198a944&ipo=images"> <img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Femoji.gg%2Fassets%2Femoji%2F1129-discord.png&f=1&nofb=1&ipt=c507c1ea551b33c06e1f2bc2baa5f1d3eb29bca0d721db852876a86de198a944&ipo=images">
<span class="user-quote__username">David</span> <span class="user-quote__username">David</span>
<p>Nova is good. I love it because [it's] free and easy.</p> <p>Nova is good. I love it because [it's] free and easy.</p>
</div> </div>
<div class="user-quote__field" onclick="window.open('https://discord.com/channels/1117511140440821852/1149316497265660045/1149316515724787752', '_blank')"> <div class="user-quote__field box" onclick="window.open('https://discord.com/channels/1117511140440821852/1149316497265660045/1149316515724787752', '_blank')">
<img src="https://i.ibb.co/XzDPQJs/image.png"> <img src="https://i.ibb.co/XzDPQJs/image.png">
<span class="user-quote__username">Tolgchu (co-owner of PurGPT)</span> <span class="user-quote__username">Tolgchu (co-owner of PurGPT)</span>
<p>Would you like a fully open-source API including GPT-4-32k? It's even fully free and has 0% reverse engineering!</p> <p>Would you like a fully open-source API including GPT-4-32k? It's even fully free and has 0% reverse engineering!</p>
</div> </div>
<div class="user-quote__field" onclick="window.open('https://discord.com/channels/1120753218071310346/1149291492335177818/1149293863597514772', '_blank')"> <div class="user-quote__field box" onclick="window.open('https://discord.com/channels/1120753218071310346/1149291492335177818/1149293863597514772', '_blank')">
<img src="https://i.ibb.co/DGxdgWX/image.png"> <img src="https://i.ibb.co/DGxdgWX/image.png">
<span class="user-quote__username">Koru (owner of Skailar)</span> <span class="user-quote__username">Koru (owner of Skailar)</span>
<p>Nova [is] an open source, ethically sourced (no reverse engineering, just like us) API with high limits and a big amount of available models.</p> <p>Nova [is] an open source, ethically sourced (no reverse engineering, just like us) API with high limits and a big amount of available models.</p>
</div> </div>
<div class="user-quote__field" onclick="window.open('https://discord.com/channels/1120037287300976640/1120037729644855427/1145324231236780092', '_blank')"> <div class="user-quote__field box" onclick="window.open('https://discord.com/channels/1120037287300976640/1120037729644855427/1145324231236780092', '_blank')">
<img src="https://i.ibb.co/DKDm8hg/image.png"> <img src="https://i.ibb.co/DKDm8hg/image.png">
<span class="user-quote__username">Perl (owner of GeniusAI)</span> <span class="user-quote__username">Perl (owner of GeniusAI)</span>
<p>I love the credit system in this API.</p> <p>I love the credit system in this API.</p>
</div> </div>
<div class="user-quote__field" onclick="window.open('https://discord.com/channels/1120037287300976640/1120037729644855427/1143784944888447037', '_blank')"> <div class="user-quote__field box" onclick="window.open('https://discord.com/channels/1120037287300976640/1120037729644855427/1143784944888447037', '_blank')">
<img src="https://i.ibb.co/9grjkW2/b461ea5ea97a773d000300e4d978e24d.png"> <img src="https://i.ibb.co/9grjkW2/b461ea5ea97a773d000300e4d978e24d.png">
<span class="user-quote__username">Flying Elephant</span> <span class="user-quote__username">Flying Elephant</span>
<p>You are so excellent [...]. Thank you for your contributions.</p> <p>You are so excellent [...]. Thank you for your contributions.</p>

0
web/tos.py Normal file → Executable file
View file