| @ -0,0 +1,49 @@ | |||||
| # Instalación | |||||
| Desde el directorio K8S ejecutar make (esto hace todo: la imagen, para los pods y los lanza otra vez) | |||||
| La primera vez, hay que entrar en el pod de vehículos con 'entra.sh' y | |||||
| python manage.py createsuperuser | |||||
| python manage.py makemigrations | |||||
| python manage.py migrate | |||||
| ## Comprobar la base de datos | |||||
| Con la shell entraPsql.sh: | |||||
| \l para listar las BD | |||||
| \c reymota para usar nuestra db | |||||
| \dt para ver las tablas | |||||
| # De dónde cogí ideas | |||||
| https://learndjango.com/tutorials/django-login-and-logout-tutorial | |||||
| Username: {{ user.username }} | |||||
| User Full name: {{ user.get_full_name }} | |||||
| User Group: {{ user.groups.all.0 }} | |||||
| Email: {{ user.email }} | |||||
| Session Started at: {{ user.last_login }} | |||||
| ## Para funcionar con gunicorn y nginx | |||||
| https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/ | |||||
| ## Cambiar la secuencia de lo sid | |||||
| ALTER SEQUENCE tablename_id_seq RESTART WITH nn; | |||||
| esto se hace cuando restauro un volcado de la bd sobre una instalación nueva. Si hay índices ya creados, hay que reinciar a partir del último. | |||||
| @ -0,0 +1 @@ | |||||
| migrations/ | |||||
| @ -0,0 +1,3 @@ | |||||
| from django.contrib import admin | |||||
| # Register your models here. | |||||
| @ -0,0 +1,6 @@ | |||||
| from django.apps import AppConfig | |||||
| class AccountsConfig(AppConfig): | |||||
| default_auto_field = 'django.db.models.BigAutoField' | |||||
| name = 'accounts' | |||||
| @ -0,0 +1,3 @@ | |||||
| from django.db import models | |||||
| # Create your models here. | |||||
| @ -0,0 +1,3 @@ | |||||
| from django.test import TestCase | |||||
| # Create your tests here. | |||||
| @ -0,0 +1,8 @@ | |||||
| # accounts/urls.py | |||||
| from django.urls import path | |||||
| from django.contrib.auth import views as auth_views | |||||
| urlpatterns = [ | |||||
| path('login/', auth_views.LoginView.as_view(), name='login'), | |||||
| path('logout/', auth_views.LogoutView.as_view(), name='logout'), | |||||
| ] | |||||
| @ -0,0 +1 @@ | |||||
| from django.shortcuts import render | |||||
| @ -0,0 +1,8 @@ | |||||
| from django.contrib import admin | |||||
| # Register your models here. | |||||
| from .models import Autor, Libro | |||||
| admin.site.register(Autor) | |||||
| admin.site.register(Libro) | |||||
| @ -0,0 +1,6 @@ | |||||
| from django.apps import AppConfig | |||||
| class LibrosConfig(AppConfig): | |||||
| default_auto_field = 'django.db.models.BigAutoField' | |||||
| name = 'libros' | |||||
| @ -0,0 +1,30 @@ | |||||
| from django import forms | |||||
| from .models import Autor, Libro | |||||
| class AutorForm(forms.ModelForm): | |||||
| class Meta: | |||||
| model = Autor | |||||
| fields = ['nombre', 'biografia', 'foto'] | |||||
| nombre = forms.CharField( | |||||
| widget=forms.TextInput(attrs={'class': 'form-control'})) | |||||
| biografia = forms.CharField( | |||||
| widget=forms.TextInput(attrs={'class': 'form-control'})) | |||||
| class LibroForm(forms.ModelForm): | |||||
| class Meta: | |||||
| model = Libro | |||||
| fields = ['titulo', 'autor', 'fecha_publicacion', 'descripcion', | |||||
| 'archivo', 'portada'] | |||||
| titulo = forms.CharField( | |||||
| widget=forms.TextInput(attrs={'class': 'form-control'})) | |||||
| descripcion = forms.CharField( | |||||
| widget=forms.TextInput(attrs={'class': 'form-control'})) | |||||
| autor = forms.ModelChoiceField( | |||||
| queryset=Autor.objects.all(), | |||||
| widget=forms.Select(attrs={'class': 'form-control'})) | |||||
| @ -0,0 +1,38 @@ | |||||
| # Generated by Django 4.2 on 2024-09-09 14:52 | |||||
| import django.core.validators | |||||
| from django.db import migrations, models | |||||
| import django.db.models.deletion | |||||
| import libros.models | |||||
| class Migration(migrations.Migration): | |||||
| initial = True | |||||
| dependencies = [ | |||||
| ] | |||||
| operations = [ | |||||
| migrations.CreateModel( | |||||
| name='Autor', | |||||
| fields=[ | |||||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |||||
| ('nombre', models.CharField(max_length=200)), | |||||
| ('biografia', models.TextField(blank=True, null=True)), | |||||
| ('foto', models.ImageField(blank=True, null=True, upload_to='autores/')), | |||||
| ], | |||||
| ), | |||||
| migrations.CreateModel( | |||||
| name='Libro', | |||||
| fields=[ | |||||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |||||
| ('titulo', models.CharField(max_length=200)), | |||||
| ('fecha_publicacion', models.PositiveBigIntegerField(default=2024, validators=[django.core.validators.MinValueValidator(1984), libros.models.max_value_current_year])), | |||||
| ('descripcion', models.TextField(blank=True, null=True)), | |||||
| ('archivo', models.FileField(upload_to='libros/')), | |||||
| ('portada', models.ImageField(blank=True, null=True, upload_to='portadas/')), | |||||
| ('autor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='libros.autor')), | |||||
| ], | |||||
| ), | |||||
| ] | |||||
| @ -0,0 +1,32 @@ | |||||
| from django.db import models | |||||
| import datetime | |||||
| from django.core.validators import MaxValueValidator, MinValueValidator | |||||
| def current_year(): | |||||
| return datetime.date.today().year | |||||
| def max_value_current_year(value): | |||||
| return MaxValueValidator(current_year())(value) | |||||
| class Autor(models.Model): | |||||
| nombre = models.CharField(max_length=200) | |||||
| biografia = models.TextField(blank=True, null=True) | |||||
| foto = models.ImageField(upload_to='autores/', blank=True, null=True) # Nuevo campo | |||||
| def __str__(self): | |||||
| return self.nombre | |||||
| class Libro(models.Model): | |||||
| titulo = models.CharField(max_length=200) | |||||
| autor = models.ForeignKey(Autor, on_delete=models.CASCADE) | |||||
| fecha_publicacion = models.PositiveBigIntegerField(default=current_year(), validators=[MinValueValidator(1984), max_value_current_year]) | |||||
| descripcion = models.TextField(blank=True, null=True) | |||||
| archivo = models.FileField(upload_to='libros/') | |||||
| portada = models.ImageField(upload_to='portadas/', blank=True, null=True) # Nuevo campo | |||||
| def __str__(self): | |||||
| return self.titulo | |||||
| @ -0,0 +1,3 @@ | |||||
| from django.test import TestCase | |||||
| # Create your tests here. | |||||
| @ -0,0 +1,21 @@ | |||||
| from django.urls import path | |||||
| from . import views | |||||
| app_name = 'libros' | |||||
| urlpatterns = [ | |||||
| path('', views.principal, name='principal'), | |||||
| path('autores/', views.lista_autores, name='lista_autores'), | |||||
| path('autores/nuevo/', views.nuevo_autor, name='nuevo_autor'), | |||||
| path('autores/<int:autor_id>/', views.detalle_autor, name='detalle_autor'), | |||||
| path('autores/<int:autor_id>/editar/', views.editar_autor, name='editar_autor'), | |||||
| path('autores/<int:autor_id>/eliminar/', views.eliminar_autor, name='eliminar_autor'), | |||||
| path('libros/', views.lista_libros, name='lista_libros'), | |||||
| path('libros/nuevo/', views.nuevo_libro, name='nuevo_libro'), | |||||
| path('libros/<int:libro_id>/', views.detalle_libro, name='detalle_libro'), | |||||
| path('libros/<int:libro_id>/editar/', views.editar_libro, name='editar_libro'), | |||||
| path('libros/<int:libro_id>/eliminar/', views.eliminar_libro, name='eliminar_libro'), | |||||
| ] | |||||
| @ -0,0 +1,103 @@ | |||||
| from django.shortcuts import render, get_object_or_404, redirect | |||||
| from django.contrib.auth.decorators import login_required | |||||
| from .models import Autor, Libro | |||||
| from .forms import AutorForm, LibroForm | |||||
| @login_required | |||||
| def principal(request): | |||||
| return render(request, 'libros/index.html') | |||||
| # Vistas para los autores | |||||
| @login_required | |||||
| def lista_autores(request): | |||||
| autores = Autor.objects.all() | |||||
| return render(request, 'libros/lista_autores.html', {'autores': autores}) | |||||
| @login_required | |||||
| def detalle_autor(request, autor_id): | |||||
| autor = get_object_or_404(Autor, pk=autor_id) | |||||
| libros = Libro.objects.filter(autor=autor_id) | |||||
| return render(request, 'libros/detalle_autor.html', {'autor': autor, 'libros': libros}) | |||||
| @login_required | |||||
| def nuevo_autor(request): | |||||
| if request.method == 'POST': | |||||
| form = AutorForm(request.POST, request.FILES) | |||||
| if form.is_valid(): | |||||
| form.save() | |||||
| return redirect('libros:lista_autores') | |||||
| else: | |||||
| form = AutorForm() | |||||
| return render(request, 'libros/form_autor.html', {'form': form}) | |||||
| @login_required | |||||
| def editar_autor(request, autor_id): | |||||
| autor = get_object_or_404(Autor, pk=autor_id) | |||||
| if request.method == 'POST': | |||||
| form = AutorForm(request.POST, request.FILES, instance=autor) | |||||
| if form.is_valid(): | |||||
| form.save() | |||||
| return redirect('libros:lista_autores') | |||||
| else: | |||||
| form = AutorForm(instance=autor) | |||||
| return render(request, 'libros/form_autor.html', {'form': form}) | |||||
| @login_required | |||||
| def eliminar_autor(request, autor_id): | |||||
| autor = get_object_or_404(Autor, pk=autor_id) | |||||
| autor.delete() | |||||
| return redirect('libros:lista_autores') | |||||
| # Vistas para los libros | |||||
| @login_required | |||||
| def lista_libros(request): | |||||
| libros = Libro.objects.all() | |||||
| return render(request, 'libros/lista_libros.html', {'libros': libros}) | |||||
| @login_required | |||||
| def detalle_libro(request, libro_id): | |||||
| libro = get_object_or_404(Libro, pk=libro_id) | |||||
| return render(request, 'libros/detalle_libro.html', {'libro': libro}) | |||||
| @login_required | |||||
| def nuevo_libro(request): | |||||
| if request.method == 'POST': | |||||
| form = LibroForm(request.POST, request.FILES) | |||||
| if form.is_valid(): | |||||
| form.save() | |||||
| return redirect('libros:lista_libros') | |||||
| else: | |||||
| form = LibroForm() | |||||
| return render(request, 'libros/form_libro.html', {'form': form}) | |||||
| @login_required | |||||
| def editar_libro(request, libro_id): | |||||
| libro = get_object_or_404(Libro, pk=libro_id) | |||||
| if request.method == 'POST': | |||||
| form = LibroForm(request.POST, request.FILES, instance=libro) | |||||
| if form.is_valid(): | |||||
| form.save() | |||||
| return redirect('libros:lista_libros') | |||||
| else: | |||||
| form = LibroForm(instance=libro) | |||||
| return render(request, 'libros/form_libro.html', {'form': form}) | |||||
| @login_required | |||||
| def eliminar_libro(request, libro_id): | |||||
| libro = get_object_or_404(Libro, pk=libro_id) | |||||
| libro.delete() | |||||
| return redirect('libros:lista_libros') | |||||
| @ -0,0 +1,9 @@ | |||||
| from django.contrib import admin | |||||
| # Register your models here. | |||||
| from .models import Artista, Album, Song | |||||
| admin.site.register(Artista) | |||||
| admin.site.register(Album) | |||||
| admin.site.register(Song) | |||||
| @ -0,0 +1,6 @@ | |||||
| from django.apps import AppConfig | |||||
| class LyricsConfig(AppConfig): | |||||
| default_auto_field = 'django.db.models.BigAutoField' | |||||
| name = 'lyrics' | |||||
| @ -0,0 +1,41 @@ | |||||
| from django import forms | |||||
| from .models import Artista, Album, Song | |||||
| class ArtistaForm(forms.ModelForm): | |||||
| class Meta: | |||||
| model = Artista | |||||
| fields = ['nombre', 'biografia', 'foto'] | |||||
| nombre = forms.CharField( | |||||
| widget=forms.TextInput(attrs={'class': 'form-control'})) | |||||
| biografia = forms.CharField( | |||||
| widget=forms.TextInput(attrs={'class': 'form-control'})) | |||||
| class AlbumForm(forms.ModelForm): | |||||
| class Meta: | |||||
| model = Album | |||||
| fields = ['name', 'artist', 'year', 'cover_image'] | |||||
| # year = forms.DateField( | |||||
| # widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'})) | |||||
| artist = forms.ModelChoiceField( | |||||
| queryset=Artista.objects.all(), | |||||
| widget=forms.Select(attrs={'class': 'form-control'})) | |||||
| class SongForm(forms.ModelForm): | |||||
| class Meta: | |||||
| model = Song | |||||
| fields = ['title', 'artist', 'album', 'year', 'lyrics'] | |||||
| artist = forms.ModelChoiceField( | |||||
| queryset=Artista.objects.all(), | |||||
| widget=forms.Select(attrs={'class': 'form-control'})) | |||||
| album = forms.ModelChoiceField( | |||||
| queryset=Album.objects.all(), # habría que seleccionar los álbumes del artista | |||||
| widget=forms.Select(attrs={'class': 'form-control'})) | |||||
| @ -0,0 +1,23 @@ | |||||
| from django.core.management.base import BaseCommand, CommandError | |||||
| from lyrics.models import Album, Artista, Song | |||||
| import csv | |||||
| import argparse | |||||
| from datetime import datetime | |||||
| class Command(BaseCommand): | |||||
| help = "Importa la lista de letras" | |||||
| def add_arguments(self, parser): | |||||
| parser.add_argument("fichero_csv", type=str, help='Ruta al fichero csv') | |||||
| def handle(self, *args, **options): | |||||
| fichero = options["fichero_csv"] | |||||
| with open(fichero, 'r') as file: | |||||
| reader = csv.DictReader(file) | |||||
| for row in reader: | |||||
| name = row['name'], | |||||
| artist = row['artist'], | |||||
| year = row['year'], | |||||
| cover_image = row['cover_image'] | |||||
| print(name, ",", artist, ",", year, ",", cover_image) | |||||
| @ -0,0 +1,23 @@ | |||||
| from django.core.management.base import BaseCommand, CommandError | |||||
| from lyrics.models import Album, Artista, Song | |||||
| import csv | |||||
| import argparse | |||||
| from datetime import datetime | |||||
| class Command(BaseCommand): | |||||
| help = "Importa la lista de letras" | |||||
| def add_arguments(self, parser): | |||||
| parser.add_argument("fichero_csv", type=str, help='Ruta al fichero csv') | |||||
| def handle(self, *args, **options): | |||||
| fichero = options["fichero_csv"] | |||||
| with open(fichero, 'r') as file: | |||||
| reader = csv.DictReader(file) | |||||
| for row in reader: | |||||
| name = row['name'], | |||||
| artist = row['artist'], | |||||
| year = row['year'], | |||||
| cover_image = row['cover_image'] | |||||
| print(name, ",", artist, ",", year, ",", cover_image) | |||||
| @ -0,0 +1,26 @@ | |||||
| from django.core.management.base import BaseCommand, CommandError | |||||
| from lyrics.models import Album, Artista, Song | |||||
| import csv | |||||
| import argparse | |||||
| from datetime import datetime | |||||
| import pandas as pd | |||||
| class Command(BaseCommand): | |||||
| help = "Importa la lista de letras" | |||||
| def add_arguments(self, parser): | |||||
| parser.add_argument("fichero_csv", type=str, help='Ruta al fichero csv') | |||||
| def handle(self, *args, **options): | |||||
| fichero = options["fichero_csv"] | |||||
| contenido = pd.read_csv(fichero) | |||||
| for fila in contenido.iterrows(): | |||||
| print(fila[1].title, ", ", fila[1].artist, ", ", fila[1].album) | |||||
| artista = Artista.objects.get(nombre=fila[1].artist) | |||||
| album = Album.objects.get(pk=fila[1].album) | |||||
| cancion = Song(title=fila[1].title, artist=artista, album=album, pista=fila[1].pista, lyrics=fila[1].lyrics) | |||||
| cancion.save() | |||||
| @ -0,0 +1,52 @@ | |||||
| # Generated by Django 4.2 on 2024-09-10 13:23 | |||||
| import django.core.validators | |||||
| from django.db import migrations, models | |||||
| import django.db.models.deletion | |||||
| import lyrics.models | |||||
| class Migration(migrations.Migration): | |||||
| initial = True | |||||
| dependencies = [ | |||||
| ] | |||||
| operations = [ | |||||
| migrations.CreateModel( | |||||
| name='Album', | |||||
| fields=[ | |||||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |||||
| ('name', models.CharField(max_length=200)), | |||||
| ('year', models.PositiveBigIntegerField(default=2024, validators=[django.core.validators.MinValueValidator(1984), lyrics.models.max_value_current_year])), | |||||
| ('cover_image', models.ImageField(blank=True, null=True, upload_to='cover_image/')), | |||||
| ], | |||||
| ), | |||||
| migrations.CreateModel( | |||||
| name='Artista', | |||||
| fields=[ | |||||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |||||
| ('nombre', models.CharField(max_length=200)), | |||||
| ('biografia', models.TextField(blank=True, null=True)), | |||||
| ('foto', models.ImageField(blank=True, null=True, upload_to='artistas/')), | |||||
| ], | |||||
| ), | |||||
| migrations.CreateModel( | |||||
| name='Song', | |||||
| fields=[ | |||||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |||||
| ('title', models.CharField(max_length=200)), | |||||
| ('year', models.PositiveBigIntegerField(default=2024, validators=[django.core.validators.MinValueValidator(1984), lyrics.models.max_value_current_year])), | |||||
| ('lyrics', models.CharField(max_length=1000)), | |||||
| ('pista', models.DecimalField(blank=True, decimal_places=0, max_digits=5, null=True)), | |||||
| ('album', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='lyrics.album')), | |||||
| ('artist', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='lyrics.artista')), | |||||
| ], | |||||
| ), | |||||
| migrations.AddField( | |||||
| model_name='album', | |||||
| name='artist', | |||||
| field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='lyrics.artista'), | |||||
| ), | |||||
| ] | |||||
| @ -0,0 +1,42 @@ | |||||
| from django.db import models | |||||
| import datetime | |||||
| from django.core.validators import MaxValueValidator, MinValueValidator | |||||
| def current_year(): | |||||
| return datetime.date.today().year | |||||
| def max_value_current_year(value): | |||||
| return MaxValueValidator(current_year())(value) | |||||
| class Artista(models.Model): | |||||
| nombre = models.CharField(max_length=200) | |||||
| biografia = models.TextField(blank=True, null=True) | |||||
| foto = models.ImageField(upload_to='artistas/', blank=True, null=True) # Nuevo campo | |||||
| def __str__(self): | |||||
| return self.nombre | |||||
| class Album(models.Model): | |||||
| name = models.CharField(max_length=200) | |||||
| artist = models.ForeignKey(Artista, on_delete=models.CASCADE) | |||||
| year = models.PositiveBigIntegerField(default=current_year(), validators=[MinValueValidator(1945), max_value_current_year]) | |||||
| cover_image = models.ImageField(upload_to='cover_image/', blank=True, null=True) # Nuevo campo | |||||
| def __str__(self): | |||||
| return self.name | |||||
| class Song(models.Model): | |||||
| title = models.CharField(max_length=200) | |||||
| artist = models.ForeignKey(Artista, on_delete=models.CASCADE) | |||||
| album = models.ForeignKey(Album, on_delete=models.CASCADE, related_name='song') | |||||
| year = models.DecimalField(max_digits=4, decimal_places=0, blank=False, null=False) | |||||
| lyrics = models.TextField() | |||||
| pista = models.DecimalField(max_digits=5, decimal_places=0, blank=True, null=True) | |||||
| def __str__(self): | |||||
| return self.title | |||||
| @ -0,0 +1,3 @@ | |||||
| from django.test import TestCase | |||||
| # Create your tests here. | |||||
| @ -0,0 +1,27 @@ | |||||
| from django.urls import path | |||||
| from . import views | |||||
| app_name = 'lyrics' | |||||
| urlpatterns = [ | |||||
| path('', views.principal, name='principal'), | |||||
| path('artistas/', views.lista_artistas, name='lista_artistas'), | |||||
| path('artistas/nuevo/', views.nuevo_artista, name='nuevo_artista'), | |||||
| path('artistas/<int:artista_id>/', views.detalle_artista, name='detalle_artista'), | |||||
| path('artistas/<int:artista_id>/editar/', views.editar_artista, name='editar_artista'), | |||||
| path('artistas/<int:artista_id>/eliminar/', views.eliminar_artista, name='eliminar_artista'), | |||||
| path('album/', views.lista_albumes, name='lista_albumes'), | |||||
| path('album/nuevo/', views.nuevo_album, name='nuevo_album'), | |||||
| path('album/<int:album_id>/', views.detalle_album, name='detalle_album'), | |||||
| path('album/<int:album_id>/editar/', views.editar_album, name='editar_album'), | |||||
| path('album/<int:album_id>/eliminar/', views.eliminar_album, name='eliminar_album'), | |||||
| path('song/', views.lista_songs, name='lista_songs'), | |||||
| path('song/nuevo/', views.nuevo_song, name='nuevo_song'), | |||||
| path('song/<int:song_id>/', views.detalle_song, name='detalle_song'), | |||||
| path('song/<int:song_id>/editar/', views.editar_song, name='editar_song'), | |||||
| path('song/<int:song_id>/eliminar/', views.eliminar_song, name='eliminar_song'), | |||||
| ] | |||||
| @ -0,0 +1,190 @@ | |||||
| # Create your views here. | |||||
| from django.shortcuts import render, get_object_or_404, redirect | |||||
| from django.contrib.auth.decorators import login_required | |||||
| from .models import Artista, Album, Song | |||||
| from .forms import ArtistaForm, AlbumForm, SongForm | |||||
| import logging | |||||
| logger = logging.getLogger(__name__) | |||||
| @login_required | |||||
| def principal(request): | |||||
| artistas = Artista.objects.all() | |||||
| albumes = Album.objects.all() | |||||
| return render(request, 'lyrics/index.html', {'artistas': artistas, 'albumes': albumes}) | |||||
| ######################### | |||||
| # Vistas para los artistas | |||||
| @login_required | |||||
| def lista_artistas(request): | |||||
| artistas = Artista.objects.all() | |||||
| return render(request, 'lyrics/lista_artistas.html', {'artistas': artistas}) | |||||
| @login_required | |||||
| def detalle_artista(request, artista_id): | |||||
| artista = get_object_or_404(Artista, pk=artista_id) | |||||
| albumes = Album.objects.filter(artist=artista_id) | |||||
| return render(request, 'lyrics/detalle_artista.html', {'artista': artista, 'albumes': albumes}) | |||||
| @login_required | |||||
| def nuevo_artista(request): | |||||
| if request.method == 'POST': | |||||
| form = ArtistaForm(request.POST, request.FILES) | |||||
| if form.is_valid(): | |||||
| form.save() | |||||
| return redirect('lyrics:lista_artistas') | |||||
| else: | |||||
| form = ArtistaForm() | |||||
| return render(request, 'lyrics/form_artista.html', {'form': form}) | |||||
| @login_required | |||||
| def editar_artista(request, artista_id): | |||||
| artista = get_object_or_404(Artista, pk=artista_id) | |||||
| if request.method == 'POST': | |||||
| form = ArtistaForm(request.POST, request.FILES, instance=artista) | |||||
| if form.is_valid(): | |||||
| form.save() | |||||
| return redirect('lyrics:lista_artistas') | |||||
| else: | |||||
| form = ArtistaForm(instance=artista) | |||||
| return render(request, 'lyrics/form_artista.html', {'form': form}) | |||||
| @login_required | |||||
| def eliminar_artista(request, artista_id): | |||||
| artista = get_object_or_404(Artista, pk=artista_id) | |||||
| artista.delete() | |||||
| return redirect('lyrics:lista_artistas') | |||||
| ######################### | |||||
| # Vistas para los albumes | |||||
| @login_required | |||||
| def lista_albumes(request): | |||||
| albumes = Album.objects.all() | |||||
| return render(request, 'lyrics/lista_albumes.html', {'albumes': albumes}) | |||||
| @login_required | |||||
| def detalle_album(request, album_id): | |||||
| album = get_object_or_404(Album, pk=album_id) | |||||
| songs = Song.objects.filter(album_id=album_id) | |||||
| return render(request, 'lyrics/detalle_album.html', {'album': album, 'songs': songs}) | |||||
| @login_required | |||||
| def nuevo_album(request): | |||||
| artistas = Artista.objects.all() # vamos a ver si hay vehículos dados de alta | |||||
| if artistas: | |||||
| if request.method == 'POST': | |||||
| form = AlbumForm(request.POST, request.FILES) | |||||
| if form.is_valid(): | |||||
| form.save() | |||||
| return redirect('lyrics:lista_albumes') | |||||
| else: | |||||
| form = AlbumForm() | |||||
| return render(request, 'lyrics/form_album.html', {'form': form}) | |||||
| else: | |||||
| return render(request, 'lyrics/index.html') | |||||
| @login_required | |||||
| def editar_album(request, album_id): | |||||
| album = get_object_or_404(Album, pk=album_id) | |||||
| if request.method == 'POST': | |||||
| form = AlbumForm(request.POST, request.FILES, instance=album) | |||||
| if form.is_valid(): | |||||
| form.save() | |||||
| return redirect('lyrics:lista_albumes') | |||||
| else: | |||||
| form = AlbumForm(instance=album) | |||||
| return render(request, 'lyrics/form_album.html', {'form': form}) | |||||
| @login_required | |||||
| def eliminar_album(request, album_id): | |||||
| album = Album.objects.get(pk=album_id) | |||||
| album.delete() | |||||
| return redirect('lyrics:lista_albumes') | |||||
| ######################### | |||||
| # Vistas para los songs | |||||
| @login_required | |||||
| def lista_songs(request): | |||||
| songs = Song.objects.all() | |||||
| return render(request, 'lyrics/lista_songs.html', {'songs': songs}) | |||||
| @login_required | |||||
| def detalle_song(request, song_id): | |||||
| song = get_object_or_404(Song, pk=song_id) | |||||
| albumes = Album.objects.filter(song=song_id) | |||||
| return render(request, 'lyrics/detalle_song.html', {'song': song, 'albumes': albumes}) | |||||
| @login_required | |||||
| def nuevo_song(request): | |||||
| album_id = request.GET.get('album_id') # Obtener el album_id de los parámetros de la URL | |||||
| if request.method == 'POST': | |||||
| form = SongForm(request.POST, request.FILES) | |||||
| if form.is_valid(): | |||||
| album = form.cleaned_data['album'] | |||||
| song_count = album.song.count() | |||||
| nueva_cancion = form.save(commit=False) | |||||
| nueva_cancion.pista = song_count + 1 | |||||
| nueva_cancion.save() | |||||
| logger.info("Canción creada %s", nueva_cancion.title) | |||||
| return redirect('lyrics:lista_songs') | |||||
| else: | |||||
| if album_id: | |||||
| # Si tenemos un album_id, preseleccionamos ese álbum en el formulario | |||||
| album = get_object_or_404(Album, id=album_id) | |||||
| form = SongForm(initial={'album': album, 'artist': album.artist, 'year': album.year}) | |||||
| else: | |||||
| form = SongForm() | |||||
| return render(request, 'lyrics/form_song.html', {'form': form}) | |||||
| @login_required | |||||
| def editar_song(request, song_id): | |||||
| song = get_object_or_404(Song, pk=song_id) | |||||
| if request.method == 'POST': | |||||
| form = SongForm(request.POST, request.FILES, instance=song) | |||||
| if form.is_valid(): | |||||
| form.save() | |||||
| return redirect('lyrics:lista_songs') | |||||
| else: | |||||
| form = SongForm(instance=song) | |||||
| return render(request, 'lyrics/form_song.html', {'form': form}) | |||||
| @login_required | |||||
| def eliminar_song(request, song_id): | |||||
| song = get_object_or_404(Song, pk=song_id) | |||||
| song.delete() | |||||
| return redirect('lyrics:lista_songs') | |||||
| @ -0,0 +1,22 @@ | |||||
| #!/usr/bin/env python | |||||
| """Django's command-line utility for administrative tasks.""" | |||||
| import os | |||||
| import sys | |||||
| def main(): | |||||
| """Run administrative tasks.""" | |||||
| os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'reymota.settings') | |||||
| try: | |||||
| from django.core.management import execute_from_command_line | |||||
| except ImportError as exc: | |||||
| raise ImportError( | |||||
| "Couldn't import Django. Are you sure it's installed and " | |||||
| "available on your PYTHONPATH environment variable? Did you " | |||||
| "forget to activate a virtual environment?" | |||||
| ) from exc | |||||
| execute_from_command_line(sys.argv) | |||||
| if __name__ == '__main__': | |||||
| main() | |||||
| @ -0,0 +1 @@ | |||||
| migrations/ | |||||
| @ -0,0 +1,9 @@ | |||||
| from django.contrib import admin | |||||
| # Register your models here. | |||||
| from .models import Vehiculo, Repostaje | |||||
| admin.site.register(Vehiculo) | |||||
| admin.site.register(Repostaje) | |||||
| @ -0,0 +1,6 @@ | |||||
| from django.apps import AppConfig | |||||
| class RepostajesConfig(AppConfig): | |||||
| default_auto_field = 'django.db.models.BigAutoField' | |||||
| name = 'repostajes' | |||||
| @ -0,0 +1,41 @@ | |||||
| from django import forms | |||||
| from .models import Vehiculo, Repostaje | |||||
| class VehiculoForm(forms.ModelForm): | |||||
| class Meta: | |||||
| model = Vehiculo | |||||
| fields = ['marca', 'modelo', 'matricula', 'foto'] | |||||
| marca = forms.CharField( | |||||
| widget=forms.TextInput(attrs={'class': 'form-control'})) | |||||
| modelo = forms.CharField( | |||||
| widget=forms.TextInput(attrs={'class': 'form-control'})) | |||||
| matricula = forms.CharField( | |||||
| widget=forms.TextInput(attrs={'class': 'form-control'})) | |||||
| class RepostajeForm(forms.ModelForm): | |||||
| class Meta: | |||||
| model = Repostaje | |||||
| fields = ['fecha', 'vehiculo', 'kms', 'litros', 'importe'] | |||||
| exclude = ['descuento', 'precioxlitro'] | |||||
| fecha = forms.DateField( | |||||
| widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'})) | |||||
| vehiculo = forms.ModelChoiceField( | |||||
| queryset=Vehiculo.objects.all(), | |||||
| widget=forms.Select(attrs={'class': 'form-control'})) | |||||
| kms = forms.DecimalField( | |||||
| widget=forms.TextInput(attrs={'class': 'form-control'})) | |||||
| litros = forms.DecimalField( | |||||
| widget=forms.NumberInput(attrs={'class': 'form-control'})) | |||||
| importe = forms.DecimalField( | |||||
| widget=forms.NumberInput(attrs={'class': 'form-control'})) | |||||
| aplica_descuento = forms.BooleanField(initial=False, required=False) | |||||
| @ -0,0 +1,27 @@ | |||||
| from django.db import models | |||||
| from django.core.validators import MaxValueValidator | |||||
| class Vehiculo(models.Model): | |||||
| marca = models.CharField(max_length=200) | |||||
| modelo = models.CharField(max_length=200) | |||||
| matricula = models.CharField(max_length=200) | |||||
| foto = models.ImageField(upload_to='vehiculos/', blank=True, null=True) # Nuevo campo | |||||
| def __str__(self): | |||||
| return self.marca | |||||
| class Repostaje(models.Model): | |||||
| vehiculo = models.ForeignKey(Vehiculo, on_delete=models.CASCADE) | |||||
| fecha = models.DateField() | |||||
| kms = models.DecimalField(max_digits=10, decimal_places=0, blank=True, null=True) | |||||
| litros = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True) | |||||
| descuento = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True) | |||||
| importe = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True) | |||||
| precioxlitro = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True) | |||||
| kmsrecorridos = models.DecimalField(max_digits=10, decimal_places=0, blank=True, null=True) | |||||
| consumo = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True) | |||||
| def __str__(self): | |||||
| return str(self.fecha) | |||||
| @ -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 */ | |||||