| @ -0,0 +1,16 @@ | |||
| export IMG_VERSION = 1.1 | |||
| # limpia todo | |||
| all: imagen clean install | |||
| imagen: | |||
| cd ../servicios; make | |||
| install: | |||
| envsubst < libros-deployment.yaml |kubectl create -f - | |||
| kubectl create -f pv-local-libros.yaml | |||
| kubectl create -f libros-prod-persistentvolumeclaim.yaml | |||
| clean: | |||
| envsubst < libros-deployment.yaml |kubectl delete -f - | |||
| kubectl delete -f libros-prod-persistentvolumeclaim.yaml | |||
| kubectl delete -f pv-local-libros.yaml | |||
| @ -0,0 +1 @@ | |||
| kubectl exec -ti deployment.apps/librospy -- /bin/bash | |||
| @ -0,0 +1,61 @@ | |||
| apiVersion: v1 | |||
| kind: Service | |||
| metadata: | |||
| name: librospy | |||
| spec: | |||
| type: NodePort | |||
| ports: | |||
| - name: http | |||
| port: 5000 | |||
| nodePort: 30340 | |||
| targetPort: librospy | |||
| selector: | |||
| app: librospy | |||
| --- | |||
| apiVersion: apps/v1 | |||
| kind: Deployment | |||
| metadata: | |||
| name: librospy | |||
| labels: | |||
| app: librospy | |||
| spec: | |||
| replicas: 1 | |||
| selector: | |||
| matchLabels: | |||
| app: librospy | |||
| strategy: | |||
| type: Recreate | |||
| template: | |||
| metadata: | |||
| labels: | |||
| app: librospy | |||
| spec: | |||
| containers: | |||
| - args: | |||
| - gunicorn | |||
| - --bind | |||
| - 0.0.0.0:5000 | |||
| - libros:create_app() | |||
| name: librospy | |||
| image: creylopez/libros:$IMG_VERSION | |||
| env: | |||
| - name: SALUDO_DEMO | |||
| value: "Hola, mundo" | |||
| ports: | |||
| - containerPort: 5000 | |||
| name: librospy | |||
| resources: {} | |||
| volumeMounts: | |||
| - mountPath: /libros/instance | |||
| name: librospy-instance | |||
| - mountPath: /libros/migrations | |||
| name: librospy-migrations | |||
| restartPolicy: Always | |||
| volumes: | |||
| - name: librospy-instance | |||
| persistentVolumeClaim: | |||
| claimName: librospy-instance | |||
| - name: librospy-migrations | |||
| persistentVolumeClaim: | |||
| claimName: librospy-migrations | |||
| status: {} | |||
| @ -0,0 +1,30 @@ | |||
| apiVersion: v1 | |||
| kind: PersistentVolumeClaim | |||
| metadata: | |||
| creationTimestamp: null | |||
| labels: | |||
| io.kompose.service: librospy-instance | |||
| name: librospy-instance | |||
| spec: | |||
| accessModes: | |||
| - ReadWriteOnce | |||
| resources: | |||
| requests: | |||
| storage: 100Mi | |||
| status: {} | |||
| --- | |||
| apiVersion: v1 | |||
| kind: PersistentVolumeClaim | |||
| metadata: | |||
| creationTimestamp: null | |||
| labels: | |||
| io.kompose.service: librospy-migrations | |||
| name: librospy-migrations | |||
| spec: | |||
| accessModes: | |||
| - ReadWriteOnce | |||
| resources: | |||
| requests: | |||
| storage: 50Mi | |||
| status: {} | |||
| @ -0,0 +1,28 @@ | |||
| apiVersion: v1 | |||
| kind: PersistentVolume | |||
| metadata: | |||
| name: librospy-instance-folder | |||
| labels: | |||
| app: librospy | |||
| spec: | |||
| capacity: | |||
| storage: 100Mi | |||
| accessModes: | |||
| - ReadWriteOnce | |||
| hostPath: | |||
| path: "/mnt/Externo/librospy/instance" | |||
| --- | |||
| apiVersion: v1 | |||
| kind: PersistentVolume | |||
| metadata: | |||
| name: librospy-migrations-folder | |||
| labels: | |||
| app: librospy | |||
| spec: | |||
| capacity: | |||
| storage: 50Mi | |||
| accessModes: | |||
| - ReadWriteOnce | |||
| hostPath: | |||
| path: "/mnt/Externo/librospy/migrations" | |||
| @ -0,0 +1 @@ | |||
| docker run -it creylopez/libros:1.0 bash | |||
| @ -0,0 +1,44 @@ | |||
| # Arreglar el fallo del nombre de constrain en migraciones | |||
| https://stackoverflow.com/questions/62640576/flask-migrate-valueerror-constraint-must-have-a-name | |||
| # Uso de migraciones | |||
| https://www.digitalocean.com/community/tutorials/how-to-perform-flask-sqlalchemy-migrations-using-flask-migrate | |||
| # Migración de la base de datos. | |||
| Cada vez que se haga un cambio en la bd | |||
| - flask db migrate -m "Mensaje" | |||
| - flask db upgrade | |||
| ## Migration Workflow | |||
| From this point on you have a project that is fully enabled to use database migrations. The normal migration process goes as follows: | |||
| - You will make some changes to your models in your Python source code. | |||
| - You will then run flask db migrate to generate a new database migration for these changes. | |||
| - You will finally apply the changes to the database by running flask db upgrade. | |||
| This cycle repeats every time new changes to the database schema are needed. | |||
| # Insertar los datos en la base de datos desde fichero sql | |||
| 1. Borrar la base de datos. | |||
| 2. flask db init | |||
| 3. flask db migrate | |||
| 4. flask db upgrade | |||
| con lo anterior queda creada la base de datos. | |||
| 5. Insertar los ficheros SQL con: | |||
| sqlite3 repostajes.db < vehiculos.sql | |||
| sqlite3 repostajes.db < repostajes.sql | |||
| ¡ojo! estos ficheros son un volcado de mysql. Hay que 'tocarlos' para ajustarse al nuevo modelo. Los que hay aquí ya están adaptados | |||
| @ -0,0 +1,21 @@ | |||
| # syntax=docker/dockerfile:1 | |||
| FROM python:3.8-slim-buster | |||
| WORKDIR /libros | |||
| # set environment variables | |||
| ENV PYTHONDONTWRITEBYTECODE=1 | |||
| ENV PYTHONUNBUFFERED=1 | |||
| # install system dependencies | |||
| RUN apt-get update && apt-get install -y sqlite3 | |||
| COPY requirements.txt requirements.txt | |||
| RUN pip install --upgrade pip | |||
| RUN pip3 install -r requirements.txt | |||
| COPY . . | |||
| # run entrypoint.sh | |||
| ENTRYPOINT ["/libros/entrypoint.sh"] | |||
| @ -0,0 +1,8 @@ | |||
| install: | |||
| echo "Creando imagen con version ${IMG_VERSION}" | |||
| docker build --no-cache -t creylopez/libros:${IMG_VERSION} . | |||
| docker push creylopez/libros:${IMG_VERSION} | |||
| @ -0,0 +1,3 @@ | |||
| docker build --no-cache -t creylopez/reymotapy:2.0 . | |||
| docker push creylopez/reymotapy:2.0 | |||
| @ -0,0 +1,3 @@ | |||
| #!/bin/bash | |||
| exec "$@" | |||
| @ -0,0 +1,2 @@ | |||
| export FLASK_DEBUG=1 | |||
| python3 -m flask --app libros run | |||
| @ -0,0 +1,70 @@ | |||
| import os | |||
| from flask import Flask, url_for | |||
| from flask_sqlalchemy import SQLAlchemy | |||
| from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user | |||
| from flask_migrate import Migrate | |||
| from sqlalchemy import MetaData | |||
| convention = { | |||
| "ix": 'ix_%(column_0_label)s', | |||
| "uq": "uq_%(table_name)s_%(column_0_name)s", | |||
| "ck": "ck_%(table_name)s_%(constraint_name)s", | |||
| "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", | |||
| "pk": "pk_%(table_name)s" | |||
| } | |||
| # init SQLAlchemy so we can use it later in our models | |||
| metadata = MetaData(naming_convention=convention) | |||
| db = SQLAlchemy(metadata=metadata) | |||
| migrate = Migrate(render_as_batch=True) | |||
| from libros import paginas, auth | |||
| def create_app(): | |||
| app = Flask(__name__) | |||
| app.config.from_prefixed_env() | |||
| app.config['SECRET_KEY'] = 'secret-key-goes-here' | |||
| app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///libros.db' | |||
| app.config['UPLOAD_FOLDER'] = os.path.join(app.instance_path, 'uploads') | |||
| app.config['TEMPLATES_AUTO_RELOAD'] = True | |||
| app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16 MB máximo | |||
| from .models import db | |||
| db.init_app(app) | |||
| migrate.init_app(app, db) | |||
| login_manager = LoginManager() | |||
| login_manager.login_view = 'auth.login' | |||
| login_manager.init_app(app) | |||
| from .models import User | |||
| @login_manager.user_loader | |||
| def load_user(user_id): | |||
| # since the user_id is just the primary key of our user table, use it in the query for the user | |||
| return User.query.get(int(user_id)) | |||
| from . import models | |||
| with app.app_context(): | |||
| db.create_all() | |||
| # basededatos.init_app(app) | |||
| app.register_blueprint(paginas.bp) | |||
| app.register_blueprint(auth.bp) | |||
| print(f"Current Environment: {os.getenv('ENVIRONMENT')}") | |||
| print(f"Using Database: {app.config.get('SQLALCHEMY_DATABASE_URI')}") | |||
| print(f"Directorio de uploads: {app.config.get('UPLOAD_FOLDER')}") | |||
| print(f"instance: {app.instance_path}") | |||
| return app | |||
| @ -0,0 +1,84 @@ | |||
| from flask import Blueprint, render_template, redirect, url_for, request, flash, current_app | |||
| from werkzeug.security import generate_password_hash, check_password_hash | |||
| from werkzeug.utils import secure_filename | |||
| from flask_login import login_user, logout_user, login_required | |||
| from .models import User | |||
| import os | |||
| from . import db | |||
| bp = Blueprint('auth', __name__) | |||
| @bp.route('/login') | |||
| def login(): | |||
| return render_template('login.html') | |||
| @bp.route('/login', methods=['POST']) | |||
| def login_post(): | |||
| username = request.form.get('username') | |||
| password = request.form.get('password') | |||
| remember = True if request.form.get('remember') else False | |||
| user = User.query.filter_by(username=username).first() | |||
| # check if the user actually exists | |||
| # take the user-supplied password, hash it, and compare it to the hashed password in the database | |||
| if not user or not check_password_hash(user.password, password): | |||
| flash('Por favor, comprueba los datos de registro y vuelve a intentarlo.') | |||
| return redirect(url_for('auth.login')) # if the user doesn't exist or password is wrong, reload the page | |||
| # if the above check passes, then we know the user has the right credentials | |||
| login_user(user, remember=remember) | |||
| return redirect(url_for('paginas.index')) | |||
| @bp.route('/signup') | |||
| def signup(): | |||
| return render_template('signup.html') | |||
| @bp.route('/signup', methods=['POST']) | |||
| def signup_post(): | |||
| if 'photo' not in request.files: | |||
| flash('No file part') | |||
| return redirect(request.url) | |||
| username = request.form.get('username') | |||
| # email = request.form.get('email') | |||
| password = request.form.get('password') | |||
| confirm_password = request.form.get('confirm_password') | |||
| photo = request.files['photo'] | |||
| if password != confirm_password: | |||
| flash('Passwords do not match.') | |||
| return redirect(url_for('auth.signup')) | |||
| user = User.query.filter_by(username=username).first() # if this returns a user, then the user already exists in database | |||
| if user: # if a user is found, we want to redirect back to signup page so user can try again | |||
| flash('Ese usuario ya existe') | |||
| return redirect(url_for('auth.signup')) | |||
| if photo: | |||
| photo_filename = secure_filename(photo.filename) | |||
| print("Foto: ", photo_filename) | |||
| photo.save(os.path.join(current_app.config['UPLOAD_FOLDER'], photo_filename)) | |||
| else: | |||
| print("No hay foto") | |||
| photo_filename = "" | |||
| # create a new user with the form data. Hash the password so the plaintext version isn't saved. | |||
| new_user = User(username=username, password=generate_password_hash(password, method='pbkdf2:sha256'), photo=photo_filename) | |||
| # add the new user to the database | |||
| db.session.add(new_user) | |||
| db.session.commit() | |||
| return redirect(url_for('auth.login')) | |||
| @bp.route('/logout') | |||
| @login_required | |||
| def logout(): | |||
| logout_user() | |||
| return redirect(url_for('paginas.index')) | |||
| @ -0,0 +1,32 @@ | |||
| from flask_sqlalchemy import SQLAlchemy | |||
| from flask_login import UserMixin | |||
| from . import db | |||
| class User(UserMixin, db.Model): | |||
| id = db.Column(db.Integer, primary_key=True) | |||
| username = db.Column(db.String(150), unique=True, nullable=False) | |||
| password = db.Column(db.String(150), nullable=False) | |||
| photo = db.Column(db.String(150), nullable=False) | |||
| class Autores(db.Model): | |||
| id = db.Column(db.Integer, primary_key=True) | |||
| nombre = db.Column(db.String(100), nullable=False) | |||
| apellido = db.Column(db.String(100), nullable=False) | |||
| libros = db.relationship('Libros', backref='autores', lazy=True) | |||
| foto = db.Column(db.String(100), nullable=True) | |||
| def __repr__(self): | |||
| return f'<Autores {self.apellido}>' | |||
| class Libros(db.Model): | |||
| id = db.Column(db.Integer, primary_key=True) | |||
| fecha = db.Column(db.Date, nullable=False) | |||
| titulo = db.Column(db.String(100), nullable=False) | |||
| portada = db.Column(db.String(100), nullable=True) | |||
| autor_id = db.Column(db.Integer, db.ForeignKey('autores.id'), nullable=False) | |||
| def __repr__(self): | |||
| return f'<Libros {self.titulo}>' | |||
| @ -0,0 +1,184 @@ | |||
| from flask import Blueprint, render_template, request, redirect, url_for, current_app, send_from_directory | |||
| from werkzeug.utils import secure_filename | |||
| from flask_login import login_user, logout_user, login_required | |||
| from sqlalchemy import desc | |||
| import datetime | |||
| import os | |||
| from .models import db, Libros, Autores | |||
| bp = Blueprint("paginas", __name__) | |||
| @bp.route('/') | |||
| def index(): | |||
| autores = Autores.query.all() | |||
| libros = Libros.query.all() | |||
| return render_template('index.html', autores=autores, libros=libros) | |||
| @bp.route('/libros') | |||
| def libros(): | |||
| libros = Libros.query.all() | |||
| return render_template('libros.html', libros=libros) | |||
| @bp.route('/autores') | |||
| def autores(): | |||
| autores = Autores.query.all() | |||
| #return render_template('autores.html', autores=autores) | |||
| return render_template('autorescard.html', autores=autores) | |||
| @bp.route('/404') | |||
| def cuatrocerocuatro(): | |||
| return render_template('404.html') | |||
| @bp.route('/help') | |||
| def help(): | |||
| return render_template('help.html') | |||
| @bp.route('/account') | |||
| @login_required | |||
| def account(): | |||
| return render_template('account.html') | |||
| """ | |||
| @bp.route('/libro/<int:libro_id>') | |||
| def libro(libro_id): | |||
| libro = Libros.query.get_or_404(libro_id) | |||
| autor = Autores.query.get_or_404(libro.autor_id) | |||
| return render_template('libro.html', libro=libro, autor=autor) | |||
| """ | |||
| @bp.route('/add_libro', methods=['GET', 'POST']) | |||
| @login_required | |||
| def add_libro(): | |||
| if request.method == 'POST': | |||
| fecha = datetime.date.fromisoformat(request.form['fecha']) | |||
| autor_id = request.form['autor_id'] | |||
| titulo = request.form['titulo'] | |||
| if 'portada' not in request.files: | |||
| return "Falta el fichero de imagen", 400 | |||
| portada = request.files['portada'] | |||
| # Verificar que se ha seleccionado un archivo | |||
| if portada.filename == '': | |||
| return "No selected file", 400 | |||
| if portada: | |||
| image_filename = secure_filename(portada.filename) | |||
| portada.save(os.path.join(current_app.config['UPLOAD_FOLDER'], image_filename)) | |||
| else: | |||
| image_filename = None | |||
| new_libro = Libros(fecha=fecha, autor_id=autor_id, titulo=titulo, portada=image_filename) | |||
| db.session.add(new_libro) | |||
| db.session.commit() | |||
| return redirect(url_for('paginas.autor', autor_id=autor_id)) | |||
| autores = Autores.query.all() | |||
| return render_template('add_libro.html', autores=autores) | |||
| @bp.route('/edit_libro/<int:libro_id>', methods=['GET', 'POST']) | |||
| @login_required | |||
| def edit_libro(libro_id): | |||
| libro = Libros.query.get_or_404(libro_id) | |||
| autores = Autores.query.all() | |||
| if request.method == 'POST': | |||
| libro.fecha = request.form['fecha'] | |||
| libro.autor_id = request.form['autor_id'] | |||
| libro.titulo = request.form['titulo'] | |||
| if 'portada' in request.files: | |||
| portada = request.files['portada'] | |||
| if portada.filename != '': | |||
| image_filename = secure_filename(portada.filename) | |||
| portada.save(os.path.join(current_app.config['UPLOAD_FOLDER'], image_filename)) | |||
| libro.portada = image_filename | |||
| db.session.commit() | |||
| return redirect(url_for('paginas.libro', libro_id=libro.id)) | |||
| autores = Autores.query.all() | |||
| return render_template('edit_libro.html', libro=libro, autores=autores) | |||
| @bp.route('/add_autor', methods=['GET', 'POST']) | |||
| @login_required | |||
| def add_autor(): | |||
| if request.method == 'POST': | |||
| nombre = request.form['nombre'] | |||
| apellido = request.form['apellido'] | |||
| # Verificar que el campo 'cover_image' está en request.files | |||
| if 'foto' not in request.files: | |||
| return "No file part in the request", 400 | |||
| foto = request.files['foto'] | |||
| # Verificar que se ha seleccionado un archivo | |||
| if foto.filename == '': | |||
| return "No selected file", 400 | |||
| if foto: | |||
| image_filename = secure_filename(foto.filename) | |||
| foto.save(os.path.join(current_app.config['UPLOAD_FOLDER'], image_filename)) | |||
| else: | |||
| image_filename = None | |||
| new_autor = Autores(nombre=nombre, apellido=apellido, foto=image_filename) | |||
| db.session.add(new_autor) | |||
| db.session.commit() | |||
| return redirect(url_for('paginas.index')) | |||
| return render_template('add_autor.html') | |||
| @bp.route('/edit_autor/<int:autor_id>', methods=['GET', 'POST']) | |||
| @login_required | |||
| def edit_autor(autor_id): | |||
| autor = Autores.query.get_or_404(autor_id) | |||
| if request.method == 'POST': | |||
| autor.nombre = request.form['nombre'] | |||
| autor.apellido = request.form['apellido'] | |||
| if 'foto' in request.files: | |||
| foto = request.files['foto'] | |||
| if foto.filename != '': | |||
| image_filename = secure_filename(foto.filename) | |||
| foto.save(os.path.join(current_app.config['UPLOAD_FOLDER'], image_filename)) | |||
| autor.foto = image_filename | |||
| db.session.commit() | |||
| return redirect(url_for('paginas.autor', autor_id=autor.id)) | |||
| return render_template('edit_autor.html', autor=autor) | |||
| @bp.route('/autor/<int:autor_id>') | |||
| def autor(autor_id): | |||
| autor = Autores.query.get_or_404(autor_id) | |||
| libros = Libros.query.filter_by(autor_id=autor_id).all() | |||
| return render_template('autor.html', autor=autor, libros=libros) | |||
| @bp.route('/uploads/<filename>') | |||
| def uploaded_file(filename): | |||
| return send_from_directory(current_app.config['UPLOAD_FOLDER'], filename) | |||
| @bp.route('/searchautor') | |||
| def searchautor(): | |||
| query = request.args.get('query', '') | |||
| if query: | |||
| autores = Autores.query.filter(autor.name.contains(query)).all() | |||
| else: | |||
| autores = [] | |||
| return render_template('searchautor.html', query=query, autores=autores) | |||
| @ -0,0 +1,21 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <svg width="215px" height="215px" viewBox="0 0 215 215" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | |||
| <!-- Generator: Sketch 52.6 (67491) - http://www.bohemiancoding.com/sketch --> | |||
| <title>portal-logo</title> | |||
| <desc>Created with Sketch.</desc> | |||
| <defs> | |||
| <path d="M51.165,8.742 C54.505,12.619 56.876,17.365 57.892,22.588 C60.148,17.225 65.452,13.46 71.636,13.46 C79.867,13.46 86.541,20.134 86.541,28.365 C86.541,36.597 79.867,43.269 71.636,43.269 C63.404,43.269 56.728,36.597 56.728,28.365 C56.728,12.7 44.03,0 28.365,0 C12.7,0 0,12.7 0,28.365 C0,44.031 12.7,56.731 28.365,56.731 C36.419,56.731 43.695,53.393 48.858,48.003 C45.501,44.117 43.128,39.383 42.108,34.14 C39.852,39.504 34.548,43.269 28.365,43.269 C20.133,43.269 13.46,36.597 13.46,28.365 C13.46,20.134 20.133,13.46 28.365,13.46 C36.966,13.46 43.27,20.577 43.27,28.365 C43.27,44.031 55.97,56.731 71.636,56.731 C87.3,56.731 100,44.031 100,28.365 C100,12.7 87.3,0 71.636,0 C63.589,0 56.327,3.358 51.165,8.742 Z" id="path-1"></path> | |||
| </defs> | |||
| <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> | |||
| <g id="portal-logo"> | |||
| <circle id="Oval" fill="#51B37F" fill-rule="nonzero" cx="107.5" cy="107.5" r="107.5"></circle> | |||
| <g id="logo" transform="translate(58.000000, 79.000000)"> | |||
| <mask id="mask-2" fill="white"> | |||
| <use xlink:href="#path-1"></use> | |||
| </mask> | |||
| <g id="Clip-2"></g> | |||
| <polygon id="Fill-1" fill="#FFFFFE" mask="url(#mask-2)" points="-5 61.73 105 61.73 105 -5 -5 -5"></polygon> | |||
| </g> | |||
| </g> | |||
| </g> | |||
| </svg> | |||
| @ -0,0 +1,21 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <svg width="215px" height="215px" viewBox="0 0 215 215" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | |||
| <!-- Generator: Sketch 52.6 (67491) - http://www.bohemiancoding.com/sketch --> | |||
| <title>portal-logo</title> | |||
| <desc>Created with Sketch.</desc> | |||
| <defs> | |||
| <path d="M51.165,8.742 C54.505,12.619 56.876,17.365 57.892,22.588 C60.148,17.225 65.452,13.46 71.636,13.46 C79.867,13.46 86.541,20.134 86.541,28.365 C86.541,36.597 79.867,43.269 71.636,43.269 C63.404,43.269 56.728,36.597 56.728,28.365 C56.728,12.7 44.03,0 28.365,0 C12.7,0 0,12.7 0,28.365 C0,44.031 12.7,56.731 28.365,56.731 C36.419,56.731 43.695,53.393 48.858,48.003 C45.501,44.117 43.128,39.383 42.108,34.14 C39.852,39.504 34.548,43.269 28.365,43.269 C20.133,43.269 13.46,36.597 13.46,28.365 C13.46,20.134 20.133,13.46 28.365,13.46 C36.966,13.46 43.27,20.577 43.27,28.365 C43.27,44.031 55.97,56.731 71.636,56.731 C87.3,56.731 100,44.031 100,28.365 C100,12.7 87.3,0 71.636,0 C63.589,0 56.327,3.358 51.165,8.742 Z" id="path-1"></path> | |||
| </defs> | |||
| <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> | |||
| <g id="portal-logo"> | |||
| <circle id="Oval" fill="#09B6CA" fill-rule="nonzero" cx="107.5" cy="107.5" r="107.5"></circle> | |||
| <g id="logo" transform="translate(58.000000, 79.000000)"> | |||
| <mask id="mask-2" fill="white"> | |||
| <use xlink:href="#path-1"></use> | |||
| </mask> | |||
| <g id="Clip-2"></g> | |||
| <polygon id="Fill-1" fill="#FFFFFE" mask="url(#mask-2)" points="-5 61.73 105 61.73 105 -5 -5 -5"></polygon> | |||
| </g> | |||
| </g> | |||
| </g> | |||
| </svg> | |||
| @ -0,0 +1,18 @@ | |||
| <svg width="400" height="400" xmlns="http://www.w3.org/2000/svg"> | |||
| <!-- Fondo --> | |||
| <rect width="400" height="400" fill="#ffffff" /> | |||
| <!-- Corona --> | |||
| <g transform="translate(100, 100) scale(2)"> | |||
| <polygon points="50,150 75,50 100,150" fill="#FFD700" /> | |||
| <polygon points="0,150 50,0 100,150" fill="#FFD700" /> | |||
| <polygon points="100,150 125,50 150,150" fill="#FFD700" /> | |||
| </g> | |||
| <!-- Letra R --> | |||
| <!-- | |||
| <text x="100" y="360" font-family="Arial, sans-serif" font-size="400" fill="#000000" font-weight="bold">R</text> | |||
| --> | |||
| <text x="100" y="360" font-family="Open Sans" font-size="400" fill="#000000" font-weight="bold">R</text> | |||
| </svg> | |||
| @ -0,0 +1,96 @@ | |||
| 'use strict'; | |||
| /* ===== Enable Bootstrap Popover (on element ====== */ | |||
| const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]') | |||
| const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl)) | |||
| /* ==== Enable Bootstrap Alert ====== */ | |||
| //var alertList = document.querySelectorAll('.alert') | |||
| //alertList.forEach(function (alert) { | |||
| // new bootstrap.Alert(alert) | |||
| //}); | |||
| const alertList = document.querySelectorAll('.alert') | |||
| const alerts = [...alertList].map(element => new bootstrap.Alert(element)) | |||
| /* ===== Responsive Sidepanel ====== */ | |||
| const sidePanelToggler = document.getElementById('sidepanel-toggler'); | |||
| const sidePanel = document.getElementById('app-sidepanel'); | |||
| const sidePanelDrop = document.getElementById('sidepanel-drop'); | |||
| const sidePanelClose = document.getElementById('sidepanel-close'); | |||
| window.addEventListener('load', function(){ | |||
| responsiveSidePanel(); | |||
| }); | |||
| window.addEventListener('resize', function(){ | |||
| responsiveSidePanel(); | |||
| }); | |||
| function responsiveSidePanel() { | |||
| let w = window.innerWidth; | |||
| if(w >= 1200) { | |||
| // if larger | |||
| //console.log('larger'); | |||
| sidePanel.classList.remove('sidepanel-hidden'); | |||
| sidePanel.classList.add('sidepanel-visible'); | |||
| } else { | |||
| // if smaller | |||
| //console.log('smaller'); | |||
| sidePanel.classList.remove('sidepanel-visible'); | |||
| sidePanel.classList.add('sidepanel-hidden'); | |||
| } | |||
| }; | |||
| sidePanelToggler.addEventListener('click', () => { | |||
| if (sidePanel.classList.contains('sidepanel-visible')) { | |||
| console.log('visible'); | |||
| sidePanel.classList.remove('sidepanel-visible'); | |||
| sidePanel.classList.add('sidepanel-hidden'); | |||
| } else { | |||
| console.log('hidden'); | |||
| sidePanel.classList.remove('sidepanel-hidden'); | |||
| sidePanel.classList.add('sidepanel-visible'); | |||
| } | |||
| }); | |||
| sidePanelClose.addEventListener('click', (e) => { | |||
| e.preventDefault(); | |||
| sidePanelToggler.click(); | |||
| }); | |||
| sidePanelDrop.addEventListener('click', (e) => { | |||
| sidePanelToggler.click(); | |||
| }); | |||
| /* ====== Mobile search ======= */ | |||
| const searchMobileTrigger = document.querySelector('.search-mobile-trigger'); | |||
| const searchBox = document.querySelector('.app-search-box'); | |||
| searchMobileTrigger.addEventListener('click', () => { | |||
| searchBox.classList.toggle('is-visible'); | |||
| let searchMobileTriggerIcon = document.querySelector('.search-mobile-trigger-icon'); | |||
| if(searchMobileTriggerIcon.classList.contains('fa-magnifying-glass')) { | |||
| searchMobileTriggerIcon.classList.remove('fa-magnifying-glass'); | |||
| searchMobileTriggerIcon.classList.add('fa-xmark'); | |||
| } else { | |||
| searchMobileTriggerIcon.classList.remove('fa-xmark'); | |||
| searchMobileTriggerIcon.classList.add('fa-magnifying-glass'); | |||
| } | |||
| }); | |||
| @ -0,0 +1,366 @@ | |||
| 'use strict'; | |||
| /* Chart.js docs: https://www.chartjs.org/ */ | |||
| window.chartColors = { | |||
| green: '#75c181', // rgba(117,193,129, 1) | |||
| blue: '#5b99ea', // rgba(91,153,234, 1) | |||
| gray: '#a9b5c9', | |||
| text: '#252930', | |||
| border: '#e7e9ed' | |||
| }; | |||
| /* Random number generator for demo purpose */ | |||
| var randomDataPoint = function(){ return Math.round(Math.random()*100)}; | |||
| //Area line Chart Demo | |||
| var lineChartConfig = { | |||
| type: 'line', | |||
| data: { | |||
| labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'], | |||
| datasets: [{ | |||
| label: 'Dataset', | |||
| backgroundColor: "rgba(117,193,129,0.2)", | |||
| borderColor: "rgba(117,193,129, 0.8)", | |||
| data: [ | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint() | |||
| ], | |||
| }] | |||
| }, | |||
| options: { | |||
| responsive: true, | |||
| legend: { | |||
| display: true, | |||
| position: 'bottom', | |||
| align: 'end', | |||
| }, | |||
| tooltips: { | |||
| mode: 'index', | |||
| intersect: false, | |||
| titleMarginBottom: 10, | |||
| bodySpacing: 10, | |||
| xPadding: 16, | |||
| yPadding: 16, | |||
| borderColor: window.chartColors.border, | |||
| borderWidth: 1, | |||
| backgroundColor: '#fff', | |||
| bodyFontColor: window.chartColors.text, | |||
| titleFontColor: window.chartColors.text, | |||
| callbacks: { | |||
| label: function(tooltipItem, data) { | |||
| return tooltipItem.value + '%'; | |||
| } | |||
| }, | |||
| }, | |||
| hover: { | |||
| mode: 'nearest', | |||
| intersect: true | |||
| }, | |||
| scales: { | |||
| xAxes: [{ | |||
| display: true, | |||
| gridLines: { | |||
| drawBorder: false, | |||
| color: window.chartColors.border, | |||
| }, | |||
| scaleLabel: { | |||
| display: false, | |||
| } | |||
| }], | |||
| yAxes: [{ | |||
| display: true, | |||
| gridLines: { | |||
| drawBorder: false, | |||
| color: window.chartColors.border, | |||
| }, | |||
| scaleLabel: { | |||
| display: false, | |||
| }, | |||
| ticks: { | |||
| beginAtZero: true, | |||
| userCallback: function(value, index, values) { | |||
| return value.toLocaleString() + '%'; | |||
| } | |||
| }, | |||
| }] | |||
| } | |||
| } | |||
| }; | |||
| //Bar Chart Demo | |||
| var barChartConfig = { | |||
| type: 'bar', | |||
| data: { | |||
| labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'], | |||
| datasets: [{ | |||
| label: 'Dataset 1', | |||
| backgroundColor: "rgba(117,193,129,0.8)", | |||
| hoverBackgroundColor: "rgba(117,193,129,1)", | |||
| data: [ | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint() | |||
| ] | |||
| }, | |||
| { | |||
| label: 'Dataset 2', | |||
| backgroundColor: "rgba(91,153,234,0.8)", | |||
| hoverBackgroundColor: "rgba(91,153,234,1)", | |||
| data: [ | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint() | |||
| ] | |||
| } | |||
| ] | |||
| }, | |||
| options: { | |||
| responsive: true, | |||
| legend: { | |||
| position: 'bottom', | |||
| align: 'end', | |||
| }, | |||
| tooltips: { | |||
| mode: 'index', | |||
| intersect: false, | |||
| titleMarginBottom: 10, | |||
| bodySpacing: 10, | |||
| xPadding: 16, | |||
| yPadding: 16, | |||
| borderColor: window.chartColors.border, | |||
| borderWidth: 1, | |||
| backgroundColor: '#fff', | |||
| bodyFontColor: window.chartColors.text, | |||
| titleFontColor: window.chartColors.text, | |||
| callbacks: { | |||
| label: function(tooltipItem, data) { | |||
| return tooltipItem.value + '%'; | |||
| } | |||
| }, | |||
| }, | |||
| scales: { | |||
| xAxes: [{ | |||
| display: true, | |||
| gridLines: { | |||
| drawBorder: false, | |||
| color: window.chartColors.border, | |||
| }, | |||
| }], | |||
| yAxes: [{ | |||
| display: true, | |||
| gridLines: { | |||
| drawBorder: false, | |||
| color: window.chartColors.borders, | |||
| }, | |||
| ticks: { | |||
| beginAtZero: true, | |||
| userCallback: function(value, index, values) { | |||
| return value + '%'; | |||
| } | |||
| }, | |||
| }] | |||
| } | |||
| } | |||
| } | |||
| // Pie Chart Demo | |||
| var pieChartConfig = { | |||
| type: 'pie', | |||
| data: { | |||
| datasets: [{ | |||
| data: [ | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| ], | |||
| backgroundColor: [ | |||
| window.chartColors.green, | |||
| window.chartColors.blue, | |||
| window.chartColors.gray, | |||
| ], | |||
| label: 'Dataset 1' | |||
| }], | |||
| labels: [ | |||
| 'Green', | |||
| 'Blue', | |||
| 'Gray', | |||
| ] | |||
| }, | |||
| options: { | |||
| responsive: true, | |||
| legend: { | |||
| display: true, | |||
| position: 'bottom', | |||
| align: 'center', | |||
| }, | |||
| tooltips: { | |||
| titleMarginBottom: 10, | |||
| bodySpacing: 10, | |||
| xPadding: 16, | |||
| yPadding: 16, | |||
| borderColor: window.chartColors.border, | |||
| borderWidth: 1, | |||
| backgroundColor: '#fff', | |||
| bodyFontColor: window.chartColors.text, | |||
| titleFontColor: window.chartColors.text, | |||
| /* Display % in tooltip - https://stackoverflow.com/questions/37257034/chart-js-2-0-doughnut-tooltip-percentages */ | |||
| callbacks: { | |||
| label: function(tooltipItem, data) { | |||
| //get the concerned dataset | |||
| var dataset = data.datasets[tooltipItem.datasetIndex]; | |||
| //calculate the total of this data set | |||
| var total = dataset.data.reduce(function(previousValue, currentValue, currentIndex, array) { | |||
| return previousValue + currentValue; | |||
| }); | |||
| //get the current items value | |||
| var currentValue = dataset.data[tooltipItem.index]; | |||
| //calculate the precentage based on the total and current item, also this does a rough rounding to give a whole number | |||
| var percentage = Math.floor(((currentValue/total) * 100)+0.5); | |||
| return percentage + "%"; | |||
| }, | |||
| }, | |||
| }, | |||
| } | |||
| }; | |||
| // Doughnut Chart Demo | |||
| var doughnutChartConfig = { | |||
| type: 'doughnut', | |||
| data: { | |||
| datasets: [{ | |||
| data: [ | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| ], | |||
| backgroundColor: [ | |||
| window.chartColors.green, | |||
| window.chartColors.blue, | |||
| window.chartColors.gray, | |||
| ], | |||
| label: 'Dataset 1' | |||
| }], | |||
| labels: [ | |||
| 'Green', | |||
| 'Blue', | |||
| 'Gray', | |||
| ] | |||
| }, | |||
| options: { | |||
| responsive: true, | |||
| legend: { | |||
| display: true, | |||
| position: 'bottom', | |||
| align: 'center', | |||
| }, | |||
| tooltips: { | |||
| titleMarginBottom: 10, | |||
| bodySpacing: 10, | |||
| xPadding: 16, | |||
| yPadding: 16, | |||
| borderColor: window.chartColors.border, | |||
| borderWidth: 1, | |||
| backgroundColor: '#fff', | |||
| bodyFontColor: window.chartColors.text, | |||
| titleFontColor: window.chartColors.text, | |||
| animation: { | |||
| animateScale: true, | |||
| animateRotate: true | |||
| }, | |||
| /* Display % in tooltip - https://stackoverflow.com/questions/37257034/chart-js-2-0-doughnut-tooltip-percentages */ | |||
| callbacks: { | |||
| label: function(tooltipItem, data) { | |||
| //get the concerned dataset | |||
| var dataset = data.datasets[tooltipItem.datasetIndex]; | |||
| //calculate the total of this data set | |||
| var total = dataset.data.reduce(function(previousValue, currentValue, currentIndex, array) { | |||
| return previousValue + currentValue; | |||
| }); | |||
| //get the current items value | |||
| var currentValue = dataset.data[tooltipItem.index]; | |||
| //calculate the precentage based on the total and current item, also this does a rough rounding to give a whole number | |||
| var percentage = Math.floor(((currentValue/total) * 100)+0.5); | |||
| return percentage + "%"; | |||
| }, | |||
| }, | |||
| }, | |||
| } | |||
| }; | |||
| // Generate charts on load | |||
| window.addEventListener('load', function(){ | |||
| var lineChart = document.getElementById('chart-line').getContext('2d'); | |||
| window.myLine = new Chart(lineChart, lineChartConfig); | |||
| var barChart = document.getElementById('chart-bar').getContext('2d'); | |||
| window.myBar = new Chart(barChart, barChartConfig); | |||
| var pieChart = document.getElementById('chart-pie').getContext('2d'); | |||
| window.myPie = new Chart(pieChart, pieChartConfig); | |||
| var doughnutChart = document.getElementById('chart-doughnut').getContext('2d'); | |||
| window.myDoughnut = new Chart(doughnutChart, doughnutChartConfig); | |||
| }); | |||
| @ -0,0 +1,224 @@ | |||
| 'use strict'; | |||
| /* Chart.js docs: https://www.chartjs.org/ */ | |||
| window.chartColors = { | |||
| green: '#75c181', | |||
| gray: '#a9b5c9', | |||
| text: '#252930', | |||
| border: '#e7e9ed' | |||
| }; | |||
| /* Random number generator for demo purpose */ | |||
| var randomDataPoint = function(){ return Math.round(Math.random()*10000)}; | |||
| //Chart.js Line Chart Example | |||
| var lineChartConfig = { | |||
| type: 'line', | |||
| data: { | |||
| labels: ['Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5', 'Day 6', 'Day 7'], | |||
| datasets: [{ | |||
| label: 'Current week', | |||
| fill: false, | |||
| backgroundColor: window.chartColors.green, | |||
| borderColor: window.chartColors.green, | |||
| data: [ | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint() | |||
| ], | |||
| }, { | |||
| label: 'Previous week', | |||
| borderDash: [3, 5], | |||
| backgroundColor: window.chartColors.gray, | |||
| borderColor: window.chartColors.gray, | |||
| data: [ | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint(), | |||
| randomDataPoint() | |||
| ], | |||
| fill: false, | |||
| }] | |||
| }, | |||
| options: { | |||
| responsive: true, | |||
| aspectRatio: 1.5, | |||
| legend: { | |||
| display: true, | |||
| position: 'bottom', | |||
| align: 'end', | |||
| }, | |||
| title: { | |||
| display: true, | |||
| text: 'Chart.js Line Chart Example', | |||
| }, | |||
| tooltips: { | |||
| mode: 'index', | |||
| intersect: false, | |||
| titleMarginBottom: 10, | |||
| bodySpacing: 10, | |||
| xPadding: 16, | |||
| yPadding: 16, | |||
| borderColor: window.chartColors.border, | |||
| borderWidth: 1, | |||
| backgroundColor: '#fff', | |||
| bodyFontColor: window.chartColors.text, | |||
| titleFontColor: window.chartColors.text, | |||
| callbacks: { | |||
| //Ref: https://stackoverflow.com/questions/38800226/chart-js-add-commas-to-tooltip-and-y-axis | |||
| label: function(tooltipItem, data) { | |||
| if (parseInt(tooltipItem.value) >= 1000) { | |||
| return "$" + tooltipItem.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | |||
| } else { | |||
| return '$' + tooltipItem.value; | |||
| } | |||
| } | |||
| }, | |||
| }, | |||
| hover: { | |||
| mode: 'nearest', | |||
| intersect: true | |||
| }, | |||
| scales: { | |||
| xAxes: [{ | |||
| display: true, | |||
| gridLines: { | |||
| drawBorder: false, | |||
| color: window.chartColors.border, | |||
| }, | |||
| scaleLabel: { | |||
| display: false, | |||
| } | |||
| }], | |||
| yAxes: [{ | |||
| display: true, | |||
| gridLines: { | |||
| drawBorder: false, | |||
| color: window.chartColors.border, | |||
| }, | |||
| scaleLabel: { | |||
| display: false, | |||
| }, | |||
| ticks: { | |||
| beginAtZero: true, | |||
| userCallback: function(value, index, values) { | |||
| return '$' + value.toLocaleString(); //Ref: https://stackoverflow.com/questions/38800226/chart-js-add-commas-to-tooltip-and-y-axis | |||
| } | |||
| }, | |||
| }] | |||
| } | |||
| } | |||
| }; | |||
| // Chart.js Bar Chart Example | |||
| var barChartConfig = { | |||
| type: 'bar', | |||
| data: { | |||
| labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], | |||
| datasets: [{ | |||
| label: 'Orders', | |||
| backgroundColor: window.chartColors.green, | |||
| borderColor: window.chartColors.green, | |||
| borderWidth: 1, | |||
| maxBarThickness: 16, | |||
| data: [ | |||
| 23, | |||
| 45, | |||
| 76, | |||
| 75, | |||
| 62, | |||
| 37, | |||
| 83 | |||
| ] | |||
| }] | |||
| }, | |||
| options: { | |||
| responsive: true, | |||
| aspectRatio: 1.5, | |||
| legend: { | |||
| position: 'bottom', | |||
| align: 'end', | |||
| }, | |||
| title: { | |||
| display: true, | |||
| text: 'Chart.js Bar Chart Example' | |||
| }, | |||
| tooltips: { | |||
| mode: 'index', | |||
| intersect: false, | |||
| titleMarginBottom: 10, | |||
| bodySpacing: 10, | |||
| xPadding: 16, | |||
| yPadding: 16, | |||
| borderColor: window.chartColors.border, | |||
| borderWidth: 1, | |||
| backgroundColor: '#fff', | |||
| bodyFontColor: window.chartColors.text, | |||
| titleFontColor: window.chartColors.text, | |||
| }, | |||
| scales: { | |||
| xAxes: [{ | |||
| display: true, | |||
| gridLines: { | |||
| drawBorder: false, | |||
| color: window.chartColors.border, | |||
| }, | |||
| }], | |||
| yAxes: [{ | |||
| display: true, | |||
| gridLines: { | |||
| drawBorder: false, | |||
| color: window.chartColors.borders, | |||
| }, | |||
| }] | |||
| } | |||
| } | |||
| } | |||
| // Generate charts on load | |||
| window.addEventListener('load', function(){ | |||
| var lineChart = document.getElementById('canvas-linechart').getContext('2d'); | |||
| window.myLine = new Chart(lineChart, lineChartConfig); | |||
| var barChart = document.getElementById('canvas-barchart').getContext('2d'); | |||
| window.myBar = new Chart(barChart, barChartConfig); | |||
| }); | |||
| @ -0,0 +1,593 @@ | |||
| /*! | |||
| * Bootstrap Reboot v5.3.0 (https://getbootstrap.com/) | |||
| * Copyright 2011-2023 The Bootstrap Authors | |||
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) | |||
| */ | |||
| :root, | |||
| [data-bs-theme=light] { | |||
| --bs-blue: #0d6efd; | |||
| --bs-indigo: #6610f2; | |||
| --bs-purple: #6f42c1; | |||
| --bs-pink: #d63384; | |||
| --bs-red: #dc3545; | |||
| --bs-orange: #fd7e14; | |||
| --bs-yellow: #ffc107; | |||
| --bs-green: #198754; | |||
| --bs-teal: #20c997; | |||
| --bs-cyan: #0dcaf0; | |||
| --bs-black: #000; | |||
| --bs-white: #fff; | |||
| --bs-gray: #6c757d; | |||
| --bs-gray-dark: #343a40; | |||
| --bs-gray-100: #f8f9fa; | |||
| --bs-gray-200: #e9ecef; | |||
| --bs-gray-300: #dee2e6; | |||
| --bs-gray-400: #ced4da; | |||
| --bs-gray-500: #adb5bd; | |||
| --bs-gray-600: #6c757d; | |||
| --bs-gray-700: #495057; | |||
| --bs-gray-800: #343a40; | |||
| --bs-gray-900: #212529; | |||
| --bs-primary: #0d6efd; | |||
| --bs-secondary: #6c757d; | |||
| --bs-success: #198754; | |||
| --bs-info: #0dcaf0; | |||
| --bs-warning: #ffc107; | |||
| --bs-danger: #dc3545; | |||
| --bs-light: #f8f9fa; | |||
| --bs-dark: #212529; | |||
| --bs-primary-rgb: 13, 110, 253; | |||
| --bs-secondary-rgb: 108, 117, 125; | |||
| --bs-success-rgb: 25, 135, 84; | |||
| --bs-info-rgb: 13, 202, 240; | |||
| --bs-warning-rgb: 255, 193, 7; | |||
| --bs-danger-rgb: 220, 53, 69; | |||
| --bs-light-rgb: 248, 249, 250; | |||
| --bs-dark-rgb: 33, 37, 41; | |||
| --bs-primary-text-emphasis: #052c65; | |||
| --bs-secondary-text-emphasis: #2b2f32; | |||
| --bs-success-text-emphasis: #0a3622; | |||
| --bs-info-text-emphasis: #055160; | |||
| --bs-warning-text-emphasis: #664d03; | |||
| --bs-danger-text-emphasis: #58151c; | |||
| --bs-light-text-emphasis: #495057; | |||
| --bs-dark-text-emphasis: #495057; | |||
| --bs-primary-bg-subtle: #cfe2ff; | |||
| --bs-secondary-bg-subtle: #e2e3e5; | |||
| --bs-success-bg-subtle: #d1e7dd; | |||
| --bs-info-bg-subtle: #cff4fc; | |||
| --bs-warning-bg-subtle: #fff3cd; | |||
| --bs-danger-bg-subtle: #f8d7da; | |||
| --bs-light-bg-subtle: #fcfcfd; | |||
| --bs-dark-bg-subtle: #ced4da; | |||
| --bs-primary-border-subtle: #9ec5fe; | |||
| --bs-secondary-border-subtle: #c4c8cb; | |||
| --bs-success-border-subtle: #a3cfbb; | |||
| --bs-info-border-subtle: #9eeaf9; | |||
| --bs-warning-border-subtle: #ffe69c; | |||
| --bs-danger-border-subtle: #f1aeb5; | |||
| --bs-light-border-subtle: #e9ecef; | |||
| --bs-dark-border-subtle: #adb5bd; | |||
| --bs-white-rgb: 255, 255, 255; | |||
| --bs-black-rgb: 0, 0, 0; | |||
| --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; | |||
| --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; | |||
| --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); | |||
| --bs-body-font-family: var(--bs-font-sans-serif); | |||
| --bs-body-font-size: 1rem; | |||
| --bs-body-font-weight: 400; | |||
| --bs-body-line-height: 1.5; | |||
| --bs-body-color: #212529; | |||
| --bs-body-color-rgb: 33, 37, 41; | |||
| --bs-body-bg: #fff; | |||
| --bs-body-bg-rgb: 255, 255, 255; | |||
| --bs-emphasis-color: #000; | |||
| --bs-emphasis-color-rgb: 0, 0, 0; | |||
| --bs-secondary-color: rgba(33, 37, 41, 0.75); | |||
| --bs-secondary-color-rgb: 33, 37, 41; | |||
| --bs-secondary-bg: #e9ecef; | |||
| --bs-secondary-bg-rgb: 233, 236, 239; | |||
| --bs-tertiary-color: rgba(33, 37, 41, 0.5); | |||
| --bs-tertiary-color-rgb: 33, 37, 41; | |||
| --bs-tertiary-bg: #f8f9fa; | |||
| --bs-tertiary-bg-rgb: 248, 249, 250; | |||
| --bs-heading-color: inherit; | |||
| --bs-link-color: #0d6efd; | |||
| --bs-link-color-rgb: 13, 110, 253; | |||
| --bs-link-decoration: underline; | |||
| --bs-link-hover-color: #0a58ca; | |||
| --bs-link-hover-color-rgb: 10, 88, 202; | |||
| --bs-code-color: #d63384; | |||
| --bs-highlight-bg: #fff3cd; | |||
| --bs-border-width: 1px; | |||
| --bs-border-style: solid; | |||
| --bs-border-color: #dee2e6; | |||
| --bs-border-color-translucent: rgba(0, 0, 0, 0.175); | |||
| --bs-border-radius: 0.375rem; | |||
| --bs-border-radius-sm: 0.25rem; | |||
| --bs-border-radius-lg: 0.5rem; | |||
| --bs-border-radius-xl: 1rem; | |||
| --bs-border-radius-xxl: 2rem; | |||
| --bs-border-radius-2xl: var(--bs-border-radius-xxl); | |||
| --bs-border-radius-pill: 50rem; | |||
| --bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); | |||
| --bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); | |||
| --bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175); | |||
| --bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075); | |||
| --bs-focus-ring-width: 0.25rem; | |||
| --bs-focus-ring-opacity: 0.25; | |||
| --bs-focus-ring-color: rgba(13, 110, 253, 0.25); | |||
| --bs-form-valid-color: #198754; | |||
| --bs-form-valid-border-color: #198754; | |||
| --bs-form-invalid-color: #dc3545; | |||
| --bs-form-invalid-border-color: #dc3545; | |||
| } | |||
| [data-bs-theme=dark] { | |||
| color-scheme: dark; | |||
| --bs-body-color: #adb5bd; | |||
| --bs-body-color-rgb: 173, 181, 189; | |||
| --bs-body-bg: #212529; | |||
| --bs-body-bg-rgb: 33, 37, 41; | |||
| --bs-emphasis-color: #fff; | |||
| --bs-emphasis-color-rgb: 255, 255, 255; | |||
| --bs-secondary-color: rgba(173, 181, 189, 0.75); | |||
| --bs-secondary-color-rgb: 173, 181, 189; | |||
| --bs-secondary-bg: #343a40; | |||
| --bs-secondary-bg-rgb: 52, 58, 64; | |||
| --bs-tertiary-color: rgba(173, 181, 189, 0.5); | |||
| --bs-tertiary-color-rgb: 173, 181, 189; | |||
| --bs-tertiary-bg: #2b3035; | |||
| --bs-tertiary-bg-rgb: 43, 48, 53; | |||
| --bs-primary-text-emphasis: #6ea8fe; | |||
| --bs-secondary-text-emphasis: #a7acb1; | |||
| --bs-success-text-emphasis: #75b798; | |||
| --bs-info-text-emphasis: #6edff6; | |||
| --bs-warning-text-emphasis: #ffda6a; | |||
| --bs-danger-text-emphasis: #ea868f; | |||
| --bs-light-text-emphasis: #f8f9fa; | |||
| --bs-dark-text-emphasis: #dee2e6; | |||
| --bs-primary-bg-subtle: #031633; | |||
| --bs-secondary-bg-subtle: #161719; | |||
| --bs-success-bg-subtle: #051b11; | |||
| --bs-info-bg-subtle: #032830; | |||
| --bs-warning-bg-subtle: #332701; | |||
| --bs-danger-bg-subtle: #2c0b0e; | |||
| --bs-light-bg-subtle: #343a40; | |||
| --bs-dark-bg-subtle: #1a1d20; | |||
| --bs-primary-border-subtle: #084298; | |||
| --bs-secondary-border-subtle: #41464b; | |||
| --bs-success-border-subtle: #0f5132; | |||
| --bs-info-border-subtle: #087990; | |||
| --bs-warning-border-subtle: #997404; | |||
| --bs-danger-border-subtle: #842029; | |||
| --bs-light-border-subtle: #495057; | |||
| --bs-dark-border-subtle: #343a40; | |||
| --bs-heading-color: inherit; | |||
| --bs-link-color: #6ea8fe; | |||
| --bs-link-hover-color: #8bb9fe; | |||
| --bs-link-color-rgb: 110, 168, 254; | |||
| --bs-link-hover-color-rgb: 139, 185, 254; | |||
| --bs-code-color: #e685b5; | |||
| --bs-border-color: #495057; | |||
| --bs-border-color-translucent: rgba(255, 255, 255, 0.15); | |||
| --bs-form-valid-color: #75b798; | |||
| --bs-form-valid-border-color: #75b798; | |||
| --bs-form-invalid-color: #ea868f; | |||
| --bs-form-invalid-border-color: #ea868f; | |||
| } | |||
| *, | |||
| *::before, | |||
| *::after { | |||
| box-sizing: border-box; | |||
| } | |||
| @media (prefers-reduced-motion: no-preference) { | |||
| :root { | |||
| scroll-behavior: smooth; | |||
| } | |||
| } | |||
| body { | |||
| margin: 0; | |||
| font-family: var(--bs-body-font-family); | |||
| font-size: var(--bs-body-font-size); | |||
| font-weight: var(--bs-body-font-weight); | |||
| line-height: var(--bs-body-line-height); | |||
| color: var(--bs-body-color); | |||
| text-align: var(--bs-body-text-align); | |||
| background-color: var(--bs-body-bg); | |||
| -webkit-text-size-adjust: 100%; | |||
| -webkit-tap-highlight-color: rgba(0, 0, 0, 0); | |||
| } | |||
| hr { | |||
| margin: 1rem 0; | |||
| color: inherit; | |||
| border: 0; | |||
| border-top: var(--bs-border-width) solid; | |||
| opacity: 0.25; | |||
| } | |||
| h6, h5, h4, h3, h2, h1 { | |||
| margin-top: 0; | |||
| margin-bottom: 0.5rem; | |||
| font-weight: 500; | |||
| line-height: 1.2; | |||
| color: var(--bs-heading-color); | |||
| } | |||
| h1 { | |||
| font-size: calc(1.375rem + 1.5vw); | |||
| } | |||
| @media (min-width: 1200px) { | |||
| h1 { | |||
| font-size: 2.5rem; | |||
| } | |||
| } | |||
| h2 { | |||
| font-size: calc(1.325rem + 0.9vw); | |||
| } | |||
| @media (min-width: 1200px) { | |||
| h2 { | |||
| font-size: 2rem; | |||
| } | |||
| } | |||
| h3 { | |||
| font-size: calc(1.3rem + 0.6vw); | |||
| } | |||
| @media (min-width: 1200px) { | |||
| h3 { | |||
| font-size: 1.75rem; | |||
| } | |||
| } | |||
| h4 { | |||
| font-size: calc(1.275rem + 0.3vw); | |||
| } | |||
| @media (min-width: 1200px) { | |||
| h4 { | |||
| font-size: 1.5rem; | |||
| } | |||
| } | |||
| h5 { | |||
| font-size: 1.25rem; | |||
| } | |||
| h6 { | |||
| font-size: 1rem; | |||
| } | |||
| p { | |||
| margin-top: 0; | |||
| margin-bottom: 1rem; | |||
| } | |||
| abbr[title] { | |||
| -webkit-text-decoration: underline dotted; | |||
| text-decoration: underline dotted; | |||
| cursor: help; | |||
| -webkit-text-decoration-skip-ink: none; | |||
| text-decoration-skip-ink: none; | |||
| } | |||
| address { | |||
| margin-bottom: 1rem; | |||
| font-style: normal; | |||
| line-height: inherit; | |||
| } | |||
| ol, | |||
| ul { | |||
| padding-left: 2rem; | |||
| } | |||
| ol, | |||
| ul, | |||
| dl { | |||
| margin-top: 0; | |||
| margin-bottom: 1rem; | |||
| } | |||
| ol ol, | |||
| ul ul, | |||
| ol ul, | |||
| ul ol { | |||
| margin-bottom: 0; | |||
| } | |||
| dt { | |||
| font-weight: 700; | |||
| } | |||
| dd { | |||
| margin-bottom: 0.5rem; | |||
| margin-left: 0; | |||
| } | |||
| blockquote { | |||
| margin: 0 0 1rem; | |||
| } | |||
| b, | |||
| strong { | |||
| font-weight: bolder; | |||
| } | |||
| small { | |||
| font-size: 0.875em; | |||
| } | |||
| mark { | |||
| padding: 0.1875em; | |||
| background-color: var(--bs-highlight-bg); | |||
| } | |||
| sub, | |||
| sup { | |||
| position: relative; | |||
| font-size: 0.75em; | |||
| line-height: 0; | |||
| vertical-align: baseline; | |||
| } | |||
| sub { | |||
| bottom: -0.25em; | |||
| } | |||
| sup { | |||
| top: -0.5em; | |||
| } | |||
| a { | |||
| color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)); | |||
| text-decoration: underline; | |||
| } | |||
| a:hover { | |||
| --bs-link-color-rgb: var(--bs-link-hover-color-rgb); | |||
| } | |||
| a:not([href]):not([class]), a:not([href]):not([class]):hover { | |||
| color: inherit; | |||
| text-decoration: none; | |||
| } | |||
| pre, | |||
| code, | |||
| kbd, | |||
| samp { | |||
| font-family: var(--bs-font-monospace); | |||
| font-size: 1em; | |||
| } | |||
| pre { | |||
| display: block; | |||
| margin-top: 0; | |||
| margin-bottom: 1rem; | |||
| overflow: auto; | |||
| font-size: 0.875em; | |||
| } | |||
| pre code { | |||
| font-size: inherit; | |||
| color: inherit; | |||
| word-break: normal; | |||
| } | |||
| code { | |||
| font-size: 0.875em; | |||
| color: var(--bs-code-color); | |||
| word-wrap: break-word; | |||
| } | |||
| a > code { | |||
| color: inherit; | |||
| } | |||
| kbd { | |||
| padding: 0.1875rem 0.375rem; | |||
| font-size: 0.875em; | |||
| color: var(--bs-body-bg); | |||
| background-color: var(--bs-body-color); | |||
| border-radius: 0.25rem; | |||
| } | |||
| kbd kbd { | |||
| padding: 0; | |||
| font-size: 1em; | |||
| } | |||
| figure { | |||
| margin: 0 0 1rem; | |||
| } | |||
| img, | |||
| svg { | |||
| vertical-align: middle; | |||
| } | |||
| table { | |||
| caption-side: bottom; | |||
| border-collapse: collapse; | |||
| } | |||
| caption { | |||
| padding-top: 0.5rem; | |||
| padding-bottom: 0.5rem; | |||
| color: var(--bs-secondary-color); | |||
| text-align: left; | |||
| } | |||
| th { | |||
| text-align: inherit; | |||
| text-align: -webkit-match-parent; | |||
| } | |||
| thead, | |||
| tbody, | |||
| tfoot, | |||
| tr, | |||
| td, | |||
| th { | |||
| border-color: inherit; | |||
| border-style: solid; | |||
| border-width: 0; | |||
| } | |||
| label { | |||
| display: inline-block; | |||
| } | |||
| button { | |||
| border-radius: 0; | |||
| } | |||
| button:focus:not(:focus-visible) { | |||
| outline: 0; | |||
| } | |||
| input, | |||
| button, | |||
| select, | |||
| optgroup, | |||
| textarea { | |||
| margin: 0; | |||
| font-family: inherit; | |||
| font-size: inherit; | |||
| line-height: inherit; | |||
| } | |||
| button, | |||
| select { | |||
| text-transform: none; | |||
| } | |||
| [role=button] { | |||
| cursor: pointer; | |||
| } | |||
| select { | |||
| word-wrap: normal; | |||
| } | |||
| select:disabled { | |||
| opacity: 1; | |||
| } | |||
| [list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator { | |||
| display: none !important; | |||
| } | |||
| button, | |||
| [type=button], | |||
| [type=reset], | |||
| [type=submit] { | |||
| -webkit-appearance: button; | |||
| } | |||
| button:not(:disabled), | |||
| [type=button]:not(:disabled), | |||
| [type=reset]:not(:disabled), | |||
| [type=submit]:not(:disabled) { | |||
| cursor: pointer; | |||
| } | |||
| ::-moz-focus-inner { | |||
| padding: 0; | |||
| border-style: none; | |||
| } | |||
| textarea { | |||
| resize: vertical; | |||
| } | |||
| fieldset { | |||
| min-width: 0; | |||
| padding: 0; | |||
| margin: 0; | |||
| border: 0; | |||
| } | |||
| legend { | |||
| float: left; | |||
| width: 100%; | |||
| padding: 0; | |||
| margin-bottom: 0.5rem; | |||
| font-size: calc(1.275rem + 0.3vw); | |||
| line-height: inherit; | |||
| } | |||
| @media (min-width: 1200px) { | |||
| legend { | |||
| font-size: 1.5rem; | |||
| } | |||
| } | |||
| legend + * { | |||
| clear: left; | |||
| } | |||
| ::-webkit-datetime-edit-fields-wrapper, | |||
| ::-webkit-datetime-edit-text, | |||
| ::-webkit-datetime-edit-minute, | |||
| ::-webkit-datetime-edit-hour-field, | |||
| ::-webkit-datetime-edit-day-field, | |||
| ::-webkit-datetime-edit-month-field, | |||
| ::-webkit-datetime-edit-year-field { | |||
| padding: 0; | |||
| } | |||
| ::-webkit-inner-spin-button { | |||
| height: auto; | |||
| } | |||
| [type=search] { | |||
| outline-offset: -2px; | |||
| -webkit-appearance: textfield; | |||
| } | |||
| /* rtl:raw: | |||
| [type="tel"], | |||
| [type="url"], | |||
| [type="email"], | |||
| [type="number"] { | |||
| direction: ltr; | |||
| } | |||
| */ | |||
| ::-webkit-search-decoration { | |||
| -webkit-appearance: none; | |||
| } | |||
| ::-webkit-color-swatch-wrapper { | |||
| padding: 0; | |||
| } | |||
| ::-webkit-file-upload-button { | |||
| font: inherit; | |||
| -webkit-appearance: button; | |||
| } | |||
| ::file-selector-button { | |||
| font: inherit; | |||
| -webkit-appearance: button; | |||
| } | |||
| output { | |||
| display: inline-block; | |||
| } | |||
| iframe { | |||
| border: 0; | |||
| } | |||
| summary { | |||
| display: list-item; | |||
| cursor: pointer; | |||
| } | |||
| progress { | |||
| vertical-align: baseline; | |||
| } | |||
| [hidden] { | |||
| display: none !important; | |||
| } | |||
| /*# sourceMappingURL=bootstrap-reboot.css.map */ | |||
| @ -0,0 +1,590 @@ | |||
| /*! | |||
| * Bootstrap Reboot v5.3.0 (https://getbootstrap.com/) | |||
| * Copyright 2011-2023 The Bootstrap Authors | |||
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) | |||
| */ | |||
| :root, | |||
| [data-bs-theme=light] { | |||
| --bs-blue: #0d6efd; | |||
| --bs-indigo: #6610f2; | |||
| --bs-purple: #6f42c1; | |||
| --bs-pink: #d63384; | |||
| --bs-red: #dc3545; | |||
| --bs-orange: #fd7e14; | |||
| --bs-yellow: #ffc107; | |||
| --bs-green: #198754; | |||
| --bs-teal: #20c997; | |||
| --bs-cyan: #0dcaf0; | |||
| --bs-black: #000; | |||
| --bs-white: #fff; | |||
| --bs-gray: #6c757d; | |||
| --bs-gray-dark: #343a40; | |||
| --bs-gray-100: #f8f9fa; | |||
| --bs-gray-200: #e9ecef; | |||
| --bs-gray-300: #dee2e6; | |||
| --bs-gray-400: #ced4da; | |||
| --bs-gray-500: #adb5bd; | |||
| --bs-gray-600: #6c757d; | |||
| --bs-gray-700: #495057; | |||
| --bs-gray-800: #343a40; | |||
| --bs-gray-900: #212529; | |||
| --bs-primary: #0d6efd; | |||
| --bs-secondary: #6c757d; | |||
| --bs-success: #198754; | |||
| --bs-info: #0dcaf0; | |||
| --bs-warning: #ffc107; | |||
| --bs-danger: #dc3545; | |||
| --bs-light: #f8f9fa; | |||
| --bs-dark: #212529; | |||
| --bs-primary-rgb: 13, 110, 253; | |||
| --bs-secondary-rgb: 108, 117, 125; | |||
| --bs-success-rgb: 25, 135, 84; | |||
| --bs-info-rgb: 13, 202, 240; | |||
| --bs-warning-rgb: 255, 193, 7; | |||
| --bs-danger-rgb: 220, 53, 69; | |||
| --bs-light-rgb: 248, 249, 250; | |||
| --bs-dark-rgb: 33, 37, 41; | |||
| --bs-primary-text-emphasis: #052c65; | |||
| --bs-secondary-text-emphasis: #2b2f32; | |||
| --bs-success-text-emphasis: #0a3622; | |||
| --bs-info-text-emphasis: #055160; | |||
| --bs-warning-text-emphasis: #664d03; | |||
| --bs-danger-text-emphasis: #58151c; | |||
| --bs-light-text-emphasis: #495057; | |||
| --bs-dark-text-emphasis: #495057; | |||
| --bs-primary-bg-subtle: #cfe2ff; | |||
| --bs-secondary-bg-subtle: #e2e3e5; | |||
| --bs-success-bg-subtle: #d1e7dd; | |||
| --bs-info-bg-subtle: #cff4fc; | |||
| --bs-warning-bg-subtle: #fff3cd; | |||
| --bs-danger-bg-subtle: #f8d7da; | |||
| --bs-light-bg-subtle: #fcfcfd; | |||
| --bs-dark-bg-subtle: #ced4da; | |||
| --bs-primary-border-subtle: #9ec5fe; | |||
| --bs-secondary-border-subtle: #c4c8cb; | |||
| --bs-success-border-subtle: #a3cfbb; | |||
| --bs-info-border-subtle: #9eeaf9; | |||
| --bs-warning-border-subtle: #ffe69c; | |||
| --bs-danger-border-subtle: #f1aeb5; | |||
| --bs-light-border-subtle: #e9ecef; | |||
| --bs-dark-border-subtle: #adb5bd; | |||
| --bs-white-rgb: 255, 255, 255; | |||
| --bs-black-rgb: 0, 0, 0; | |||
| --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; | |||
| --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; | |||
| --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); | |||
| --bs-body-font-family: var(--bs-font-sans-serif); | |||
| --bs-body-font-size: 1rem; | |||
| --bs-body-font-weight: 400; | |||
| --bs-body-line-height: 1.5; | |||
| --bs-body-color: #212529; | |||
| --bs-body-color-rgb: 33, 37, 41; | |||
| --bs-body-bg: #fff; | |||
| --bs-body-bg-rgb: 255, 255, 255; | |||
| --bs-emphasis-color: #000; | |||
| --bs-emphasis-color-rgb: 0, 0, 0; | |||
| --bs-secondary-color: rgba(33, 37, 41, 0.75); | |||
| --bs-secondary-color-rgb: 33, 37, 41; | |||
| --bs-secondary-bg: #e9ecef; | |||
| --bs-secondary-bg-rgb: 233, 236, 239; | |||
| --bs-tertiary-color: rgba(33, 37, 41, 0.5); | |||
| --bs-tertiary-color-rgb: 33, 37, 41; | |||
| --bs-tertiary-bg: #f8f9fa; | |||
| --bs-tertiary-bg-rgb: 248, 249, 250; | |||
| --bs-heading-color: inherit; | |||
| --bs-link-color: #0d6efd; | |||
| --bs-link-color-rgb: 13, 110, 253; | |||
| --bs-link-decoration: underline; | |||
| --bs-link-hover-color: #0a58ca; | |||
| --bs-link-hover-color-rgb: 10, 88, 202; | |||
| --bs-code-color: #d63384; | |||
| --bs-highlight-bg: #fff3cd; | |||
| --bs-border-width: 1px; | |||
| --bs-border-style: solid; | |||
| --bs-border-color: #dee2e6; | |||
| --bs-border-color-translucent: rgba(0, 0, 0, 0.175); | |||
| --bs-border-radius: 0.375rem; | |||
| --bs-border-radius-sm: 0.25rem; | |||
| --bs-border-radius-lg: 0.5rem; | |||
| --bs-border-radius-xl: 1rem; | |||
| --bs-border-radius-xxl: 2rem; | |||
| --bs-border-radius-2xl: var(--bs-border-radius-xxl); | |||
| --bs-border-radius-pill: 50rem; | |||
| --bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); | |||
| --bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); | |||
| --bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175); | |||
| --bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075); | |||
| --bs-focus-ring-width: 0.25rem; | |||
| --bs-focus-ring-opacity: 0.25; | |||
| --bs-focus-ring-color: rgba(13, 110, 253, 0.25); | |||
| --bs-form-valid-color: #198754; | |||
| --bs-form-valid-border-color: #198754; | |||
| --bs-form-invalid-color: #dc3545; | |||
| --bs-form-invalid-border-color: #dc3545; | |||
| } | |||
| [data-bs-theme=dark] { | |||
| color-scheme: dark; | |||
| --bs-body-color: #adb5bd; | |||
| --bs-body-color-rgb: 173, 181, 189; | |||
| --bs-body-bg: #212529; | |||
| --bs-body-bg-rgb: 33, 37, 41; | |||
| --bs-emphasis-color: #fff; | |||
| --bs-emphasis-color-rgb: 255, 255, 255; | |||
| --bs-secondary-color: rgba(173, 181, 189, 0.75); | |||
| --bs-secondary-color-rgb: 173, 181, 189; | |||
| --bs-secondary-bg: #343a40; | |||
| --bs-secondary-bg-rgb: 52, 58, 64; | |||
| --bs-tertiary-color: rgba(173, 181, 189, 0.5); | |||
| --bs-tertiary-color-rgb: 173, 181, 189; | |||
| --bs-tertiary-bg: #2b3035; | |||
| --bs-tertiary-bg-rgb: 43, 48, 53; | |||
| --bs-primary-text-emphasis: #6ea8fe; | |||
| --bs-secondary-text-emphasis: #a7acb1; | |||
| --bs-success-text-emphasis: #75b798; | |||
| --bs-info-text-emphasis: #6edff6; | |||
| --bs-warning-text-emphasis: #ffda6a; | |||
| --bs-danger-text-emphasis: #ea868f; | |||
| --bs-light-text-emphasis: #f8f9fa; | |||
| --bs-dark-text-emphasis: #dee2e6; | |||
| --bs-primary-bg-subtle: #031633; | |||
| --bs-secondary-bg-subtle: #161719; | |||
| --bs-success-bg-subtle: #051b11; | |||
| --bs-info-bg-subtle: #032830; | |||
| --bs-warning-bg-subtle: #332701; | |||
| --bs-danger-bg-subtle: #2c0b0e; | |||
| --bs-light-bg-subtle: #343a40; | |||
| --bs-dark-bg-subtle: #1a1d20; | |||
| --bs-primary-border-subtle: #084298; | |||
| --bs-secondary-border-subtle: #41464b; | |||
| --bs-success-border-subtle: #0f5132; | |||
| --bs-info-border-subtle: #087990; | |||
| --bs-warning-border-subtle: #997404; | |||
| --bs-danger-border-subtle: #842029; | |||
| --bs-light-border-subtle: #495057; | |||
| --bs-dark-border-subtle: #343a40; | |||
| --bs-heading-color: inherit; | |||
| --bs-link-color: #6ea8fe; | |||
| --bs-link-hover-color: #8bb9fe; | |||
| --bs-link-color-rgb: 110, 168, 254; | |||
| --bs-link-hover-color-rgb: 139, 185, 254; | |||
| --bs-code-color: #e685b5; | |||
| --bs-border-color: #495057; | |||
| --bs-border-color-translucent: rgba(255, 255, 255, 0.15); | |||
| --bs-form-valid-color: #75b798; | |||
| --bs-form-valid-border-color: #75b798; | |||
| --bs-form-invalid-color: #ea868f; | |||
| --bs-form-invalid-border-color: #ea868f; | |||
| } | |||
| *, | |||
| *::before, | |||
| *::after { | |||
| box-sizing: border-box; | |||
| } | |||
| @media (prefers-reduced-motion: no-preference) { | |||
| :root { | |||
| scroll-behavior: smooth; | |||
| } | |||
| } | |||
| body { | |||
| margin: 0; | |||
| font-family: var(--bs-body-font-family); | |||
| font-size: var(--bs-body-font-size); | |||
| font-weight: var(--bs-body-font-weight); | |||
| line-height: var(--bs-body-line-height); | |||
| color: var(--bs-body-color); | |||
| text-align: var(--bs-body-text-align); | |||
| background-color: var(--bs-body-bg); | |||
| -webkit-text-size-adjust: 100%; | |||
| -webkit-tap-highlight-color: rgba(0, 0, 0, 0); | |||
| } | |||
| hr { | |||
| margin: 1rem 0; | |||
| color: inherit; | |||
| border: 0; | |||
| border-top: var(--bs-border-width) solid; | |||
| opacity: 0.25; | |||
| } | |||
| h6, h5, h4, h3, h2, h1 { | |||
| margin-top: 0; | |||
| margin-bottom: 0.5rem; | |||
| font-weight: 500; | |||
| line-height: 1.2; | |||
| color: var(--bs-heading-color); | |||
| } | |||
| h1 { | |||
| font-size: calc(1.375rem + 1.5vw); | |||
| } | |||
| @media (min-width: 1200px) { | |||
| h1 { | |||
| font-size: 2.5rem; | |||
| } | |||
| } | |||
| h2 { | |||
| font-size: calc(1.325rem + 0.9vw); | |||
| } | |||
| @media (min-width: 1200px) { | |||
| h2 { | |||
| font-size: 2rem; | |||
| } | |||
| } | |||
| h3 { | |||
| font-size: calc(1.3rem + 0.6vw); | |||
| } | |||
| @media (min-width: 1200px) { | |||
| h3 { | |||
| font-size: 1.75rem; | |||
| } | |||
| } | |||
| h4 { | |||
| font-size: calc(1.275rem + 0.3vw); | |||
| } | |||
| @media (min-width: 1200px) { | |||
| h4 { | |||
| font-size: 1.5rem; | |||
| } | |||
| } | |||
| h5 { | |||
| font-size: 1.25rem; | |||
| } | |||
| h6 { | |||
| font-size: 1rem; | |||
| } | |||
| p { | |||
| margin-top: 0; | |||
| margin-bottom: 1rem; | |||
| } | |||
| abbr[title] { | |||
| -webkit-text-decoration: underline dotted; | |||
| text-decoration: underline dotted; | |||
| cursor: help; | |||
| -webkit-text-decoration-skip-ink: none; | |||
| text-decoration-skip-ink: none; | |||
| } | |||
| address { | |||
| margin-bottom: 1rem; | |||
| font-style: normal; | |||
| line-height: inherit; | |||
| } | |||
| ol, | |||
| ul { | |||
| padding-right: 2rem; | |||
| } | |||
| ol, | |||
| ul, | |||
| dl { | |||
| margin-top: 0; | |||
| margin-bottom: 1rem; | |||
| } | |||
| ol ol, | |||
| ul ul, | |||
| ol ul, | |||
| ul ol { | |||
| margin-bottom: 0; | |||
| } | |||
| dt { | |||
| font-weight: 700; | |||
| } | |||
| dd { | |||
| margin-bottom: 0.5rem; | |||
| margin-right: 0; | |||
| } | |||
| blockquote { | |||
| margin: 0 0 1rem; | |||
| } | |||
| b, | |||
| strong { | |||
| font-weight: bolder; | |||
| } | |||
| small { | |||
| font-size: 0.875em; | |||
| } | |||
| mark { | |||
| padding: 0.1875em; | |||
| background-color: var(--bs-highlight-bg); | |||
| } | |||
| sub, | |||
| sup { | |||
| position: relative; | |||
| font-size: 0.75em; | |||
| line-height: 0; | |||
| vertical-align: baseline; | |||
| } | |||
| sub { | |||
| bottom: -0.25em; | |||
| } | |||
| sup { | |||
| top: -0.5em; | |||
| } | |||
| a { | |||
| color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)); | |||
| text-decoration: underline; | |||
| } | |||
| a:hover { | |||
| --bs-link-color-rgb: var(--bs-link-hover-color-rgb); | |||
| } | |||
| a:not([href]):not([class]), a:not([href]):not([class]):hover { | |||
| color: inherit; | |||
| text-decoration: none; | |||
| } | |||
| pre, | |||
| code, | |||
| kbd, | |||
| samp { | |||
| font-family: var(--bs-font-monospace); | |||
| font-size: 1em; | |||
| } | |||
| pre { | |||
| display: block; | |||
| margin-top: 0; | |||
| margin-bottom: 1rem; | |||
| overflow: auto; | |||
| font-size: 0.875em; | |||
| } | |||
| pre code { | |||
| font-size: inherit; | |||
| color: inherit; | |||
| word-break: normal; | |||
| } | |||
| code { | |||
| font-size: 0.875em; | |||
| color: var(--bs-code-color); | |||
| word-wrap: break-word; | |||
| } | |||
| a > code { | |||
| color: inherit; | |||
| } | |||
| kbd { | |||
| padding: 0.1875rem 0.375rem; | |||
| font-size: 0.875em; | |||
| color: var(--bs-body-bg); | |||
| background-color: var(--bs-body-color); | |||
| border-radius: 0.25rem; | |||
| } | |||
| kbd kbd { | |||
| padding: 0; | |||
| font-size: 1em; | |||
| } | |||
| figure { | |||
| margin: 0 0 1rem; | |||
| } | |||
| img, | |||
| svg { | |||
| vertical-align: middle; | |||
| } | |||
| table { | |||
| caption-side: bottom; | |||
| border-collapse: collapse; | |||
| } | |||
| caption { | |||
| padding-top: 0.5rem; | |||
| padding-bottom: 0.5rem; | |||
| color: var(--bs-secondary-color); | |||
| text-align: right; | |||
| } | |||
| th { | |||
| text-align: inherit; | |||
| text-align: -webkit-match-parent; | |||
| } | |||
| thead, | |||
| tbody, | |||
| tfoot, | |||
| tr, | |||
| td, | |||
| th { | |||
| border-color: inherit; | |||
| border-style: solid; | |||
| border-width: 0; | |||
| } | |||
| label { | |||
| display: inline-block; | |||
| } | |||
| button { | |||
| border-radius: 0; | |||
| } | |||
| button:focus:not(:focus-visible) { | |||
| outline: 0; | |||
| } | |||
| input, | |||
| button, | |||
| select, | |||
| optgroup, | |||
| textarea { | |||
| margin: 0; | |||
| font-family: inherit; | |||
| font-size: inherit; | |||
| line-height: inherit; | |||
| } | |||
| button, | |||
| select { | |||
| text-transform: none; | |||
| } | |||
| [role=button] { | |||
| cursor: pointer; | |||
| } | |||
| select { | |||
| word-wrap: normal; | |||
| } | |||
| select:disabled { | |||
| opacity: 1; | |||
| } | |||
| [list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator { | |||
| display: none !important; | |||
| } | |||
| button, | |||
| [type=button], | |||
| [type=reset], | |||
| [type=submit] { | |||
| -webkit-appearance: button; | |||
| } | |||
| button:not(:disabled), | |||
| [type=button]:not(:disabled), | |||
| [type=reset]:not(:disabled), | |||
| [type=submit]:not(:disabled) { | |||
| cursor: pointer; | |||
| } | |||
| ::-moz-focus-inner { | |||
| padding: 0; | |||
| border-style: none; | |||
| } | |||
| textarea { | |||
| resize: vertical; | |||
| } | |||
| fieldset { | |||
| min-width: 0; | |||
| padding: 0; | |||
| margin: 0; | |||
| border: 0; | |||
| } | |||
| legend { | |||
| float: right; | |||
| width: 100%; | |||
| padding: 0; | |||
| margin-bottom: 0.5rem; | |||
| font-size: calc(1.275rem + 0.3vw); | |||
| line-height: inherit; | |||
| } | |||
| @media (min-width: 1200px) { | |||
| legend { | |||
| font-size: 1.5rem; | |||
| } | |||
| } | |||
| legend + * { | |||
| clear: right; | |||
| } | |||
| ::-webkit-datetime-edit-fields-wrapper, | |||
| ::-webkit-datetime-edit-text, | |||
| ::-webkit-datetime-edit-minute, | |||
| ::-webkit-datetime-edit-hour-field, | |||
| ::-webkit-datetime-edit-day-field, | |||
| ::-webkit-datetime-edit-month-field, | |||
| ::-webkit-datetime-edit-year-field { | |||
| padding: 0; | |||
| } | |||
| ::-webkit-inner-spin-button { | |||
| height: auto; | |||
| } | |||
| [type=search] { | |||
| outline-offset: -2px; | |||
| -webkit-appearance: textfield; | |||
| } | |||
| [type="tel"], | |||
| [type="url"], | |||
| [type="email"], | |||
| [type="number"] { | |||
| direction: ltr; | |||
| } | |||
| ::-webkit-search-decoration { | |||
| -webkit-appearance: none; | |||
| } | |||
| ::-webkit-color-swatch-wrapper { | |||
| padding: 0; | |||
| } | |||
| ::-webkit-file-upload-button { | |||
| font: inherit; | |||
| -webkit-appearance: button; | |||
| } | |||
| ::file-selector-button { | |||
| font: inherit; | |||
| -webkit-appearance: button; | |||
| } | |||
| output { | |||
| display: inline-block; | |||
| } | |||
| iframe { | |||
| border: 0; | |||
| } | |||
| summary { | |||
| display: list-item; | |||
| cursor: pointer; | |||
| } | |||
| progress { | |||
| vertical-align: baseline; | |||
| } | |||
| [hidden] { | |||
| display: none !important; | |||
| } | |||
| /*# sourceMappingURL=bootstrap-reboot.rtl.css.map */ | |||
| @ -0,0 +1,165 @@ | |||
| Fonticons, Inc. (https://fontawesome.com) | |||
| -------------------------------------------------------------------------------- | |||
| Font Awesome Free License | |||
| Font Awesome Free is free, open source, and GPL friendly. You can use it for | |||
| commercial projects, open source projects, or really almost whatever you want. | |||
| Full Font Awesome Free license: https://fontawesome.com/license/free. | |||
| -------------------------------------------------------------------------------- | |||
| # Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/) | |||
| The Font Awesome Free download is licensed under a Creative Commons | |||
| Attribution 4.0 International License and applies to all icons packaged | |||
| as SVG and JS file types. | |||
| -------------------------------------------------------------------------------- | |||
| # Fonts: SIL OFL 1.1 License | |||
| In the Font Awesome Free download, the SIL OFL license applies to all icons | |||
| packaged as web and desktop font files. | |||
| Copyright (c) 2023 Fonticons, Inc. (https://fontawesome.com) | |||
| with Reserved Font Name: "Font Awesome". | |||
| This Font Software is licensed under the SIL Open Font License, Version 1.1. | |||
| This license is copied below, and is also available with a FAQ at: | |||
| http://scripts.sil.org/OFL | |||
| SIL OPEN FONT LICENSE | |||
| Version 1.1 - 26 February 2007 | |||
| PREAMBLE | |||
| The goals of the Open Font License (OFL) are to stimulate worldwide | |||
| development of collaborative font projects, to support the font creation | |||
| efforts of academic and linguistic communities, and to provide a free and | |||
| open framework in which fonts may be shared and improved in partnership | |||
| with others. | |||
| The OFL allows the licensed fonts to be used, studied, modified and | |||
| redistributed freely as long as they are not sold by themselves. The | |||
| fonts, including any derivative works, can be bundled, embedded, | |||
| redistributed and/or sold with any software provided that any reserved | |||
| names are not used by derivative works. The fonts and derivatives, | |||
| however, cannot be released under any other type of license. The | |||
| requirement for fonts to remain under this license does not apply | |||
| to any document created using the fonts or their derivatives. | |||
| DEFINITIONS | |||
| "Font Software" refers to the set of files released by the Copyright | |||
| Holder(s) under this license and clearly marked as such. This may | |||
| include source files, build scripts and documentation. | |||
| "Reserved Font Name" refers to any names specified as such after the | |||
| copyright statement(s). | |||
| "Original Version" refers to the collection of Font Software components as | |||
| distributed by the Copyright Holder(s). | |||
| "Modified Version" refers to any derivative made by adding to, deleting, | |||
| or substituting — in part or in whole — any of the components of the | |||
| Original Version, by changing formats or by porting the Font Software to a | |||
| new environment. | |||
| "Author" refers to any designer, engineer, programmer, technical | |||
| writer or other person who contributed to the Font Software. | |||
| PERMISSION & CONDITIONS | |||
| Permission is hereby granted, free of charge, to any person obtaining | |||
| a copy of the Font Software, to use, study, copy, merge, embed, modify, | |||
| redistribute, and sell modified and unmodified copies of the Font | |||
| Software, subject to the following conditions: | |||
| 1) Neither the Font Software nor any of its individual components, | |||
| in Original or Modified Versions, may be sold by itself. | |||
| 2) Original or Modified Versions of the Font Software may be bundled, | |||
| redistributed and/or sold with any software, provided that each copy | |||
| contains the above copyright notice and this license. These can be | |||
| included either as stand-alone text files, human-readable headers or | |||
| in the appropriate machine-readable metadata fields within text or | |||
| binary files as long as those fields can be easily viewed by the user. | |||
| 3) No Modified Version of the Font Software may use the Reserved Font | |||
| Name(s) unless explicit written permission is granted by the corresponding | |||
| Copyright Holder. This restriction only applies to the primary font name as | |||
| presented to the users. | |||
| 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |||
| Software shall not be used to promote, endorse or advertise any | |||
| Modified Version, except to acknowledge the contribution(s) of the | |||
| Copyright Holder(s) and the Author(s) or with their explicit written | |||
| permission. | |||
| 5) The Font Software, modified or unmodified, in part or in whole, | |||
| must be distributed entirely under this license, and must not be | |||
| distributed under any other license. The requirement for fonts to | |||
| remain under this license does not apply to any document created | |||
| using the Font Software. | |||
| TERMINATION | |||
| This license becomes null and void if any of the above conditions are | |||
| not met. | |||
| DISCLAIMER | |||
| THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |||
| OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |||
| COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |||
| INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |||
| DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |||
| FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |||
| OTHER DEALINGS IN THE FONT SOFTWARE. | |||
| -------------------------------------------------------------------------------- | |||
| # Code: MIT License (https://opensource.org/licenses/MIT) | |||
| In the Font Awesome Free download, the MIT license applies to all non-font and | |||
| non-icon files. | |||
| Copyright 2023 Fonticons, Inc. | |||
| Permission is hereby granted, free of charge, to any person obtaining a copy of | |||
| this software and associated documentation files (the "Software"), to deal in the | |||
| Software without restriction, including without limitation the rights to use, copy, | |||
| modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, | |||
| and to permit persons to whom the Software is furnished to do so, subject to the | |||
| following conditions: | |||
| The above copyright notice and this permission notice shall be included in all | |||
| copies or substantial portions of the Software. | |||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | |||
| INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | |||
| PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |||
| HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |||
| SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||
| -------------------------------------------------------------------------------- | |||
| # Attribution | |||
| Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font | |||
| Awesome Free files already contain embedded comments with sufficient | |||
| attribution, so you shouldn't need to do anything additional when using these | |||
| files normally. | |||
| We've kept attribution comments terse, so we ask that you do not actively work | |||
| to remove them from files, especially code. They're a great way for folks to | |||
| learn about Font Awesome. | |||
| -------------------------------------------------------------------------------- | |||
| # Brand Icons | |||
| All brand icons are trademarks of their respective owners. The use of these | |||
| trademarks does not indicate endorsement of the trademark holder by Font | |||
| Awesome, nor vice versa. **Please do not use brand logos for any purpose except | |||
| to represent the company, product, or service to which they refer.** | |||
| @ -0,0 +1,19 @@ | |||
| /*! | |||
| * Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com | |||
| * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) | |||
| * Copyright 2023 Fonticons, Inc. | |||
| */ | |||
| :root, :host { | |||
| --fa-style-family-classic: 'Font Awesome 6 Free'; | |||
| --fa-font-regular: normal 400 1em/1 'Font Awesome 6 Free'; } | |||
| @font-face { | |||
| font-family: 'Font Awesome 6 Free'; | |||
| font-style: normal; | |||
| font-weight: 400; | |||
| font-display: block; | |||
| src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); } | |||
| .far, | |||
| .fa-regular { | |||
| font-weight: 400; } | |||
| @ -0,0 +1,6 @@ | |||
| /*! | |||
| * Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com | |||
| * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) | |||
| * Copyright 2023 Fonticons, Inc. | |||
| */ | |||
| :host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-weight:400} | |||
| @ -0,0 +1,19 @@ | |||
| /*! | |||
| * Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com | |||
| * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) | |||
| * Copyright 2023 Fonticons, Inc. | |||
| */ | |||
| :root, :host { | |||
| --fa-style-family-classic: 'Font Awesome 6 Free'; | |||
| --fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free'; } | |||
| @font-face { | |||
| font-family: 'Font Awesome 6 Free'; | |||
| font-style: normal; | |||
| font-weight: 900; | |||
| font-display: block; | |||
| src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); } | |||
| .fas, | |||
| .fa-solid { | |||
| font-weight: 900; } | |||