| @ -0,0 +1,15 @@ | |||||
| export IMG_VERSION = 6.4 | |||||
| imagen: | |||||
| cd ../servicios; make | |||||
| install: | |||||
| envsubst < lyrics-deployment.yaml |kubectl create -f - | |||||
| storage: | |||||
| kubectl create -f pv-local-recetaspy.yaml | |||||
| kubectl create -f recetaspy-prod-persistentvolumeclaim.yaml | |||||
| clean: | |||||
| envsubst < lyrics-deployment.yaml |kubectl delete -f - | |||||
| @ -0,0 +1 @@ | |||||
| kubectl exec -ti deployment.apps/lyrics -- /bin/bash | |||||
| @ -0,0 +1,14 @@ | |||||
| apiVersion: v1 | |||||
| kind: PersistentVolume | |||||
| metadata: | |||||
| name: recetaspy-app-folder | |||||
| labels: | |||||
| app: recetaspy | |||||
| spec: | |||||
| capacity: | |||||
| storage: 100Mi | |||||
| accessModes: | |||||
| - ReadWriteOnce | |||||
| hostPath: | |||||
| path: "/mnt/Externo/recetaspy" | |||||
| @ -0,0 +1,56 @@ | |||||
| apiVersion: v1 | |||||
| kind: Service | |||||
| metadata: | |||||
| name: recetaspy | |||||
| spec: | |||||
| type: NodePort | |||||
| ports: | |||||
| - name: http | |||||
| port: 5000 | |||||
| nodePort: 30340 | |||||
| targetPort: recetaspy | |||||
| selector: | |||||
| app: recetaspy | |||||
| --- | |||||
| apiVersion: apps/v1 | |||||
| kind: Deployment | |||||
| metadata: | |||||
| name: recetaspy | |||||
| labels: | |||||
| app: recetaspy | |||||
| spec: | |||||
| replicas: 1 | |||||
| selector: | |||||
| matchLabels: | |||||
| app: recetaspy | |||||
| strategy: | |||||
| type: Recreate | |||||
| template: | |||||
| metadata: | |||||
| labels: | |||||
| app: recetaspy | |||||
| spec: | |||||
| containers: | |||||
| - args: | |||||
| - gunicorn | |||||
| - --bind | |||||
| - 0.0.0.0:5000 | |||||
| - recetaspy:create_app() | |||||
| image: creylopez/recetaspy:$IMG_VERSION | |||||
| name: recetaspy | |||||
| env: | |||||
| - name: SALUDO_DEMO | |||||
| value: "Hola, mundo" | |||||
| ports: | |||||
| - containerPort: 5000 | |||||
| name: recetaspy | |||||
| resources: {} | |||||
| volumeMounts: | |||||
| - mountPath: /recetaspy/instance | |||||
| name: recetaspy-prod | |||||
| restartPolicy: Always | |||||
| volumes: | |||||
| - name: recetaspy-prod | |||||
| persistentVolumeClaim: | |||||
| claimName: recetaspy-prod | |||||
| status: {} | |||||
| @ -0,0 +1,14 @@ | |||||
| apiVersion: v1 | |||||
| kind: PersistentVolumeClaim | |||||
| metadata: | |||||
| creationTimestamp: null | |||||
| labels: | |||||
| io.kompose.service: recetaspy-prod | |||||
| name: recetaspy-prod | |||||
| spec: | |||||
| accessModes: | |||||
| - ReadWriteOnce | |||||
| resources: | |||||
| requests: | |||||
| storage: 100Mi | |||||
| status: {} | |||||
| @ -0,0 +1 @@ | |||||
| docker run -it creylopez/lyrics:5.3 bash | |||||
| @ -0,0 +1,106 @@ | |||||
| from flask import Flask, render_template, request, redirect, url_for, flash | |||||
| from flask_sqlalchemy import SQLAlchemy | |||||
| from models import db, User, Recipe, Ingredient, Instruction | |||||
| from werkzeug.security import generate_password_hash, check_password_hash | |||||
| from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user | |||||
| app = Flask(__name__) | |||||
| app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///recipes.db' | |||||
| app.config['SECRET_KEY'] = 'your_secret_key' | |||||
| db.init_app(app) | |||||
| login_manager = LoginManager() | |||||
| login_manager.init_app(app) | |||||
| login_manager.login_view = 'login' | |||||
| @login_manager.user_loader | |||||
| def load_user(user_id): | |||||
| return User.query.get(int(user_id)) | |||||
| @app.route('/') | |||||
| def index(): | |||||
| recipes = Recipe.query.all() | |||||
| return render_template('index.html', recipes=recipes) | |||||
| @app.route('/recipe/<int:recipe_id>') | |||||
| def recipe(recipe_id): | |||||
| recipe = Recipe.query.get_or_404(recipe_id) | |||||
| return render_template('recipe.html', recipe=recipe) | |||||
| @app.route('/new_recipe', methods=['GET', 'POST']) | |||||
| @login_required | |||||
| def new_recipe(): | |||||
| if request.method == 'POST': | |||||
| title = request.form['title'] | |||||
| description = request.form['description'] | |||||
| ingredients = request.form.getlist('ingredient') | |||||
| quantities = request.form.getlist('quantity') | |||||
| steps = request.form.getlist('step') | |||||
| step_descriptions = request.form.getlist('step_description') | |||||
| recipe = Recipe(title=title, description=description, author=current_user) | |||||
| for ingredient, quantity in zip(ingredients, quantities): | |||||
| recipe.ingredients.append(Ingredient(name=ingredient, quantity=quantity)) | |||||
| for step, description in zip(steps, step_descriptions): | |||||
| recipe.instructions.append(Instruction(step=step, description=description)) | |||||
| db.session.add(recipe) | |||||
| db.session.commit() | |||||
| flash('Recipe created successfully!', 'success') | |||||
| return redirect(url_for('index')) | |||||
| return render_template('new_recipe.html') | |||||
| @app.route('/login', methods=['GET', 'POST']) | |||||
| def login(): | |||||
| if request.method == 'POST': | |||||
| email = request.form['email'] | |||||
| password = request.form['password'] | |||||
| user = User.query.filter_by(email=email).first() | |||||
| if user and check_password_hash(user.password, password): | |||||
| login_user(user) | |||||
| flash('Logged in successfully.') | |||||
| return redirect(url_for('index')) | |||||
| else: | |||||
| flash('Invalid email or password.') | |||||
| return render_template('login.html') | |||||
| @app.route('/logout') | |||||
| @login_required | |||||
| def logout(): | |||||
| logout_user() | |||||
| flash('Logged out successfully.') | |||||
| return redirect(url_for('index')) | |||||
| @app.route('/register', methods=['GET', 'POST']) | |||||
| def register(): | |||||
| if request.method == 'POST': | |||||
| username = request.form['username'] | |||||
| email = request.form['email'] | |||||
| password = request.form['password'] | |||||
| confirm_password = request.form['confirm_password'] | |||||
| if password != confirm_password: | |||||
| flash('Passwords do not match.') | |||||
| return redirect(url_for('register')) | |||||
| hashed_password = generate_password_hash(password, method='pbkdf2:sha256') | |||||
| new_user = User(username=username, email=email, password=hashed_password) | |||||
| try: | |||||
| db.session.add(new_user) | |||||
| db.session.commit() | |||||
| flash('Registration successful.') | |||||
| return redirect(url_for('login')) | |||||
| except: | |||||
| flash('Email address already exists.') | |||||
| return redirect(url_for('register')) | |||||
| return render_template('register.html') | |||||
| if __name__ == '__main__': | |||||
| with app.app_context(): | |||||
| db.create_all() | |||||
| app.run(debug=True) | |||||
| @ -0,0 +1,30 @@ | |||||
| from flask_sqlalchemy import SQLAlchemy | |||||
| db = SQLAlchemy() | |||||
| class User(db.Model): | |||||
| id = db.Column(db.Integer, primary_key=True) | |||||
| username = db.Column(db.String(150), unique=True, nullable=False) | |||||
| email = db.Column(db.String(150), unique=True, nullable=False) | |||||
| password = db.Column(db.String(150), nullable=False) | |||||
| recipes = db.relationship('Recipe', backref='author', lazy=True) | |||||
| class Recipe(db.Model): | |||||
| id = db.Column(db.Integer, primary_key=True) | |||||
| title = db.Column(db.String(150), nullable=False) | |||||
| description = db.Column(db.Text, nullable=False) | |||||
| user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) | |||||
| ingredients = db.relationship('Ingredient', backref='recipe', lazy=True) | |||||
| instructions = db.relationship('Instruction', backref='recipe', lazy=True) | |||||
| class Ingredient(db.Model): | |||||
| id = db.Column(db.Integer, primary_key=True) | |||||
| name = db.Column(db.String(100), nullable=False) | |||||
| quantity = db.Column(db.String(50), nullable=False) | |||||
| recipe_id = db.Column(db.Integer, db.ForeignKey('recipe.id'), nullable=False) | |||||
| class Instruction(db.Model): | |||||
| id = db.Column(db.Integer, primary_key=True) | |||||
| step = db.Column(db.Integer, nullable=False) | |||||
| description = db.Column(db.Text, nullable=False) | |||||
| recipe_id = db.Column(db.Integer, db.ForeignKey('recipe.id'), nullable=False) | |||||
| @ -0,0 +1,38 @@ | |||||
| body { | |||||
| font-family: Arial, sans-serif; | |||||
| padding: 20px; | |||||
| } | |||||
| nav { | |||||
| margin-bottom: 20px; | |||||
| } | |||||
| nav a { | |||||
| margin-right: 10px; | |||||
| } | |||||
| .flashes { | |||||
| list-style-type: none; | |||||
| padding: 0; | |||||
| } | |||||
| .flashes li { | |||||
| margin-bottom: 10px; | |||||
| padding: 10px; | |||||
| border-radius: 5px; | |||||
| } | |||||
| .flashes .success { | |||||
| background-color: #d4edda; | |||||
| color: #155724; | |||||
| } | |||||
| .flashes .error { | |||||
| background-color: #f8d7da; | |||||
| color: #721c24; | |||||
| } | |||||
| .content { | |||||
| max-width: 800px; | |||||
| margin: auto; | |||||
| } | |||||
| @ -0,0 +1,32 @@ | |||||
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>{% block title %}Recipe App{% endblock %}</title> | |||||
| <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> | |||||
| </head> | |||||
| <body> | |||||
| <nav> | |||||
| <a href="{{ url_for('index') }}">Home</a> | |||||
| {% if current_user.is_authenticated %} | |||||
| <a href="{{ url_for('new_recipe') }}">New Recipe</a> | |||||
| <a href="{{ url_for('logout') }}">Logout</a> | |||||
| {% else %} | |||||
| <a href="{{ url_for('login') }}">Login</a> | |||||
| <a href="{{ url_for('register') }}">Register</a> | |||||
| {% endif %} | |||||
| </nav> | |||||
| <div class="content"> | |||||
| {% with messages = get_flashed_messages(with_categories=true) %} | |||||
| {% if messages %} | |||||
| <ul class="flashes"> | |||||
| {% for category, message in messages %} | |||||
| <li class="{{ category }}">{{ message }}</li> | |||||
| {% endfor %} | |||||
| </ul> | |||||
| {% endif %} | |||||
| {% endwith %} | |||||
| {% block content %}{% endblock %} | |||||
| </div> | |||||
| </body> | |||||
| </html> | |||||
| @ -0,0 +1,12 @@ | |||||
| {% extends 'base.html' %} | |||||
| {% block title %}Home{% endblock %} | |||||
| {% block content %} | |||||
| <h1>Recipes</h1> | |||||
| <ul> | |||||
| {% for recipe in recipes %} | |||||
| <li><a href="{{ url_for('recipe', recipe_id=recipe.id) }}">{{ recipe.title }}</a></li> | |||||
| {% endfor %} | |||||
| </ul> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,14 @@ | |||||
| {% extends 'base.html' %} | |||||
| {% block title %}Login{% endblock %} | |||||
| {% block content %} | |||||
| <h1>Login</h1> | |||||
| <form action="{{ url_for('login') }}" method="post"> | |||||
| <label for="email">Email:</label> | |||||
| <input type="email" id="email" name="email" required> | |||||
| <label for="password">Password:</label> | |||||
| <input type="password" id="password" name="password" required> | |||||
| <button type="submit">Login</button> | |||||
| </form> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,48 @@ | |||||
| {% extends 'base.html' %} | |||||
| {% block title %}New Recipe{% endblock %} | |||||
| {% block content %} | |||||
| <h1>New Recipe</h1> | |||||
| <form action="{{ url_for('new_recipe') }}" method="post"> | |||||
| <label for="title">Title:</label> | |||||
| <input type="text" id="title" name="title" required> | |||||
| <label for="description">Description:</label> | |||||
| <textarea id="description" name="description" required></textarea> | |||||
| <h2>Ingredients</h2> | |||||
| <div id="ingredients"> | |||||
| <div class="ingredient"> | |||||
| <input type="text" name="ingredient" placeholder="Ingredient" required> | |||||
| <input type="text" name="quantity" placeholder="Quantity" required> | |||||
| </div> | |||||
| </div> | |||||
| <button type="button" onclick="addIngredient()">Add Ingredient</button> | |||||
| <h2>Instructions</h2> | |||||
| <div id="instructions"> | |||||
| <div class="instruction"> | |||||
| <input type="text" name="step" placeholder="Step Number" required> | |||||
| <textarea name="step_description" placeholder="Step Description" required></textarea> | |||||
| </div> | |||||
| </div> | |||||
| <button type="button" onclick="addInstruction()">Add Instruction</button> | |||||
| <button type="submit">Create Recipe</button> | |||||
| </form> | |||||
| <script> | |||||
| function addIngredient() { | |||||
| var ingredientsDiv = document.getElementById('ingredients'); | |||||
| var newIngredientDiv = document.createElement('div'); | |||||
| newIngredientDiv.className = 'ingredient'; | |||||
| newIngredientDiv.innerHTML = '<input type="text" name="ingredient" placeholder="Ingredient" required> <input type="text" name="quantity" placeholder="Quantity" required>'; | |||||
| ingredientsDiv.appendChild(newIngredientDiv); | |||||
| } | |||||
| function addInstruction() { | |||||
| var instructionsDiv = document.getElementById('instructions'); | |||||
| var newInstructionDiv = document.createElement('div'); | |||||
| newInstructionDiv.className = 'instruction'; | |||||
| newInstructionDiv.innerHTML = '<input type="text" name="step" placeholder="Step Number" required> <textarea name="step_description" placeholder="Step Description" required></textarea>'; | |||||
| instructionsDiv.appendChild(newInstructionDiv); | |||||
| } | |||||
| </script> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,20 @@ | |||||
| {% extends 'base.html' %} | |||||
| {% block title %}Recipe: {{ recipe.title }}{% endblock %} | |||||
| {% block content %} | |||||
| <h1>{{ recipe.title }}</h1> | |||||
| <p>{{ recipe.description }}</p> | |||||
| <h2>Ingredients</h2> | |||||
| <ul> | |||||
| {% for ingredient in recipe.ingredients %} | |||||
| <li>{{ ingredient.quantity }} {{ ingredient.name }}</li> | |||||
| {% endfor %} | |||||
| </ul> | |||||
| <h2>Instructions</h2> | |||||
| <ol> | |||||
| {% for instruction in recipe.instructions %} | |||||
| <li>{{ instruction.description }}</li> | |||||
| {% endfor %} | |||||
| </ol> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,18 @@ | |||||
| {% extends 'base.html' %} | |||||
| {% block title %}Register{% endblock %} | |||||
| {% block content %} | |||||
| <h1>Register</h1> | |||||
| <form action="{{ url_for('register') }}" method="post"> | |||||
| <label for="username">Username:</label> | |||||
| <input type="text" id="username" name="username" required> | |||||
| <label for="email">Email:</label> | |||||
| <input type="email" id="email" name="email" required> | |||||
| <label for="password">Password:</label> | |||||
| <input type="password" id="password" name="password" required> | |||||
| <label for="confirm_password">Confirm Password:</label> | |||||
| <input type="password" id="confirm_password" name="confirm_password" required> | |||||
| <button type="submit">Register</button> | |||||
| </form> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,20 @@ | |||||
| # syntax=docker/dockerfile:1 | |||||
| FROM python:3.8-slim-buster | |||||
| WORKDIR /reymotapy | |||||
| # 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 pip3 install -r requirements.txt | |||||
| COPY . . | |||||
| # run entrypoint.sh | |||||
| ENTRYPOINT ["/reymotapy/entrypoint.sh"] | |||||
| @ -0,0 +1,8 @@ | |||||
| install: | |||||
| echo "Creando imagen con version ${IMG_VERSION}" | |||||
| docker build --no-cache -t creylopez/lyrics:${IMG_VERSION} . | |||||
| docker push creylopez/lyrics:${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 @@ | |||||
| python3 -m flask --app recetaspy run | |||||
| @ -0,0 +1,55 @@ | |||||
| 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 | |||||
| # init SQLAlchemy so we can use it later in our models | |||||
| db = SQLAlchemy() | |||||
| from recetaspy 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:///recipes.db' | |||||
| app.config['UPLOAD_FOLDER'] = os.path.join(app.instance_path, 'uploads') | |||||
| app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16 MB máximo | |||||
| # Asegúrate de que el directorio de carga existe | |||||
| os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) | |||||
| from .models import db | |||||
| db.init_app(app) | |||||
| 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() | |||||
| app.register_blueprint(paginas.bp) | |||||
| app.register_blueprint(auth.bp) | |||||
| print(f"Current Environment: {os.getenv('ENVIRONMENT')}") | |||||
| print(f"Using Database: {app.config.get('DATABASE')}") | |||||
| print(f"Directorio de uploads: {app.config.get('UPLOAD_FOLDER')}") | |||||
| print(f"instance: {app.instance_path}") | |||||
| print(f"Saludo: {os.getenv('SALUDO_DEMO')}") | |||||
| return app | |||||
| @ -0,0 +1,82 @@ | |||||
| 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(): | |||||
| email = request.form['email'] | |||||
| password = request.form['password'] | |||||
| remember = True if request.form.get('remember') else False | |||||
| user = User.query.filter_by(email=email).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(): | |||||
| username = request.form['username'] | |||||
| email = request.form['email'] | |||||
| password = request.form['password'] | |||||
| confirm_password = request.form['confirm_password'] | |||||
| photo = request.files['fotoperfil'] | |||||
| if password != confirm_password: | |||||
| flash('Passwords do not match.') | |||||
| return redirect(url_for('auth.signup')) | |||||
| user = User.query.filter_by(email=email).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) | |||||
| photo.save(os.path.join(current_app.config['UPLOAD_FOLDER'], photo_filename)) | |||||
| else: | |||||
| photo_filename = "" | |||||
| # create a new user with the form data. Hash the password so the plaintext version isn't saved. | |||||
| new_user = User(email=email, username=username, password=generate_password_hash(password, method='pbkdf2:sha256'), photo=photo_filename) | |||||
| # add the new user to the database | |||||
| # try: | |||||
| db.session.add(new_user) | |||||
| db.session.commit() | |||||
| flash('Registration successful.') | |||||
| return redirect(url_for('auth.login')) | |||||
| # except: | |||||
| # flash('Email address already exists.') | |||||
| # return redirect(url_for('auth.signup')) | |||||
| @bp.route('/logout') | |||||
| @login_required | |||||
| def logout(): | |||||
| logout_user() | |||||
| return redirect(url_for('paginas.index')) | |||||
| @ -0,0 +1,33 @@ | |||||
| 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) | |||||
| email = db.Column(db.String(150), unique=True, nullable=False) | |||||
| password = db.Column(db.String(150), nullable=False) | |||||
| recipes = db.relationship('Recipe', backref='author', lazy=True) | |||||
| photo = db.Column(db.String(150), nullable=False) | |||||
| class Recipe(db.Model): | |||||
| id = db.Column(db.Integer, primary_key=True) | |||||
| title = db.Column(db.String(150), nullable=False) | |||||
| description = db.Column(db.Text, nullable=False) | |||||
| user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) | |||||
| ingredients = db.relationship('Ingredient', backref='recipe', lazy=True) | |||||
| instructions = db.relationship('Instruction', backref='recipe', lazy=True) | |||||
| class Ingredient(db.Model): | |||||
| id = db.Column(db.Integer, primary_key=True) | |||||
| name = db.Column(db.String(100), nullable=False) | |||||
| quantity = db.Column(db.String(50), nullable=False) | |||||
| recipe_id = db.Column(db.Integer, db.ForeignKey('recipe.id'), nullable=False) | |||||
| class Instruction(db.Model): | |||||
| id = db.Column(db.Integer, primary_key=True) | |||||
| step = db.Column(db.Integer, nullable=False) | |||||
| description = db.Column(db.Text, nullable=False) | |||||
| recipe_id = db.Column(db.Integer, db.ForeignKey('recipe.id'), nullable=False) | |||||
| @ -0,0 +1,87 @@ | |||||
| from flask import Blueprint, render_template, request, redirect, url_for, current_app, send_from_directory,flash | |||||
| from werkzeug.utils import secure_filename | |||||
| from flask_login import login_user, logout_user, login_required, current_user | |||||
| import os | |||||
| from .models import db, Recipe, Ingredient, Instruction | |||||
| bp = Blueprint("paginas", __name__) | |||||
| @bp.route('/') | |||||
| def index(): | |||||
| recipes = Recipe.query.all() | |||||
| return render_template('index.html', recipes=recipes) | |||||
| @bp.route('/recipe/<int:recipe_id>') | |||||
| def recipe(recipe_id): | |||||
| recipe = Recipe.query.get_or_404(recipe_id) | |||||
| return render_template('recipe.html', recipe=recipe) | |||||
| @bp.route('/new_recipe', methods=['GET', 'POST']) | |||||
| @login_required | |||||
| def new_recipe(): | |||||
| if request.method == 'POST': | |||||
| title = request.form['title'] | |||||
| description = request.form['description'] | |||||
| ingredients = request.form.getlist('ingredient') | |||||
| quantities = request.form.getlist('quantity') | |||||
| steps = request.form.getlist('step') | |||||
| step_descriptions = request.form.getlist('step_description') | |||||
| recipe = Recipe(title=title, description=description, author=current_user) | |||||
| for ingredient, quantity in zip(ingredients, quantities): | |||||
| recipe.ingredients.append(Ingredient(name=ingredient, quantity=quantity)) | |||||
| for step, description in zip(steps, step_descriptions): | |||||
| recipe.instructions.append(Instruction(step=step, description=description)) | |||||
| db.session.add(recipe) | |||||
| db.session.commit() | |||||
| flash('Recipe created successfully!', 'success') | |||||
| return redirect(url_for('index')) | |||||
| return render_template('new_recipe.html') | |||||
| @bp.route('/account') | |||||
| @login_required | |||||
| def account(): | |||||
| return render_template('account.html') | |||||
| @bp.route('/settings') | |||||
| @login_required | |||||
| def settings(): | |||||
| return render_template('settings.html') | |||||
| @bp.route('/reset-password') | |||||
| def resetpassword(): | |||||
| return render_template('reset-password.html') | |||||
| @bp.route('/404') | |||||
| def cuatrocerocuatro(): | |||||
| return render_template('404.html') | |||||
| @bp.route('/charts') | |||||
| @login_required | |||||
| def charts(): | |||||
| return render_template('charts.html') | |||||
| @bp.route('/help') | |||||
| def help(): | |||||
| return render_template('help.html') | |||||
| @bp.route('/uploads/<filename>') | |||||
| def uploaded_file(filename): | |||||
| return send_from_directory(current_app.config['UPLOAD_FOLDER'], filename) | |||||
| @bp.route('/notifications') | |||||
| def notifications(): | |||||
| return render_template('notifications.html') | |||||
| @bp.route('/docs') | |||||
| def docs(): | |||||
| return render_template('docs.html') | |||||
| @bp.route('/orders') | |||||
| def orders(): | |||||
| return render_template('orders.html') | |||||
| @ -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 */ | |||||