Add basic Image Group functions

Make the Upload Pannel usable on mobile
Remove useless code as Django had built-in functions to read the config
Remove useless JS code
Cleanup tempaltes
This commit is contained in:
Michał 2023-03-09 23:31:58 +00:00
parent 35c5951318
commit e3a0eaf60b
18 changed files with 531 additions and 213 deletions

View file

@ -84,6 +84,8 @@ def create_app(test_config=None):
ALLOWED_EXTENSIONS=conf['upload']['allowed-extensions'], ALLOWED_EXTENSIONS=conf['upload']['allowed-extensions'],
MAX_CONTENT_LENGTH=1024 * 1024 * conf['upload']['max-size'], MAX_CONTENT_LENGTH=1024 * 1024 * conf['upload']['max-size'],
WEBSITE=conf['website'], WEBSITE=conf['website'],
ADMIN=conf['admin'],
APP_VERSION='23.03.09',
) )
if test_config is None: if test_config is None:
@ -139,6 +141,10 @@ def create_app(test_config=None):
app.register_blueprint(routing.blueprint) app.register_blueprint(routing.blueprint)
app.add_url_rule('/', endpoint='index') app.add_url_rule('/', endpoint='index')
# Load routes for groups
from . import groups
app.register_blueprint(groups.blueprint)
# Load routes for settings # Load routes for settings
from . import settings from . import settings
app.register_blueprint(settings.blueprint) app.register_blueprint(settings.blueprint)

View file

@ -6,10 +6,11 @@ from uuid import uuid4
import os import os
import io import io
import logging import logging
import json
from datetime import datetime as dt from datetime import datetime as dt
from flask import ( from flask import (
Blueprint, current_app, send_from_directory, send_file, request, g, abort, flash, jsonify) Blueprint, send_from_directory, send_file, abort, flash, jsonify, request, g, current_app)
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from colorthief import ColorThief from colorthief import ColorThief
@ -128,7 +129,6 @@ def upload():
if os.path.isdir(current_app.config['UPLOAD_FOLDER']) is False: if os.path.isdir(current_app.config['UPLOAD_FOLDER']) is False:
os.mkdir(current_app.config['UPLOAD_FOLDER']) os.mkdir(current_app.config['UPLOAD_FOLDER'])
# Save file # Save file
try: try:
form_file.save(img_path) form_file.save(img_path)
@ -159,14 +159,13 @@ def upload():
return 'Gwa Gwa' return 'Gwa Gwa'
@blueprint.route('/delete/<int:image_id>', methods=['POST'])
@blueprint.route('/remove/<int:img_id>', methods=['POST'])
@login_required @login_required
def remove(img_id): def delete_image(image_id):
""" """
Deletes an image from the server and database Deletes an image from the server and database
""" """
img = db_session.query(db.Posts).filter_by(id=img_id).first() img = db_session.query(db.Posts).filter_by(id=image_id).first()
if img is None: if img is None:
abort(404) abort(404)
@ -183,17 +182,65 @@ def remove(img_id):
abort(500) abort(500)
try: try:
db_session.query(db.Posts).filter_by(id=img_id).delete() db_session.query(db.Posts).filter_by(id=image_id).delete()
groups = db_session.query(db.GroupJunction).filter_by(post_id=image_id).all()
for group in groups:
db_session.delete(group)
db_session.commit() db_session.commit()
except Exception as err: except Exception as err:
logging.error('Could not remove from database: %s', err) logging.error('Could not remove from database: %s', err)
abort(500) abort(500)
logging.info('Removed image (%s) %s', img_id, img.file_name) logging.info('Removed image (%s) %s', image_id, img.file_name)
flash(['Image was all in Le Head!', 1]) flash(['Image was all in Le Head!', 1])
return 'Gwa Gwa' return 'Gwa Gwa'
@blueprint.route('/group/create', methods=['POST'])
@login_required
def create_group():
"""
Creates a group
"""
group_name = request.form['name']
group_description = request.form['description']
group_author = g.user.id
new_group = db.Groups(name=group_name,
description=group_description,
author_id=group_author,
created_at=dt.utcnow())
db_session.add(new_group)
db_session.commit()
return ':3'
@blueprint.route('/group/modify', methods=['POST'])
@login_required
def modify_group():
"""
Changes the images in a group
"""
group_id = request.form['group_id']
image_id = request.form['images']
action = request.form['action']
if action == 'add':
# Check if image is already in group
if db_session.query(db.GroupJunction).filter_by(group_id=group_id, post_id=image_id).first() is None:
db_session.add(db.GroupJunction(group_id=group_id, post_id=image_id, date_added=dt.utcnow()))
db_session.commit()
elif action == 'remove':
db_session.query(db.GroupJunction).filter_by(group_id=group_id, post_id=image_id).delete()
db_session.commit()
return ':3'
@blueprint.route('/metadata/<int:img_id>', methods=['GET']) @blueprint.route('/metadata/<int:img_id>', methods=['GET'])
def metadata(img_id): def metadata(img_id):
""" """

View file

@ -3,7 +3,6 @@ OnlyLegs - Database
Database models and functions for SQLAlchemy Database models and functions for SQLAlchemy
""" """
import os import os
from datetime import datetime
import platformdirs import platformdirs
from sqlalchemy import create_engine, Column, Integer, String, Boolean, DateTime, ForeignKey, PickleType from sqlalchemy import create_engine, Column, Integer, String, Boolean, DateTime, ForeignKey, PickleType
@ -83,6 +82,7 @@ class GroupJunction (base): # pylint: disable=too-few-public-methods, C0103
__tablename__ = 'group_junction' __tablename__ = 'group_junction'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
date_added = Column(DateTime, nullable=False)
group_id = Column(Integer, ForeignKey('groups.id')) group_id = Column(Integer, ForeignKey('groups.id'))
post_id = Column(Integer, ForeignKey('posts.id')) post_id = Column(Integer, ForeignKey('posts.id'))

89
gallery/groups.py Normal file
View file

@ -0,0 +1,89 @@
"""
Onlylegs - API endpoints
Used intermally by the frontend and possibly by other applications
"""
import logging
import json
from datetime import datetime as dt
from flask import Blueprint, abort, jsonify, render_template, url_for, request, g
from sqlalchemy.orm import sessionmaker
from gallery.auth import login_required
from . import db # Import db to create a session
blueprint = Blueprint('group', __name__, url_prefix='/group')
db_session = sessionmaker(bind=db.engine)
db_session = db_session()
@blueprint.route('/', methods=['GET'])
def groups():
"""
Group overview, shows all image groups
"""
groups = db_session.query(db.Groups).all()
for group in groups:
thumbnail = db_session.query(db.GroupJunction.post_id).filter(db.GroupJunction.group_id == group.id).order_by(db.GroupJunction.date_added.desc()).first()
if thumbnail is not None:
thumbnail_filename = db_session.query(db.Posts.file_name).filter(db.Posts.id == thumbnail[0]).first()
group.thumbnail = thumbnail_filename[0]
else:
group.thumbnail = None
return render_template('groups/list.html', groups=groups)
@blueprint.route('/<int:group_id>')
def group(group_id):
"""
Group view, shows all images in a group
"""
group = db_session.query(db.Groups).filter(db.Groups.id == group_id).first()
if group is None:
abort(404, 'Group not found')
group_images = db_session.query(db.GroupJunction.post_id).filter(db.GroupJunction.group_id == group_id).order_by(db.GroupJunction.date_added.desc()).all()
images = []
for image in group_images:
image = db_session.query(db.Posts).filter(db.Posts.id == image[0]).first()
images.append(image)
return render_template('groups/group.html', group=group, images=images)
@blueprint.route('/<int:group_id>/<int:image_id>')
def group_post(group_id, image_id):
"""
Image view, shows the image and its metadata from a specific group
"""
img = db_session.query(db.Posts).filter(db.Posts.id == image_id).first()
if img is None:
abort(404, 'Image not found')
img.author_username = db_session.query(db.Users.username).filter(db.Users.id == img.author_id).first()[0]
groups = db_session.query(db.GroupJunction.group_id).filter(db.GroupJunction.post_id == image_id).all()
img.groups = []
for group in groups:
group = db_session.query(db.Groups).filter(db.Groups.id == group[0]).first()
img.groups.append(group)
next = db_session.query(db.GroupJunction.post_id).filter(db.GroupJunction.group_id == group_id).filter(db.GroupJunction.post_id > image_id).order_by(db.GroupJunction.date_added.asc()).first()
prev = db_session.query(db.GroupJunction.post_id).filter(db.GroupJunction.group_id == group_id).filter(db.GroupJunction.post_id < image_id).order_by(db.GroupJunction.date_added.desc()).first()
if next is not None:
next = url_for('group.group_post', group_id=group_id, image_id=next[0])
if prev is not None:
prev = url_for('group.group_post', group_id=group_id, image_id=prev[0])
return render_template('image.html',
image=img,
next_url=next,
prev_url=prev)

View file

@ -3,7 +3,7 @@ Onlylegs Gallery - Routing
""" """
from datetime import datetime as dt from datetime import datetime as dt
from flask import Blueprint, render_template, current_app, request, g from flask import Blueprint, render_template, url_for
from werkzeug.exceptions import abort from werkzeug.exceptions import abort
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
@ -24,15 +24,10 @@ def index():
""" """
images = db_session.query(db.Posts.file_name, images = db_session.query(db.Posts.file_name,
db.Posts.image_colours, db.Posts.image_colours,
db.Posts.author_id,
db.Posts.created_at, db.Posts.created_at,
db.Posts.id).order_by(db.Posts.id.desc()).all() db.Posts.id).order_by(db.Posts.id.desc()).all()
return render_template('index.html', return render_template('index.html', images=images)
images=images,
image_count=len(images),
name=current_app.config['WEBSITE']['name'],
motto=current_app.config['WEBSITE']['motto'])
@blueprint.route('/image/<int:image_id>') @blueprint.route('/image/<int:image_id>')
def image(image_id): def image(image_id):
@ -42,53 +37,25 @@ def image(image_id):
img = db_session.query(db.Posts).filter(db.Posts.id == image_id).first() img = db_session.query(db.Posts).filter(db.Posts.id == image_id).first()
if img is None: if img is None:
abort(404, 'Image not found') abort(404, 'Image not found :<')
author = db_session.query(db.Users.username).filter(db.Users.id == img.author_id).first()[0] img.author_username = db_session.query(db.Users.username).filter(db.Users.id == img.author_id).first()[0]
img.author_username = author
groups = db_session.query(db.GroupJunction.group_id).filter(db.GroupJunction.post_id == image_id).all()
img.groups = []
for group in groups:
group = db_session.query(db.Groups).filter(db.Groups.id == group[0]).first()
img.groups.append(group)
next = db_session.query(db.Posts.id).filter(db.Posts.id > image_id).order_by(db.Posts.id.asc()).first() next = db_session.query(db.Posts.id).filter(db.Posts.id > image_id).order_by(db.Posts.id.asc()).first()
prev = db_session.query(db.Posts.id).filter(db.Posts.id < image_id).order_by(db.Posts.id.desc()).first() prev = db_session.query(db.Posts.id).filter(db.Posts.id < image_id).order_by(db.Posts.id.desc()).first()
if next is not None: if next is not None:
next = next[0] next = url_for('gallery.image', image_id=next[0])
if prev is not None: if prev is not None:
prev = prev[0] prev = url_for('gallery.image', image_id=prev[0])
return render_template('image.html', return render_template('image.html', image=img, next_url=next, prev_url=prev)
image=img,
exif=img.image_exif,
next=next,
prev=prev,
next_id=next,
prev_id=prev)
@blueprint.route('/group', methods=['GET', 'POST'])
def groups():
"""
Group overview, shows all image groups
"""
if request.method == 'GET':
groups = db_session.query(db.Groups.name, db.Groups.author_id).all()
return render_template('group.html', groups=groups)
elif request.method == 'POST':
group_name = request.form['name']
group_description = request.form['description']
group_author = g.user.id
new_group = db.Groups(name=group_name, description=group_description, author_id=group_author, created_at=dt.now())
db_session.add(new_group)
return ':3'
@blueprint.route('/group/<int:group_id>')
def group(group_id):
"""
Group view, shows all images in a group
"""
return render_template('group.html', group_id=group_id)
@blueprint.route('/profile') @blueprint.route('/profile')
def profile(): def profile():

View file

@ -1,18 +1,3 @@
let navToggle = true;
document.onscroll = function() {
if (document.body.scrollTop > 300 || document.documentElement.scrollTop > 20) {
document.querySelector('.jumpUp').classList = 'jumpUp jumpUp--show';
} else {
document.querySelector('.jumpUp').classList = 'jumpUp';
}
}
document.querySelector('.jumpUp').onclick = function() {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
}
function imgFade(obj) { function imgFade(obj) {
$(obj).animate({opacity: 1}, 250); $(obj).animate({opacity: 1}, 250);
} }
@ -36,6 +21,43 @@ for (let i = 0; i < times.length; i++) {
times[i].innerHTML = dateTime.toLocaleDateString() + ' ' + dateTime.toLocaleTimeString(); times[i].innerHTML = dateTime.toLocaleDateString() + ' ' + dateTime.toLocaleTimeString();
} }
let images = document.querySelectorAll('.gallery-item img');
function loadOnView() {
for (let i = 0; i < images.length; i++) {
let image = images[i];
if (image.getBoundingClientRect().top < window.innerHeight && image.getBoundingClientRect().bottom > 0) {
if (!image.src) {
image.src = `/api/uploads/${image.getAttribute('data-src')}?w=500&h=500`
}
}
}
}
if (images.length > 0) {
window.onload = function() {
loadOnView();
};
window.onscroll = function() {
loadOnView();
};
window.onresize = function() {
loadOnView();
};
}
document.onscroll = function() {
if (document.body.scrollTop > 300 || document.documentElement.scrollTop > 20) {
document.querySelector('.jumpUp').classList = 'jumpUp jumpUp--show';
} else {
document.querySelector('.jumpUp').classList = 'jumpUp';
}
}
document.querySelector('.jumpUp').onclick = function() {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
}
function uploadFile(){ function uploadFile(){
// AJAX takes control of subby form // AJAX takes control of subby form
event.preventDefault(); event.preventDefault();

View file

@ -1,43 +0,0 @@
{% extends 'layout.html' %}
{% block nav_groups %}navigation-item__selected{% endblock %}
{% block content %}
<div class="banner">
<img src="{{ url_for('static', filename='images/bg.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
<span></span>
<div class="banner-content">
{% block banner_subtitle%}{% endblock %}
<h1>Groups</h1>
<p>gwa gwa</p>
</div>
</div>
<div class="gallery-grid">
{% for group in groups %}
<a id="group-{{ group['id'] }}" class="gallery-item" href="/group/{{ group['id'] }}">
<span>
<p>{{ group['id'] }}</p>
<h2><span class="time">{{ group['created_at'] }}</span></h2>
</span>
<img
data-src="{{ group['file_name'] }}"
onload="imgFade(this)"
style="opacity:0;"
/>
</a>
{% endfor %}
</div>
<form action="/group" method="post" enctype="multipart/form-data">
<input type="text" name="name" placeholder="name">
<input type="text" name="description" placeholder="description">
<button type="submit">Submit</button>
</form>
{% endblock %}
{% block script %}
<script>
</script>
{% endblock %}

View file

@ -0,0 +1,60 @@
{% extends 'layout.html' %}
{% block nav_groups %}navigation-item__selected{% endblock %}
{% block content %}
<div class="banner">
{% if images %}
<img
src="/api/uploads/{{ images.0.file_name }}?w=1920&h=1080"
onload="imgFade(this)"
style="opacity:0; background-color:rgb({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }})"
/>
<span
style="background-image: linear-gradient(to right, rgb({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }}), transparent)"
></span>
{% else %}
<img src="{{ url_for('static', filename='images/bg.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
<span></span>
{% endif %}
<div class="banner-content">
<p>{{ group.description }}</p>
<h1>{{ group.name }}</h1>
<p>{{ images|length }} Images</p>
</div>
</div>
<form action="/api/group/modify" method="post" enctype="multipart/form-data">
<input type="text" name="group_id" placeholder="group id" value="{{ group.id }}">
<input type="text" name="images" placeholder="image id">
<input type="text" name="action" placeholder="add/remove" value="add">
<button type="submit">Submit</button>
</form>
{% if images %}
<div class="gallery-grid">
{% for image in images %}
<a id="image-{{ image.id }}" class="gallery-item" href="{{ url_for('group.group_post', group_id=group.id, image_id=image.id) }}" style="background-color: rgb({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }})">
<span>
<p></p>
<h2><span class="time">{{ image.created_at }}</span></h2>
</span>
<img data-src="{{ image.file_name }}" onload="imgFade(this)" style="opacity:0;"/>
</a>
{% endfor %}
</div>
{% else %}
<div class="big-text">
<h1>No image!</h1>
{% if g.user %}
<p>You can get started by uploading an image!</p>
{% else %}
<p>Login to start uploading images!</p>
{% endif %}
</div>
{% endif %}
{% endblock %}
{% block script %}
<script></script>
{% endblock %}

View file

@ -0,0 +1,52 @@
{% extends 'layout.html' %}
{% block nav_groups %}navigation-item__selected{% endblock %}
{% block content %}
<div class="banner">
<img src="{{ url_for('static', filename='images/bg.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
<span></span>
<div class="banner-content">
<p>{{ config.WEBSITE.motto }}</p>
<h1>Groups</h1>
<p>{{ groups|length }} Groups</p>
</div>
</div>
<form action="/api/group/create" method="post" enctype="multipart/form-data">
<input type="text" name="name" placeholder="name">
<input type="text" name="description" placeholder="description">
<button type="submit">Submit</button>
</form>
{% if groups %}
<div class="gallery-grid">
{% for group in groups %}
<a id="group-{{ group.id }}" class="gallery-item" href="{{ url_for('group.group', group_id=group.id) }}">
<span>
<h2>{{ group.name }}</h2>
</span>
{% if group.thumbnail %}
<img data-src="{{ group.thumbnail }}" onload="imgFade(this)" style="opacity:0;"/>
{% else %}
<img src="{{ url_for('static', filename='images/error.png') }}" onload="imgFade(this)" style="opacity:0;"/>
{% endif %}
</a>
{% endfor %}
</div>
{% else %}
<div class="big-text">
<h1>No image groups!</h1>
{% if g.user %}
<p>You can get started by creating a new image group!</p>
{% else %}
<p>Login to get started!</p>
{% endif %}
</div>
{% endif %}
{% endblock %}
{% block script %}
<script></script>
{% endblock %}

View file

@ -4,8 +4,8 @@
{% block content %} {% block content %}
<div class="background"> <div class="background">
<img src="/api/uploads/{{ image['file_name'] }}?w=1000&h=1000" onload="imgFade(this)" style="opacity:0;"/> <img src="/api/uploads/{{ image.file_name }}?w=1000&h=1000" onload="imgFade(this)" style="opacity:0;"/>
<span style="background-image: linear-gradient(to top, rgba({{ image['image_colours'][0][0] }}, {{ image['image_colours'][0][1] }}, {{ image['image_colours'][0][2] }}, 1), transparent);"></span> <span style="background-image: linear-gradient(to top, rgba({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }}, 1), transparent);"></span>
</div> </div>
<div class="image-fullscreen"> <div class="image-fullscreen">
@ -15,18 +15,18 @@
<div class="image-grid"> <div class="image-grid">
<div class="image-container" id="image-container"> <div class="image-container" id="image-container">
<img <img
src="/api/uploads/{{ image['file_name'] }}?w=1000&h=1000" src="/api/uploads/{{ image.file_name }}?w=1000&h=1000"
onload="imgFade(this)" style="opacity:0;" onload="imgFade(this)" style="opacity:0;"
onerror="this.src='/static/images/error.png'" onerror="this.src='/static/images/error.png'"
width="{{ exif['File']['Width']['raw'] }}" width="{{ image.image_exif.File.Width.raw }}"
height="{{ exif['File']['Height']['raw'] }}" height="{{ image.image_exif.File.Height.raw }}"
/> />
</div> </div>
<div class="pill-row" id="image-tools"> <div class="pill-row" id="image-tools">
{% if next_id %} {% if next_url %}
<div> <div>
<a class="pill-item" href="{{ url_for('gallery.image', image_id=next_id) }}"> <a class="pill-item" href="{{ next_url }}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -5 24 24" width="24" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -5 24 24" width="24" fill="currentColor">
<path d="M3.414 7.657l3.95 3.95A1 1 0 0 1 5.95 13.02L.293 7.364a.997.997 0 0 1 0-1.414L5.95.293a1 1 0 1 1 1.414 1.414l-3.95 3.95H13a1 1 0 0 1 0 2H3.414z"></path> <path d="M3.414 7.657l3.95 3.95A1 1 0 0 1 5.95 13.02L.293 7.364a.997.997 0 0 1 0-1.414L5.95.293a1 1 0 1 1 1.414 1.414l-3.95 3.95H13a1 1 0 0 1 0 2H3.414z"></path>
</svg> </svg>
@ -52,7 +52,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,48,88H208a8,8,0,0,1,5.66,13.66Z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,48,88H208a8,8,0,0,1,5.66,13.66Z"></path></svg>
</span> </span>
</button> </button>
<a class="pill-item" id="img-download" href="/api/uploads/{{ image['file_name'] }}" download> <a class="pill-item" id="img-download" href="/api/uploads/{{ image.file_name }}" download>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M232,136v64a8,8,0,0,1-8,8H32a8,8,0,0,1-8-8V136a8,8,0,0,1,8-8H224A8,8,0,0,1,232,136Z" opacity="0.2"></path><path d="M240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16H72a8,8,0,0,1,0,16H32v64H224V136H184a8,8,0,0,1,0-16h40A16,16,0,0,1,240,136Zm-117.66-2.34a8,8,0,0,0,11.32,0l48-48a8,8,0,0,0-11.32-11.32L136,108.69V24a8,8,0,0,0-16,0v84.69L85.66,74.34A8,8,0,0,0,74.34,85.66ZM200,168a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M232,136v64a8,8,0,0,1-8,8H32a8,8,0,0,1-8-8V136a8,8,0,0,1,8-8H224A8,8,0,0,1,232,136Z" opacity="0.2"></path><path d="M240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16H72a8,8,0,0,1,0,16H32v64H224V136H184a8,8,0,0,1,0-16h40A16,16,0,0,1,240,136Zm-117.66-2.34a8,8,0,0,0,11.32,0l48-48a8,8,0,0,0-11.32-11.32L136,108.69V24a8,8,0,0,0-16,0v84.69L85.66,74.34A8,8,0,0,0,74.34,85.66ZM200,168a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z"></path></svg>
<span class="tool-tip"> <span class="tool-tip">
Download Download
@ -60,7 +60,7 @@
</span> </span>
</a> </a>
</div> </div>
{% if g.user['id'] == image['author_id'] %} {% if image.author_id == g.user.id %}
<div> <div>
<button class="pill-item pill__critical" id="img-delete"> <button class="pill-item pill__critical" id="img-delete">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M200,56V208a8,8,0,0,1-8,8H64a8,8,0,0,1-8-8V56Z" opacity="0.2"></path><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M200,56V208a8,8,0,0,1-8,8H64a8,8,0,0,1-8-8V56Z" opacity="0.2"></path><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"></path></svg>
@ -78,9 +78,9 @@
</button> </button>
</div> </div>
{% endif %} {% endif %}
{% if prev_id %} {% if prev_url %}
<div> <div>
<a class="pill-item" href="{{ url_for('gallery.image', image_id=prev_id) }}"> <a class="pill-item" href="{{ prev_url }}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -5 24 24" width="24" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -5 24 24" width="24" fill="currentColor">
<path d="M10.586 5.657l-3.95-3.95A1 1 0 0 1 8.05.293l5.657 5.657a.997.997 0 0 1 0 1.414L8.05 13.021a1 1 0 1 1-1.414-1.414l3.95-3.95H1a1 1 0 1 1 0-2h9.586z"></path> <path d="M10.586 5.657l-3.95-3.95A1 1 0 0 1 8.05.293l5.657 5.657a.997.997 0 0 1 0 1.414L8.05 13.021a1 1 0 1 1-1.414-1.414l3.95-3.95H1a1 1 0 1 1 0-2h9.586z"></path>
</svg> </svg>
@ -94,23 +94,23 @@
</div> </div>
<div class="info-container" id="image-info"> <div class="info-container" id="image-info">
{% if image['post_description'] %} {% if image.post_description %}
<div class="info-tab"> <div class="info-tab">
<div class="info-header"> <div class="info-header">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#e8e3e3" viewBox="0 0 256 256"><path d="M224,56V200a8,8,0,0,1-8,8H40a8,8,0,0,1-8-8V56a8,8,0,0,1,8-8H216A8,8,0,0,1,224,56Z" opacity="0.2"></path><path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,160H40V56H216V200ZM184,96a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,96Zm0,32a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,128Zm0,32a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,160Z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M224,56V200a8,8,0,0,1-8,8H40a8,8,0,0,1-8-8V56a8,8,0,0,1,8-8H216A8,8,0,0,1,224,56Z" opacity="0.2"></path><path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,160H40V56H216V200ZM184,96a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,96Zm0,32a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,128Zm0,32a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,160Z"></path></svg>
<h2>Description</h2> <h2>Description</h2>
<svg class="collapse-indicator" xmlns="http://www.w3.org/2000/svg" viewBox="-5 -8 24 24" fill="currentColor"> <svg class="collapse-indicator" xmlns="http://www.w3.org/2000/svg" viewBox="-5 -8 24 24" fill="currentColor">
<path d="M7.071 5.314l4.95-4.95a1 1 0 1 1 1.414 1.414L7.778 7.435a1 1 0 0 1-1.414 0L.707 1.778A1 1 0 1 1 2.121.364l4.95 4.95z"></path> <path d="M7.071 5.314l4.95-4.95a1 1 0 1 1 1.414 1.414L7.778 7.435a1 1 0 0 1-1.414 0L.707 1.778A1 1 0 1 1 2.121.364l4.95 4.95z"></path>
</svg> </svg>
</div> </div>
<div class="info-table"> <div class="info-table">
<p>{{ image['post_description'] }}</p> <p>{{ image.post_description }}</p>
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div class="info-tab"> <div class="info-tab">
<div class="info-header"> <div class="info-header">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#e8e3e3" viewBox="0 0 256 256"><path d="M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z" opacity="0.2"></path><path d="M144,176a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176Zm88-48A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128ZM124,96a12,12,0,1,0-12-12A12,12,0,0,0,124,96Z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z" opacity="0.2"></path><path d="M144,176a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176Zm88-48A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128ZM124,96a12,12,0,1,0-12-12A12,12,0,0,0,124,96Z"></path></svg>
<h2>Info</h2> <h2>Info</h2>
<svg class="collapse-indicator" xmlns="http://www.w3.org/2000/svg" viewBox="-5 -8 24 24" fill="currentColor"> <svg class="collapse-indicator" xmlns="http://www.w3.org/2000/svg" viewBox="-5 -8 24 24" fill="currentColor">
<path d="M7.071 5.314l4.95-4.95a1 1 0 1 1 1.414 1.414L7.778 7.435a1 1 0 0 1-1.414 0L.707 1.778A1 1 0 1 1 2.121.364l4.95 4.95z"></path> <path d="M7.071 5.314l4.95-4.95a1 1 0 1 1 1.414 1.414L7.778 7.435a1 1 0 0 1-1.414 0L.707 1.778A1 1 0 1 1 2.121.364l4.95 4.95z"></path>
@ -124,21 +124,35 @@
</tr> </tr>
<tr> <tr>
<td>Author</td> <td>Author</td>
<td>{{ image['author_username'] }}</td> <td>{{ image.author_username }}</td>
</tr> </tr>
<tr> <tr>
<td>Upload date</td> <td>Upload date</td>
<td><span class="time">{{ image['created_at'] }}</span></td> <td><span class="time">{{ image.created_at }}</span></td>
</tr> </tr>
</table> </table>
<div class="img-colours"> <div class="img-colours">
{% for col in image.image_colours %} {% for col in image.image_colours %}
<span style="background-color: rgb({{col[0]}}, {{col[1]}}, {{col[2]}})"></span> <span style="background-color: rgb({{col.0}}, {{col.1}}, {{col.2}})"></span>
{% endfor %} {% endfor %}
</div> </div>
<div class="img-groups">
{% for group in image.groups %}
<a href="/group/{{ group.id }}" class="tag-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M224,88V200.89a7.11,7.11,0,0,1-7.11,7.11H40a8,8,0,0,1-8-8V64a8,8,0,0,1,8-8H93.33a8,8,0,0,1,4.8,1.6l27.74,20.8a8,8,0,0,0,4.8,1.6H216A8,8,0,0,1,224,88Z" opacity="0.2"></path><path d="M216,72H130.67L102.93,51.2a16.12,16.12,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V200a16,16,0,0,0,16,16H216.89A15.13,15.13,0,0,0,232,200.89V88A16,16,0,0,0,216,72Zm0,128H40V64H93.33l27.74,20.8a16.12,16.12,0,0,0,9.6,3.2H216Z"></path></svg>
{{ group['name'] }}
</a>
{% endfor %}
{% if image.author_id == g.user.id %}
<button class="tag-icon" id="#img-group">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z"></path></svg>
Add
</button>
{% endif %}
</div> </div>
</div> </div>
{% for tag in exif %} </div>
{% for tag in image.image_exif %}
<div class="info-tab"> <div class="info-tab">
<div class="info-header"> <div class="info-header">
{% if tag == 'Photographer' %} {% if tag == 'Photographer' %}
@ -163,17 +177,17 @@
</div> </div>
<div class="info-table"> <div class="info-table">
<table> <table>
{% for subtag in exif[tag] %} {% for subtag in image.image_exif[tag] %}
<tr> <tr>
<td>{{ subtag }}</td> <td>{{ subtag }}</td>
{% if exif[tag][subtag]['formatted'] %} {% if image.image_exif[tag][subtag]['formatted'] %}
{% if exif[tag][subtag]['type'] == 'date' %} {% if image.image_exif[tag][subtag]['type'] == 'date' %}
<td><span class="time">{{exif[tag][subtag]['formatted']}}</span></td> <td><span class="time">{{ image.image_exif[tag][subtag]['formatted'] }}</span></td>
{% else %} {% else %}
<td>{{exif[tag][subtag]['formatted']}}</td> <td>{{ image.image_exif[tag][subtag]['formatted'] }}</td>
{% endif %} {% endif %}
{% elif exif[tag][subtag]['raw'] %} {% elif image.image_exif[tag][subtag]['raw'] %}
<td>{{exif[tag][subtag]['raw']}}</td> <td>{{ image.image_exif[tag][subtag]['raw'] }}</td>
{% else %} {% else %}
<td class="empty-table">Oops, an error</td> <td class="empty-table">Oops, an error</td>
{% endif %} {% endif %}
@ -213,7 +227,7 @@
$('.image-fullscreen').addClass('image-fullscreen__active'); $('.image-fullscreen').addClass('image-fullscreen__active');
if ($('.image-fullscreen img').attr('src') == '') { if ($('.image-fullscreen img').attr('src') == '') {
$('.image-fullscreen img').attr('src', '/api/uploads/{{ image['file_name'] }}'); $('.image-fullscreen img').attr('src', '/api/uploads/{{ image.file_name }}');
} }
}); });
@ -235,18 +249,14 @@
'DESTRUCTION!!!!!!', 'DESTRUCTION!!!!!!',
'This will delete the image and all of its data!!! This action is irreversible!!!!! Are you sure you want to do this?????', '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>', '<button class="pop-up__btn pop-up__btn-critical-fill" onclick="deleteImage()">Dewww eeeet!</button>',
'<img src="/api/uploads/{{ image['file_name'] }}?w=1000&h=1000" />' '<img src="/api/uploads/{{ image.file_name }}?w=1000&h=1000" />'
); );
}); });
$('#img-edit').click(function() {
window.location.href = '/image/{{ image['id'] }}/edit';
});
function deleteImage() { function deleteImage() {
popupDissmiss(); popupDissmiss();
$.ajax({ $.ajax({
url: '/api/remove/{{ image['id'] }}', url: '{{ url_for('api.delete_image', image_id=image['id']) }}',
type: 'post', type: 'post',
data: { data: {
action: 'delete' action: 'delete'
@ -259,6 +269,10 @@
} }
}); });
} }
$('#img-edit').click(function() {
window.location.href = '/image/{{ image.id }}/edit';
});
{% endif %} {% endif %}
</script> </script>
{% endblock %} {% endblock %}

View file

@ -8,53 +8,45 @@
<span></span> <span></span>
<div class="banner-content"> <div class="banner-content">
<p>{{ motto }}</p> <p>{{ config.WEBSITE.motto }}</p>
<h1>{{ name }}</h1> <h1>{{ config.WEBSITE.name }}</h1>
<p>Serving {{ image_count }} images</p> <p>Serving {{ images|length }} images</p>
</div> </div>
</div> </div>
{% if images %}
<div class="gallery-grid"> <div class="gallery-grid">
{% for image in images %} {% for image in images %}
<a id="image-{{ image['id'] }}" class="gallery-item" href="/image/{{ image['id'] }}" style="background-color: rgb({{ image['image_colours'][0][0] }}, {{ image['image_colours'][0][1] }}, {{ image['image_colours'][0][2] }})"> <a id="image-{{ image.id }}" class="gallery-item" href="/image/{{ image.id }}" style="background-color: rgb({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }})">
<span> <span>
<p></p> <p></p>
<h2><span class="time">{{ image['created_at'] }}</span></h2> <h2><span class="time">{{ image.created_at }}</span></h2>
</span> </span>
<img <img data-src="{{ image.file_name }}" onload="imgFade(this)" style="opacity:0;"/>
data-src="{{ image['file_name'] }}"
onload="imgFade(this)"
style="opacity:0;"
/>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
{% else %}
<div class="big-text">
<h1>No image!</h1>
{% if g.user %}
<p>You can get started by uploading an image!</p>
{% else %}
<p>Login to start uploading images!</p>
{% endif %}
</div>
{% endif %}
<!--
<footer>
<a>V{{ config['APP_VERSION'] }}</a>
<p>Rights reserved to {{ config['ADMIN']['name'] }}</p>
</footer>
-->
{% endblock %} {% endblock %}
{% block script %} {% block script %}
<script> <script>
function loadOnView() {
var images = document.querySelectorAll('.gallery-item img');
for (var i = 0; i < images.length; i++) {
var image = images[i];
if (image.getBoundingClientRect().top < window.innerHeight && image.getBoundingClientRect().bottom > 0) {
if (!image.src) {
image.src = `/api/uploads/${image.getAttribute('data-src')}?w=500&h=500`
}
}
}
}
window.onload = function() {
loadOnView();
};
window.onscroll = function() {
loadOnView();
};
window.onresize = function() {
loadOnView();
};
if (document.referrer.includes('image')) { if (document.referrer.includes('image')) {
try { try {
var referrerId = document.referrer.split('/').pop(); var referrerId = document.referrer.split('/').pop();

View file

@ -50,7 +50,7 @@
</span> </span>
</a> </a>
<a href="{{url_for('gallery.groups')}}" class="navigation-item {% block nav_groups %}{% endblock %}"> <a href="{{url_for('group.groups')}}" class="navigation-item {% block nav_groups %}{% endblock %}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M208,88v24H146.42a8.07,8.07,0,0,0-4.44,1.34l-20,13.32a8.07,8.07,0,0,1-4.44,1.34H69.42A8,8,0,0,0,62,133L32,208V64a8,8,0,0,1,8-8H93.33a8,8,0,0,1,4.8,1.6l27.74,20.8a8,8,0,0,0,4.8,1.6H200A8,8,0,0,1,208,88Z" opacity="0.2"></path><path d="M245,110.64A16,16,0,0,0,232,104H216V88a16,16,0,0,0-16-16H130.67L102.94,51.2a16.14,16.14,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V208h0a8,8,0,0,0,8,8H211.1a8,8,0,0,0,7.59-5.47l28.49-85.47A16.05,16.05,0,0,0,245,110.64ZM93.34,64l27.73,20.8a16.12,16.12,0,0,0,9.6,3.2H200v16H146.43a16,16,0,0,0-8.88,2.69l-20,13.31H69.42a15.94,15.94,0,0,0-14.86,10.06L40,166.46V64Zm112,136H43.82l25.6-64h48.16a16,16,0,0,0,8.88-2.69l20-13.31H232Z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M208,88v24H146.42a8.07,8.07,0,0,0-4.44,1.34l-20,13.32a8.07,8.07,0,0,1-4.44,1.34H69.42A8,8,0,0,0,62,133L32,208V64a8,8,0,0,1,8-8H93.33a8,8,0,0,1,4.8,1.6l27.74,20.8a8,8,0,0,0,4.8,1.6H200A8,8,0,0,1,208,88Z" opacity="0.2"></path><path d="M245,110.64A16,16,0,0,0,232,104H216V88a16,16,0,0,0-16-16H130.67L102.94,51.2a16.14,16.14,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V208h0a8,8,0,0,0,8,8H211.1a8,8,0,0,0,7.59-5.47l28.49-85.47A16.05,16.05,0,0,0,245,110.64ZM93.34,64l27.73,20.8a16.12,16.12,0,0,0,9.6,3.2H200v16H146.43a16,16,0,0,0-8.88,2.69l-20,13.31H69.42a15.94,15.94,0,0,0-14.86,10.06L40,166.46V64Zm112,136H43.82l25.6-64h48.16a16,16,0,0,0,8.88-2.69l20-13.31H232Z"></path></svg>
<span> <span>
Groups Groups
@ -97,11 +97,6 @@
{% endif %} {% endif %}
</div> </div>
<div class="content">
{% block content %}
{% endblock %}
</div>
{% if g.user %} {% if g.user %}
<div class="upload-panel"> <div class="upload-panel">
<span class="click-off" onclick="closeUploadTab()"></span> <span class="click-off" onclick="closeUploadTab()"></span>
@ -119,6 +114,11 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div class="content">
{% block content %}
{% endblock %}
</div>
</div> </div>
<script> <script>

View file

@ -0,0 +1,24 @@
.tag-icon
margin: 0
padding: 0.25rem 0.5rem
display: flex
align-items: center
justify-content: center
gap: 0.25rem
font-size: 1rem
font-weight: 500
text-decoration: none
border-radius: $rad-inner
background-color: $primary
color: $black
border: none
cursor: pointer
svg
width: 1.15rem
height: 1.15rem

View file

@ -1,11 +1,11 @@
.upload-panel .upload-panel
position: fixed position: fixed
top: 0 left: 3.5rem
left: 0 bottom: 0
display: none display: none
width: 100% width: calc(100% - 3.5rem)
height: 100vh height: 100vh
background-color: rgba($black, 0) background-color: rgba($black, 0)
@ -98,8 +98,8 @@
padding: 0.5rem padding: 0.5rem
position: absolute position: absolute
top: 0 bottom: 0
left: calc(-400px + 3.5rem) left: -400px
width: 400px width: 400px
height: 100% height: 100%
@ -113,7 +113,7 @@
z-index: +2 z-index: +2
transition: left 0.25s cubic-bezier(0.76, 0, 0.17, 1), opacity 0.25s cubic-bezier(0.76, 0, 0.17, 1) transition: left 0.25s cubic-bezier(0.76, 0, 0.17, 1), bottom 0.25s cubic-bezier(0.76, 0, 0.17, 1), opacity 0.25s cubic-bezier(0.76, 0, 0.17, 1)
.upload-jobs .upload-jobs
display: flex display: flex
@ -213,7 +213,27 @@
background-color: rgba($black, 0.5) background-color: rgba($black, 0.5)
.container .container
left: 3.5rem left: 0
opacity: 1 opacity: 1
@media (max-width: $breakpoint)
.upload-panel
width: 100%
height: calc(100vh - 3.5rem)
left: 0
bottom: 3.5rem
.container
padding: 1rem
width: 100%
height: calc(100% - 10rem)
left: 0
bottom: calc(-100vh + 3.5rem)
&.open
.container
left: 0
bottom: 0

View file

@ -44,7 +44,7 @@
transform: scale(1.05) // scale up transform: scale(1.05) // scale up
transition: all 0.3s cubic-bezier(.79, .14, .15, .86) transition: all 0.3s cubic-bezier(.79, .14, .15, .86)
h2 > h2
margin: 0 margin: 0
padding: 0 padding: 0
@ -59,7 +59,7 @@
opacity: 0 // hide opacity: 0 // hide
transition: all 0.2s ease-in-out transition: all 0.2s ease-in-out
p > p
margin: 0 margin: 0
padding: 0 padding: 0
@ -101,7 +101,7 @@
opacity: 1 opacity: 1
transform: scale(1) transform: scale(1)
h2, p > h2, > p
opacity: 1 opacity: 1
img img

View file

@ -4,6 +4,7 @@
display: flex display: flex
flex-direction: column flex-direction: column
gap: 0.5rem
background-color: $black background-color: $black
@ -93,7 +94,7 @@
display: flex display: flex
flex-direction: column flex-direction: column
gap: 0.5rem gap: 1rem
color: $white color: $white
@ -163,9 +164,6 @@
padding-bottom: 0 padding-bottom: 0
.img-colours .img-colours
margin: 0
padding: 0
width: 100% width: 100%
display: flex display: flex
@ -182,5 +180,11 @@
justify-content: center justify-content: center
align-items: center align-items: center
border-radius: $rad border-radius: $rad-inner
border: 1px solid $white // border: 1px solid $white
.img-groups
width: 100%
display: flex
gap: 0.5rem

View file

@ -51,7 +51,6 @@
padding: 0 padding: 0
.info-container .info-container
gap: 0.5rem
background: transparent background: transparent
.info-header .info-header

View file

@ -7,6 +7,7 @@
@import "components/elements/notification" @import "components/elements/notification"
@import "components/elements/pop-up" @import "components/elements/pop-up"
@import "components/elements/upload-panel" @import "components/elements/upload-panel"
@import "components/elements/tags"
@import "components/navigation" @import "components/navigation"
@import "components/banner" @import "components/banner"
@ -46,6 +47,30 @@ html, body
background-color: $white background-color: $white
color: $black color: $black
.big-text
height: 60vh
display: flex
flex-direction: column
justify-content: center
align-items: center
color: $black
h1
margin: 0 2rem
font-size: 4rem
font-weight: 900
text-align: center
p
margin: 0 2rem
max-width: 40rem
font-size: 1rem
font-weight: 400
text-align: center
.error-page .error-page
width: 100% width: 100%
@ -77,10 +102,50 @@ html, body
color: $white color: $white
footer
margin: 0
padding: 0.25rem 1rem
width: 100%
display: flex
justify-content: center
align-items: center
gap: 1rem
background-color: $white
p
margin: 0
padding: 0
font-size: 0.75rem
font-weight: 400
text-align: center
color: $black
a
margin: 0
padding: 0
font-size: 0.75rem
font-weight: 400
text-align: center
color: $primary
@media (max-width: $breakpoint) @media (max-width: $breakpoint)
.wrapper .wrapper
padding: 0 0 3.5rem 0 padding: 0 0 3.5rem 0
.big-text
height: calc(75vh - 3.5rem)
h1
font-size: 3.5rem
.error-page .error-page
height: calc(100vh - 3.5rem) height: calc(100vh - 3.5rem)