Unifying style of the gallery

Fixed HTML and Sass layout
Moved upload and login pages to popups
Added deletion confirmation
This commit is contained in:
Michał 2023-01-25 15:13:56 +00:00
parent 0414cda5d3
commit bb98701430
44 changed files with 1777 additions and 1631 deletions

View file

@ -5,59 +5,62 @@ print("""
| |_| | | | | | |_| | |__| __/ (_| \\__ \\
\\___/|_| |_|_|\\__, |_____\\___|\\__, |___/
|___/ |___/
Created by Fluffy Bean - Version 170123
Created by Fluffy Bean - Version 210123
""")
from flask import Flask, render_template
from flask_compress import Compress
from dotenv import load_dotenv
import yaml
import os
def create_app(test_config=None):
compress = Compress()
def create_app(test_config=None):
# create and configure the app
app = Flask(__name__)
compress.init_app(app)
# Get environment variables
load_dotenv(os.path.join(app.root_path, 'user', '.env'))
print("Loaded env")
# Get config file
with open(os.path.join(app.root_path, 'user', 'conf.yml'), 'r') as f:
conf = yaml.load(f, Loader=yaml.FullLoader)
print("Loaded config")
# App configuration
# App configuration
app.config.from_mapping(
SECRET_KEY=os.environ.get('FLASK_SECRET'),
DATABASE=os.path.join(app.instance_path, 'gallery.sqlite'),
UPLOAD_FOLDER=os.path.join(app.root_path, 'user', 'uploads'),
MAX_CONTENT_LENGTH = 1024 * 1024 * conf['upload']['max-size'],
MAX_CONTENT_LENGTH=1024 * 1024 * conf['upload']['max-size'],
ALLOWED_EXTENSIONS=conf['upload']['allowed-extensions'],
)
if test_config is None:
# load the instance config, if it exists, when not testing
app.config.from_pyfile('config.py', silent=True)
else:
# load the test config if passed in
app.config.from_mapping(test_config)
# ensure the instance folder exists
try:
os.makedirs(app.instance_path)
except OSError:
pass
# Load database
from . import db
db.init_app(app)
# Load theme
from . import sassy
sassy.compile('default', app.root_path)
@app.errorhandler(405)
def method_not_allowed(e):
error = '405'
@ -87,21 +90,16 @@ def create_app(test_config=None):
error = '500'
msg = 'Server died inside :c'
return render_template('error.html', error=error, msg=msg), 500
# Load login, registration and logout manager
from . import auth
app.register_blueprint(auth.blueprint)
# Load routes for home and images
from . import gallery
app.register_blueprint(gallery.blueprint)
app.add_url_rule('/', endpoint='index')
# Load routes for images
from . import image
app.register_blueprint(image.blueprint)
# Load APIs
from . import api
app.register_blueprint(api.blueprint)

View file

@ -14,18 +14,24 @@ blueprint = Blueprint('viewsbp', __name__, url_prefix='/api')
def uploads(file, quality):
# If quality is 0, return original file
if quality == 0:
return send_from_directory(current_app.config['UPLOAD_FOLDER'], secure_filename(file), as_attachment=True)
return send_from_directory(current_app.config['UPLOAD_FOLDER'],
secure_filename(file),
as_attachment=True)
# Set variables
set_ext = {'jpg': 'jpeg', 'jpeg': 'jpeg', 'png': 'png', 'webp': 'webp'}
buff = io.BytesIO()
# Open image and set extension
img = Image.open(os.path.join(current_app.config['UPLOAD_FOLDER'], secure_filename(file)))
img_ext = os.path.splitext(secure_filename(file))[-1].lower().replace('.', '')
img = Image.open(
os.path.join(current_app.config['UPLOAD_FOLDER'],
secure_filename(file)))
img_ext = os.path.splitext(secure_filename(file))[-1].lower().replace(
'.', '')
img_ext = set_ext[img_ext]
img_icc = img.info.get("icc_profile") # Get ICC profile as it alters colours
img_icc = img.info.get(
"icc_profile") # Get ICC profile as it alters colours
# Resize image and orientate correctly
img.thumbnail((quality, quality), Image.LANCZOS)
img = ImageOps.exif_transpose(img)
@ -34,7 +40,8 @@ def uploads(file, quality):
# Seek to beginning of buffer and return
buff.seek(0)
return send_file(buff, mimetype='image/'+img_ext)
return send_file(buff, mimetype='image/' + img_ext)
@blueprint.route('/upload', methods=['POST'])
@login_required
@ -44,56 +51,59 @@ def upload():
if not form_file:
return abort(404)
img_ext = os.path.splitext(secure_filename(form_file.filename))[-1].lower()
img_name = f"GWAGWA_{uuid4().__str__()}{img_ext}"
if not img_ext in current_app.config['ALLOWED_EXTENSIONS']:
abort(403)
# Save to database
try:
db = get_db()
db.execute(
'INSERT INTO posts (file_name, author_id, description, alt)'
' VALUES (?, ?, ?, ?)',
(img_name, g.user['id'], form['description'], form['alt'])
)
(img_name, g.user['id'], form['description'], form['alt']))
db.commit()
except Exception as e:
abort(500)
# Save file
# Save file
try:
form_file.save(os.path.join(current_app.config['UPLOAD_FOLDER'], img_name))
form_file.save(
os.path.join(current_app.config['UPLOAD_FOLDER'], img_name))
except:
abort(500)
return 'Gwa Gwa'
@blueprint.route('/remove/<int:id>', methods=['POST'])
@login_required
def remove(id):
img = get_db().execute(
'SELECT author_id, file_name FROM posts WHERE id = ?',
(id,)
).fetchone()
(id, )).fetchone()
if img is None:
abort(404)
if img['author_id'] != g.user['id']:
abort(403)
try:
os.remove(os.path.join(current_app.config['UPLOAD_FOLDER'], img['file_name']))
os.remove(
os.path.join(current_app.config['UPLOAD_FOLDER'],
img['file_name']))
except Exception as e:
abort(500)
try:
db = get_db()
db.execute('DELETE FROM posts WHERE id = ?', (id,))
db.execute('DELETE FROM posts WHERE id = ?', (id, ))
db.commit()
except:
abort(500)
flash(['Image was all in Le Head!', 1])
return 'Gwa Gwa'

View file

@ -1,7 +1,8 @@
import functools
from flask import Blueprint, flash, g, redirect, render_template, request, session, url_for
from flask import Blueprint, flash, g, redirect, render_template, request, session, url_for, abort, jsonify
from werkzeug.security import check_password_hash, generate_password_hash
from gallery.db import get_db
import re
blueprint = Blueprint('auth', __name__, url_prefix='/auth')
@ -17,61 +18,68 @@ def load_logged_in_user():
'SELECT * FROM users WHERE id = ?', (user_id,)
).fetchone()
@blueprint.route('/register', methods=('GET', 'POST'))
@blueprint.route('/register', methods=['POST'])
def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
error = None
username = request.form['username']
email = request.form['email']
password = request.form['password']
password_repeat = request.form['password-repeat']
db = get_db()
error = []
if not username:
error = 'Username is required.'
elif not password:
error = 'Password is required.'
if not username:
error.append('Username is empty!')
if not email:
error.append('Email is empty!')
elif not re.match(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', email):
error.append('Email is invalid!')
if not password:
error.append('Password is empty!')
elif len(password) < 8:
error.append('Password is too short! Longer than 8 characters pls')
if not password_repeat:
error.append('Password repeat is empty!')
elif password_repeat != password:
error.append('Passwords do not match!')
if error is None:
try:
db.execute(
"INSERT INTO users (username, email, password) VALUES (?, ?, ?)",
(username,'dummy@email.com' , generate_password_hash(password)),
)
db.commit()
except db.IntegrityError:
error = f"User {username} is already registered."
else:
return redirect(url_for("auth.login"))
flash(error)
return render_template('auth/register.html')
if not error:
try:
db.execute(
"INSERT INTO users (username, email, password) VALUES (?, ?, ?)",
(username, email, generate_password_hash(password)),
)
db.commit()
except db.IntegrityError:
error.append(f"User {username} is already registered!")
else:
return 'gwa gwa'
return jsonify(error)
@blueprint.route('/login', methods=('GET', 'POST'))
@blueprint.route('/login', methods=['POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
error = None
user = db.execute(
'SELECT * FROM users WHERE username = ?', (username,)
).fetchone()
username = request.form['username']
password = request.form['password']
db = get_db()
error = None
user = db.execute(
'SELECT * FROM users WHERE username = ?', (username,)
).fetchone()
if user is None:
error = 'Incorrect username.'
elif not check_password_hash(user['password'], password):
error = 'Incorrect password.'
if user is None:
abort(403)
elif not check_password_hash(user['password'], password):
abort(403)
if error is None:
session.clear()
session['user_id'] = user['id']
return redirect(url_for('index'))
flash(error)
return render_template('auth/login.html')
if error is None:
session.clear()
session['user_id'] = user['id']
flash(['Logged in successfully!', '4'])
return 'gwa gwa'
@blueprint.route('/logout')

View file

@ -6,10 +6,8 @@ from flask import current_app, g
def get_db():
if 'db' not in g:
g.db = sqlite3.connect(
current_app.config['DATABASE'],
detect_types=sqlite3.PARSE_DECLTYPES
)
g.db = sqlite3.connect(current_app.config['DATABASE'],
detect_types=sqlite3.PARSE_DECLTYPES)
g.db.row_factory = sqlite3.Row
return g.db

View file

@ -4,8 +4,10 @@ from werkzeug.utils import secure_filename
from gallery.auth import login_required
from gallery.db import get_db
from PIL import Image
from PIL.ExifTags import TAGS
import os
import datetime
dt = datetime.datetime.now()
blueprint = Blueprint('gallery', __name__)
@ -14,19 +16,60 @@ blueprint = Blueprint('gallery', __name__)
@blueprint.route('/')
def index():
db = get_db()
images = db.execute(
'SELECT * FROM posts'
' ORDER BY created_at DESC'
).fetchall()
images = db.execute('SELECT * FROM posts'
' ORDER BY created_at DESC').fetchall()
return render_template('index.html', images=images)
@blueprint.route('/image/<int:id>')
def image(id):
# Get image from database
db = get_db()
image = db.execute('SELECT * FROM posts'
' WHERE id = ?', (id, )).fetchone()
if image is None:
abort(404)
# Get exif data from image
try:
file = Image.open(
os.path.join(current_app.config['UPLOAD_FOLDER'],
image['file_name']))
raw_exif = file.getexif()
human_exif = {}
for tag in raw_exif:
name = TAGS.get(tag, tag)
value = raw_exif.get(tag)
if isinstance(value, bytes):
value = value.decode()
human_exif[name] = value
if len(human_exif) == 0:
human_exif = False
except:
# Cringe, no file present
human_exif = False
file = False
# All in le head
return render_template('image.html',
image=image,
exif=human_exif,
file=file)
@blueprint.route('/group')
def groups():
return render_template('group.html', group_id='gwa gwa')
@blueprint.route('/group/<int:id>')
def group(id):
def group(id):
return render_template('group.html', group_id=id)

View file

@ -1,49 +0,0 @@
from flask import Blueprint, flash, g, redirect, render_template, request, url_for, jsonify, current_app
from werkzeug.exceptions import abort
from werkzeug.utils import secure_filename
from gallery.db import get_db
import os
import datetime
from PIL import Image
from PIL.ExifTags import TAGS
dt = datetime.datetime.now()
blueprint = Blueprint('image', __name__, url_prefix='/image')
@blueprint.route('/<int:id>')
def image(id):
# Get image from database
db = get_db()
image = db.execute(
'SELECT * FROM posts'
' WHERE id = ?',
(id,)
).fetchone()
if image is None:
abort(404)
# Get exif data from image
try:
file = Image.open(os.path.join(current_app.config['UPLOAD_FOLDER'], image['file_name']))
raw_exif = file.getexif()
human_exif = {}
for tag in raw_exif:
name = TAGS.get(tag, tag)
value = raw_exif.get(tag)
if isinstance(value, bytes):
value = value.decode()
human_exif[name] = value
if len(human_exif) == 0:
human_exif = False
except:
# Cringe, no file present
human_exif = False
file = False
# All in le head
return render_template('image.html', image=image, exif=human_exif, file=file)

View file

@ -1,43 +1,56 @@
import datetime
now = datetime.datetime.now()
import sys
import shutil
import os
import sass
class compile():
def __init__(self, theme, dir):
print(f"Loading '{theme}' theme...")
theme_path = os.path.join(dir, 'user', 'themes', theme, 'style.scss')
theme_path = os.path.join(dir, 'user', 'themes', theme)
font_path = os.path.join(dir, 'user', 'themes', theme, 'fonts')
dest = os.path.join(dir, 'static', 'theme')
print(f"Theme path: {theme_path}")
if os.path.exists(theme_path):
if os.path.exists(os.path.join(theme_path, 'style.scss')):
theme_path = os.path.join(theme_path, 'style.scss')
elif os.path.exists(os.path.join(theme_path, 'style.sass')):
theme_path = os.path.join(theme_path, 'style.sass')
else:
print("Theme does not contain a style file!")
sys.exit(1)
self.sass = sass
self.loadTheme(theme_path, dest)
self.loadFonts(font_path, dest)
else:
print("No theme found!")
sys.exit(1)
print(f"{now.hour}:{now.minute}:{now.second} - Done!\n")
def loadTheme (self, theme, dest):
def loadTheme(self, theme, dest):
with open(os.path.join(dest, 'style.css'), 'w') as f:
try:
f.write(self.sass.compile(filename=theme, output_style='compressed'))
f.write(
self.sass.compile(filename=theme,
output_style='compressed'))
print("Compiled successfully!")
except self.sass.CompileError as e:
print("Failed to compile!\n", e)
sys.exit(1)
def loadFonts (self, source, dest):
def loadFonts(self, source, dest):
dest = os.path.join(dest, 'fonts')
if os.path.exists(dest):
print("Removing old fonts...")
try:
@ -45,7 +58,7 @@ class compile():
except Exception as e:
print("Failed to remove old fonts!\n", e)
sys.exit(1)
try:
shutil.copytree(source, dest)
print("Copied fonts to:", dest)

View file

@ -1,43 +0,0 @@
{% extends 'layout.html' %}
{% block header %}
<img src="{{ url_for('static', filename='images/background.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
{% endblock %}
{% block content %}
<div class="app">
<div class="login box-ui">
<div class="box-ui-header">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -2 24 24" fill="currentColor">
<path d="M3 0h10a3 3 0 0 1 3 3v14a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm2 1h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2zm0 12h2a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2zm0-4h6a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2zm0-4h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2z"></path>
</svg>
<h2>Login</h2>
</div>
<div class="box-ui-content">
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
<form method="post" class="nice-form">
<span class="form-box">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor">
<path d="M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm0-14a4 4 0 0 1 4 4v2a4 4 0 1 1-8 0V8a4 4 0 0 1 4-4zm0 2a2 2 0 0 0-2 2v2a2 2 0 1 0 4 0V8a2 2 0 0 0-2-2zM5.91 16.876a8.033 8.033 0 0 1-1.58-1.232 5.57 5.57 0 0 1 2.204-1.574 1 1 0 1 1 .733 1.86c-.532.21-.993.538-1.358.946zm8.144.022a3.652 3.652 0 0 0-1.41-.964 1 1 0 1 1 .712-1.868 5.65 5.65 0 0 1 2.284 1.607 8.032 8.032 0 0 1-1.586 1.225z"></path>
</svg>
<input name="username" id="username" required>
</span>
<span class="form-box">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -2 24 24" fill="currentColor">
<path d="M2 12v6h10v-6H2zm10-2a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-6a2 2 0 0 1 2-2V5a5 5 0 1 1 10 0v5zm-2 0V5a3 3 0 1 0-6 0v5h6zm-3 7a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"></path>
</svg>
<input type="password" name="password" id="password" required>
</span>
<input class="form-button" type="submit" value="Log In">
</form>
<a class="form-button faded" href="{{ url_for('auth.register') }}">Register</a>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,43 +0,0 @@
{% extends 'layout.html' %}
{% block header %}
<img src="{{ url_for('static', filename='images/background.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
{% endblock %}
{% block content %}
<div class="app">
<div class="register box-ui">
<div class="box-ui-header">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -2 24 24" fill="currentColor">
<path d="M3 0h10a3 3 0 0 1 3 3v14a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm2 1h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2zm0 12h2a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2zm0-4h6a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2zm0-4h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2z"></path>
</svg>
<h2>Register</h2>
</div>
<div class="box-ui-content">
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
<form method="post" class="nice-form">
<span class="form-box">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor">
<path d="M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm0-14a4 4 0 0 1 4 4v2a4 4 0 1 1-8 0V8a4 4 0 0 1 4-4zm0 2a2 2 0 0 0-2 2v2a2 2 0 1 0 4 0V8a2 2 0 0 0-2-2zM5.91 16.876a8.033 8.033 0 0 1-1.58-1.232 5.57 5.57 0 0 1 2.204-1.574 1 1 0 1 1 .733 1.86c-.532.21-.993.538-1.358.946zm8.144.022a3.652 3.652 0 0 0-1.41-.964 1 1 0 1 1 .712-1.868 5.65 5.65 0 0 1 2.284 1.607 8.032 8.032 0 0 1-1.586 1.225z"></path>
</svg>
<input name="username" id="username" required>
</span>
<span class="form-box">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -2 24 24" fill="currentColor">
<path d="M2 12v6h10v-6H2zm10-2a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-6a2 2 0 0 1 2-2V5a5 5 0 1 1 10 0v5zm-2 0V5a3 3 0 1 0-6 0v5h6zm-3 7a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"></path>
</svg>
<input type="password" name="password" id="password" required>
</span>
<input class="form-button" type="submit" value="Register">
</form>
<a class="form-button faded" href="{{ url_for('auth.login') }}">Login</a>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,12 +1,13 @@
{% extends 'layout.html' %}
{% block header %}
<img src="{{ url_for('static', filename='images/background.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
<div class="background-decoration">
<img src="{{ url_for('static', filename='images/background.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
<span></span>
</div>
{% endblock %}
{% block content %}
<div class="app err-warning">
<h1>{{error}}</h1>
<p>{{msg}}</p>
</div>
<h1>{{error}}</h1>
<p>{{msg}}</p>
{% endblock %}

View file

@ -1,12 +1,15 @@
{% extends 'layout.html' %}
{% block header %}
<img src="{{ url_for('static', filename='images/background.svg') }}" onload="imgFade(this)" style="opacity:0;">
<div class="background-decoration">
<img src="{{ url_for('static', filename='images/background.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
<span></span>
</div>
{% endblock %}
{% block nav_groups %}navigation-item__selected{% endblock %}
{% block content %}
<div class="app">
<h1>Image Group</h1>
<p>{{group_id}}</p>
</div>
<h1>Image Group</h1>
<p>{{group_id}}</p>
{% endblock %}

View file

@ -1,11 +1,16 @@
{% extends 'layout.html' %}
{% block header %}
<img src="/api/uploads/{{ image['file_name'] }}/1000" alt="leaves" onload="imgFade(this)" style="opacity:0;"/>
<div class="background-decoration">
<img src="/api/uploads/{{ image['file_name'] }}/1000" onload="imgFade(this)" style="opacity:0;"/>
<span></span>
</div>
{% endblock %}
{% block wrapper_class %}image-wrapper{% endblock %}
{% block content %}
<div class="image__fullscreen">
<div class="image-fullscreen">
<img
src=""
onload="imgFade(this);"
@ -13,91 +18,94 @@
/>
</div>
<div class="app">
<div class="image__container">
<img
class="image__item"
src="/api/uploads/{{ image['file_name'] }}/1000"
onload="imgFade(this)" style="opacity:0;"
onerror="this.src='/static/images/error.png'"
width="{{ file['width'] }}"
height="{{ file['height'] }}"
/>
<div class="image-container">
<img
src="/api/uploads/{{ image['file_name'] }}/1000"
onload="imgFade(this)" style="opacity:0;"
onerror="this.src='/static/images/error.png'"
width="{{ file['width'] }}"
height="{{ file['height'] }}"
/>
</div>
<div class="pill-row" id="image-tools">
<div>
<button class="pill-item" id="img-fullscreen">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 24 24" fill="currentColor">
<path d="M12.586 2H11a1 1 0 0 1 0-2h4a1 1 0 0 1 1 1v4a1 1 0 0 1-2 0V3.414L9.414 8 14 12.586V11a1 1 0 0 1 2 0v4a1 1 0 0 1-1 1h-4a1 1 0 0 1 0-2h1.586L8 9.414 3.414 14H5a1 1 0 0 1 0 2H1a1 1 0 0 1-1-1v-4a1 1 0 0 1 2 0v1.586L6.586 8 2 3.414V5a1 1 0 1 1-2 0V1a1 1 0 0 1 1-1h4a1 1 0 1 1 0 2H3.414L8 6.586 12.586 2z"></path>
</svg>
<span class="tool-tip">Fullscreen</span>
</button>
<button class="pill-item" id="img-share">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -3 24 24" fill="currentColor">
<path d="M3.19 9.345a.97.97 0 0 1 1.37 0 .966.966 0 0 1 0 1.367l-2.055 2.052a1.932 1.932 0 0 0 0 2.735 1.94 1.94 0 0 0 2.74 0l4.794-4.787a.966.966 0 0 0 0-1.367.966.966 0 0 1 0-1.368.97.97 0 0 1 1.37 0 2.898 2.898 0 0 1 0 4.103l-4.795 4.787a3.879 3.879 0 0 1-5.48 0 3.864 3.864 0 0 1 0-5.47L3.19 9.344zm11.62-.69a.97.97 0 0 1-1.37 0 .966.966 0 0 1 0-1.367l2.055-2.052a1.932 1.932 0 0 0 0-2.735 1.94 1.94 0 0 0-2.74 0L7.962 7.288a.966.966 0 0 0 0 1.367.966.966 0 0 1 0 1.368.97.97 0 0 1-1.37 0 2.898 2.898 0 0 1 0-4.103l4.795-4.787a3.879 3.879 0 0 1 5.48 0 3.864 3.864 0 0 1 0 5.47L14.81 8.656z"></path>
</svg>
<span class="tool-tip">Share</span>
</button>
</div>
<div class="img-tools">
{% if g.user['id'] == image['author_id'] %}
<div>
<button class="tool-btn" id="img-fullscreen">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 24 24" fill="currentColor">
<path d="M12.586 2H11a1 1 0 0 1 0-2h4a1 1 0 0 1 1 1v4a1 1 0 0 1-2 0V3.414L9.414 8 14 12.586V11a1 1 0 0 1 2 0v4a1 1 0 0 1-1 1h-4a1 1 0 0 1 0-2h1.586L8 9.414 3.414 14H5a1 1 0 0 1 0 2H1a1 1 0 0 1-1-1v-4a1 1 0 0 1 2 0v1.586L6.586 8 2 3.414V5a1 1 0 1 1-2 0V1a1 1 0 0 1 1-1h4a1 1 0 1 1 0 2H3.414L8 6.586 12.586 2z"></path>
<button class="pill-item pill__critical" id="img-delete">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -2 24 24" fill="currentColor">
<path d="M6 2V1a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v1h4a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2h-.133l-.68 10.2a3 3 0 0 1-2.993 2.8H5.826a3 3 0 0 1-2.993-2.796L2.137 7H2a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h4zm10 2H2v1h14V4zM4.141 7l.687 10.068a1 1 0 0 0 .998.932h6.368a1 1 0 0 0 .998-.934L13.862 7h-9.72zM7 8a1 1 0 0 1 1 1v7a1 1 0 0 1-2 0V9a1 1 0 0 1 1-1zm4 0a1 1 0 0 1 1 1v7a1 1 0 0 1-2 0V9a1 1 0 0 1 1-1z"></path>
</svg>
<span class="tool-tip">Fullscreen</span>
<span class="tool-tip">Delete</span>
</button>
<button class="tool-btn" id="img-share">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -3 24 24" fill="currentColor">
<path d="M3.19 9.345a.97.97 0 0 1 1.37 0 .966.966 0 0 1 0 1.367l-2.055 2.052a1.932 1.932 0 0 0 0 2.735 1.94 1.94 0 0 0 2.74 0l4.794-4.787a.966.966 0 0 0 0-1.367.966.966 0 0 1 0-1.368.97.97 0 0 1 1.37 0 2.898 2.898 0 0 1 0 4.103l-4.795 4.787a3.879 3.879 0 0 1-5.48 0 3.864 3.864 0 0 1 0-5.47L3.19 9.344zm11.62-.69a.97.97 0 0 1-1.37 0 .966.966 0 0 1 0-1.367l2.055-2.052a1.932 1.932 0 0 0 0-2.735 1.94 1.94 0 0 0-2.74 0L7.962 7.288a.966.966 0 0 0 0 1.367.966.966 0 0 1 0 1.368.97.97 0 0 1-1.37 0 2.898 2.898 0 0 1 0-4.103l4.795-4.787a3.879 3.879 0 0 1 5.48 0 3.864 3.864 0 0 1 0 5.47L14.81 8.656z"></path>
<button class="pill-item pill__critical" id="img-edit">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2.5 -2.5 24 24" fill="currentColor">
<path d="M12.238 5.472L3.2 14.51l-.591 2.016 1.975-.571 9.068-9.068-1.414-1.415zM13.78 3.93l1.414 1.414 1.318-1.318a.5.5 0 0 0 0-.707l-.708-.707a.5.5 0 0 0-.707 0L13.781 3.93zm3.439-2.732l.707.707a2.5 2.5 0 0 1 0 3.535L5.634 17.733l-4.22 1.22a1 1 0 0 1-1.237-1.241l1.248-4.255 12.26-12.26a2.5 2.5 0 0 1 3.535 0z"></path>
</svg>
<span class="tool-tip">Share</span>
</button>
</div>
{% if g.user['id'] == image['author_id'] %}
<div>
<button class="tool-btn tool-btn--evil" id="img-delete">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -2 24 24" fill="currentColor">
<path d="M6 2V1a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v1h4a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2h-.133l-.68 10.2a3 3 0 0 1-2.993 2.8H5.826a3 3 0 0 1-2.993-2.796L2.137 7H2a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h4zm10 2H2v1h14V4zM4.141 7l.687 10.068a1 1 0 0 0 .998.932h6.368a1 1 0 0 0 .998-.934L13.862 7h-9.72zM7 8a1 1 0 0 1 1 1v7a1 1 0 0 1-2 0V9a1 1 0 0 1 1-1zm4 0a1 1 0 0 1 1 1v7a1 1 0 0 1-2 0V9a1 1 0 0 1 1-1z"></path>
</svg>
<span class="tool-tip">Delete</span>
</button>
<button class="tool-btn tool-btn--evil" id="img-edit">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2.5 -2.5 24 24" fill="currentColor">
<path d="M12.238 5.472L3.2 14.51l-.591 2.016 1.975-.571 9.068-9.068-1.414-1.415zM13.78 3.93l1.414 1.414 1.318-1.318a.5.5 0 0 0 0-.707l-.708-.707a.5.5 0 0 0-.707 0L13.781 3.93zm3.439-2.732l.707.707a2.5 2.5 0 0 1 0 3.535L5.634 17.733l-4.22 1.22a1 1 0 0 1-1.237-1.241l1.248-4.255 12.26-12.26a2.5 2.5 0 0 1 3.535 0z"></path>
</svg>
<span class="tool-tip">Edit</span>
</button>
</div>
{% endif %}
<div>
<button class="tool-btn" id="img-info">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor">
<path d="M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm0-10a1 1 0 0 1 1 1v5a1 1 0 0 1-2 0V9a1 1 0 0 1 1-1zm0-1a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"></path>
</svg>
<span class="tool-tip">Info</span>
<span class="tool-tip">Edit</span>
</button>
</div>
{% endif %}
<!--
<div>
<button class="tool-btn" id="img-info">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor">
<path d="M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm0-10a1 1 0 0 1 1 1v5a1 1 0 0 1-2 0V9a1 1 0 0 1 1-1zm0-1a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"></path>
</svg>
<span class="tool-tip">Info</span>
</button>
</div>
-->
</div>
<div class="image-info__container">
{% if image['alt'] != '' %}
<div class="image__info">
<div class="image__info-header">
<div class="image-info">
<div class="image-info__header">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2.5 24 24" fill="currentColor">
<path d="M3.656 17.979A1 1 0 0 1 2 17.243V15a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H8.003l-4.347 2.979zm.844-3.093a.536.536 0 0 0 .26-.069l2.355-1.638A1 1 0 0 1 7.686 13H12a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v5c0 .54.429.982 1 1 .41.016.707.083.844.226.128.134.135.36.156.79.003.063.003.177 0 .37a.5.5 0 0 0 .5.5z"></path><path d="M16 10.017a7.136 7.136 0 0 0 0 .369v-.37c.02-.43.028-.656.156-.79.137-.143.434-.21.844-.226.571-.018 1-.46 1-1V3a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1H5V2a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2v2.243a1 1 0 0 1-1.656.736L16 13.743v-3.726z"></path>
</svg>
<h2>Alt</h2>
</div>
<div class="image__info-content">
<div class="image-info__content">
<p>{{ image['alt'] }}</p>
</div>
</div>
{% endif %}
{% if image['description'] != '' %}
<div class="image__info">
<div class="image__info-header">
<div class="image-info">
<div class="image-info__header">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -2 24 24" fill="currentColor">
<path d="M3 0h10a3 3 0 0 1 3 3v14a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm2 1h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2zm0 12h2a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2zm0-4h6a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2zm0-4h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2z"></path>
</svg>
<h2>Description</h2>
</div>
<div class="image__info-content">
<div class="image-info__content">
<p>{{ image['description'] }}</p>
</div>
</div>
{% endif %}
<div class="image__info">
<div class="image__info-header">
<div class="image-info">
<div class="image-info__header">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor">
<path d="M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm0-10a1 1 0 0 1 1 1v5a1 1 0 0 1-2 0V9a1 1 0 0 1 1-1zm0-1a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"></path>
</svg>
<h2>Info</h2>
</div>
<div class="image__info-content">
<div class="image-info__content">
<p>Filename: {{ image['file_name'] }}</p>
<p>Image ID: {{ image['id'] }}</p>
<p>Author: {{ image['author_id'] }}</p>
@ -108,14 +116,14 @@
</div>
</div>
{% if exif is not false %}
<div class="image__info">
<div class="image__info-header">
<div class="image-info">
<div class="image-info__header">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor">
<path d="M14.95 7.879l-.707-.707a1 1 0 0 1 1.414-1.415l.707.707 1.414-1.414-2.828-2.828L2.222 14.95l2.828 2.828 1.414-1.414L5.05 14.95a1 1 0 0 1 1.414-1.414L7.88 14.95l1.414-1.414-.707-.708A1 1 0 0 1 10 11.414l.707.707 1.414-1.414-1.414-1.414a1 1 0 0 1 1.414-1.414l1.415 1.414 1.414-1.414zM.808 13.536L13.536.808a2 2 0 0 1 2.828 0l2.828 2.828a2 2 0 0 1 0 2.828L6.464 19.192a2 2 0 0 1-2.828 0L.808 16.364a2 2 0 0 1 0-2.828z"></path>
</svg>
<h2>Metadata</h2>
</div>
<div class="image__info-content">
<div class="image-info__content">
{% for tag in exif %}
<p>{{ tag }}: {{ exif[tag] }}</p>
{% endfor %}
@ -123,15 +131,18 @@
</div>
{% endif %}
</div>
{% endblock %}
{% block script %}
<script>
$('.image__fullscreen').click(function() {
$('.image__fullscreen').removeClass('image__fullscreen--active');
$('.image-fullscreen').click(function() {
$('.image-fullscreen').removeClass('image-fullscreen__active');
});
$('#img-fullscreen').click(function() {
$('.image__fullscreen').addClass('image__fullscreen--active');
if ($('.image__fullscreen img').attr('src') == '') {
$('.image__fullscreen img').attr('src', '/api/uploads/{{ image['file_name'] }}/0');
$('.image-fullscreen').addClass('image-fullscreen__active');
if ($('.image-fullscreen img').attr('src') == '') {
$('.image-fullscreen img').attr('src', '/api/uploads/{{ image['file_name'] }}/0');
}
});
$('#img-share').click(function() {
@ -148,27 +159,34 @@
{% if g.user['id'] == image['author_id'] %}
$('#img-delete').click(function() {
popUpShow(
'DESTRUCTION!!!!!!',
'This will delete the image and all of its data!!! This action is irreversible!!!!! Are you sure you want to do this?????',
'<button class="pop-up__btn pop-up__btn-critical-fill" onclick="deleteImage()">Dewww eeeet!</button>',
'<img src="/api/uploads/{{ image['file_name'] }}/1000" />'
);
});
$('#img-edit').click(function() {
window.location.href = '/image/{{ image['id'] }}/edit';
});
function deleteImage() {
popupDissmiss();
$.ajax({
url: '/api/remove/{{ image['id'] }}',
type: 'post',
data: {
action: 'delete'
},
success: function (response) {
addNotification("Image was all in le head", 1);
setTimeout(function() {
window.location.href = '/';
}, 1000);
window.location.href = '/';
},
error: function (response) {
addNotification(`Image *clings*: ${response}`, 2);
}
});
});
$('#img-edit').click(function() {
window.location.href = '/image/{{ image['id'] }}/edit';
});
}
{% endif %}
</script>
{% endblock %}

View file

@ -1,35 +1,34 @@
{% extends 'layout.html' %}
{% block header %}
<img src="{{ url_for('static', filename='images/background.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
{% endblock %}
{% block nav_home %}navigation-item__selected{% endblock %}
{% block wrapper_class %}index-wrapper{% endblock %}
{% block content %}
<div class="app">
<!--<h1>Gallery</h1>-->
<div id="gallery" class="gallery">
{% for image in images %}
<a class="gallery__item" href="/image/{{ image['id'] }}">
<div class="gallery__item-info">
<p>{{ image['id'] }}</p>
<h2>{{ image['created_at'] }}</h2>
</div>
<span class="gallery__item-filter"></span>
<img
class="gallery__item-image"
src="/api/uploads/{{ image['file_name'] }}/400"
onload="imgFade(this)"
style="opacity:0;"
loading="lazy"
/>
</a>
{% endfor %}
</div>
<div class="gallery">
{% for image in images %}
<a class="gallery__item" href="/image/{{ image['id'] }}">
<div class="gallery__item-info">
<p>{{ image['id'] }}</p>
<h2>{{ image['created_at'] }}</h2>
</div>
<span class="gallery__item-filter"></span>
<img
class="gallery__item-image"
src="/api/uploads/{{ image['file_name'] }}/400"
onload="imgFade(this)"
style="opacity:0;"
loading="lazy"
/>
</a>
{% endfor %}
</div>
{% endblock %}
{% block script %}
<script>
let imageList = [];
let imageIndex = 0;
function loadMore(startIndex, amount = 20) {
for (let i = startIndex; i < startIndex + amount; i++) {
if (i < imageList.length) {

View file

@ -5,21 +5,27 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gallery</title>
<link rel="stylesheet" href="{{url_for('static', filename='theme/style.css')}}" defer>
<script src="{{url_for('static', filename='jquery-3.6.3.min.js')}}">
</script>
<script src="{{url_for('static', filename='jquery-3.6.3.min.js')}}"></script>
</head>
<body>
<div class="sniffle"></div>
<nav id="navRoot">
<div>
<a href="{{url_for('gallery.index')}}">
<div class="wrapper">
<div class="notifications"></div>
<svg class="jumpUp" xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor">
<path d="M11 8.414V14a1 1 0 0 1-2 0V8.414L6.464 10.95A1 1 0 1 1 5.05 9.536l4.243-4.243a.997.997 0 0 1 1.414 0l4.243 4.243a1 1 0 1 1-1.414 1.414L11 8.414zM10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16z"></path>
</svg>
{% block header %}{% endblock %}
<div class="navigation">
<a href="{{url_for('gallery.index')}}" class="navigation-item {% block nav_home %}{% endblock %}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" width="24" fill="currentColor">
<path d="M2 8v10h12V8H2zm2-2V2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-2v4a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2zm2 0h8a2 2 0 0 1 2 2v4h2V2H6v4zm0 9a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"></path><path d="M7 6a3 3 0 1 1 6 0h-2a1 1 0 0 0-2 0H7zm1.864 13.518l2.725-4.672a1 1 0 0 1 1.6-.174l1.087 1.184 1.473-1.354-1.088-1.183a3 3 0 0 0-4.8.52L7.136 18.51l1.728 1.007zm6.512-12.969a2.994 2.994 0 0 1 3.285.77l1.088 1.183-1.473 1.354-1.087-1.184A1 1 0 0 0 16 8.457V8c0-.571-.24-1.087-.624-1.451z"></path>
</svg>
<span>Home</span>
</a>
<a href="{{url_for('gallery.groups')}}">
<a href="{{url_for('gallery.groups')}}" class="navigation-item {% block nav_groups %}{% endblock %}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -4 24 24" width="24" fill="currentColor">
<path d="M17 4H9.415l-.471-1.334A1.001 1.001 0 0 0 8 2H3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1zm-6.17-2H17a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h5c1.306 0 2.417.835 2.83 2z"></path>
</svg>
@ -27,88 +33,93 @@
</a>
{% if g.user %}
<a href="{{url_for('gallery.upload')}}">
<button class="navigation-item {% block nav_upload %}{% endblock %}" onclick="showUpload()">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -5 24 24" width="24" fill="currentColor">
<path d="M8 3.414v5.642a1 1 0 1 1-2 0V3.414L4.879 4.536A1 1 0 0 1 3.464 3.12L6.293.293a1 1 0 0 1 1.414 0l2.829 2.828A1 1 0 1 1 9.12 4.536L8 3.414zM1 12h12a1 1 0 0 1 0 2H1a1 1 0 0 1 0-2z"></path>
</svg>
<span>Upload</span>
</a>
</button>
{% endif %}
</div>
<div>
<span></span>
{% if g.user %}
<a href="{{url_for('gallery.profile')}}">
<a href="{{url_for('gallery.profile')}}" class="navigation-item {% block nav_profile %}{% endblock %}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" width="24" fill="currentColor">
<path d="M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm0-14a4 4 0 0 1 4 4v2a4 4 0 1 1-8 0V8a4 4 0 0 1 4-4zm0 2a2 2 0 0 0-2 2v2a2 2 0 1 0 4 0V8a2 2 0 0 0-2-2zM5.91 16.876a8.033 8.033 0 0 1-1.58-1.232 5.57 5.57 0 0 1 2.204-1.574 1 1 0 1 1 .733 1.86c-.532.21-.993.538-1.358.946zm8.144.022a3.652 3.652 0 0 0-1.41-.964 1 1 0 1 1 .712-1.868 5.65 5.65 0 0 1 2.284 1.607 8.032 8.032 0 0 1-1.586 1.225z"></path>
</svg>
<span>Profile</span>
</a>
<a href="{{url_for('gallery.settings')}}">
<a href="{{url_for('gallery.settings')}}" class="navigation-item {% block nav_settings %}{% endblock %}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -2 24 24" width="24" fill="currentColor">
<path d="M9.815 3.094a3.467 3.467 0 0 1-2.78-1.09l-.084-.001a3.467 3.467 0 0 1-2.781 1.09 3.477 3.477 0 0 1-1.727 2.51 3.471 3.471 0 0 1 0 2.794 3.477 3.477 0 0 1 1.727 2.51 3.467 3.467 0 0 1 2.78 1.09h.084a3.467 3.467 0 0 1 2.78-1.09 3.477 3.477 0 0 1 1.727-2.51 3.471 3.471 0 0 1 0-2.794 3.477 3.477 0 0 1-1.726-2.51zM14 5.714a1.474 1.474 0 0 0 0 2.572l-.502 1.684a1.473 1.473 0 0 0-1.553 2.14l-1.443 1.122A1.473 1.473 0 0 0 8.143 14l-2.304-.006a1.473 1.473 0 0 0-2.352-.765l-1.442-1.131A1.473 1.473 0 0 0 .5 9.968L0 8.278a1.474 1.474 0 0 0 0-2.555l.5-1.69a1.473 1.473 0 0 0 1.545-2.13L3.487.77A1.473 1.473 0 0 0 5.84.005L8.143 0a1.473 1.473 0 0 0 2.358.768l1.444 1.122a1.473 1.473 0 0 0 1.553 2.14L14 5.714zm-5.812 9.198a7.943 7.943 0 0 0 2.342-.73 3.468 3.468 0 0 1-.087.215 3.477 3.477 0 0 1 1.727 2.51 3.467 3.467 0 0 1 2.78 1.09h.084a3.467 3.467 0 0 1 2.78-1.09 3.477 3.477 0 0 1 1.727-2.51 3.471 3.471 0 0 1 0-2.794 3.477 3.477 0 0 1-1.726-2.51 3.467 3.467 0 0 1-2.78-1.09h-.084l-.015.016a8.077 8.077 0 0 0 .002-2.016L16.144 6a1.473 1.473 0 0 0 2.358.768l1.444 1.122a1.473 1.473 0 0 0 1.553 2.14L22 11.714a1.474 1.474 0 0 0 0 2.572l-.502 1.684a1.473 1.473 0 0 0-1.553 2.14l-1.443 1.122a1.473 1.473 0 0 0-2.359.768l-2.304-.006a1.473 1.473 0 0 0-2.352-.765l-1.442-1.131a1.473 1.473 0 0 0-1.545-2.13l-.312-1.056zM7 10a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm8 8a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"></path>
</svg>
<span>Settings</span>
</a>
{% else %}
<a href="{{url_for('auth.login')}}">
<button class="navigation-item {% block nav_login %}{% endblock %}" onclick="showLogin()">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -3 24 24" fill="currentColor">
<path d="M6.641 9.828H1a1 1 0 1 1 0-2h5.641l-1.12-1.12a1 1 0 0 1 1.413-1.415L9.763 8.12a.997.997 0 0 1 0 1.415l-2.829 2.828A1 1 0 0 1 5.52 10.95l1.121-1.122zM13 0a1 1 0 0 1 1 1v16a1 1 0 0 1-2 0V1a1 1 0 0 1 1-1z"></path>
</svg>
<span>Register</span>
</a>
<span>Login</span>
</button>
{% endif %}
</div>
</nav>
<main>
<header>
{% block header %}{% endblock %}
<span></span>
</header>
{% block content %}
{% endblock %}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor" id="topButton">
<path d="M11 8.414V14a1 1 0 0 1-2 0V8.414L6.464 10.95A1 1 0 1 1 5.05 9.536l4.243-4.243a.997.997 0 0 1 1.414 0l4.243 4.243a1 1 0 1 1-1.414 1.414L11 8.414zM10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16z"></path>
</svg>
</main>
<div class="content {% block wrapper_class %}{% endblock %}">
{% block content %}
{% endblock %}
</div>
<div class="pop-up">
<div class="pop-up-wrapper">
<div class="pop-up-content">
<h3>Title</h3>
<p>Very very very drawn out example description</p>
</div>
<div class="pop-up-controlls">
<button class="pop-up__btn" onclick="popupClose()">Cancel</button>
</div>
</div>
</div>
</div>
<script>
let navToggle = true;
document.onscroll = function() {
document.querySelector('header').style.opacity = `${1 - window.scrollY / 621}`;
document.querySelector('header').style.top = `-${window.scrollY / 5}px`;
document.querySelector('.background-decoration').style.opacity = `${1 - window.scrollY / 621}`;
document.querySelector('.background-decoration').style.top = `-${window.scrollY / 5}px`;
if (document.body.scrollTop > 300 || document.documentElement.scrollTop > 20) {
document.querySelector('#topButton').style.opacity = 1;
document.querySelector('#topButton').style.right = "0.75rem";
document.querySelector('.jumpUp').style.opacity = 1;
document.querySelector('.jumpUp').style.right = "0.75rem";
} else {
document.querySelector('#topButton').style.opacity = 0;
document.querySelector('#topButton').style.right = "-3rem";
document.querySelector('.jumpUp').style.opacity = 0;
document.querySelector('.jumpUp').style.right = "-3rem";
}
}
document.querySelector('#topButton').onclick = function() {
document.querySelector('.jumpUp').onclick = function() {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
}
function imgFade(obj) {
$(obj).animate({opacity: 1}, 500);
$(obj).parent().css('background', 'transparent');
//$(obj).parent().style.backgroundColor = 'transparent';
}
function addNotification(text='Sample notification', type=4) {
var container = document.querySelector('.sniffle');
var container = document.querySelector('.notifications');
// Create notification element
var div = document.createElement('div');
div.classList.add('sniffle__notification');
div.onclick = function() {
if (div.parentNode) {
div.style.opacity = 0;
div.style.transform = 'translateX(100%)';
div.classList.add('sniffle__notification--hide');
setTimeout(function() {
container.removeChild(div);
@ -119,26 +130,31 @@
// Create icon element and append to notification
var icon = document.createElement('span');
icon.classList.add('sniffle__notification-icon');
if (type == 1) {
div.classList.add('sniffle__notification--success');
icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -7 24 24" fill="currentColor">\
<path d="M5.486 9.73a.997.997 0 0 1-.707-.292L.537 5.195A1 1 0 1 1 1.95 3.78l3.535 3.535L11.85.952a1 1 0 0 1 1.415 1.414L6.193 9.438a.997.997 0 0 1-.707.292z"></path>\
</svg>';
} else if (type == 2) {
div.classList.add('sniffle__notification--error');
icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-6 -6 24 24" fill="currentColor">\
<path d="M7.314 5.9l3.535-3.536A1 1 0 1 0 9.435.95L5.899 4.485 2.364.95A1 1 0 1 0 .95 2.364l3.535 3.535L.95 9.435a1 1 0 1 0 1.414 1.414l3.535-3.535 3.536 3.535a1 1 0 1 0 1.414-1.414L7.314 5.899z"></path>\
</svg>';
} else if (type == 3) {
div.classList.add('sniffle__notification--warning');
icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -3 24 24" fill="currentColor">\
<path d="M12.8 1.613l6.701 11.161c.963 1.603.49 3.712-1.057 4.71a3.213 3.213 0 0 1-1.743.516H3.298C1.477 18 0 16.47 0 14.581c0-.639.173-1.264.498-1.807L7.2 1.613C8.162.01 10.196-.481 11.743.517c.428.276.79.651 1.057 1.096zm-2.22.839a1.077 1.077 0 0 0-1.514.365L2.365 13.98a1.17 1.17 0 0 0-.166.602c0 .63.492 1.14 1.1 1.14H16.7c.206 0 .407-.06.581-.172a1.164 1.164 0 0 0 .353-1.57L10.933 2.817a1.12 1.12 0 0 0-.352-.365zM10 14a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0-9a1 1 0 0 1 1 1v4a1 1 0 0 1-2 0V6a1 1 0 0 1 1-1z"></path>\
</svg>';
} else if (type == 4) {
div.classList.add('sniffle__notification--info');
icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor">\
<path d="M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm0-10a1 1 0 0 1 1 1v5a1 1 0 0 1-2 0V9a1 1 0 0 1 1-1zm0-1a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"></path>\
</svg>';
switch (type) {
case 1:
div.classList.add('sniffle__notification--success');
icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -7 24 24" fill="currentColor">\
<path d="M5.486 9.73a.997.997 0 0 1-.707-.292L.537 5.195A1 1 0 1 1 1.95 3.78l3.535 3.535L11.85.952a1 1 0 0 1 1.415 1.414L6.193 9.438a.997.997 0 0 1-.707.292z"></path>\
</svg>';
break;
case 2:
div.classList.add('sniffle__notification--error');
icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-6 -6 24 24" fill="currentColor">\
<path d="M7.314 5.9l3.535-3.536A1 1 0 1 0 9.435.95L5.899 4.485 2.364.95A1 1 0 1 0 .95 2.364l3.535 3.535L.95 9.435a1 1 0 1 0 1.414 1.414l3.535-3.535 3.536 3.535a1 1 0 1 0 1.414-1.414L7.314 5.899z"></path>\
</svg>';
break;
case 3:
div.classList.add('sniffle__notification--warning');
icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -3 24 24" fill="currentColor">\
<path d="M12.8 1.613l6.701 11.161c.963 1.603.49 3.712-1.057 4.71a3.213 3.213 0 0 1-1.743.516H3.298C1.477 18 0 16.47 0 14.581c0-.639.173-1.264.498-1.807L7.2 1.613C8.162.01 10.196-.481 11.743.517c.428.276.79.651 1.057 1.096zm-2.22.839a1.077 1.077 0 0 0-1.514.365L2.365 13.98a1.17 1.17 0 0 0-.166.602c0 .63.492 1.14 1.1 1.14H16.7c.206 0 .407-.06.581-.172a1.164 1.164 0 0 0 .353-1.57L10.933 2.817a1.12 1.12 0 0 0-.352-.365zM10 14a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0-9a1 1 0 0 1 1 1v4a1 1 0 0 1-2 0V6a1 1 0 0 1 1-1z"></path>\
</svg>';
break;
default:
div.classList.add('sniffle__notification--info');
icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor">\
<path d="M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm0-10a1 1 0 0 1 1 1v5a1 1 0 0 1-2 0V9a1 1 0 0 1 1-1zm0-1a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"></path>\
</svg>';
break;
}
div.appendChild(icon);
@ -155,6 +171,9 @@
// Append notification to container
container.appendChild(div);
setTimeout(function() {
div.classList.add('sniffle__notification-show');
}, 100);
// Remove notification after 5 seconds
setTimeout(function() {
@ -167,6 +186,232 @@
}
}, 5000);
}
function popUpShow(title, body, actions, content) {
var popup = document.querySelector('.pop-up');
var popupContent = document.querySelector('.pop-up-content');
var popupActions = document.querySelector('.pop-up-controlls');
// Set tile and description
h3 = document.createElement('h3');
h3.innerHTML = title;
p = document.createElement('p');
p.innerHTML = body;
popupContent.innerHTML = '';
popupContent.appendChild(h3);
popupContent.appendChild(p);
// Set content
if (content != '') {
popupContent.innerHTML += content;
}
// Set buttons that will be displayed
popupActions.innerHTML = '';
if (actions != '') {
popupActions.innerHTML += actions;
}
popupActions.innerHTML += '<button class="pop-up__btn pop-up__btn-fill" onclick="popupDissmiss()">Nooooooo</button>';
// Show popup
popup.classList.add('pop-up__active');
}
function popupDissmiss() {
var popup = document.querySelector('.pop-up');
popup.classList.remove('pop-up__active');
}
{% if g.user %}
function showUpload() {
popUpShow(
'Upload funny stuff',
'May the world see your stuff 👀',
'',
'<form onsubmit="return uploadFile(event)">\
<input class="pop-up__input" type="file" id="file"/>\
<input class="pop-up__input" type="text" placeholder="alt" id="alt"/>\
<input class="pop-up__input" type="text" placeholder="description" id="description"/>\
<input class="pop-up__input" type="text" placeholder="tags" id="tags"/>\
<button class="pop-up__btn pop-up__btn-primary-fill">Upload</button>\
</form>'
);
};
function uploadFile(){
// AJAX takes control of subby form
event.preventDefault();
// Check for empty upload
if ($("#file").val() === "") {
addNotification("Please select a file to upload", 2);
} else {
// Make form
var formData = new FormData();
formData.append("file", $("#file").prop("files")[0]);
formData.append("alt", $("#alt").val());
formData.append("description", $("#description").val());
formData.append("tags", $("#tags").val());
formData.append("submit", $("#submit").val());
// Upload the information
$.ajax({
url: '/api/upload',
type: 'post',
data: formData,
contentType: false,
processData: false,
success: function (response) {
addNotification("File uploaded successfully!", 1);
// popupDissmiss(); // Close popup
},
error: function (response) {
switch (response.status) {
case 500:
addNotification('Server exploded, F\'s in chat', 2);
break;
case 400:
case 404:
addNotification('Error uploading. Blame yourself', 2);
break;
case 403:
addNotification('None but devils play past here...', 2);
break;
case 413:
addNotification('File too large!!!!!!', 3);
break;
default:
addNotification('Error uploading file, blame someone', 2);
break;
}
}
});
// Empty values
$("#file").val("");
$("#alt").val("");
$("#description").val("");
$("#tags").val("");
}
};
{% else %}
function showLogin() {
popUpShow(
'idk what to put here, just login please',
'Need an account? <span class="pop-up__link" onclick="showRegister()">Register!</span>',
'',
'<form onsubmit="return login(event)">\
<input class="pop-up__input" type="text" placeholder="Namey" id="username"/>\
<input class="pop-up__input" type="password" placeholder="Passywassy" id="password"/>\
<button class="pop-up__btn pop-up__btn-primary-fill">Login</button>\
</form>'
);
};
function showRegister() {
popUpShow(
'Who are you?',
'Already have an account? <span class="pop-up__link" onclick="showLogin()">Login!</span>',
'',
'<form onsubmit="return register(event)">\
<input class="pop-up__input" type="text" placeholder="Namey" id="username"/>\
<input class="pop-up__input" type="text" placeholder="E mail!" id="email"/>\
<input class="pop-up__input" type="password" placeholder="Passywassy" id="password"/>\
<input class="pop-up__input" type="password" placeholder="Passywassy again!" id="password-repeat"/>\
<button class="pop-up__btn pop-up__btn-primary-fill">Register</button>\
</form>'
);
};
function login(event) {
// AJAX takes control of subby form
event.preventDefault();
if ($("#username").val() === "" || $("#password").val() === "") {
addNotification("Please fill in all fields", 3);
} else {
// Make form
var formData = new FormData();
formData.append("username", $("#username").val());
formData.append("password", $("#password").val());
$.ajax({
url: '/auth/login',
type: 'post',
data: formData,
contentType: false,
processData: false,
success: function (response) {
location.reload();
},
error: function (response) {
switch (response.status) {
case 500:
addNotification('Server exploded, F\'s in chat', 2);
break;
case 403:
addNotification('None but devils play past here... Wrong information', 2);
break;
default:
addNotification('Error logging in, blame someone', 2);
break;
}
}
});
}
}
function register(obj) {
// AJAX takes control of subby form
event.preventDefault();
if ($("#username").val() === "" || $("#email").val() === "" || $("#password").val() === "" || $("#password-repeat").val() === "") {
addNotification("Please fill in all fields", 3);
} else {
// Make form
var formData = new FormData();
formData.append("username", $("#username").val());
formData.append("email", $("#email").val());
formData.append("password", $("#password").val());
formData.append("password-repeat", $("#password-repeat").val());
$.ajax({
url: '/auth/register',
type: 'post',
data: formData,
contentType: false,
processData: false,
success: function (response) {
if (response === "gwa gwa") {
addNotification('Registered successfully! Now please login to continue', 1);
showLogin();
} else {
for (var i = 0; i < response.length; i++) {
addNotification(response[i], 2);
}
}
},
error: function (response) {
switch (response.status) {
case 500:
addNotification('Server exploded, F\'s in chat', 2);
break;
case 403:
addNotification('None but devils play past here...', 2);
break;
default:
addNotification('Error logging in, blame someone', 2);
break;
}
}
});
}
}
{% endif %}
{% for message in get_flashed_messages() %}
// Show notifications on page load
addNotification('{{ message[0] }}', '{{ message[1] }}');
{% endfor %}
</script>
{% block script %}{% endblock %}
</body>
</html>

View file

@ -1,22 +1,25 @@
{% extends 'layout.html' %}
{% block header %}
<img src="{{ url_for('static', filename='images/background.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
{% endblock %}
{% block content %}
<div class="app">
<h1>User</h1>
<p>{{user_id}}</p>
<ul>
{% if g.user %}
<li><span>{{ g.user['username'] }}</span>
<li><a href="{{ url_for('auth.logout') }}">Log Out</a>
{% else %}
<li><a href="{{ url_for('auth.register') }}">Register</a>
<li><a href="{{ url_for('auth.login') }}">Log In</a>
{% endif %}
</ul>
<div class="background-decoration">
<img src="{{ url_for('static', filename='images/background.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
<span></span>
</div>
{% endblock %}
{% block nav_profile %}navigation-item__selected{% endblock %}
{% block content %}
<h1>User</h1>
<p>{{user_id}}</p>
<ul>
{% if g.user %}
<li><span>{{ g.user['username'] }}</span>
<li><a href="{{ url_for('auth.logout') }}">Log Out</a>
{% else %}
<li><a href="{{ url_for('auth.register') }}">Register</a>
<li><a href="{{ url_for('auth.login') }}">Log In</a>
{% endif %}
</ul>
{% endblock %}

View file

@ -1,11 +1,14 @@
{% extends 'layout.html' %}
{% block header %}
<img src="{{ url_for('static', filename='images/background.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
<div class="background-decoration">
<img src="{{ url_for('static', filename='images/background.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
<span></span>
</div>
{% endblock %}
{% block nav_settings %}navigation-item__selected{% endblock %}
{% block content %}
<div class="app">
<h1>Settings</h1>
</div>
<h1>Settings</h1>
{% endblock %}

View file

@ -1,97 +0,0 @@
{% extends 'layout.html' %}
{% block header %}
<img src="{{ url_for('static', filename='images/background.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
{% endblock %}
{% block content %}
<div class="app">
<div class="upload box-ui">
<div class="box-ui-header">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -2 24 24" fill="currentColor">
<path d="M3 0h10a3 3 0 0 1 3 3v14a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm2 1h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2zm0 12h2a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2zm0-4h6a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2zm0-4h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2z"></path>
</svg>
<h2>Upload!!!!!!!</h2>
</div>
<div class="box-ui-content">
<form method="post" class="nice-form" id="uploadForm">
<span class="form-box">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -5 24 24" width="24" fill="currentColor">
<path d="M8 3.414v5.642a1 1 0 1 1-2 0V3.414L4.879 4.536A1 1 0 0 1 3.464 3.12L6.293.293a1 1 0 0 1 1.414 0l2.829 2.828A1 1 0 1 1 9.12 4.536L8 3.414zM1 12h12a1 1 0 0 1 0 2H1a1 1 0 0 1 0-2z"></path>
</svg>
<input type="file" name="file" id="file" class="input-file"/>
</span>
<span class="form-box">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -2 24 24" width="24" fill="currentColor">
<path d="M14 8.322V2H2v12h3.576l3.97-5.292A3 3 0 0 1 14 8.322zm0 3.753l-1.188-2.066a1 1 0 0 0-1.667-.101L8.076 14H14v-1.925zM14 16H2v2h12v-2zM2 0h12a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm4 9a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"></path>
</svg>
<input type="text" name="alt" placeholder="alt" id="alt"/>
</span>
<span class="form-box">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" width="24" fill="currentColor">
<path d="M5.72 14.456l1.761-.508 10.603-10.73a.456.456 0 0 0-.003-.64l-.635-.642a.443.443 0 0 0-.632-.003L6.239 12.635l-.52 1.82zM18.703.664l.635.643c.876.887.884 2.318.016 3.196L8.428 15.561l-3.764 1.084a.901.901 0 0 1-1.11-.623.915.915 0 0 1-.002-.506l1.095-3.84L15.544.647a2.215 2.215 0 0 1 3.159.016zM7.184 1.817c.496 0 .898.407.898.909a.903.903 0 0 1-.898.909H3.592c-.992 0-1.796.814-1.796 1.817v10.906c0 1.004.804 1.818 1.796 1.818h10.776c.992 0 1.797-.814 1.797-1.818v-3.635c0-.502.402-.909.898-.909s.898.407.898.91v3.634c0 2.008-1.609 3.636-3.593 3.636H3.592C1.608 19.994 0 18.366 0 16.358V5.452c0-2.007 1.608-3.635 3.592-3.635h3.592z"></path>
</svg>
<input type="text" name="description" placeholder="description" id="description"/>
</span>
<span class="form-box">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -3 24 24" width="24" fill="currentColor">
<path d="M11.586 15.071L13 13.657l1.414 1.414 6.165-6.165 1.09-3.552-2.484-2.483-1.079.336-1.598-1.598L18.591.96a2 2 0 0 1 2.008.496l2.483 2.483a2 2 0 0 1 .498 2L22.345 9.97l-7.93 7.93-2.83-2.828zM14.236.75l2.482 2.483a2 2 0 0 1 .498 2l-1.235 4.028-7.93 7.931-7.78-7.778L8.17 1.516 12.227.254a2 2 0 0 1 2.008.496zM3.1 9.414l4.95 4.95 6.164-6.165 1.09-3.552-2.484-2.483-3.585 1.115L3.1 9.414zm7.424-2.475a1.5 1.5 0 1 1 2.121-2.121 1.5 1.5 0 0 1-2.12 2.121zm6.886 1.022l.782-2.878c.45.152.755.325.917.518a1.5 1.5 0 0 1-.185 2.113c-.29.244-.795.326-1.514.247z"></path>
</svg>
<input type="text" name="tags" placeholder="tags" id="tags"/>
</span>
<input class="form-button" type="submit" value="Upload" name="submit" id="submit"/>
</form>
</div>
</div>
</div>
<script>
$("#uploadForm").submit(function(event) {
// AJAX takes control of subby form
event.preventDefault();
// Check for empty upload
if ($("#file").val() === "") {
addNotification("Please select a file to upload", 2);
} else {
// Make form
var formData = new FormData();
formData.append("file", $("#file").prop("files")[0]);
formData.append("alt", $("#alt").val());
formData.append("description", $("#description").val());
formData.append("tags", $("#tags").val());
formData.append("submit", $("#submit").val());
// Upload the information
$.ajax({
url: '/api/upload',
type: 'post',
data: formData,
contentType: false,
processData: false,
success: function (response) {
addNotification("File uploaded successfully!", 1);
},
error: function (response) {
if (response.status === 500) {
addNotification('Error uploading file, blame the server', 2);
} else if (response.status === 400 || response.status === 404 || response.status === 403) {
addNotification('Error uploading file, blame yourself', 2);
} else {
addNotification('Error uploading file, blame someone', 2);
}
}
});
// Empty values
$("#file").val("");
$("#alt").val("");
$("#description").val("");
$("#tags").val("");
}
});
</script>
{% endblock %}

View file

@ -0,0 +1,13 @@
@keyframes imgLoading
0%
background-position: -468px 0
100%
background-position: 468px 0
@keyframes notificationTimeout
0%
left: -100%
100%
left: 0%

View file

@ -1,15 +0,0 @@
.btn {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
border: 1px solid rgba($white100, 0.3);
background-color: rgba($white100, 0);
color: $white100;
transition: background-color 0.3s ease-in-out, border-color 0.3s ease-in-out, color 0.3s ease-in-out;
&:active {
border-color: rgba($white100, 1);
color: $white100;
}
}

View file

@ -1,79 +0,0 @@
.tool-btn {
margin: 0;
padding: 0.5rem;
width: 2.5rem;
height: 2.5rem;
position: relative;
border: none;
background-color: transparent;
color: $white100;
svg {
width: 1.25rem;
height: 1.25rem;
}
&:hover {
cursor: pointer;
color: $green;
.tool-tip {
opacity: 1;
top: -2.5rem;
transform: translateX(calc(-50% + 1.25rem ));
}
}
}
.tool-btn--evil {
color: $red;
span {
color: $red;
}
&:hover {
color: $white100;
}
}
.tool-btn--blu {
color: $blue;
span {
color: $blue;
}
&:hover {
color: $white100;
}
}
.tool-tip {
margin: 0;
padding: 0.5rem 0.75rem;
width: auto;
display: block;
position: absolute;
top: -1.7rem;
left: 0;
transform: translateX(calc(-50% + 1.25rem ));
font-family: $font-body;
font-size: 1rem;
font-weight: 600;
background-color: $black300;
color: $white100;
opacity: 0;
border-radius: $rad;
transition: opacity 0.2s cubic-bezier(.76,0,.17,1), top 0.2s cubic-bezier(.76,0,.17,1);
pointer-events: none;
}

View file

@ -0,0 +1,29 @@
.jumpUp
margin: 0
padding: 0.5rem
width: 2.5rem
height: 2.5rem
position: fixed
bottom: 0.75rem
right: -3rem
display: flex // hidden
justify-content: center
align-items: center
border-radius: 50%
background-color: $black
color: $white
opacity: 0 // hidden
z-index: 2
cursor: pointer
transition: all 0.2s cubic-bezier(.86, 0, .07, 1)
&:hover
background-color: $black
color: $primary

View file

@ -0,0 +1,94 @@
.pill-row
margin: 0
padding: 0
width: 100%
height: auto
display: flex
justify-content: center
align-items: center
gap: 0.5rem
> div
margin: 0
padding: 0
display: flex
background-color: $black
border-radius: $rad
.pill-item
margin: 0
padding: 0.5rem
width: 2.5rem
height: 2.5rem
display: flex
justify-content: center
align-items: center
position: relative
border: none
background-color: transparent
color: $white
svg
width: 1.25rem
height: 1.25rem
&:hover
cursor: pointer
color: $primary
.tool-tip
opacity: 1
top: -2.5rem
transform: translateX(calc(-50% + 1.25rem ))
.pill__critical
color: $critical
span
color: $critical
&:hover
color: $white
.pill__info
color: $info
span
color: $info
&:hover
color: $white
.tool-tip
margin: 0
padding: 0.5rem 0.75rem
width: auto
display: block
position: absolute
top: -1.7rem
left: 0
transform: translateX(calc(-50% + 1.25rem ))
font-size: 1rem
font-weight: 600
background-color: $black2
color: $white
opacity: 0
border-radius: $rad-inner
transition: opacity 0.2s cubic-bezier(.76,0,.17,1), top 0.2s cubic-bezier(.76,0,.17,1)
pointer-events: none

View file

@ -1,30 +0,0 @@
#topButton {
margin: 0;
padding: 0.5rem;
width: 2.5rem;
height: 2.5rem;
position: fixed;
bottom: 0.75rem;
right: -3rem;
display: flex; // hidden
justify-content: center;
align-items: center;
border-radius: 50%;
background-color: $black300;
opacity: 0; // hidden
z-index: 2;
cursor: pointer;
transition: all 0.2s cubic-bezier(.86, 0, .07, 1);
&:hover {
background-color: $black200;
color: $green;
}
}

View file

@ -0,0 +1,39 @@
// Default theme for OnlyLegs by FluffyBean
// Mockup link: https://www.figma.com/file/IMZT5kZr3sAngrSHSGu5di/OnlyLegs?node-id=0%3A1
@import "variables"
@import "animations"
@import "ui/notification"
@import "ui/pop-up"
@import "ui/navigation"
@import "ui/content"
@import "ui/background"
@import "ui/gallery"
@import "buttons/jumpUp"
@import "buttons/pill"
// Reset
*
box-sizing: border-box
font-family: $font
html, body
margin: 0
padding: 0
min-height: 100vh
background-color: $white
scroll-behavior: smooth
.wrapper
margin: 0
padding: 0
min-height: 100vh
background-color: $white
color: $black

View file

@ -1,464 +0,0 @@
@import 'variables/variables';
@import 'variables/fonts';
@import 'ui/reset';
@import 'ui/nav';
@import 'ui/main';
@import 'ui/gallery';
@import 'ui/notification';
@import 'buttons/img-tool';
@import 'buttons/btn';
@import 'buttons/up';
.app {
margin: 0 0 0 3.5rem;
padding: 0.5rem;
width: auto;
min-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
gap: 1rem;
//background-color: $black100;
color: $white100;
box-sizing: border-box;
z-index: 1;
overflow: unset;
> h1 {
margin: 0 auto;
padding: 0;
font-family: $font-header;
font-size: 5rem;
font-weight: 900;
line-height: 1;
text-align: center;
color: $green;
}
> p {
margin: 0 auto;
padding: 0;
font-family: $font-body;
font-size: 2rem;
font-weight: 600;
line-height: 1;
text-align: center;
color: $white100;
}
/*
h1 {
margin: 0;
padding: 0;
font-family: $font-header;
font-size: 2.5rem;
font-stretch: ultra-expanded;
font-weight: 600;
color: $green;
}
*/
}
.box-ui {
margin: 0 auto;
padding: 0;
width: 100%;
height: 100%;
max-width: 621px;
position: relative;
display: flex;
flex-direction: column;
background-color: $black200;
border-radius: $rad;
}
.box-ui-header {
margin: 0;
padding: 0.5rem;
width: 100%;
height: 2.5rem;
display: flex;
justify-content: start;
align-items: center;
gap: 0.5rem;
background-color: $black300;
border-radius: $rad $rad 0 0;
svg {
margin: 0;
padding: 0;
width: 1.25rem;
height: 1.25rem;
fill: $green;
}
h2 {
margin: 0;
padding: 0;
font-family: $font-header;
font-size: 1.25rem;
font-stretch: ultra-expanded;
font-weight: 600;
color: $green;
text-overflow: ellipsis;
overflow: hidden;
}
}
.box-ui-content {
margin: 0;
padding: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
p {
margin: 0;
padding: 0;
font-family: $font-body;
font-size: 1rem;
font-weight: 500;
color: $white100;
text-overflow: ellipsis;
overflow: hidden;
}
}
.nice-form {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-box {
margin: 0;
padding: 0;
width: 100%;
height: 2rem;
display: flex;
flex-direction: row;
svg {
margin: 0;
padding: 0.3rem;
width: 2rem;
height: 2rem;
background-color: $black300;
color: $white100;
border: 1px solid $black400;
border-radius: $rad 0 0 $rad;
border-right: none;
}
input {
margin: 0;
padding: 0.5rem;
width: 100%;
height: 2rem;
font-family: $font-body;
font-size: 1rem;
font-weight: 500;
color: $white100;
background-color: $black200;
border: 1px solid $black400;
border-radius: 0 $rad $rad 0;
border-left: none;
&:focus {
outline: none;
~ svg {
color: $green;
background-color: $black200;
}
}
&::placeholder {
color: $white100;
}
}
}
.form-button {
margin: 0;
padding: 0;
width: 100%;
height: 2rem;
display: flex;
justify-content: center;
align-items: center;
font-family: $font-body;
font-size: 1rem;
font-weight: 500;
text-align: center;
text-decoration: none;
color: $white100;
background-color: $black300;
border: 1px solid $black400;
border-radius: $rad;
&:hover {
cursor: pointer;
color: $green;
}
}
.faded {
background-color: $black200;
border-color: $black200;
}
.err-warning {
min-height: 100vh;
gap: 0;
> h1 {
margin: 0 auto;
padding: 0;
font-family: $font-header;
font-size: 5rem;
font-weight: 900;
line-height: 1;
text-align: center;
color: $green;
}
> p {
margin: 0 auto;
padding: 0;
font-family: $font-body;
font-size: 2rem;
font-weight: 600;
line-height: 1;
text-align: center;
color: $white100;
}
}
.image__fullscreen {
margin: 0;
padding: 0 0 0 3.5rem;
width: 100%;
height: 100dvh;
position: fixed;
top: -100%;
left: 0;
display: flex;
opacity: 0; // hide
background-color: rgba($black100, 0.8);
backdrop-filter: blur(1rem);
z-index: 21;
box-sizing: border-box;
img {
margin: auto;
padding: 0;
width: auto;
height: auto;
max-width: calc(100% - 1rem);
max-height: calc(100% - 1rem);
object-fit: contain;
object-position: center;
transform: scale(0.8);
border-radius: $rad;
}
}
.image__fullscreen--active {
top: 0;
opacity: 1; // show
transition: opacity 0.3s cubic-bezier(.79, .14, .15, .86);
img {
transform: scale(1);
transition: transform 0.2s cubic-bezier(.68,-0.55,.27,1.55);
}
}
.image__container {
margin: 0;
padding: 0;
width: 100%;
height: auto;
max-width: 100%;
max-height: 69vh;
position: sticky;
top: 0;
display: flex;
overflow: hidden;
background: linear-gradient(-45deg, $black100, $black400 40%, $black100);
background-size: 400% 400%;
border-radius: $rad;
animation: imgLoading 10s ease infinite;
border-radius: $rad;
box-sizing: border-box;
img {
margin: auto;
padding: 0;
width: auto;
height: auto;
max-width: 100%;
max-height: 69vh;
background-color: $black200;
object-fit: contain;
object-position: center;
border-radius: $rad;
}
}
.image__info {
margin: 0;
padding: 0;
width: 100%;
display: flex;
flex-direction: column;
background-color: $black200;
border-radius: $rad;
//border-left: $rad solid $green;
box-sizing: border-box;
}
.image__info-header {
margin: 0;
padding: 0.5rem;
width: 100%;
height: 2.5rem;
display: flex;
justify-content: start;
align-items: center;
gap: 0.5rem;
background-color: $black300;
border-radius: $rad $rad 0 0;
svg {
margin: 0;
padding: 0;
width: 1.25rem;
height: 1.25rem;
fill: $green;
}
h2 {
margin: 0;
padding: 0;
font-family: $font-header;
font-size: 1.25rem;
font-stretch: ultra-expanded;
font-weight: 600;
color: $green;
text-overflow: ellipsis;
overflow: hidden;
}
}
.image__info-content {
margin: 0;
padding: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
p {
margin: 0;
padding: 0;
font-family: $font-body;
font-size: 1rem;
font-weight: 500;
color: $white100;
text-overflow: ellipsis;
overflow: hidden;
}
}
.img-tools {
width: 100%;
height: 2rem;
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
> div {
margin: 0;
padding: 0;
display: flex;
//gap: 0.5rem;
background-color: $black200;
border-radius: $rad;
}
}

View file

@ -0,0 +1,45 @@
.background-decoration
margin: 0
padding: 0
width: 100%
height: 100vh
position: fixed
top: 0
left: 0
background-color: $white
background-image: linear-gradient(to right, darken($white, 1%) 15%, darken($white, 10%) 35%, darken($white, 1%) 50%)
background-size: 1000px 640px
animation: imgLoading 1.8s linear infinite forwards
user-select: none
overflow: hidden
img
position: absolute
top: 0
left: 0
width: 100%
height: 100%
filter: blur(1rem)
transform: scale(1.1)
object-fit: cover
object-position: center center
span
position: absolute
top: 0
left: 0
width: 100%
height: 100%
background-image: linear-gradient(to bottom, rgba($white, 0), rgba($white, 1))
z-index: +1

View file

@ -0,0 +1,14 @@
@import "wrappers/index"
@import "wrappers/image"
.content
width: calc(100% - 3.5rem)
min-height: 100vh
position: relative
left: 3.5rem
@media (max-width: $breakpoint)
.content
width: 100%
left: 0

View file

@ -0,0 +1,117 @@
.gallery
margin: 0
padding: 0
width: 100%
display: grid
grid-template-columns: auto auto auto auto auto auto
gap: 0.5rem
@media (max-width: 1300px)
.gallery
grid-template-columns: auto auto auto auto
@media (max-width: 800px)
.gallery
grid-template-columns: auto auto auto
.gallery__item
margin: 0
padding: 0
height: auto
position: relative
background: linear-gradient(to right, darken($white, 1%) 15%, darken($white, 10%) 35%, darken($white, 1%) 50%)
background-size: 1000px 640px
animation: imgLoading 1.8s linear infinite forwards
border-radius: $rad
box-sizing: border-box
overflow: hidden
&:after
content: ""
display: block
padding-bottom: 100%
.gallery__item-info
margin: 0
padding: 0
width: 100%
height: 100%
position: absolute
left: 0
bottom: 0
display: flex
flex-direction: column
justify-content: flex-end
background-image: linear-gradient(to bottom, rgba($black, 0), rgba($black, 0.8))
z-index: +1
opacity: 0 // hide
transform: scale(1.05) // scale up
transition: all 0.5s cubic-bezier(.79, .14, .15, .86)
h2
margin: 0
padding: 0 1rem 0.5rem
font-size: 1rem
font-weight: 600
color: $primary
text-overflow: ellipsis
overflow: hidden
opacity: 0 // hide
transition: all 0.2s ease-in-out
p
margin: 0
padding: 0 1rem 0.5rem
font-size: 0.8rem
font-weight: 500
color: $white
text-overflow: ellipsis
overflow: hidden
opacity: 0 // hide
transition: all 0.2s ease-in-out
&:hover
opacity: 1
transform: scale(1)
h2, p
opacity: 1
.gallery__item-image
margin: 0
padding: 0
width: 100%
height: 100%
position: absolute
top: 0
left: 0
right: 0
bottom: 0
object-fit: cover
object-position: center
//background-color: $black
border-radius: $rad

View file

@ -1,161 +0,0 @@
@keyframes imgLoading {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.gallery {
margin: 0;
padding: 0;
width: 100%;
display: grid;
grid-template-columns: auto auto auto auto auto auto auto auto;
gap: 0.5rem;
}
@media (max-width: 1550px) {
.gallery {
grid-template-columns: auto auto auto auto auto auto auto;
}
}
@media (max-width: 1300px) {
.gallery {
grid-template-columns: auto auto auto auto auto auto;
}
}
@media (max-width: 1050px) {
.gallery {
grid-template-columns: auto auto auto auto auto;
}
}
@media (max-width: 800px) {
.gallery {
grid-template-columns: auto auto auto auto;
}
}
@media (max-width: 550px) {
.gallery {
grid-template-columns: auto auto auto;
}
}
.gallery__item {
margin: 0;
padding: 0;
height: auto;
position: relative;
background: linear-gradient(-45deg, $black100, $black400, $black100);
background-size: 400% 400%;
border-radius: $rad;
animation: imgLoading 10s ease infinite;
box-sizing: border-box;
overflow: hidden;
&:after {
content: "";
display: block;
padding-bottom: 100%;
}
}
.gallery__item-info {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
position: absolute;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: flex-end;
background-image: linear-gradient(to bottom, #00000000, rgba($black100, 0.8));
z-index: +1;
opacity: 0; // hide
transform: scale(1.05); // scale up
transition: all 0.5s cubic-bezier(.79, .14, .15, .86);
h2 {
margin: 0;
padding: 0 1rem 0.5rem;
font-family: $font-header;
font-size: 1rem;
font-stretch: ultra-expanded;
font-weight: 600;
color: $green;
text-overflow: ellipsis;
overflow: hidden;
opacity: 0; // hide
transition: all 0.2s ease-in-out;
}
p {
margin: 0;
padding: 0 1rem 0.5rem;
font-family: $font-body;
font-size: 0.8rem;
font-weight: 500;
color: $white100;
text-overflow: ellipsis;
overflow: hidden;
opacity: 0; // hide
transition: all 0.2s ease-in-out;
}
&:hover {
opacity: 1;
transform: scale(1);
h2,
p {
opacity: 1;
}
}
}
.gallery__item-image {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
object-fit: cover;
object-position: center;
background-color: $black200;
border-radius: $rad;
}

View file

@ -1,57 +0,0 @@
main {
margin: 0;
padding: 0;
background-color: $black100;
color: $white100;
min-height: 100vh;
overflow-y: auto;
box-sizing: border-box;
header {
margin: 0;
padding: 0;
width: 100%;
height: 69vh;
position: fixed;
top: 0;
left: 0;
background-color: $black200;
border-radius: $rad;
box-sizing: border-box;
user-select: none;
img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center 0px;
}
span {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: linear-gradient(to bottom, #00000000, rgba($black100, 1));
backdrop-filter: blur(1rem);
z-index: +1;
}
}
}

View file

@ -1,95 +0,0 @@
nav {
margin: 0;
padding: 0;
max-width: 100vw;
width: 3.5rem;
height: 100dvh;
display: flex;
flex-direction: column;
justify-content: space-between;
position: fixed;
top: 0;
left: 0;
background-color: $black300;
color: $white100;
box-sizing: border-box;
z-index: 69;
transition: width 0.4s cubic-bezier(.76,0,.17,1), background-color 0.3s ease-in-out;
div {
display: flex;
flex-direction: column;
//gap: 0.25rem;
a {
margin: 0;
padding: 1rem;
width: 3.5rem;
height: 3.5rem;
display: flex;
flex-direction: row;
align-items: center;
//gap: 0.5rem;
position: relative;
text-decoration: none;
color: $white100;
box-sizing: border-box;
i, svg {
margin: 0;
font-size: 1.5rem;
color: $white100;
transition: color 0.2s ease-out;
}
span {
margin: 0;
padding: 0.5rem 0.75rem;
display: block;
position: absolute;
top: 50%;
left: 3rem;
transform: translateY(-50%);
font-family: $font-body;
font-size: 1rem;
font-weight: 600;
background-color: $black300;
color: $white100;
opacity: 0;
border-radius: $rad;
transition: opacity 0.2s cubic-bezier(.76,0,.17,1), left 0.2s cubic-bezier(.76,0,.17,1);
pointer-events: none;
}
&:hover {
//background-color: $black200;
i, svg {
color: $green;
}
span {
opacity: 1;
left: 3.8rem;
transform: translateY(-50%);
}
}
}
}
}

View file

@ -0,0 +1,127 @@
.navigation
margin: 0
padding: 0
width: 3.5rem
height: 100dvh
display: flex
flex-direction: column
justify-content: space-between
position: fixed
top: 0
left: 0
background-color: $black2
color: $white
z-index: 69
// Spacer
> span
height: 100%
.navigation-item
margin: 0
padding: 1rem
width: 3.5rem
height: 3.5rem
display: flex
flex-direction: row
align-items: center
position: relative
background-color: $black2
border: none
text-decoration: none
svg
margin: 0
font-size: 1.5rem
color: $white
transition: color 0.2s ease-out
span
margin: 0
padding: 0.5rem 0.75rem
display: block
position: absolute
top: 50%
left: 3rem
transform: translateY(-50%)
font-size: 1rem
font-weight: 600
background-color: $black2
color: $white
opacity: 0
border-radius: $rad-inner
transition: opacity 0.2s cubic-bezier(.76,0,.17,1), left 0.2s cubic-bezier(.76,0,.17,1)
pointer-events: none
&:hover
background-color: $black2
svg
color: $primary
span
opacity: 1
left: 3.8rem
.navigation-item__selected
background: $primary
svg
color: $black
&:hover
background: $primary
svg
color: $white
@media (max-width: $breakpoint)
.navigation
width: 100vw
height: 3.5rem
flex-direction: row
justify-content: space-around
position: fixed
top: unset
bottom: 0
left: 0
> span
display: none
.navigation-item
margin: 0.25rem
padding: 0
width: 3rem
height: 3rem
border-radius: $rad-inner
svg
margin: auto
width: 1.5rem
height: 1.5rem
span
display: none

View file

@ -0,0 +1,131 @@
@mixin notification($color)
color: $color
.sniffle__notification-time
background-color: $color
.notifications
margin: 0
padding: 0
width: 450px
height: auto
position: fixed
top: 0.3rem
right: 0.3rem
display: flex
flex-direction: column
gap: 0.3rem
z-index: 70
.sniffle__notification
margin: 0
padding: 0
width: 450px
height: auto
display: flex
flex-direction: row
position: relative
background-color: $black
border-radius: $rad-inner
color: $white
opacity: 0
transform: scale(0.8)
box-sizing: border-box
overflow: hidden
transition: all 0.25s ease-in-out, opacity 0.2s ease-in-out, transform 0.2s cubic-bezier(.68,-0.55,.27,1.55)
.sniffle__notification-icon
margin: 0
padding: 1rem
width: auto
height: auto
display: flex
justify-content: center
align-items: center
background-color: $black2
svg
width: 1.25rem
height: 1.25rem
.sniffle__notification-text
margin: 0
padding: 1rem
width: auto
height: auto
display: flex
flex-direction: column
justify-self: center
align-self: center
font-size: 1rem
font-weight: 600
line-height: 1
text-align: left
.sniffle__notification-time
margin: 0
padding: 0
width: 450px
height: 3px
position: absolute
bottom: 0px
left: 0px
background-color: $white
animation: notificationTimeout 4.9s linear
.sniffle__notification--success
@include notification($succes)
.sniffle__notification--error
@include notification($critical)
.sniffle__notification--warning
@include notification($warning)
.sniffle__notification--info
@include notification($info)
.sniffle__notification-show
opacity: 1
transform: scale(1)
.sniffle__notification--hide
opacity: 0
transform: translateX(100%)
transition: all 0.25s ease-in-out
@media (max-width: $breakpoint)
.notifications
width: calc(100vw - 0.6rem)
height: auto
.sniffle__notification
width: 100%
.sniffle__notification-time
width: 100%
.sniffle__notification--hide
opacity: 0
transform: translateY(-5rem)

View file

@ -1,131 +0,0 @@
@keyframes sniffle {
0% {
left: -450px;
}
100% {
left: 0;
}
}
.sniffle {
margin: 0;
padding: 0;
width: 450px;
height: auto;
position: fixed;
top: 0.3rem;
right: 0.3rem;
display: flex;
flex-direction: column;
gap: 0.3rem;
z-index: 6969;
}
.sniffle__notification {
margin: 0;
padding: 0;
width: 450px;
height: auto;
display: flex;
flex-direction: row;
position: relative;
background-color: $black200;
border-radius: $rad;
color: $white100;
box-sizing: border-box;
overflow: hidden;
transition: all 0.25s ease-in-out;
}
.sniffle__notification-icon {
margin: 0;
padding: 1rem;
width: auto;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: $black300;
svg {
width: 1.25rem;
height: 1.25rem;
}
}
.sniffle__notification-text {
margin: 0;
padding: 1rem;
width: auto;
height: auto;
display: flex;
flex-direction: column;
justify-self: center;
align-self: center;
font-family: $font-body;
font-size: 1rem;
font-weight: 600;
line-height: 1;
text-align: left;
}
.sniffle__notification-time {
margin: 0;
padding: 0;
width: 450px;
height: 3px;
position: absolute;
bottom: 0px;
left: 0px;
background-color: $white100;
animation: sniffle 4.9s linear;
}
.sniffle__notification--success {
color: $green;
.sniffle__notification-time {
background-color: $green;
}
}
.sniffle__notification--error {
color: $red;
.sniffle__notification-time {
background-color: $red;
}
}
.sniffle__notification--warning {
color: $yellow;
.sniffle__notification-time {
background-color: $yellow;
}
}
.sniffle__notification--info {
color: $blue;
.sniffle__notification-time {
background-color: $blue;
}
}
.sniffle__notification--hide {
opacity: 0;
transform: translateX(100%);
}

View file

@ -0,0 +1,259 @@
@mixin pop-up-btn($color, $fill: false)
@if $fill
color: $white
background-color: $color
border: 2px solid $color
&:hover
background-color: $white
color: $color
@else
color: $color
background-color: $white
border: 2px solid $color
&:hover
background-color: $color
color: $white
.pop-up
width: calc(100% - 3.5rem)
height: 100vh
position: fixed
top: 100%
left: 3.5rem
background-color: rgba($black, 0.8)
backdrop-filter: blur(1rem)
opacity: 0
z-index: 68
transition: opacity 0.2s ease
.pop-up-wrapper
margin: 0
padding: 0.5rem
width: 621px
height: auto
position: absolute
bottom: 50%
left: 50%
transform: translate(-50%, 50%) scale(0.8)
display: flex
flex-direction: column
gap: 0.5rem
background-color: $white
border-radius: $rad
overflow: hidden
transition: transform 0.2s cubic-bezier(.68,-0.55,.27,1.55)
.pop-up-content
margin: 0
padding: 0
width: 100%
height: auto
max-height: 50vh
display: flex
flex-direction: column
gap: 0.5rem
overflow-y: auto
overflow-x: hidden
text-size-adjust: auto;
text-overflow: ellipsis
h3
margin: 0
width: 100%
position: sticky
top: 0
font-size: 2.5rem
font-weight: 600
text-align: center
line-height: 1
color: $black
background-color: $white
p
margin: 0
width: 100%
font-size: 1rem
font-weight: 500
text-align: center
line-height: 1
color: $black
img
margin: auto
padding: 0
width: auto
height: auto
max-width: 100%
max-height: 40vh
border-radius: $rad-inner
form
margin: 0
padding: 0
width: 100%
height: auto
display: flex
flex-direction: column
gap: 0.5rem
justify-content: center
.pop-up-controlls
margin: 0
padding: 0
width: 100%
height: auto
display: flex
flex-direction: column
gap: 0.5rem
justify-content: center
.pop-up__btn
margin: 0
padding: 0.5rem
width: 100%
height: 2.5rem
display: flex
justify-content: center
align-items: center
font-size: 1rem
font-weight: 600
text-align: center
line-height: 1
border-radius: $rad-inner
cursor: pointer
transition: background-color 0.2s ease, color 0.2s ease
@include pop-up-btn($black)
&:focus
outline: none
.pop-up__btn-fill
@include pop-up-btn($black, true)
.pop-up__btn-primary
@include pop-up-btn($primary)
.pop-up__btn-primary-fill
@include pop-up-btn($primary, true)
.pop-up__btn-info
@include pop-up-btn($info)
.pop-up__btn-info-fill
@include pop-up-btn($info, true)
.pop-up__btn-warning
@include pop-up-btn($warning)
.pop-up__btn-warning-fill
@include pop-up-btn($warning, true)
.pop-up__btn-critical
@include pop-up-btn($critical)
.pop-up__btn-critical-fill
@include pop-up-btn($critical, true)
.pop-up__input
margin: 0
padding: 0.5rem
width: 100%
height: 2.5rem
font-size: 1rem
font-weight: 600
text-align: left
line-height: 1
border-radius: $rad-inner
background-color: $white
border: 2px solid $black
&:focus
outline: none
border-color: $primary
.pop-up__link
color: $primary
text-decoration: none
&:hover
text-decoration: underline
cursor: pointer
.pop-up__active
opacity: 1
top: 0
.pop-up-wrapper
transform: translate(-50%, 50%) scale(1)
@media (max-width: $breakpoint)
.pop-up
width: 100%
height: calc(100vh - 3.5rem)
height: calc(100dvh - 3.5rem)
position: fixed
left: 0
bottom: 3.5rem
backdrop-filter: blur(0.5rem)
.pop-up-wrapper
width: 100%
max-height: calc(100vh - 1rem)
max-height: calc(100dvh - 1rem)
left: 0
bottom: 0
border-radius: $rad
transform: translateY(5rem)
box-shadow: 0px 8px 0px $black2;
.pop-up-content
max-height: 100%
img
max-height: 50vh
.pop-up__active
opacity: 1
top: unset
.pop-up-wrapper
transform: translateY(0)

View file

@ -1,16 +0,0 @@
* {
box-sizing: border-box;
line-height: 1;
}
html,
body {
margin: 0;
padding: 0;
min-height: 100vh;
background-color: $black100;
scroll-behavior: smooth;
}

View file

@ -0,0 +1,216 @@
.image-wrapper
padding: 0
display: grid
grid-template-areas: 'info image' 'info tools'
grid-template-columns: 25rem 1fr
grid-template-rows: 1fr auto
gap: 0
height: 100vh
.image-fullscreen
margin: 0
padding: 0 0 0 3.5rem
width: 100%
height: 100dvh
position: fixed
top: -100%
left: 0
display: flex
opacity: 0 // hide
background-color: rgba($black, 0.8)
backdrop-filter: blur(1rem)
z-index: 21
box-sizing: border-box
img
margin: auto
padding: 0
width: auto
height: auto
max-width: 100%
max-height: 100%
object-fit: contain
object-position: center
transform: scale(0.8)
&__active
top: 0
opacity: 1 // show
transition: opacity 0.3s cubic-bezier(.79, .14, .15, .86)
img
transform: scale(1)
transition: transform 0.2s cubic-bezier(.68,-0.55,.27,1.55)
.image-container
margin: auto
padding: 0.5rem
width: 100%
height: 100%
display: flex
overflow: hidden
grid-area: image
img
margin: auto
padding: 0
width: auto
height: auto
max-width: 100%
max-height: 100%
object-fit: contain
object-position: center
border-radius: $rad
.image-info__container
margin: 0
padding: 0
width: 100%
height: 100%
display: flex
flex-direction: column
background-color: $black
overflow-y: auto
grid-area: info
.image-info
margin: 0
padding: 0
width: 100%
display: flex
flex-direction: column
background-color: $black
border-radius: $rad
.image-info__header
margin: 0
padding: 0.5rem
width: 100%
height: 2.5rem
display: flex
justify-content: start
align-items: center
gap: 0.5rem
background-color: $black2
svg
margin: 0
padding: 0
width: 1.25rem
height: 1.25rem
fill: $primary
h2
margin: 0
padding: 0
font-size: 1.25rem
font-weight: 600
color: $primary
text-overflow: ellipsis
overflow: hidden
.image-info__content
margin: 0
padding: 0.5rem
display: flex
flex-direction: column
gap: 0.5rem
p
margin: 0
padding: 0
font-size: 1rem
font-weight: 500
color: $white
text-overflow: ellipsis
overflow: hidden
#image-tools
margin-bottom: 0.5rem
grid-area: tools
@media (max-width: 1100px)
.image-wrapper
padding: 0.5rem
display: flex !important
flex-direction: column
gap: 0.5rem
height: auto
.image-container
margin: 0 auto
padding: 0
max-height: 69vh
img
max-height: 69vh
#image-tools
margin: 0
.image-info__container
margin: 0
padding: 0
width: 100%
height: auto
display: flex
flex-direction: column
gap: 0.5rem
background: none
.image-info__container
border-radius: $rad
.image-info__header
border-radius: $rad $rad 0 0
@media (max-width: $breakpoint)
.image-fullscreen
padding: 0 0 3.5rem 0
.image-wrapper
padding-bottom: 4rem

View file

@ -0,0 +1,12 @@
.index-wrapper
padding: 0.5rem
position: relative
display: flex
flex-direction: column
gap: 0.5rem
@media (max-width: $breakpoint)
.index-wrapper
padding-bottom: 4rem

View file

@ -0,0 +1,40 @@
$black: #151515
$black2: #101010
$white: #E8E3E3
$red: #B66467
$orange: #D8A657
$yellow: #D9BC8C
$green: #8C977D
$blue: #8DA3B9
$purple: #A988B0
$primary: $green
$warning: $orange
$critical: $red
$succes: $green
$info: $blue
$rad: 8px
$rad-inner: 3px
//$font: "Work Sans", sans-serif
$font: "Work Sans", sans-serif
$breakpoint: 800px // responsive breakpoint for mobile
// Work Sans
@font-face
font-family: 'Work Sans'
src: url('fonts/worksans-regular.woff2')
font-weight: 400
@font-face
font-family: 'Work Sans'
src: url('fonts/worksans-bold.woff2')
font-weight: 600
@font-face
font-family: 'Work Sans'
src: url('fonts/worksans-black.woff2')
font-weight: 900

View file

@ -1,35 +0,0 @@
@font-face {
font-family: "Mona-Sans";
src: url("fonts/Mona-Sans.woff2") format("woff2 supports variations"),
url("fonts/Mona-Sans.woff2") format("woff2-variations");
font-weight: 200 900;
font-stretch: 75% 125%;
font-display: swap;
}
@font-face {
font-family: "Hubot-Sans";
src: url("fonts/Hubot-Sans.woff2") format("woff2 supports variations"),
url("fonts/Hubot-Sans.woff2") format("woff2-variations");
font-weight: 200 900;
font-stretch: 75% 125%;
font-display: swap;
}
@font-face {
font-family: 'Work Sans';
src: url('fonts/worksans-regular.woff2');
font-weight: 400;
}
@font-face {
font-family: 'Work Sans';
src: url('fonts/worksans-bold.woff2');
font-weight: 600;
}
@font-face {
font-family: 'Work Sans';
src: url('fonts/worksans-black.woff2');
font-weight: 900;
}

View file

@ -1,18 +0,0 @@
$black100: #1a1a1a;
$black200: #151515;
$black300: #101010;
$black400: #0b0b0b;
$white100: #e8e3e3;
$red: #B66467;
$orange: #D8A657;
$yellow: #D9BC8C;
$green: #8C977D;
$blue: #8DA3B9;
$purple: #A988B0;
$rad: 3px;
$font-header: "Work Sans", sans-serif;
$font-body: "Work Sans", sans-serif;

View file

@ -2,11 +2,12 @@ from setuptools import find_packages, setup
setup(
name='onlylegs',
version='170123',
version='210123',
packages=find_packages(),
include_package_data=True,
install_requires=[
'flask',
'flask-compress',
'libsass',
'python-dotenv',
'pillow',