From 00ad7ddef1b0cbd5e4890ebe91f60251193a1561 Mon Sep 17 00:00:00 2001 From: aneuhmanh Date: Sun, 20 Apr 2025 23:35:38 +0300 Subject: [PATCH] Google auth provided --- .gitignore | 3 +- app.py | 22 +- auth.py | 87 +- config.py | 23 +- models.py | 7 +- static/css/styles.scss | 2426 +++++++++++++++++---------------- static/css/styles.scss.map | 1 + static/icons/google.svg | 14 + static/js/navbar.js | 1 - templates/login-modal.html | 9 +- templates/register-modal.html | 12 +- templates/subnav.html | 14 +- 12 files changed, 1394 insertions(+), 1225 deletions(-) create mode 100644 static/css/styles.scss.map create mode 100644 static/icons/google.svg diff --git a/.gitignore b/.gitignore index f5a7b4f..29b009a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ /__pycache__/ static/css/*.css static/css/*.css.map -.env \ No newline at end of file +.env +/flask_session/ \ No newline at end of file diff --git a/app.py b/app.py index 2cd3d54..5a4d806 100644 --- a/app.py +++ b/app.py @@ -7,12 +7,16 @@ import random import asyncio from datetime import datetime import requests +from authlib.integrations.flask_client import OAuth +from auth import auth_bp, init_oauth +from config import Config, configure_oauth +from flask_session import Session from PIL import Image as PILImage from sqlalchemy.exc import IntegrityError from werkzeug.exceptions import BadRequest from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.utils import secure_filename -from flask import Flask, abort, render_template, redirect, url_for, request, flash, session, jsonify +from flask import Flask, abort, render_template, redirect, url_for, request, session, jsonify from flask_sqlalchemy import SQLAlchemy from flask_bcrypt import Bcrypt from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user @@ -34,9 +38,12 @@ from auth import auth_bp app = Flask(__name__) csrf = CSRFProtect(app) - app.config.from_object(Config) +oauth = configure_oauth(app) + +init_oauth(oauth) + db.init_app(app) bcrypt.init_app(app) login_manager = LoginManager(app) @@ -44,7 +51,16 @@ login_manager.login_view = 'auth.login' @login_manager.user_loader def load_user(user_id): - return User.query.get(int(user_id)) + return db.session.get(User, int(user_id)) + +from flask_session import Session + +app.config['SESSION_TYPE'] = 'filesystem' +app.config['SESSION_FILE_DIR'] = './flask_session' +app.config['SESSION_PERMANENT'] = False +app.config['SESSION_USE_SIGNER'] = True +app.config['SECRET_KEY'] = Config.SECRET_KEY +Session(app) register_admin_routes(app) app.register_blueprint(upload_bp) diff --git a/auth.py b/auth.py index 00d47ec..7b7d149 100644 --- a/auth.py +++ b/auth.py @@ -1,15 +1,24 @@ -from flask import Blueprint, render_template, redirect, url_for, request +from flask import Blueprint, render_template, redirect, url_for, request, session from flask_login import login_user, logout_user, login_required, current_user from sqlalchemy.exc import IntegrityError from models import db, User +import uuid from utils import get_client_ip from models import RegistrationForm, LoginForm, PasswordField, RecaptchaField, SubmitField from flask_bcrypt import Bcrypt from wtforms.validators import DataRequired, Length, EqualTo from config import Config +from authlib.integrations.flask_client import OAuth # type: ignore auth_bp = Blueprint('auth', __name__) bcrypt = Bcrypt() + +google = None + +def init_oauth(oauth): + global google + google = oauth.google + password = PasswordField('Password', validators=[DataRequired(), Length(min=6)]) confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')]) recaptcha = RecaptchaField() @@ -20,11 +29,15 @@ def register(): form = RegistrationForm() if form.validate_on_submit(): - hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8') ip_address = get_client_ip() + existing_user = User.query.filter_by(ip_address=ip_address).first() + + if existing_user: + return render_template('register.html', form=form, recaptcha_key=Config.RECAPTCHA_PUBLIC_KEY) + + hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8') username = form.username.data.lower() - existing_user = User.query.filter_by(ip_address=ip_address).first() user = User(username=username, encrypted_password=hashed_password, ip_address=ip_address) try: @@ -46,21 +59,24 @@ def login(): if form.validate_on_submit(): user = User.query.filter_by(username=form.username.data).first() - if user and user.check_password(form.password.data): - login_user(user) - if user.ip_address is None: - ip_address = get_client_ip() - user.ip_address = ip_address - db.session.commit() - return redirect(url_for('profile', username=user.username)) + if user: + + if user.google_id: + return redirect(url_for('auth.login_google')) + + if user.check_password(form.password.data): + login_user(user) + if user.ip_address is None: + ip_address = get_client_ip() + user.ip_address = ip_address + db.session.commit() + return redirect(url_for('profile', username=user.username)) if request.headers.get('X-Requested-With') == 'XMLHttpRequest': return render_template('login-modal.html', form=form, recaptcha_key=Config.RECAPTCHA_PUBLIC_KEY) return render_template('login.html', form=form, recaptcha_key=Config.RECAPTCHA_PUBLIC_KEY) - - @auth_bp.route('/register-modal') def register_modal(): form = RegistrationForm() @@ -75,4 +91,51 @@ def login_modal(): @auth_bp.route('/logout') def logout(): logout_user() + return redirect(url_for('index')) + +@auth_bp.route('/login/google') +def login_google(): + + base_domain = request.host_url.rstrip('/') + redirect_uri = f"{base_domain}/auth/google/callback" + + state = str(uuid.uuid4()) + nonce = str(uuid.uuid4()) + session['oauth_state'] = state + session['oauth_nonce'] = nonce + + return google.authorize_redirect(redirect_uri, state=state, nonce=nonce) + +@auth_bp.route('/auth/google/callback') +def authorize_google(): + state = session.pop('oauth_state', None) + nonce = session.pop('oauth_nonce', None) + + if not state or state != request.args.get('state'): + return "Error: Invalid state parameter", 400 + + token = google.authorize_access_token() + + try: + user_info = google.parse_id_token(token, nonce=nonce) + except Exception as e: + return "Error: Invalid ID token", 400 + + google_id = user_info['sub'] + user = User.query.filter_by(google_id=google_id).first() + if user: + + login_user(user) + return redirect(url_for('index')) + + username = f"user_{uuid.uuid4().hex[:8]}" + user = User( + username=username, + google_id=google_id, + ip_address=get_client_ip() + ) + db.session.add(user) + db.session.commit() + + login_user(user) return redirect(url_for('index')) \ No newline at end of file diff --git a/config.py b/config.py index d280d81..9614f31 100644 --- a/config.py +++ b/config.py @@ -1,5 +1,6 @@ import os from dotenv import load_dotenv, find_dotenv +from authlib.integrations.flask_client import OAuth dotenv_path = find_dotenv() load_dotenv(dotenv_path, override=True) @@ -10,6 +11,9 @@ class Config: RECAPTCHA_PUBLIC_KEY = os.getenv('RECAPTCHA_PUBLIC_KEY') RECAPTCHA_PRIVATE_KEY = os.getenv('RECAPTCHA_PRIVATE_KEY') SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI') + SQLALCHEMY_TRACK_MODIFICATIONS = False + BASE_DOMAIN = os.getenv('BASE_DOMAIN', 'http://localhost:5000') + GOOGLE_REDIRECT_URI = f"{BASE_DOMAIN}/auth/google/callback" UPLOAD_FOLDER = { 'images': 'static/arts/', 'arts': 'static/arts/', @@ -24,4 +28,21 @@ class Config: ALLOWED_IMAGE_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'} ALLOWED_VIDEO_EXTENSIONS = {'mp4', 'avi', 'mov'} MAX_IMAGE_SIZE = 15 * 1024 * 1024 - MAX_VIDEO_SIZE = 10 * 1024 * 1024 * 1024 \ No newline at end of file + MAX_VIDEO_SIZE = 10 * 1024 * 1024 * 1024 + + GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID') + GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET') + GOOGLE_REDIRECT_URI = os.getenv('GOOGLE_REDIRECT_URI', 'http://localhost:5000/auth/google/callback') + +def configure_oauth(app): + oauth = OAuth(app) + oauth.register( + name='google', + client_id=Config.GOOGLE_CLIENT_ID, + client_secret=Config.GOOGLE_CLIENT_SECRET, + server_metadata_url='https://accounts.google.com/.well-known/openid-configuration', + client_kwargs={ + 'scope': 'openid profile' + } + ) + return oauth \ No newline at end of file diff --git a/models.py b/models.py index 4673a7b..5476e17 100644 --- a/models.py +++ b/models.py @@ -54,7 +54,8 @@ class Comments(db.Model): class User(db.Model, UserMixin): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(20), unique=True, nullable=False) - encrypted_password = db.Column(db.String(60), nullable=False) + encrypted_password = db.Column(db.String(60), nullable=True) + google_id = db.Column(db.String(255), unique=True, nullable=True) ip_address = db.Column(db.String(15), nullable=True) avatar_file = db.Column(db.String(50), nullable=True) banner_file = db.Column(db.String(50), nullable=True) @@ -65,7 +66,9 @@ class User(db.Model, UserMixin): return f'' def check_password(self, password): - return bcrypt.check_password_hash(self.encrypted_password, password) + if self.encrypted_password: + return bcrypt.check_password_hash(self.encrypted_password, password) + return False class Image(db.Model): __tablename__ = 'image' diff --git a/static/css/styles.scss b/static/css/styles.scss index b80666b..dd6e796 100644 --- a/static/css/styles.scss +++ b/static/css/styles.scss @@ -1,1195 +1,1231 @@ -$dark-violet: #0D0C1C; -$violet: #3C3882; -$light-violet: #8784C9; -@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&family=Playwrite+IT+Moderna:wght@100..400&display=swap'); -@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap'); - - -body { - background-color: #05040A; - font-family: Nunito, sans-serif; - } - -html, body { - overflow: auto; - scrollbar-width: none; - -ms-overflow-style: none; - } - - html::-webkit-scrollbar, - body::-webkit-scrollbar { - display: none; - } - - -.container { - position: absolute; - display: flex; - flex-wrap: wrap; - gap: 20px; - background-color: #05040A; - padding: 10px; - box-sizing: border-box; -} - -.new-content { - width: 1502px; - height: 340px; - top: 257px; - left: 50%; - transform: translateX(-50%); - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 20px; - position: absolute; - padding: 0; - box-sizing: border-box; -} - -.img-new-content { - width: 1502px; - height: 423px; - top: 257px; - left: 50%; - transform: translateX(-50%); - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 20px; - position: absolute; - padding: 0; - box-sizing: border-box; -} - -.popular-content { - display: flex; - flex-direction: column; - align-items: flex-start; - padding: 0px; - gap: 20px; - width: 1502px; - height: 631px; - top: 653px; - left: 50%; - transform: translateX(-50%); - position: absolute; - box-sizing: border-box; -} - -.img-popular-content { - display: flex; - flex-direction: column; - align-items: flex-start; - padding: 0px; - gap: 20px; - width: 1502px; - height: 817px; - left: 209px; - position: absolute; - top: 740px; -} - -.viewed-content { - display: flex; - flex-direction: column; - align-items: flex-start; - padding: 0px; - gap: 20px; - width: 1502px; - height: 631px; - top: 1340px; - left: 50%; - transform: translateX(-50%); - position: absolute; - box-sizing: border-box; -} - -.img-viewed-content { - display: flex; - flex-direction: column; - align-items: flex-start; - padding: 0px; - gap: 20px; - width: 1502px; - height: 631px; - left: 209px; - position: absolute; - top: 1617px; -} - -.popular-categories { - display: flex; - justify-content: space-between; - align-items: center; - width: 1500px; - height: 277px; - padding: 0; - box-sizing: border-box; - position: absolute; - top: 2095px; - left: 50%; - transform: translateX(-50%); - gap: 20px; -} - -.img-popular-categories { - display: flex; - justify-content: space-between; - align-items: center; - width: 1500px; - height: 277px; - padding: 0; - box-sizing: border-box; - position: absolute; - top: 2494px; - left: 210px; - gap: 20px; -} - -.new-content-text { - width: 123px; - height: 33px; - font-weight: 500; - font-size: 24px; - line-height: 100%; - letter-spacing: 0%; - position: absolute; - top: 10px; - color: $light-violet; -} - -.popular-content-text { - width: 123px; - height: 33px; - font-weight: 500; - font-size: 24px; - line-height: 100%; - letter-spacing: 0%; - position: absolute; - top: 10px; - color: $light-violet; -} - -.viewed-content-text { - width: 123px; - height: 33px; - font-weight: 500; - font-size: 24px; - line-height: 100%; - letter-spacing: 0%; - position: absolute; - top: 10px; - color: $light-violet; -} - -.popular-categories-text { - width: 321px; - height: 33px; - font-weight: 500; - font-size: 24px; - line-height: 100%; - letter-spacing: 0%; - position: absolute; - top: 10px; - color: $light-violet; -} - -.view-more-button { - width: 1500px; - height: 40px; - border-radius: 10px; - gap: 10px; - padding-top: 10px; - padding-right: 679px; - padding-bottom: 10px; - padding-left: 679px; - background-color: $light-violet; - border: none; - color: #fff; - font-weight: 500; - font-size: 16px; - cursor: pointer; - white-space: nowrap; - position: absolute; - bottom: -22px; - left: 50%; - transform: translateX(-50%); -} - -.view-more-button:hover { - background-color: $violet; - transition: background-color 0.3s ease, color 0.3s ease; -} - -.view-more-button:hover .new-context-button-text { - color: $light-violet; - transition: color 0.3s ease; - -} - -.img-view-more-button { - width: 1500px; - height: 40px; - border-radius: 10px; - gap: 10px; - padding-top: 10px; - padding-right: 679px; - padding-bottom: 10px; - padding-left: 679px; - background-color: $light-violet; - border: none; - color: #fff; - font-weight: 500; - font-size: 16px; - cursor: pointer; - white-space: nowrap; - position: absolute; - bottom: -22px; - left: 50%; - transform: translateX(-50%); -} - -.img-view-more-button:hover { - background-color: $violet; - transition: background-color 0.3s ease, color 0.3s ease; -} - -.img-view-more-button:hover .new-context-button-text { - color: $light-violet; - transition: color 0.3s ease; -} - -.new-context-button-text { - width: 134px; - height: 22px; - font-weight: 500; - font-size: 16px; - line-height: 100%; - letter-spacing: 0%; - color: $violet; -} - -/* navbar */ - -.navbar { - width: 100%; - max-width: 1500px; - height: 120px; - background: $dark-violet; - display: flex; - align-items: center; - padding: 0 20px; - gap: 20px; -} - -.navbar-wrapper { - width: 100%; - background: $dark-violet; - display: flex; - justify-content: center; -} - -.logo { - width: 307px; - height: 60px; -} - -.search-container { - display: flex; - align-items: center; - width: 452.5px; - height: 27px; - border: 1px solid $light-violet; - border-radius: 20px; - padding: 15px; - background: $dark-violet; - position: relative; - transition: border-color 0.3s ease; -} - -.search-icon-container { - position: relative; - width: 24px; - height: 24px; - margin-right: 10px; -} - -.search-icon, -.search-hover-icon { - width: 24px; - height: 24px; - position: absolute; - top: 0; - left: 0; - transition: opacity 0.3s ease; -} - -.search-hover-icon { - opacity: 0; -} - -.search-container:hover .search-hover-icon, -.search-container:focus-within .search-hover-icon { - opacity: 1; -} - -.search-container:hover .search-icon, -.search-container:focus-within .search-icon { - opacity: 0; -} - -.search-input { - border: none; - outline: none; - flex-grow: 1; - font-size: 16px; - height: 24px; - color: $light-violet; - background-color: $dark-violet; -} - -.search-container:hover { - border-color: $violet; -} - -.search-container:focus-within { - border-color: $violet; -} - -.icon-container { - display: flex; - align-items: center; - margin-left: 10px; -} - -.video-icon { - width: 24px; - height: 24px; - margin-left: 10px; -} - -.tray-icon { - width: 11px; - height: 7px; - margin-left: 5px; -} - -.translate-btn { - position: relative; - width: 23px; - height: 27px; - border: 1px solid $light-violet; - border-radius: 10px; - display: flex; - align-items: center; - justify-content: center; - background: $dark-violet; - cursor: pointer; - padding: 15px; - transition: border-color 0.3s ease, transform 0.3s ease, opacity 0.3s ease; -} - -.translate-icon, -.translate-hover-icon { - width: 24px; - height: 24px; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - transition: opacity 0.3s ease; -} - -.translate-hover-icon { - opacity: 0; -} - -.translate-btn:hover .translate-hover-icon { - opacity: 1; -} - -.translate-btn:hover .translate-icon { - opacity: 0; -} - -.translate-btn:hover { - border-color: $violet; -} - -.overlay-icon { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 20px; - height: 20px; - opacity: 0.7; -} - -.menu { - display: flex; - gap: 22px; - width: 519.5px; - height: 22px; - justify-content: space-between; - padding-left: 60px; - padding-right: 60px; -} - -.menu a { - text-decoration: none; - color: $light-violet; - font-size: 16px; - transition: color 0.3s ease; -} - -.menu a:hover { - color: $violet; -} - -.auth-container { - display: flex; - align-items: center; - margin-left: auto; -} - -.discord-icon-container { - position: relative; - width: 42px; - height: 42px; -} - -.discord-icon, -.discord-hover-icon { - width: 42px; - height: 42px; - position: absolute; - top: 0; - left: 0; - transition: opacity 0.3s ease; -} - -.discord-hover-icon { - opacity: 0; -} - -.discord-icon-container:hover .discord-hover-icon { - opacity: 1; -} - -.discord-icon-container:hover .discord-icon { - opacity: 0; -} - -.login-btn { - width: 87px; - height: 42px; - border-radius: 20px; - background: $light-violet; - border: 1px solid $light-violet; - padding: 10px 15px; - font-size: 16px; - font-weight: 500; - line-height: 21.82px; - letter-spacing: 0%; - color: $dark-violet; - cursor: pointer; - margin-left: 10px; - transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; -} - -.login-btn:hover { - background-color: $violet; - color: $dark-violet; - border-color: $violet; -} - -.dropdown-menu { - position: absolute; - top: 100%; - right: 0; - background: $dark-violet; - border: 1px solid $violet; - border-radius: 8px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - display: none; - flex-direction: column; - width: 150px; - z-index: 10; -} - -.dropdown-item { - padding: 10px 15px; - font-size: 14px; - border-radius: 4px; - color: $light-violet; - cursor: pointer; - transition: background 0.3s ease, color 0.3s ease; -} - -.dropdown-item:hover { - background: $violet; - color: white; -} - -/* tags-list */ - -.tags-container { - width: 1500px; - height: 35px; - position: absolute; - top: 192px; - left: 50%; - transform: translateX(-50%); - display: flex; - justify-content: flex-start; - align-items: center; - gap: 10px; - border: none; - background-color: #05040A; - overflow: hidden; - - > .list-button { - position: absolute; - right: 0; - z-index: 2; - } -} - -.tag { - height: 35px; - border-radius: 5px; - padding: 6px 10px; - display: inline-flex; - align-items: center; - justify-content: center; - background-color: $violet; - border: none; - color: $light-violet; - margin: 0; - visibility: visible; - transition: visibility 0.3s ease; -} - -.list-button { - width: 35px; - height: 35px; - border-radius: 10px; - padding: 5px; - display: flex; - align-items: center; - justify-content: center; - border: 2px solid $light-violet; - background-color: transparent; - color: $violet; - margin-left: 10px; - gap: 5px; - position: relative; - z-index: 2; -} - -.taglist-shadow { - width: 64px; - height: 35px; - position: absolute; - right: 0; - background: linear-gradient(to right, rgba(5, 4, 10, 0) 30%, rgba(5, 4, 10, 0.5) 60%, #05040A 100%); -} - -/* cards */ - -.img-cards-grid { - display: grid; - grid-template-columns: repeat(6, 1fr); - grid-gap: 30px; - width: 1500px; - height: 708px; - margin-top: 60px; -} - -.img-card { - width: 225px; - height: 344px; - display: flex; - flex-direction: column; - gap: 5px; -} - -.img-card-cover { - width: 100%; - height: 280px; - background: #1D1C2E; -} - -.cards-grid { - display: grid; - grid-template-columns: repeat(4, 1fr); - grid-gap: 30px; - width: 1500px; - height: 518px; - margin-top: 60px; -} - -.card { - width: 100%; - height: 244px; - display: flex; - flex-direction: column; - gap: 5px; - overflow: hidden; -} - -.card-cover { - width: 100%; - height: 180px; - background: #1D1C2E; - position: relative; - overflow: hidden; -} - -.card-cover img { - width: 100%; - height: 100%; - object-fit: cover; - position: absolute; - top: 0; - left: 0; -} - -.card-cover video.preview-video { - width: 100%; - height: 100%; - object-fit: cover; - position: absolute; - top: 0; - left: 0; - display: none; - z-index: 1; -} - -.card-info { - width: 100%; - height: 59px; - display: flex; - flex-direction: column; - gap: 5px; -} - -.card-header { - display: flex; - justify-content: space-between; - align-items: center; -} - -.card-stats { - display: flex; - gap: 10px; -} - -.stat { - display: flex; - align-items: center; - gap: 5px; -} - -.card-text { - font-size: 14px; - color: $light-violet; -} - -.img-small-cards-grid { - display: grid; - grid-template-columns: repeat(7, 1fr); - grid-gap: 30px; - width: 100%; - margin-top: 60px; -} - -.img-small-card-cover { - width: 189px; - height: 250px; - background: #1D1C2E; -} - - -.img-small-card { - width: 189px; - height: 314px; - display: flex; - flex-direction: column; - gap: 5px; -} - -.small-cards-grid { - display: grid; - grid-template-columns: repeat(5, 1fr); - grid-gap: 30px; - width: 100%; - margin-top: 60px; -} - -.small-card { - width: 100%; - height: 227px; - display: flex; - flex-direction: column; - gap: 5px; - overflow: hidden; -} - -.small-card-cover { - width: 100%; - height: 163px; - background: #1D1C2E; - position: relative; - overflow: hidden; -} - -.small-card-cover img { - width: 100%; - height: 100%; - object-fit: cover; - position: absolute; - top: 0; - left: 0; -} - -.small-card-cover video.preview-video { - width: 100%; - height: 100%; - object-fit: cover; - position: absolute; - top: 0; - left: 0; - display: none; - z-index: 1; -} - -.small-card-info { - width: 100%; - height: 59px; - display: flex; - flex-direction: column; - gap: 5px; -} - -.small-card-header { - display: flex; - justify-content: space-between; - align-items: center; -} - -.small-card-stats { - display: flex; - gap: 10px; -} - -.small-stat { - display: flex; - align-items: center; - gap: 5px; -} - -.small-card-text { - font-size: 14px; - color: $light-violet; -} - -.pc-card { - width: 233px; - height: 164px; - gap: 5px; -} - -.pc-card-cover { - width: 100%; - height: 140px; - background-color: #1D1C2E; -} - -.pc-card-stats { - width: 100%; - height: 19px; - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 2px; - box-sizing: border-box; -} - -.category-name-text { - width: auto; - max-width: 58px; - font-weight: 400; - font-size: 14px; - line-height: 100%; - letter-spacing: 0%; - color: $light-violet; - margin-left: -2px; -} - -.categories-number { - width: auto; - max-width: 34px; - font-weight: 400; - font-size: 14px; - line-height: 100%; - letter-spacing: 0%; - color: $violet; - text-align: right; - position: relative; - right: 5px; -} - -.ac-img-small-cards-grid { - display: grid; - grid-template-columns: repeat(6, 1fr); - grid-gap: 30px; - width: 100%; - margin-top: 60px; -} - -.ct-img-card { - width: 225px; - height: 344px; - display: flex; - flex-direction: column; - gap: 5px; -} - -.ct-img-card-cover { - width: 100%; - height: 280px; - background: #1D1C2E; -} - -.ct-small-card-info { - width: 100%; - height: 59px; - display: flex; - flex-direction: column; - gap: 5px; -} - -.ct-small-card-header { - display: flex; - justify-content: space-between; - align-items: center; -} - -.ct-small-card-stats { - display: flex; - gap: 10px; -} - -.ct-small-stat { - display: flex; - align-items: center; - gap: 5px; -} - -.ct-small-card-text { - font-size: 14px; - color: $light-violet; -} - -.ac-img-small-cards-grid { - display: grid; - grid-template-columns: repeat(6, 1fr); - grid-gap: 30px; - width: 100%; - margin-top: 60px; -} - -.most-new-button { - width: 276px; - height: 40px; - border-radius: 10px; - background-color: $violet; - color: white; - border: none; - cursor: pointer; - display: flex; - justify-content: center; - align-items: center; - font-size: 14px; - text-align: center; - margin-left: -298px; -} - -.most-new-button-text { - font-weight: 500; - font-size: 16px; - line-height: 100%; - letter-spacing: 0%; - white-space: nowrap; -} - -/* pagination */ - -.pagination-container { - width: 626px; - height: 50px; - display: flex; - align-items: center; - gap: 10px; - position: absolute; - bottom: -180%; - left: 50%; - transform: translateX(-50%); - - .swipe-button { - width: 128px; - height: 50px; - border-radius: 5px; - padding: 7px 45px; - background-color: $violet; - border: none; - color: #fff; - font-weight: 500; - font-size: 16px; - cursor: pointer; - transform: rotate(0deg); - - } - - .page-buttons-container { - width: 350px; - height: 50px; - display: flex; - gap: 10px; - - .page-button { - width: 50px; - height: 50px; - border-radius: 5px; - padding: 1px 6px; - background-color: $violet; - border: none; - color: white; - font-weight: 500; - size: 16px; - cursor: pointer; - font-family: Inter; - } - } -} - -#modal-container { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background-color: rgba(13, 12, 28, 0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 9999; -} - -.modal { - width: 312px; - height: 400px; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - border-radius: 10px; - border-width: thin; - border-color: #3C3882; - border-style: solid; - padding: 30px 40px; - position: fixed; - background-color: #0D0C1C; - display: none; - z-index: 1000; -} -.modal.active { - display: block; -} -.form-inner-container { - width: 312px; - height: 400px; - gap: 10px; - display: flex; - flex-direction: column; - align-items: center; - margin: 0 auto; -} -.reg-form-title { - width: 100%; - text-align: center; - margin: 0; - font-family: 'Nunito', sans-serif; - font-weight: 500; - font-size: 16px; - text-transform: uppercase; - color: #8784C9; - padding: 10px 0; -} -.modal-register-input-container { - width: 100%; - height: 39px; - border-radius: 10px; - border: 1px solid #8784C9; - opacity: 0.6; - position: relative; - background-color: transparent; -} -.modal-register-text-input { - width: calc(100% - 31px); - height: 16px; - position: absolute; - top: 11px; - left: 15.5px; - border: none; - outline: none; - background: transparent; - color: #8784C9; - font-family: 'Nunito', sans-serif; - font-size: 14px; -} -.modal-text-input::placeholder { - color: #8784C9; - opacity: 0.6; -} -.password-input { - -webkit-text-security: disc; -} -.modal-register-button { - width: 100%; - height: 38px; - border-radius: 10px; - background-color: #8784C9; - border: none; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: background-color 0.3s ease; -} -.modal-register-button:hover { -background-color: #3C3882; -} -.modal-register-button-text { -font-family: 'Nunito', sans-serif; -font-weight: 500; -font-size: 16px; -color: #3C3882; -text-align: center; -transition: background-color 0.3s ease; -} -.modal-register-button:hover .modal-register-button-text { -color: #8784C9; -} -.login-link-container { -width: 312px; -height: 16px; -margin-bottom: 10px; -} - -.login-prompt { -display: block; -width: 100%; -height: 16px; -font-family: 'Nunito', sans-serif; -font-weight: 400; -font-size: 12px; -line-height: 30px; -letter-spacing: 0; -color: #8784C9; -} - -.login-link { -text-decoration: underline solid 0px; -text-decoration-skip-ink: auto; -color: #8784C9; -} - -.login-form-title { - width: 100%; - text-align: center; - margin: 0; - font-family: 'Nunito', sans-serif; - font-weight: 500; - font-size: 16px; - text-transform: uppercase; - color: #8784C9; - padding: 10px 0; -} -.modal-login-input-container { - width: 100%; - height: 39px; - border-radius: 10px; - border: 1px solid #8784C9; - opacity: 0.6; - position: relative; - background-color: transparent; -} -.modal-login-text-input { - width: calc(100% - 31px); - height: 16px; - position: absolute; - top: 11px; - left: 15.5px; - border: none; - outline: none; - background: transparent; - color: #8784C9; - font-family: 'Nunito', sans-serif; - font-size: 14px; -} -.modal-text-input::placeholder { - color: #8784C9; - opacity: 0.6; -} -.password-input { - -webkit-text-security: disc; -} -.modal-login-button { - width: 100%; - height: 38px; - border-radius: 10px; - background-color: #8784C9; - border: none; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: background-color 0.3s ease; -} -.modal-login-button:hover { - background-color: #9d9ad4; -} -.modal-login-button-text { -font-family: 'Nunito', sans-serif; -font-weight: 500; -font-size: 16px; -color: #3C3882; -text-align: center; -} - -.login-link-container { -width: 312px; -height: 16px; -margin-bottom: 10px; -} - -.login-prompt { -display: block; -width: 100%; -height: 16px; -font-family: 'Nunito', sans-serif; -font-weight: 400; -font-size: 12px; -line-height: 30px; -letter-spacing: 0; -color: #8784C9; -} - -.login-link { -text-decoration: underline solid 0px; -text-decoration-skip-ink: auto; -color: #8784C9; -} \ No newline at end of file +@import url("https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&family=Playwrite+IT+Moderna:wght@100..400&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"); +body { + background-color: #05040A; + font-family: Nunito, sans-serif; +} + +html, body { + overflow: auto; + scrollbar-width: none; + -ms-overflow-style: none; +} + +html::-webkit-scrollbar, +body::-webkit-scrollbar { + display: none; +} + +.container { + position: absolute; + display: flex; + flex-wrap: wrap; + gap: 20px; + background-color: #05040A; + padding: 10px; + box-sizing: border-box; +} + +.new-content { + width: 1502px; + height: 340px; + top: 257px; + left: 50%; + transform: translateX(-50%); + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 20px; + position: absolute; + padding: 0; + box-sizing: border-box; +} + +.img-new-content { + width: 1502px; + height: 423px; + top: 257px; + left: 50%; + transform: translateX(-50%); + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 20px; + position: absolute; + padding: 0; + box-sizing: border-box; + } + +.popular-content { + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 0px; + gap: 20px; + width: 1502px; + height: 631px; + top: 653px; + left: 50%; + transform: translateX(-50%); + position: absolute; + box-sizing: border-box; +} + +.img-popular-content { + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 0; + gap: 20px; + width: 1502px; + height: 817px; + left: 50%; + transform: translateX(-50%); + position: absolute; + top: 740px; + } + +.viewed-content { + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 0px; + gap: 20px; + width: 1502px; + height: 631px; + top: 1340px; + left: 50%; + transform: translateX(-50%); + position: absolute; + box-sizing: border-box; +} + +.img-viewed-content { + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 0; + gap: 20px; + width: 1502px; + height: 631px; + left: 50%; + transform: translateX(-50%); + position: absolute; + top: 1617px; + } + +.popular-categories { + display: flex; + justify-content: space-between; + align-items: center; + width: 1500px; + height: 277px; + padding: 0; + box-sizing: border-box; + position: absolute; + top: 2095px; + left: 50%; + transform: translateX(-50%); + gap: 20px; +} + +.img-popular-categories { + display: flex; + justify-content: space-between; + align-items: center; + width: 1500px; + height: 277px; + padding: 0; + box-sizing: border-box; + position: absolute; + top: 2494px; + left: 50%; + transform: translateX(-50%); + gap: 20px; + } + +.new-content-text { + width: 123px; + height: 33px; + font-weight: 500; + font-size: 24px; + line-height: 100%; + letter-spacing: 0%; + position: absolute; + top: 10px; + color: #8784C9; +} + +.popular-content-text { + width: 123px; + height: 33px; + font-weight: 500; + font-size: 24px; + line-height: 100%; + letter-spacing: 0%; + position: absolute; + top: 10px; + color: #8784C9; +} + +.viewed-content-text { + width: 123px; + height: 33px; + font-weight: 500; + font-size: 24px; + line-height: 100%; + letter-spacing: 0%; + position: absolute; + top: 10px; + color: #8784C9; +} + +.popular-categories-text { + width: 321px; + height: 33px; + font-weight: 500; + font-size: 24px; + line-height: 100%; + letter-spacing: 0%; + position: absolute; + top: 10px; + color: #8784C9; +} + +.view-more-button { + width: 1500px; + height: 40px; + border-radius: 10px; + gap: 10px; + padding-top: 10px; + padding-right: 679px; + padding-bottom: 10px; + padding-left: 679px; + background-color: #8784C9; + border: none; + color: #fff; + font-weight: 500; + font-size: 16px; + cursor: pointer; + white-space: nowrap; + position: absolute; + bottom: -22px; + left: 50%; + transform: translateX(-50%); +} + +.view-more-button:hover { + background-color: #3C3882; + transition: background-color 0.3s ease, color 0.3s ease; +} + +.view-more-button:hover .new-context-button-text { + color: #8784C9; + transition: color 0.3s ease; +} + +.img-view-more-button { + width: 1500px; + height: 40px; + border-radius: 10px; + gap: 10px; + padding-top: 10px; + padding-right: 679px; + padding-bottom: 10px; + padding-left: 679px; + background-color: #8784C9; + border: none; + color: #fff; + font-weight: 500; + font-size: 16px; + cursor: pointer; + white-space: nowrap; + position: absolute; + bottom: -22px; + left: 50%; + transform: translateX(-50%); +} + +.img-view-more-button:hover { + background-color: #3C3882; + transition: background-color 0.3s ease, color 0.3s ease; +} + +.img-view-more-button:hover .new-context-button-text { + color: #8784C9; + transition: color 0.3s ease; +} + +.new-context-button-text { + width: 134px; + height: 22px; + font-weight: 500; + font-size: 16px; + line-height: 100%; + letter-spacing: 0%; + color: #3C3882; +} + +/* navbar */ +.navbar { + width: 100%; + max-width: 1500px; + height: 120px; + background: #0D0C1C; + display: flex; + align-items: center; + padding: 0 20px; + gap: 20px; +} + +.navbar-wrapper { + width: 100%; + background: #0D0C1C; + display: flex; + justify-content: center; +} + +.logo { + width: 307px; + height: 60px; +} + +.search-container { + display: flex; + align-items: center; + width: 452.5px; + height: 27px; + border: 1px solid #8784C9; + border-radius: 20px; + padding: 15px; + background: #0D0C1C; + position: relative; + transition: border-color 0.3s ease; +} + +.search-icon-container { + position: relative; + width: 24px; + height: 24px; + margin-right: 10px; +} + +.search-icon, +.search-hover-icon { + width: 24px; + height: 24px; + position: absolute; + top: 0; + left: 0; + transition: opacity 0.3s ease; +} + +.search-hover-icon { + opacity: 0; +} + +.search-container:hover .search-hover-icon, +.search-container:focus-within .search-hover-icon { + opacity: 1; +} + +.search-container:hover .search-icon, +.search-container:focus-within .search-icon { + opacity: 0; +} + +.search-input { + border: none; + outline: none; + flex-grow: 1; + font-size: 16px; + height: 24px; + color: #8784C9; + background-color: #0D0C1C; +} + +.search-container:hover { + border-color: #3C3882; +} + +.search-container:focus-within { + border-color: #3C3882; +} + +.icon-container { + display: flex; + align-items: center; + margin-left: 10px; +} + +.video-icon { + width: 24px; + height: 24px; + margin-left: 10px; +} + +.tray-icon { + width: 11px; + height: 7px; + margin-left: 5px; +} + +.translate-btn { + position: relative; + width: 23px; + height: 27px; + border: 1px solid #8784C9; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + background: #0D0C1C; + cursor: pointer; + padding: 15px; + transition: border-color 0.3s ease, transform 0.3s ease, opacity 0.3s ease; +} + +.translate-icon, +.translate-hover-icon { + width: 24px; + height: 24px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + transition: opacity 0.3s ease; +} + +.translate-hover-icon { + opacity: 0; +} + +.translate-btn:hover .translate-hover-icon { + opacity: 1; +} + +.translate-btn:hover .translate-icon { + opacity: 0; +} + +.translate-btn:hover { + border-color: #3C3882; +} + +.overlay-icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 20px; + height: 20px; + opacity: 0.7; +} + +.menu { + display: flex; + gap: 22px; + width: 519.5px; + height: 22px; + justify-content: space-between; + padding-left: 60px; + padding-right: 60px; +} + +.menu a { + text-decoration: none; + color: #8784C9; + font-size: 16px; + transition: color 0.3s ease; +} + +.menu a:hover { + color: #3C3882; +} + +.auth-container { + display: flex; + align-items: center; + margin-left: auto; +} + +.discord-icon-container { + position: relative; + width: 42px; + height: 42px; +} + +.discord-icon, +.discord-hover-icon { + width: 42px; + height: 42px; + position: absolute; + top: 0; + left: 0; + transition: opacity 0.3s ease; +} + +.discord-hover-icon { + opacity: 0; +} + +.discord-icon-container:hover .discord-hover-icon { + opacity: 1; +} + +.discord-icon-container:hover .discord-icon { + opacity: 0; +} + +.login-btn { + width: 87px; + height: 42px; + border-radius: 20px; + background: #8784C9; + border: 1px solid #8784C9; + padding: 10px 15px; + font-size: 16px; + font-weight: 500; + line-height: 21.82px; + letter-spacing: 0%; + color: #0D0C1C; + cursor: pointer; + margin-left: 10px; + transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; +} + +.login-btn:hover { + background-color: #3C3882; + color: #0D0C1C; + border-color: #3C3882; +} + +.dropdown-menu { + position: absolute; + top: 100%; + right: 0; + background: #0D0C1C; + border: 1px solid #3C3882; + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + display: none; + flex-direction: column; + width: 150px; + z-index: 10; +} + +.dropdown-item { + padding: 10px 15px; + font-size: 14px; + border-radius: 4px; + color: #8784C9; + cursor: pointer; + transition: background 0.3s ease, color 0.3s ease; +} + +.dropdown-item:hover { + background: #3C3882; + color: white; +} + +/* tags-list */ +.tags-container { + width: 1500px; + height: 35px; + position: absolute; + top: 192px; + left: 50%; + transform: translateX(-50%); + display: flex; + justify-content: flex-start; + align-items: center; + gap: 10px; + border: none; + background-color: #05040A; + overflow: hidden; +} + +.tags-container > .list-button { + position: absolute; + right: 0; + z-index: 2; +} + +.tag { + height: 35px; + border-radius: 5px; + padding: 6px 10px; + display: inline-flex; + align-items: center; + justify-content: center; + background-color: #3C3882; + border: none; + color: #8784C9; + margin: 0; + visibility: visible; + transition: visibility 0.3s ease; +} + +.list-button { + width: 35px; + height: 35px; + border-radius: 10px; + padding: 5px; + display: flex; + align-items: center; + justify-content: center; + border: 2px solid #8784C9; + background-color: transparent; + color: #3C3882; + margin-left: 10px; + gap: 5px; + position: relative; + z-index: 2; +} + +.taglist-shadow { + width: 64px; + height: 35px; + position: absolute; + right: 0; + background: linear-gradient(to right, rgba(5, 4, 10, 0) 30%, rgba(5, 4, 10, 0.5) 60%, #05040A 100%); +} + +/* cards */ +.img-cards-grid { + display: grid; + grid-template-columns: repeat(6, 1fr); + grid-gap: 30px; + width: 1500px; + height: 708px; + margin-top: 60px; +} + +.img-card { + width: 225px; + height: 344px; + display: flex; + flex-direction: column; + gap: 5px; +} + +.img-card-cover { + width: 100%; + height: 280px; + background: #1D1C2E; +} + +.cards-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-gap: 30px; + width: 1500px; + height: 518px; + margin-top: 60px; +} + +.card { + width: 100%; + height: 244px; + display: flex; + flex-direction: column; + gap: 5px; + overflow: hidden; +} + +.card-cover { + width: 100%; + height: 180px; + background: #1D1C2E; + position: relative; + overflow: hidden; +} + +.card-cover img { + width: 100%; + height: 100%; + object-fit: cover; + position: absolute; + top: 0; + left: 0; +} + +.card-cover video.preview-video { + width: 100%; + height: 100%; + object-fit: cover; + position: absolute; + top: 0; + left: 0; + display: none; + z-index: 1; +} + +.card-info { + width: 100%; + height: 59px; + display: flex; + flex-direction: column; + gap: 5px; +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.card-stats { + display: flex; + gap: 10px; +} + +.stat { + display: flex; + align-items: center; + gap: 5px; +} + +.card-text { + font-size: 14px; + color: #8784C9; +} + +.img-small-cards-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + grid-gap: 30px; + width: 100%; + margin-top: 60px; +} + +.img-small-card-cover { + width: 189px; + height: 250px; + background: #1D1C2E; +} + +.img-small-card { + width: 189px; + height: 314px; + display: flex; + flex-direction: column; + gap: 5px; +} + +.small-cards-grid { + display: grid; + grid-template-columns: repeat(5, 1fr); + grid-gap: 30px; + width: 100%; + margin-top: 60px; +} + +.small-card { + width: 100%; + height: 227px; + display: flex; + flex-direction: column; + gap: 5px; + overflow: hidden; +} + +.small-card-cover { + width: 100%; + height: 163px; + background: #1D1C2E; + position: relative; + overflow: hidden; +} + +.small-card-cover img { + width: 100%; + height: 100%; + object-fit: cover; + position: absolute; + top: 0; + left: 0; +} + +.small-card-cover video.preview-video { + width: 100%; + height: 100%; + object-fit: cover; + position: absolute; + top: 0; + left: 0; + display: none; + z-index: 1; +} + +.small-card-info { + width: 100%; + height: 59px; + display: flex; + flex-direction: column; + gap: 5px; +} + +.small-card-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.small-card-stats { + display: flex; + gap: 10px; +} + +.small-stat { + display: flex; + align-items: center; + gap: 5px; +} + +.small-card-text { + font-size: 14px; + color: #8784C9; +} + +.pc-card { + width: 233px; + height: 164px; + gap: 5px; +} + +.pc-card-cover { + width: 100%; + height: 140px; + background-color: #1D1C2E; +} + +.pc-card-stats { + width: 100%; + height: 19px; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 2px; + box-sizing: border-box; +} + +.category-name-text { + width: auto; + max-width: 58px; + font-weight: 400; + font-size: 14px; + line-height: 100%; + letter-spacing: 0%; + color: #8784C9; + margin-left: -2px; +} + +.categories-number { + width: auto; + max-width: 34px; + font-weight: 400; + font-size: 14px; + line-height: 100%; + letter-spacing: 0%; + color: #3C3882; + text-align: right; + position: relative; + right: 5px; +} + +.ac-img-small-cards-grid { + display: grid; + grid-template-columns: repeat(6, 1fr); + grid-gap: 30px; + width: 100%; + margin-top: 60px; +} + +.ct-img-card { + width: 225px; + height: 344px; + display: flex; + flex-direction: column; + gap: 5px; +} + +.ct-img-card-cover { + width: 100%; + height: 280px; + background: #1D1C2E; +} + +.ct-small-card-info { + width: 100%; + height: 59px; + display: flex; + flex-direction: column; + gap: 5px; +} + +.ct-small-card-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.ct-small-card-stats { + display: flex; + gap: 10px; +} + +.ct-small-stat { + display: flex; + align-items: center; + gap: 5px; +} + +.ct-small-card-text { + font-size: 14px; + color: #8784C9; +} + +.ac-img-small-cards-grid { + display: grid; + grid-template-columns: repeat(6, 1fr); + grid-gap: 30px; + width: 100%; + margin-top: 60px; +} + +.most-new-button { + width: 276px; + height: 40px; + border-radius: 10px; + background-color: #3C3882; + color: white; + border: none; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + font-size: 14px; + text-align: center; + margin-left: -298px; +} + +.most-new-button-text { + font-weight: 500; + font-size: 16px; + line-height: 100%; + letter-spacing: 0%; + white-space: nowrap; +} + +/* pagination */ +.pagination-container { + width: 626px; + height: 50px; + display: flex; + align-items: center; + gap: 10px; + position: absolute; + bottom: -180%; + left: 50%; + transform: translateX(-50%); +} + +.pagination-container .swipe-button { + width: 128px; + height: 50px; + border-radius: 5px; + padding: 7px 45px; + background-color: #3C3882; + border: none; + color: #fff; + font-weight: 500; + font-size: 16px; + cursor: pointer; + transform: rotate(0deg); +} + +.pagination-container .page-buttons-container { + width: 350px; + height: 50px; + display: flex; + gap: 10px; +} + +.pagination-container .page-buttons-container .page-button { + width: 50px; + height: 50px; + border-radius: 5px; + padding: 1px 6px; + background-color: #3C3882; + border: none; + color: white; + font-weight: 500; + size: 16px; + cursor: pointer; + font-family: Inter; +} + +#modal-container { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(13, 12, 28, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; +} + +.modal { + width: 100%; + max-width: 380px; + height: auto; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-radius: 10px; + border: 1px solid #3C3882; + padding: 30px 40px; + position: fixed; + background-color: #0D0C1C; + display: none; + z-index: 1000; + box-sizing: border-box; +} + +.modal.active { + display: block; +} + +.form-inner-container { + width: 100%; + max-width: 312px; + gap: 15px; + display: flex; + flex-direction: column; + align-items: center; + margin: 0 auto; +} + + +.reg-form-title { + width: 100%; + text-align: center; + margin: 0; + font-family: "Nunito", sans-serif; + font-weight: 500; + font-size: 16px; + text-transform: uppercase; + color: #8784C9; + padding: 10px 0; +} + +.modal-register-input-container { + width: 100%; + height: 39px; + border-radius: 10px; + border: 1px solid #8784C9; + opacity: 0.6; + position: relative; + background-color: transparent; +} + +.modal-register-text-input { + width: calc(100% - 31px); + height: 16px; + position: absolute; + top: 11px; + left: 15.5px; + border: none; + outline: none; + background: transparent; + color: #8784C9; + font-family: "Nunito", sans-serif; + font-size: 14px; +} + +.modal-register-button { + width: 100%; + height: 38px; + border-radius: 10px; + background-color: #8784C9; + border: none; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.modal-register-button:hover { + background-color: #3C3882; +} + +.modal-register-button-text { + font-family: "Nunito", sans-serif; + font-weight: 500; + font-size: 16px; + color: #3C3882; + text-align: center; + transition: background-color 0.3s ease; +} + +.modal-register-button:hover .modal-register-button-text { + color: #8784C9; +} + +.login-link-container { + width: 312px; + height: 16px; + margin-bottom: 10px; +} + +.login-prompt { + display: block; + width: 100%; + height: 16px; + font-family: "Nunito", sans-serif; + font-weight: 400; + font-size: 12px; + line-height: 30px; + letter-spacing: 0; + color: #8784C9; +} + +.login-link { + text-decoration: underline solid 0px; + text-decoration-skip-ink: auto; + color: #8784C9; +} + +.login-form-title { + width: 100%; + text-align: center; + margin: 0; + font-family: "Nunito", sans-serif; + font-weight: 500; + font-size: 16px; + text-transform: uppercase; + color: #8784C9; + padding: 10px 0; +} + +.modal-login-input-container { + width: 100%; + height: 39px; + border-radius: 10px; + border: 1px solid #8784C9; + opacity: 0.6; + position: relative; + background-color: transparent; +} + +.modal-login-text-input { + width: calc(100% - 31px); + height: 16px; + position: absolute; + top: 11px; + left: 15.5px; + border: none; + outline: none; + background: transparent; + color: #8784C9; + font-family: "Nunito", sans-serif; + font-size: 14px; +} + +.modal-text-input::placeholder { + color: #8784C9; + opacity: 0.6; +} + +.password-input { + -webkit-text-security: disc; +} + +.modal-login-button { + width: 100%; + height: 38px; + border-radius: 10px; + background-color: #8784C9; + border: none; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.modal-login-button:hover { + background-color: #3C3882; +} + +.modal-login-button-text { + font-family: "Nunito", sans-serif; + font-weight: 500; + font-size: 16px; + color: #3C3882; + text-align: center; + transition: color 0.3s ease; +} + +.modal-login-button:hover .modal-login-button-text { + color: #8784C9; +} + +.modal-login-button-text { + font-family: "Nunito", sans-serif; + font-weight: 500; + font-size: 16px; + color: #3C3882; + text-align: center; +} + +.login-link { + text-decoration: underline solid 0px; + text-decoration-skip-ink: auto; + color: #8784C9; +} + +.social-login-container { + width: 100%; + display: flex; + justify-content: center; + align-items: center; +} + +.google-login-button { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + max-width: 312px; + height: 39px; + gap: 5px; + border-radius: 10px; + background-color: #222; + text-decoration: none; +} + +.google-button-content { + display: flex; + align-items: center; + gap: 5px; +} + +.google-icon { + width: 20px; + height: 20px; +} + +.google-button-text { + font-family: "Nunito", sans-serif; + font-weight: 400; + font-size: 14px; + line-height: 100%; + color: #fff; + text-align: center; +} diff --git a/static/css/styles.scss.map b/static/css/styles.scss.map new file mode 100644 index 0000000..17caa64 --- /dev/null +++ b/static/css/styles.scss.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["styles.css"],"names":[],"mappings":"AAAQ;AACA;AACR;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;AAAA;EAEE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;AAAA;EAEE;;;AAGF;AAAA;EAEE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF","file":"styles.scss"} \ No newline at end of file diff --git a/static/icons/google.svg b/static/icons/google.svg new file mode 100644 index 0000000..404283e --- /dev/null +++ b/static/icons/google.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/static/js/navbar.js b/static/js/navbar.js index 0728067..024dd01 100644 --- a/static/js/navbar.js +++ b/static/js/navbar.js @@ -15,7 +15,6 @@ document.addEventListener('DOMContentLoaded', function () { event.stopPropagation(); }); - // Делегируем клики для открытия логина/регистрации внутри любых модалок document.addEventListener('click', function (e) { const target = e.target.closest('[data-action]'); if (!target) return; diff --git a/templates/login-modal.html b/templates/login-modal.html index 8cd65ea..41ed687 100644 --- a/templates/login-modal.html +++ b/templates/login-modal.html @@ -3,7 +3,14 @@ {{ form.hidden_tag() }}
- + diff --git a/templates/register-modal.html b/templates/register-modal.html index 785039d..ee9042a 100644 --- a/templates/register-modal.html +++ b/templates/register-modal.html @@ -3,7 +3,15 @@ {{ form.hidden_tag() }}

РЕГИСТРАЦИЯ

- + + @@ -32,4 +40,4 @@
-
+ \ No newline at end of file diff --git a/templates/subnav.html b/templates/subnav.html index df68983..e735c13 100644 --- a/templates/subnav.html +++ b/templates/subnav.html @@ -12,31 +12,31 @@ .subnav-container { width: 100%; - height: 50px; + height: 3rem; /* Используем rem вместо пикселей */ display: flex; justify-content: space-between; align-items: center; background-color: #3C3882; - padding: 0 20px; + padding: 0 1.25rem; /* Относительные отступы */ box-sizing: border-box; position: absolute; - top: 112px; + top: 7rem; /* Используем rem для позиции */ } .button { flex: 1; - height: 50px; + height: 100%; /* Высота кнопки равна высоте контейнера */ background-color: #3C3882; border: none; - font-size: 16px; + font-size: 1rem; /* Относительный размер шрифта */ cursor: pointer; color: #8784C9; box-sizing: border-box; - padding: 15px 0; + padding: 0.9375rem 0; /* Относительные отступы */ } .button:not(:last-child) { - margin-right: 20px; + margin-right: 1.25rem; /* Относительный отступ между кнопками */ }