瀏覽代碼

first commit

fanch 1 年之前
父節點
當前提交
d79cc22e70
共有 65 個文件被更改,包括 3667 次插入0 次删除
  1. 1 0
      MANIFEST.in
  2. 3 0
      README.md
  3. 22 0
      manage.py
  4. 25 0
      setup.cfg
  5. 59 0
      setup.py
  6. 0 0
      src/baby/__init__.py
  7. 0 0
      src/baby/app/__init__.py
  8. 6 0
      src/baby/app/admin.py
  9. 6 0
      src/baby/app/apps.py
  10. 0 0
      src/baby/app/common/__init__.py
  11. 122 0
      src/baby/app/common/calendar.py
  12. 0 0
      src/baby/app/context/__init__.py
  13. 55 0
      src/baby/app/context/base.py
  14. 35 0
      src/baby/app/context/calendar.py
  15. 32 0
      src/baby/app/context/edit.py
  16. 17 0
      src/baby/app/context/login.py
  17. 24 0
      src/baby/app/context/main.py
  18. 15 0
      src/baby/app/context/new_cycle.py
  19. 68 0
      src/baby/app/context/parameters.py
  20. 41 0
      src/baby/app/context/stats.py
  21. 45 0
      src/baby/app/context/validate_cycle.py
  22. 28 0
      src/baby/app/migrations/0001_initial.py
  23. 0 0
      src/baby/app/migrations/__init__.py
  24. 1 0
      src/baby/app/models/__init__.py
  25. 10 0
      src/baby/app/models/const.py
  26. 282 0
      src/baby/app/models/regle.py
  27. 0 0
      src/baby/app/views/__init__.py
  28. 15 0
      src/baby/app/views/main.py
  29. 21 0
      src/baby/app/views/regle.py
  30. 16 0
      src/baby/asgi.py
  31. 1 0
      src/baby/frontend/static/css/accueil/accueil.css
  32. 106 0
      src/baby/frontend/static/css/calendar/calendar.css
  33. 57 0
      src/baby/frontend/static/css/edit/edit.css
  34. 0 0
      src/baby/frontend/static/css/login/login.css
  35. 121 0
      src/baby/frontend/static/css/main/main.css
  36. 41 0
      src/baby/frontend/static/css/parameters/parameters.css
  37. 19 0
      src/baby/frontend/static/css/stats/stats.css
  38. 1390 0
      src/baby/frontend/static/css/vendor/bootstrap-icons.css
  39. 4 0
      src/baby/frontend/static/css/vendor/bootstrap.css
  40. 二進制
      src/baby/frontend/static/css/vendor/fonts/bootstrap-icons.woff
  41. 二進制
      src/baby/frontend/static/css/vendor/fonts/bootstrap-icons.woff2
  42. 5 0
      src/baby/frontend/static/js/vendor/bootstrap.js
  43. 1 0
      src/baby/frontend/static/js/vendor/jquery.js
  44. 120 0
      src/baby/frontend/templates/calendar.html
  45. 90 0
      src/baby/frontend/templates/edit.html
  46. 102 0
      src/baby/frontend/templates/index.html
  47. 44 0
      src/baby/frontend/templates/login.html
  48. 39 0
      src/baby/frontend/templates/nav.html
  49. 72 0
      src/baby/frontend/templates/new_cycle.html
  50. 109 0
      src/baby/frontend/templates/parameters.html
  51. 110 0
      src/baby/frontend/templates/stats.html
  52. 40 0
      src/baby/frontend/templates/validate_cycle.html
  53. 0 0
      src/baby/scripts/__init__.py
  54. 0 0
      src/baby/scripts/commands/__init__.py
  55. 11 0
      src/baby/scripts/djangotools
  56. 70 0
      src/baby/settings/__init__.py
  57. 37 0
      src/baby/settings/prod.py
  58. 22 0
      src/baby/urls.py
  59. 16 0
      src/baby/wsgi.py
  60. 0 0
      tests/__init__.py
  61. 10 0
      tests/settings.py
  62. 49 0
      tests/test_a.py
  63. 30 0
      tests/test_calendar.py
  64. 0 0
      tests/util.py
  65. 2 0
      todo

+ 1 - 0
MANIFEST.in

@@ -0,0 +1 @@
+recursive-include src *.*

+ 3 - 0
README.md

@@ -0,0 +1,3 @@
+# baby
+
+A short description of the project.

+ 22 - 0
manage.py

@@ -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', 'baby.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()

+ 25 - 0
setup.cfg

@@ -0,0 +1,25 @@
+[coverage:run]
+source = src
+
+[coverage:report]
+ignore_errors = False
+show_missing = True
+
+[coverage:xml]
+output = coverage.xml
+
+[tool:pytest]
+markers =
+    noci: marks tests so that they are not executed in continuous integration (jenkins)
+testpaths = tests/
+junit_family = xunit2
+console_output_style = progress
+log_level = DEBUG
+DJANGO_SETTINGS_MODULE = tests.settings
+
+[build_sphinx]
+; python setup.py build_sphinx  # Generate the HTML documentation in dist/docs/html
+source-dir = docs/source
+build-dir = dist/docs
+all_files = 1
+

+ 59 - 0
setup.py

@@ -0,0 +1,59 @@
+from setuptools import setup, find_packages
+from pathlib import Path
+
+install_requires = [
+    "django",
+    "django-cors-headers",
+    "djangotools"
+]
+
+tests_require = [
+    "mkfab",
+    "pytest",
+    "pytest-django"
+]
+
+def get_files(path):
+    root = Path(path)
+    queue = [root]
+    ret = []
+    while queue:
+        curr = queue.pop()
+        for file in curr.iterdir():
+            if file.is_file():
+                ret.append(str(file))
+            else:
+                queue.append(file)
+    return ret
+
+
+setup(
+    name="baby",
+    version="0.1.0",
+    description="A short description of the project.",
+    author="François GAUTRAIS",
+    install_requires=install_requires,
+    packages=find_packages("src"),
+    include_package_data=True,
+    zip_safe=False,
+    data_files=[
+        ("", ["README.md"]),
+    ],
+
+    test_suite="tests",
+    tests_require=tests_require,
+    extras_require={
+        "test": tests_require,
+        "pylint": ["pylint"],
+    },
+    scripts=[
+        "src/baby/scripts/djangotools",
+    ],
+
+    entry_points={
+        "console_scripts": [
+        ]
+    },
+    package_dir={"": "src"},
+)
+

+ 0 - 0
src/baby/__init__.py


+ 0 - 0
src/baby/app/__init__.py


+ 6 - 0
src/baby/app/admin.py

@@ -0,0 +1,6 @@
+from django.contrib import admin
+
+from baby.app.models.regle import Regle
+
+# Register your models here.
+admin.site.register(Regle)

+ 6 - 0
src/baby/app/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class AppConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'baby.app'

+ 0 - 0
src/baby/app/common/__init__.py


+ 122 - 0
src/baby/app/common/calendar.py

@@ -0,0 +1,122 @@
+import datetime
+
+from djangotools.common.types import date_to_string, parse_date
+
+from baby.app.models.regle import Cycle
+
+
+class MonthCalendar:
+    JOURS = ["Lundi", "Mardi", "Mecredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]
+    JOURS_COURS = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"]
+    MOIS = ["", "Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre",
+             "Octobre", "Novembre", "Décembre" ]
+
+
+    def __init__(self,  user, mont_or_date, year=None):
+        if isinstance(mont_or_date, str):
+            mont_or_date = parse_date(mont_or_date)
+        if isinstance(mont_or_date, (datetime.date, datetime.datetime)):
+            mont_or_date, year = mont_or_date.month, mont_or_date.year
+        assert isinstance(mont_or_date, int) and isinstance(year, int)
+        if year < 100: year += 2000
+        assert 0 < mont_or_date <= 12
+        self.month = mont_or_date
+        self.user = user
+        self.year = year
+        self.month_date = datetime.date(self.year, self.month, 1)
+
+        start = datetime.date(year, mont_or_date, 1)
+        # on commence au lundi même s'il est sur le mois précédent
+        self.start = start - datetime.timedelta(days=start.weekday())
+
+
+        end = start + datetime.timedelta(days=32)
+        end = end - datetime.timedelta(days=end.day)
+        self.end = end + datetime.timedelta(days=6-end.weekday())
+
+        self.cycles = Cycle.from_date_range(user, self.start, self.end)
+
+    @property
+    def prev_month(self):
+        start = self.month_date - datetime.timedelta(days=1)
+        start = start - datetime.timedelta(days=start.day)
+        return start
+
+    @property
+    def next_month(self):
+        end =  self.month_date + datetime.timedelta(days=31)
+        return end
+
+    @property
+    def prev_calendar(self):
+        return self.__class__(self.user, mont_or_date=self.prev_month)
+
+    @property
+    def next_calendar(self):
+        return self.__class__(self.user, mont_or_date=self.next_month)
+
+
+    def get_calendar(self):
+        ret = []
+        line = []
+        for i, value in enumerate(self.cycles.iter_dates(self.start, self.end)):
+            jour = i%7
+            if jour == 0:
+                line = []
+                ret.append(line)
+
+            line.append(value)
+        return ret
+
+    def get_calendar_dict(self):
+        ret = []
+        line = []
+
+        iterator = self.cycles.iter_dates(self.start, self.end)
+        if not self.cycles:
+            x = self.start
+            iterator = []
+            while x <= self.end:
+                iterator.append((x, []))
+                x = x + datetime.timedelta(days=1)
+
+
+
+        for i, (date, tags) in enumerate(iterator):
+            jour = i%7
+            if jour == 0:
+                line = []
+                ret.append(line)
+            day = {
+                "date" : date,
+                "day" : date.day,
+                "month" : date.month,
+                "year" : date.year,
+                "current_month" : date.month == self.month,
+                "not_current_month" : date.month != self.month,
+                Cycle.OVULATION : Cycle.OVULATION in tags,
+                Cycle.FERTILITE : Cycle.FERTILITE in tags,
+                Cycle.DEBUT : Cycle.DEBUT in tags,
+                "classes" : []
+            }
+
+            for tag in ["current_month", "not_current_month", Cycle.DEBUT, Cycle.FERTILITE, Cycle.OVULATION]:
+                if day[tag]:
+                    day["classes"].append(f"tag-{tag}")
+            day["classes"] = " ".join(day["classes"])
+
+            line.append(day)
+        return ret
+
+
+    @property
+    def json(self):
+        return {
+            "month" : self.month,
+            "month_word" : self.MOIS[self.month],
+            "year" : self.start.year,
+            "next" : date_to_string(self.next_month)[3:],
+            "prev" : date_to_string(self.prev_month)[3:],
+            "calendar" : self.get_calendar_dict()
+        }
+

+ 0 - 0
src/baby/app/context/__init__.py


+ 55 - 0
src/baby/app/context/base.py

@@ -0,0 +1,55 @@
+from collections import OrderedDict
+
+from djangotools.models import UserPreferenceManager
+from djangotools.views import ContextData
+
+
+default_value = {
+    "ui" : {
+        "theme" : "dark"
+    },
+    "regle" : {
+        "min_period" : 10,
+        "max_period" : 45,
+        "average_period_window" : 12,
+        "method" : "contraception"
+    }
+}
+
+class UserPrefences(UserPreferenceManager):
+
+    preferences = {
+        "main" : default_value
+    }
+
+    def save(self, main):
+        return self.set("main", main)
+
+    @property
+    def regle_min_period(self):
+        return self.get("main")["regle"]["min_period"]
+
+
+    @property
+    def regle_max_period(self):
+        return self.get("main")["regle"]["max_period"]
+
+
+    @property
+    def regle_average_period_window(self):
+        return self.get("main")["regle"]["average_period_window"]
+
+    @property
+    def regle_method(self):
+        return self.get("main", )["regle"]["method"]
+
+
+    @property
+    def ui_theme(self):
+        return self.get("main").value["ui"]["theme"]
+
+
+class BaseContextData(ContextData):
+    preferences_class = UserPrefences
+    context_to_string = False
+    redirect_login = True

+ 35 - 0
src/baby/app/context/calendar.py

@@ -0,0 +1,35 @@
+import datetime
+
+from djangotools.common.types import date_to_string
+
+from baby.app.common.calendar import MonthCalendar
+from baby.app.context.base import BaseContextData, UserPrefences
+
+
+class ContextData(BaseContextData):
+    page = "calendar"
+
+
+    def get_context_data(self, request, action_params, **kwargs):
+        user = request.user
+        today = datetime.date.today()
+        date = date_to_string(today)
+        if "date" in getattr(request, "GET", {}):
+            date = request.GET["date"]
+        elif "date" in getattr(request, "POST", {}):
+            date = request.POST["date"]
+
+        if "month" in getattr(request, "GET", {}) and "year" in getattr(request, "GET", {}):
+            date = request.GET["month"].zfill(2)+"/"+request.GET["year"]
+        if "month" in getattr(request, "POST", {}) and "year" in getattr(request, "POST", {}):
+            date = request.POST["month"].zfill(2)+"/"+request.POST["year"]
+
+        if len(date.split("/")) == 2:
+            date = "01/"+date
+
+        cal = MonthCalendar(user, date)
+        cal = cal.json
+        cal["title"] = f"Calendrier"
+
+        return cal
+

+ 32 - 0
src/baby/app/context/edit.py

@@ -0,0 +1,32 @@
+import datetime
+
+from djangotools.common.types import date_to_string
+from baby.app.context.base import BaseContextData, UserPrefences
+from baby.app.models.regle import Regle
+
+
+class EditData(BaseContextData):
+    page = "edit"
+
+
+    def get_context_data(self, request, action_params, **kwargs):
+        user = request.user
+        debut = datetime.date.today()
+        if "year" in getattr(request, "GET", {}):
+            debut =datetime.date(int(request.GET["year"]), 1, 1)
+        elif "year" in getattr(request, "POST", {}):
+            debut = datetime.date(int(request.POST["year"]), 1, 1)
+        else:
+            debut = datetime.date(debut.year, 1 , 1)
+
+        fin = datetime.date(debut.year+1, 1, 1)
+
+        regles = Regle.objects.filter(user=user, date__gte=debut, date__lt=fin)
+        return {
+            "year" : debut.year,
+            "dates" : [{"date": date_to_string(x.date), "id": x.id} for x in regles],
+            "prev" : debut.year - 1,
+            "next" : debut.year + 1,
+            "count" : len(regles),
+            "title" : "Editer les cycles"
+        }

+ 17 - 0
src/baby/app/context/login.py

@@ -0,0 +1,17 @@
+
+
+import datetime
+
+from djangotools.common.types import date_to_string
+from baby.app.context.base import BaseContextData
+from baby.app.models.regle import Regle
+
+
+class EditData(BaseContextData):
+    page = "login"
+    need_auth = False
+
+    def get_context_data(self, request, action_params, **kwargs):
+        return {
+            "title" : "Login"
+        }

+ 24 - 0
src/baby/app/context/main.py

@@ -0,0 +1,24 @@
+import datetime
+
+from baby.app.context.base import UserPrefences, BaseContextData
+from baby.app.models.regle import Cycle
+
+
+class ContextData(BaseContextData):
+    page = "index"
+
+
+    def get_context_data(self, request, action_params, **kwargs):
+        today = datetime.date.today()
+        user = request.user
+        cycle = Cycle.from_day(user, today)
+        if cycle is not None:
+            data =  cycle.json
+            data["offset"] = (today - cycle.start).days# faire un plus 1
+            data["value"] = True
+        else:
+            data = {"value": False}
+        data["title"] = "Menu principal"
+
+        return data
+

+ 15 - 0
src/baby/app/context/new_cycle.py

@@ -0,0 +1,15 @@
+
+
+from baby.app.context.base import BaseContextData, UserPrefences
+
+
+class ContextData(BaseContextData):
+    page = "new_cycle"
+
+    def get_context_data(self, request, action_params, **kwargs):
+        user = request.user
+        return {
+            "title" :  "Nouveau cycle"
+        }
+
+

+ 68 - 0
src/baby/app/context/parameters.py

@@ -0,0 +1,68 @@
+from django.core.exceptions import ValidationError
+
+from baby.app.context.base import BaseContextData, UserPrefences
+
+
+def validate_choice(choices):
+    def wrapper(data, choices=choices):
+        choices = {k:k for k in choices}
+        if data not in choices:
+            raise ValidationError("Choix non autorisé")
+        return choices[data]
+    return wrapper
+
+def validate_int(data):
+    data = int(data)
+    if 0 < data < 50: return data
+    raise ValidationError("Valeur incorrecte")
+
+
+
+class ContextData(BaseContextData):
+    page = "parameters"
+    types = {
+        "ui.theme"  : validate_choice(["dark", "light"]),
+        "regle.method"  : validate_choice(["contraception", "procreation"]),
+        "regle.min_period" : validate_int,
+        "regle.max_period" : validate_int,
+        "regle.average_period_window" : validate_int,
+    }
+    def _do_one(self, data, key, value):
+        validation = self.types[key]
+        key =  key.split(".")
+        for k in key[:-1]:
+            if k not in data: return
+            data = data[k]
+        data[key[-1]] = validation(value)
+
+    def _apply_update(self, request):
+        args = request.POST
+        main = request.user.pref.get("main")
+        errors = {}
+        for k, v in args.items():
+            try:
+                self._do_one(main, k, v)
+            except ValidationError:
+                errors[k.replace(".", "_")] = "Erreur de validation"
+        if not errors:
+            request.user.pref.save(main)
+        return errors
+
+
+
+    def get_context_data(self, request, action_params, **kwargs):
+        errors = {}
+        is_post = False
+        if request.method == "POST":
+            errors = self._apply_update(request)
+            is_post = True
+
+        user = request.user
+        return {
+            "title" :  "Paramètres",
+            "errors" : errors,
+            "is_post" : is_post,
+            "status" : not errors
+        }
+
+

+ 41 - 0
src/baby/app/context/stats.py

@@ -0,0 +1,41 @@
+import datetime
+
+from baby.app.context.base import UserPrefences, BaseContextData
+from baby.app.models.const import get_average_month
+from baby.app.models.regle import Cycle, Regle
+
+
+class ContextData(BaseContextData):
+    page = "stats"
+
+
+    def get_context_data(self, request, action_params, **kwargs):
+        today = datetime.date.today()
+        user = request.user
+        period = request.GET.get("period", request.POST.get("period", "custom"))
+
+        period = {
+            "custom" : get_average_month(user),
+            "6" : 6,
+            "12" : 12,
+            "all" : 12*1000
+        }[period]
+
+        kwargs = {
+            "date__gte": datetime.date.today() - datetime.timedelta(days=12*30)
+        }
+        regles = Regle.objects.filter(user=user, **kwargs)
+        periods = Regle.objects.rgeles_to_period(regles)
+        cycle = Cycle.from_day(user, today, period)
+        if cycle is not None:
+            data =  cycle.json
+            data["offset"] = (today - cycle.start).days# faire un plus 1
+            data["value"] = True
+            data["average"] = sum(periods) / len(periods) if periods else None
+            data["min"] = min(periods) if periods else None
+            data["max"] = max(periods) if periods else None
+        else:
+            data = {"value": False}
+        data["title"] = "Menu principal"
+
+        return data

+ 45 - 0
src/baby/app/context/validate_cycle.py

@@ -0,0 +1,45 @@
+import datetime
+
+from djangotools.common.types import parse_date
+
+from baby.app.context.base import BaseContextData, UserPrefences
+from baby.app.models.const import get_min_period
+from baby.app.models.regle import Regle
+
+class ContextData(BaseContextData):
+    page = "validate_cycle"
+
+    def get_context_data(self, request, action_params, **kwargs):
+        user = request.user
+        params = request.GET
+        force = params.get("force", False)
+        date = params.get("date",datetime.date.today())
+        if isinstance(date, (list, tuple)):
+            if date: date = date[0]
+            else: date=None
+        today = parse_date(date)
+
+
+        last = Regle.objects.last_regle_before(user, today)
+        if last is not None:
+            days_from_begin = (today - last.date).days
+
+            if days_from_begin == 0:
+                return {
+                    "status": False,
+                    "message": "Le cycle a déja été initialisé aujourd'hui"
+                }
+
+            if days_from_begin < get_min_period(user) and not force:
+                return {
+                    "status" :  False,
+                    "message" : "Le cycle est trop court"
+                }
+
+        Regle.objects.create(user, today)
+        return {
+            "status" :  True,
+            "message" : "Le nouveau cycle a bien été pris en compte"
+        }
+
+

+ 28 - 0
src/baby/app/migrations/0001_initial.py

@@ -0,0 +1,28 @@
+# Generated by Django 4.2.5 on 2023-09-21 21:12
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Regle',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date', models.DateField()),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'unique_together': {('user', 'date')},
+            },
+        ),
+    ]

+ 0 - 0
src/baby/app/migrations/__init__.py


+ 1 - 0
src/baby/app/models/__init__.py

@@ -0,0 +1 @@
+from baby.app.models import regle

+ 10 - 0
src/baby/app/models/const.py

@@ -0,0 +1,10 @@
+
+
+def get_min_period(user, profile=None):
+    return user.pref.regle_min_period
+
+def get_max_period(user):
+    return user.pref.regle_max_period
+
+def get_average_month(user):
+    return user.pref.regle_average_period_window

+ 282 - 0
src/baby/app/models/regle.py

@@ -0,0 +1,282 @@
+import datetime
+from functools import partial
+from math import ceil
+
+from django.contrib.auth.models import User
+from django.db import models
+from djangotools.common.types import parse_date, date_to_string
+
+from baby.app.models.const import get_average_month, get_min_period, get_max_period
+
+
+class Cycles(list):
+
+    def __init__(self, start=None, end=None):
+        super().__init__()
+        self.start = start
+        self.end = end
+
+    def iter_dates(self, start=None, end=None):
+        if not self: return
+        start = start or self.start or self[0].start
+        end = end or self.end or self[-1].end
+        for cycle in self:
+            if cycle.end < start: continue
+            for date, cur in cycle:
+                if date<start: continue
+                if date>end: return
+                yield date, cur
+
+
+
+class Cycle:
+    DEBUT="debut"
+    OVULATION="ovulation"
+    FERTILITE="fertilite"
+
+
+    def __init__(self):
+        self.start : datetime.date = None
+        self.end : datetime.date = None
+        self.length : int = None
+        self.min_cycle : int = None
+        self.max_cycle : int = None
+        self.fertility_start_offset : int = None
+        self.fertility_start : datetime.date = None
+        self.fertility_end_offset : int = None
+        self.fertility_end : datetime.date = None
+        self.ovulation_offset : int = None
+        self.ovulation : datetime.date = None
+
+    def as_list(self, with_date=False):
+        if with_date:
+            ret = [(self.start + datetime.timedelta(days=i), []) for i in range(self.length)]
+            ret[0][1].append(self.DEBUT)
+            if self.length >= 14:
+                ret[self.length - 14][1].append(self.OVULATION)
+            if self.fertility_start_offset:
+                [ret[i - 1][1].append(self.FERTILITE) for i in range(self.fertility_start_offset, self.fertility_end_offset + 1)  if 0 < i < self.length]
+        else:
+            ret = [[] for _ in range(self.length)]
+            ret[0].append(self.DEBUT)
+            if self.length >= 14:
+                ret[self.length-14].append(self.OVULATION)
+            if self.fertility_start_offset:
+                [ret[i-1].append(self.FERTILITE) for i in range(self.fertility_start_offset, self.fertility_end_offset+1) if 0 > i > self.length]
+        return ret
+
+    def __iter__(self):
+        return iter(self.as_list(with_date=True))
+
+    def __repr__(self):
+        return f"<Cycle {date_to_string(self.start)} to {date_to_string(self.end)} ({self.length})>"
+
+    def __str__(self):
+        return self.__repr__()
+
+    @property
+    def json(self):
+        return {
+            "start" : date_to_string(self.start),
+            "end" : date_to_string(self.end),
+            "length" : self.length,
+            "ovulation" : date_to_string(self.ovulation),
+            "fertility" : [date_to_string(self.fertility_start),date_to_string(self.fertility_end)],
+            "ovulation_offset" : self.ovulation_offset,
+            "fertility_offset" : [self.fertility_start_offset, self.fertility_end_offset],
+        }
+
+
+    def _init(self):
+        assert self.start
+        if not self.end:
+            assert self.length
+            self.end = self.start + datetime.timedelta(days=self.length)
+        elif not self.length:
+            assert  self.end
+            self.length = (self.end - self.start).days
+        else:
+            return
+
+        self._fertility_init()
+
+        if self.fertility_start_offset is not None:
+            self.fertility_start = self.start + datetime.timedelta(days=self.fertility_start_offset-1)
+
+        if self.fertility_end_offset is not None:
+            self.fertility_end = self.start + datetime.timedelta(days=self.fertility_end_offset-1)
+
+        if self.ovulation_offset is not None:
+            self.ovulation = self.start + datetime.timedelta(days=self.ovulation_offset-1)
+
+    def _fertility_init(self):
+        self.fertility_start_offset = self.min_cycle and int(self.min_cycle - 18)
+        self.fertility_end_offset = self.max_cycle and ceil(self.max_cycle - 11)
+        self.ovulation_offset = ceil(self.length - 11)
+
+    @classmethod
+    def create(cls, user, start, end=None, length=None, monthes=None):
+        filter = partial(Regle.objects.filter, user=user)
+        kwargs = {
+            "date__lte": start
+        }
+
+        if monthes != 0:
+            monthes = monthes if monthes is not None else get_average_month(user)
+            kwargs["date__gte"] =  start - datetime.timedelta(days=monthes*30)
+
+        regles = list(filter(**kwargs ).order_by("date"))
+        periods = Regle.objects.rgeles_to_period(regles, min_period=get_min_period(user), max_period=get_max_period(user))
+        mini = None
+        maxi = None
+        if periods:
+            mini = min(periods)
+            maxi = max(periods)
+
+        cycle = cls()
+        cycle.start = start
+        if end:
+            cycle.end = end
+        elif length:
+            cycle.length = length
+        else:
+            raise ValueError
+
+        cycle.min_cycle = mini
+        cycle.max_cycle = maxi
+        cycle._init()
+        return cycle
+
+    @classmethod
+    def from_day(cls, user, ref_date=None, monthes=None):
+        today = datetime.date.today()
+        date = parse_date(ref_date) or today
+        regle = Regle.objects.last_regle_before(user, date)
+        end = Regle.objects.next_regle_after(user, date)
+        monthes = monthes if monthes is not None else get_average_month(user)
+
+        kwargs = {"user": user}
+        x = user.pref.regle_max_period
+        if ref_date and ref_date > datetime.date.today():
+            length = Regle.objects.average_period(user, monthes=monthes)
+            ilength = int(length)
+
+            offset =  int((date - regle.date).days / ilength) * int(ilength)
+            kwargs["start"] = regle.date + datetime.timedelta(days=int(offset))
+            kwargs["end"] = kwargs["start"] + datetime.timedelta(days=ilength)
+        elif regle:
+            kwargs["start"] = regle.date
+            if end:
+                kwargs["end"]  = end.date
+            else:
+                length = Regle.objects.average_period(user, monthes=monthes)
+                if length is not None:
+                    kwargs["length"]  = int(length)
+                else:
+                    return None
+        else:
+            return None
+
+        return cls.create(**kwargs)
+
+    @classmethod
+    def from_date_range(cls, user, start, end):
+        if start > end: start, end = end, start
+        cycles = Cycles(start=start, end=end)
+        curr = start
+        while curr <= end:
+            cycle = cls.from_day(user, curr)
+            if  cycle is None:
+                break
+            cycles.append(cycle)
+            curr = cycle.end + datetime.timedelta(days=1)
+
+        return cycles
+
+    @classmethod
+    def from_month(cls, user, mont_or_date, year):
+        if isinstance(mont_or_date, str):
+            mont_or_date = parse_date(mont_or_date)
+        if isinstance(mont_or_date, (datetime.date, datetime.datetime)):
+            mont_or_date, year = mont_or_date.month, mont_or_date.year
+        assert isinstance(mont_or_date, int) and isinstance(year, int)
+        if year < 100: year += 2000
+        assert 0 < mont_or_date <= 12
+        start = datetime.date(year, mont_or_date, 1)
+        end = start + datetime.timedelta(days=32)
+        end = end - datetime.timedelta(days=end.day)
+        return cls.from_date_range(user, start, end)
+
+
+class RegleManager(models.Manager):
+    def last_year(self, user):
+        return self.filter(user=user, date__gte=datetime.date.today() - datetime.timedelta(days=-395))
+
+    def create(self, user, date=None):
+        date = parse_date(date)
+        if date is None:
+            date = datetime.date.today()
+        return super().create(user=user, date=date)
+    def last_regle_before(self, user, date=None):
+        if date is None: date = datetime.date.today()
+        regle = self.filter(user=user, date__lte=date).order_by("-date")[:1]
+        regle = list(regle)
+        return regle[0] if regle else None
+
+    def average_period(self, user, monthes=None, date_min=None, date_max=None):
+        kwargs = {"user": user}
+
+        if date_min or date_max:
+            if date_min: kwargs["date__gte"] = date_min
+            if date_max: kwargs["date__lte"] = date_max
+        elif monthes:
+            kwargs["date__gte"] = datetime.date.today() - datetime.timedelta(days=monthes*30)
+
+        regles = self.filter(**kwargs).order_by("date")
+        data = self.rgeles_to_period(regles)
+        if data:
+            return sum(data) / len(data)
+        return None
+    @classmethod
+    def rgeles_to_period(cls, data, start_date=False, min_period=None, max_period=None):
+        data = iter(data)
+        periods = []
+        try:
+            last = next(data)
+            while True:
+                curr = next(data)
+                dt = curr.date - last.date
+                if start_date:
+                    dt = (last.date, dt)
+                if (min_period is not None and dt.days<min_period or
+                    max_period is not None and dt.days>max_period):
+                    last=curr
+                    continue
+                periods.append(dt.days)
+                last = curr
+        except StopIteration:
+            pass
+        return periods
+
+    def next_regle_after(self, user, date):
+        regle = self.filter(user=user, date__gt=date).order_by("date")[:1]
+        regle = list(regle)
+        return regle[0] if regle else None
+
+
+class Regle(models.Model):
+
+    objects = RegleManager()
+    user = models.ForeignKey(User, on_delete=models.CASCADE)
+    date = models.DateField()
+    DoesNotExist : Exception
+
+    class Meta:
+        unique_together = ["user", "date"]
+
+
+    def __repr__(self):
+        return f"<Regle {self.user.username}  {self.date}>"
+
+    def __str__(self):
+        return self.__repr__()

+ 0 - 0
src/baby/app/views/__init__.py


+ 15 - 0
src/baby/app/views/main.py

@@ -0,0 +1,15 @@
+from django.conf import settings
+from djangotools.views import Route, Router, render_page
+
+pages = [
+    ("", "index", "index.html"),
+    ("edit", "edit", "edit.html"),
+    ("calendar", "calendar", "calendar.html"),
+    ("new_cycle", "new_cycle", "new_cycle.html"),
+    ("validate_cycle", "validate_cycle", "validate_cycle.html"),
+    ("stats", "stats", "stats.html"),
+]
+
+Router.route("parameters", {"POST", "GET"})(render_page("parameters", "parameters.html", context_to_string=False))
+for url, context, template in pages:
+    Router.get(url, need_auth=True)(render_page(context, template, context_to_string=False))

+ 21 - 0
src/baby/app/views/regle.py

@@ -0,0 +1,21 @@
+from django.shortcuts import redirect
+from djangotools.common import response
+from djangotools.common.types import parse_date
+from djangotools.views import Router
+
+from baby.app.models.regle import Regle
+
+
+@Router.get("delete_regle")
+def delete_regle(request):
+    date = request.GET.get("date", request.POST.get("date", None))
+    if date is None:
+        return redirect("/")
+
+    parsed_date = parse_date(date)
+    regle = list(Regle.objects.filter(date=parsed_date))
+    if regle:
+        for x in regle:
+            x.delete()
+
+    return redirect(f"/edit?year={parsed_date.year}")

+ 16 - 0
src/baby/asgi.py

@@ -0,0 +1,16 @@
+"""
+ASGI config for baby project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'baby.settings')
+
+application = get_asgi_application()

+ 1 - 0
src/baby/frontend/static/css/accueil/accueil.css

@@ -0,0 +1 @@
+

+ 106 - 0
src/baby/frontend/static/css/calendar/calendar.css

@@ -0,0 +1,106 @@
+.header-month-prev_month {
+    text-decoration: none;
+    cursor: pointer;
+    font-size: 24pt;
+    margin-left: 10px;
+    border-color: 1px solid #321;
+    font-weight: 700;
+}
+
+.header-month-next_month {
+    text-decoration: none;
+    cursor: pointer;
+    font-size: 24pt;
+    margin-left: 10px;
+    font-weight: 700;
+}
+
+.header-month-current_month {
+    font-size: 22pt;
+    font-weight: 600;
+    margin-right: 10px;
+    
+}
+
+
+.calendar-day {
+    border: 1px solid #aac;
+}
+
+.tag-current_month {
+    color: rgb(197, 197, 218);
+    font-weight: 600;
+}
+
+.tag-not_current_month {
+    font-style: italic;
+}
+
+.tag-debut {
+    background-color: rgb(16, 68, 14);
+}
+
+.tag-ovulation {
+    background-color: rgb(126, 18, 18) !important;
+    
+}
+
+.tag-fertilite {
+    background-color: rgb(138, 103, 39);
+    
+}
+
+.calendar {
+    width: 100%;
+    text-align: center;
+}
+
+.calendar-content-header-row > th {
+}
+.calendar-content-header-row {
+    height: 64px;
+    
+}
+
+.calendar-content-row {
+    height: 64px;
+}
+
+.calendar-content {
+    width: 100%;
+    margin-bottom: 10px;
+}
+
+
+.calendar-date-selector {
+
+}
+
+.calendar-date-selector-form{
+}
+
+.calendar-date-month {
+    background-color: #456;
+    max-width: 250px;
+
+}
+
+.calendar-date-year {
+    background-color: #456;
+    max-width: 250px;
+}
+
+.calendar-date-submit{
+
+}
+
+.calendar-date-submit > button {
+    color: #321;
+}
+
+
+.lengend {
+    margin-top: 10px;
+    padding: 5px;
+    border: 1px solid #aac;
+}

+ 57 - 0
src/baby/frontend/static/css/edit/edit.css

@@ -0,0 +1,57 @@
+.header-month-prev_month {
+    text-decoration: none;
+    cursor: pointer;
+    font-size: 24pt;
+    margin-left: 10px;
+    border-color: 1px solid #321;
+    font-weight: 700;
+}
+
+.header-month-next_month {
+    text-decoration: none;
+    cursor: pointer;
+    font-size: 24pt;
+    margin-left: 10px;
+    font-weight: 700;
+}
+
+.header-month-current_month {
+    font-size: 22pt;
+    font-weight: 600;
+    margin-right: 10px;
+    
+}
+
+
+.regle-host {
+    text-align: left;
+}
+
+.regle-wrapper {
+    padding: 5px;
+}
+
+.regle {
+    padding: 5px;
+    background-color: var(--color-5);
+    color: var(--color-4);
+    height: 52px;
+}
+
+.regle-content {
+    font-size: 22pt;
+}
+
+.regle-action {
+    float: right;
+    cursor: pointer;
+    color: var(--color-4);
+    font-size: 22pt;
+}
+
+.no-regle {
+
+
+}
+
+

+ 0 - 0
src/baby/frontend/static/css/login/login.css


+ 121 - 0
src/baby/frontend/static/css/main/main.css

@@ -0,0 +1,121 @@
+
+
+:root {
+    --color-1: #a3a3a3;
+    --color-2: #b97070;
+    --color-3: #559663;
+    --color-4: #1f1f1f;
+    --color-5: #677fb4;
+}
+
+
+
+
+body,
+html {
+    background-color: var(--color-4);
+    color: var(--color-1);
+}
+
+
+
+.tile-holder {
+    margin-top: 10px;
+    text-align: center;
+}
+
+.tile {
+    padding: 5px;
+}
+
+.tile-content {
+    cursor: pointer;
+    background-color: var(--color-5);
+    height: 150px;
+    color: var(--color-1);
+}
+
+.tile-content-transparent {
+    cursor: pointer;
+    height: 150px;
+    border: 1px solid var(--color-1);
+
+}
+
+.tile-title-text {
+    font-size: 32pt;
+    font-weight: 600;
+    height: 90px;
+    padding-top: 10px;
+}
+
+
+.tile-img {
+    font-size: 60px;
+
+}
+
+.tile-title {
+    /*width: 100;*/
+}
+
+
+.tile-title-info {
+    color: var(--color-1);
+}
+
+
+.btn-validate-cycle {
+}
+
+.validate_cycle-input {
+    height: 50px;
+}
+
+.validate_cycle-submit {
+    height: 50px;
+}
+
+.validate_cycle-label {
+    height: 40px;
+    font-size: 16pt;
+}
+
+nav {
+    background-color: var(--color-2);
+    color: var(--color-1);
+    border-color: 1px solid var(--color-1);
+}
+
+.nav-titre{
+    font-size: 20pt;
+}
+
+.nav-home {
+    font-weight: 600;
+    font-size: 20pt;
+
+}
+
+main {
+    margin-top: 10px;
+}
+
+
+.card {
+    color: var(--color-1);
+    background-color: var(--color-4) !important;
+}
+
+.btn-login {
+    color: var(--color-4);
+    background-color: var(--color-2) !important;
+}
+
+.input-group,
+.input-group-text,
+input {
+
+    color: var(--color-1) !important;
+    background-color: var(--color-4) !important;
+}

+ 41 - 0
src/baby/frontend/static/css/parameters/parameters.css

@@ -0,0 +1,41 @@
+.btn-submit {
+    background-color: var(--color-3);
+    color: var(--color-4);
+}
+
+.btn-submit:hover {
+    background-color: var(--color-2);
+    color: var(--color-4);
+}
+
+.form-control {
+    background-color: var(--color-1);
+}
+
+.input-group-text {
+    background-color: var(--color-1);
+}
+
+.form-select
+{
+    background-color: var(--color-1);
+}
+
+.post-success {
+    background-color: var(--color-3);
+    color: var(--color-4);
+    padding:7px;
+    border: 1px solid var(--color-4);
+}
+
+.post-fail {
+    background-color: var(--color-2);
+    color: var(--color-4);
+    padding:7px;
+    border: 1px solid var(--color-4);
+}
+
+
+.invalid-feedback {
+    background-color: none;
+}

+ 19 - 0
src/baby/frontend/static/css/stats/stats.css

@@ -0,0 +1,19 @@
+.stats-period-selector{
+
+    
+}
+
+.stats-period-selector-form{
+
+    
+}
+
+.stats-period{
+
+    
+}
+
+.stats-period-submit{
+
+    
+}

+ 1390 - 0
src/baby/frontend/static/css/vendor/bootstrap-icons.css

@@ -0,0 +1,1390 @@
+@font-face {
+  font-family: "bootstrap-icons";
+  src: url("./fonts/bootstrap-icons.woff2?856008caa5eb66df68595e734e59580d") format("woff2"),
+url("./fonts/bootstrap-icons.woff?856008caa5eb66df68595e734e59580d") format("woff");
+}
+
+[class^="bi-"]::before,
+[class*=" bi-"]::before {
+  display: inline-block;
+  font-family: bootstrap-icons !important;
+  font-style: normal;
+  font-weight: normal !important;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  vertical-align: -.125em;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.bi-alarm-fill::before { content: "\f101"; }
+.bi-alarm::before { content: "\f102"; }
+.bi-align-bottom::before { content: "\f103"; }
+.bi-align-center::before { content: "\f104"; }
+.bi-align-end::before { content: "\f105"; }
+.bi-align-middle::before { content: "\f106"; }
+.bi-align-start::before { content: "\f107"; }
+.bi-align-top::before { content: "\f108"; }
+.bi-alt::before { content: "\f109"; }
+.bi-app-indicator::before { content: "\f10a"; }
+.bi-app::before { content: "\f10b"; }
+.bi-archive-fill::before { content: "\f10c"; }
+.bi-archive::before { content: "\f10d"; }
+.bi-arrow-90deg-down::before { content: "\f10e"; }
+.bi-arrow-90deg-left::before { content: "\f10f"; }
+.bi-arrow-90deg-right::before { content: "\f110"; }
+.bi-arrow-90deg-up::before { content: "\f111"; }
+.bi-arrow-bar-down::before { content: "\f112"; }
+.bi-arrow-bar-left::before { content: "\f113"; }
+.bi-arrow-bar-right::before { content: "\f114"; }
+.bi-arrow-bar-up::before { content: "\f115"; }
+.bi-arrow-clockwise::before { content: "\f116"; }
+.bi-arrow-counterclockwise::before { content: "\f117"; }
+.bi-arrow-down-circle-fill::before { content: "\f118"; }
+.bi-arrow-down-circle::before { content: "\f119"; }
+.bi-arrow-down-left-circle-fill::before { content: "\f11a"; }
+.bi-arrow-down-left-circle::before { content: "\f11b"; }
+.bi-arrow-down-left-square-fill::before { content: "\f11c"; }
+.bi-arrow-down-left-square::before { content: "\f11d"; }
+.bi-arrow-down-left::before { content: "\f11e"; }
+.bi-arrow-down-right-circle-fill::before { content: "\f11f"; }
+.bi-arrow-down-right-circle::before { content: "\f120"; }
+.bi-arrow-down-right-square-fill::before { content: "\f121"; }
+.bi-arrow-down-right-square::before { content: "\f122"; }
+.bi-arrow-down-right::before { content: "\f123"; }
+.bi-arrow-down-short::before { content: "\f124"; }
+.bi-arrow-down-square-fill::before { content: "\f125"; }
+.bi-arrow-down-square::before { content: "\f126"; }
+.bi-arrow-down-up::before { content: "\f127"; }
+.bi-arrow-down::before { content: "\f128"; }
+.bi-arrow-left-circle-fill::before { content: "\f129"; }
+.bi-arrow-left-circle::before { content: "\f12a"; }
+.bi-arrow-left-right::before { content: "\f12b"; }
+.bi-arrow-left-short::before { content: "\f12c"; }
+.bi-arrow-left-square-fill::before { content: "\f12d"; }
+.bi-arrow-left-square::before { content: "\f12e"; }
+.bi-arrow-left::before { content: "\f12f"; }
+.bi-arrow-repeat::before { content: "\f130"; }
+.bi-arrow-return-left::before { content: "\f131"; }
+.bi-arrow-return-right::before { content: "\f132"; }
+.bi-arrow-right-circle-fill::before { content: "\f133"; }
+.bi-arrow-right-circle::before { content: "\f134"; }
+.bi-arrow-right-short::before { content: "\f135"; }
+.bi-arrow-right-square-fill::before { content: "\f136"; }
+.bi-arrow-right-square::before { content: "\f137"; }
+.bi-arrow-right::before { content: "\f138"; }
+.bi-arrow-up-circle-fill::before { content: "\f139"; }
+.bi-arrow-up-circle::before { content: "\f13a"; }
+.bi-arrow-up-left-circle-fill::before { content: "\f13b"; }
+.bi-arrow-up-left-circle::before { content: "\f13c"; }
+.bi-arrow-up-left-square-fill::before { content: "\f13d"; }
+.bi-arrow-up-left-square::before { content: "\f13e"; }
+.bi-arrow-up-left::before { content: "\f13f"; }
+.bi-arrow-up-right-circle-fill::before { content: "\f140"; }
+.bi-arrow-up-right-circle::before { content: "\f141"; }
+.bi-arrow-up-right-square-fill::before { content: "\f142"; }
+.bi-arrow-up-right-square::before { content: "\f143"; }
+.bi-arrow-up-right::before { content: "\f144"; }
+.bi-arrow-up-short::before { content: "\f145"; }
+.bi-arrow-up-square-fill::before { content: "\f146"; }
+.bi-arrow-up-square::before { content: "\f147"; }
+.bi-arrow-up::before { content: "\f148"; }
+.bi-arrows-angle-contract::before { content: "\f149"; }
+.bi-arrows-angle-expand::before { content: "\f14a"; }
+.bi-arrows-collapse::before { content: "\f14b"; }
+.bi-arrows-expand::before { content: "\f14c"; }
+.bi-arrows-fullscreen::before { content: "\f14d"; }
+.bi-arrows-move::before { content: "\f14e"; }
+.bi-aspect-ratio-fill::before { content: "\f14f"; }
+.bi-aspect-ratio::before { content: "\f150"; }
+.bi-asterisk::before { content: "\f151"; }
+.bi-at::before { content: "\f152"; }
+.bi-award-fill::before { content: "\f153"; }
+.bi-award::before { content: "\f154"; }
+.bi-back::before { content: "\f155"; }
+.bi-backspace-fill::before { content: "\f156"; }
+.bi-backspace-reverse-fill::before { content: "\f157"; }
+.bi-backspace-reverse::before { content: "\f158"; }
+.bi-backspace::before { content: "\f159"; }
+.bi-badge-3d-fill::before { content: "\f15a"; }
+.bi-badge-3d::before { content: "\f15b"; }
+.bi-badge-4k-fill::before { content: "\f15c"; }
+.bi-badge-4k::before { content: "\f15d"; }
+.bi-badge-8k-fill::before { content: "\f15e"; }
+.bi-badge-8k::before { content: "\f15f"; }
+.bi-badge-ad-fill::before { content: "\f160"; }
+.bi-badge-ad::before { content: "\f161"; }
+.bi-badge-ar-fill::before { content: "\f162"; }
+.bi-badge-ar::before { content: "\f163"; }
+.bi-badge-cc-fill::before { content: "\f164"; }
+.bi-badge-cc::before { content: "\f165"; }
+.bi-badge-hd-fill::before { content: "\f166"; }
+.bi-badge-hd::before { content: "\f167"; }
+.bi-badge-tm-fill::before { content: "\f168"; }
+.bi-badge-tm::before { content: "\f169"; }
+.bi-badge-vo-fill::before { content: "\f16a"; }
+.bi-badge-vo::before { content: "\f16b"; }
+.bi-badge-vr-fill::before { content: "\f16c"; }
+.bi-badge-vr::before { content: "\f16d"; }
+.bi-badge-wc-fill::before { content: "\f16e"; }
+.bi-badge-wc::before { content: "\f16f"; }
+.bi-bag-check-fill::before { content: "\f170"; }
+.bi-bag-check::before { content: "\f171"; }
+.bi-bag-dash-fill::before { content: "\f172"; }
+.bi-bag-dash::before { content: "\f173"; }
+.bi-bag-fill::before { content: "\f174"; }
+.bi-bag-plus-fill::before { content: "\f175"; }
+.bi-bag-plus::before { content: "\f176"; }
+.bi-bag-x-fill::before { content: "\f177"; }
+.bi-bag-x::before { content: "\f178"; }
+.bi-bag::before { content: "\f179"; }
+.bi-bar-chart-fill::before { content: "\f17a"; }
+.bi-bar-chart-line-fill::before { content: "\f17b"; }
+.bi-bar-chart-line::before { content: "\f17c"; }
+.bi-bar-chart-steps::before { content: "\f17d"; }
+.bi-bar-chart::before { content: "\f17e"; }
+.bi-basket-fill::before { content: "\f17f"; }
+.bi-basket::before { content: "\f180"; }
+.bi-basket2-fill::before { content: "\f181"; }
+.bi-basket2::before { content: "\f182"; }
+.bi-basket3-fill::before { content: "\f183"; }
+.bi-basket3::before { content: "\f184"; }
+.bi-battery-charging::before { content: "\f185"; }
+.bi-battery-full::before { content: "\f186"; }
+.bi-battery-half::before { content: "\f187"; }
+.bi-battery::before { content: "\f188"; }
+.bi-bell-fill::before { content: "\f189"; }
+.bi-bell::before { content: "\f18a"; }
+.bi-bezier::before { content: "\f18b"; }
+.bi-bezier2::before { content: "\f18c"; }
+.bi-bicycle::before { content: "\f18d"; }
+.bi-binoculars-fill::before { content: "\f18e"; }
+.bi-binoculars::before { content: "\f18f"; }
+.bi-blockquote-left::before { content: "\f190"; }
+.bi-blockquote-right::before { content: "\f191"; }
+.bi-book-fill::before { content: "\f192"; }
+.bi-book-half::before { content: "\f193"; }
+.bi-book::before { content: "\f194"; }
+.bi-bookmark-check-fill::before { content: "\f195"; }
+.bi-bookmark-check::before { content: "\f196"; }
+.bi-bookmark-dash-fill::before { content: "\f197"; }
+.bi-bookmark-dash::before { content: "\f198"; }
+.bi-bookmark-fill::before { content: "\f199"; }
+.bi-bookmark-heart-fill::before { content: "\f19a"; }
+.bi-bookmark-heart::before { content: "\f19b"; }
+.bi-bookmark-plus-fill::before { content: "\f19c"; }
+.bi-bookmark-plus::before { content: "\f19d"; }
+.bi-bookmark-star-fill::before { content: "\f19e"; }
+.bi-bookmark-star::before { content: "\f19f"; }
+.bi-bookmark-x-fill::before { content: "\f1a0"; }
+.bi-bookmark-x::before { content: "\f1a1"; }
+.bi-bookmark::before { content: "\f1a2"; }
+.bi-bookmarks-fill::before { content: "\f1a3"; }
+.bi-bookmarks::before { content: "\f1a4"; }
+.bi-bookshelf::before { content: "\f1a5"; }
+.bi-bootstrap-fill::before { content: "\f1a6"; }
+.bi-bootstrap-reboot::before { content: "\f1a7"; }
+.bi-bootstrap::before { content: "\f1a8"; }
+.bi-border-all::before { content: "\f1a9"; }
+.bi-border-bottom::before { content: "\f1aa"; }
+.bi-border-center::before { content: "\f1ab"; }
+.bi-border-inner::before { content: "\f1ac"; }
+.bi-border-left::before { content: "\f1ad"; }
+.bi-border-middle::before { content: "\f1ae"; }
+.bi-border-outer::before { content: "\f1af"; }
+.bi-border-right::before { content: "\f1b0"; }
+.bi-border-style::before { content: "\f1b1"; }
+.bi-border-top::before { content: "\f1b2"; }
+.bi-border-width::before { content: "\f1b3"; }
+.bi-border::before { content: "\f1b4"; }
+.bi-bounding-box-circles::before { content: "\f1b5"; }
+.bi-bounding-box::before { content: "\f1b6"; }
+.bi-box-arrow-down-left::before { content: "\f1b7"; }
+.bi-box-arrow-down-right::before { content: "\f1b8"; }
+.bi-box-arrow-down::before { content: "\f1b9"; }
+.bi-box-arrow-in-down-left::before { content: "\f1ba"; }
+.bi-box-arrow-in-down-right::before { content: "\f1bb"; }
+.bi-box-arrow-in-down::before { content: "\f1bc"; }
+.bi-box-arrow-in-left::before { content: "\f1bd"; }
+.bi-box-arrow-in-right::before { content: "\f1be"; }
+.bi-box-arrow-in-up-left::before { content: "\f1bf"; }
+.bi-box-arrow-in-up-right::before { content: "\f1c0"; }
+.bi-box-arrow-in-up::before { content: "\f1c1"; }
+.bi-box-arrow-left::before { content: "\f1c2"; }
+.bi-box-arrow-right::before { content: "\f1c3"; }
+.bi-box-arrow-up-left::before { content: "\f1c4"; }
+.bi-box-arrow-up-right::before { content: "\f1c5"; }
+.bi-box-arrow-up::before { content: "\f1c6"; }
+.bi-box-seam::before { content: "\f1c7"; }
+.bi-box::before { content: "\f1c8"; }
+.bi-braces::before { content: "\f1c9"; }
+.bi-bricks::before { content: "\f1ca"; }
+.bi-briefcase-fill::before { content: "\f1cb"; }
+.bi-briefcase::before { content: "\f1cc"; }
+.bi-brightness-alt-high-fill::before { content: "\f1cd"; }
+.bi-brightness-alt-high::before { content: "\f1ce"; }
+.bi-brightness-alt-low-fill::before { content: "\f1cf"; }
+.bi-brightness-alt-low::before { content: "\f1d0"; }
+.bi-brightness-high-fill::before { content: "\f1d1"; }
+.bi-brightness-high::before { content: "\f1d2"; }
+.bi-brightness-low-fill::before { content: "\f1d3"; }
+.bi-brightness-low::before { content: "\f1d4"; }
+.bi-broadcast-pin::before { content: "\f1d5"; }
+.bi-broadcast::before { content: "\f1d6"; }
+.bi-brush-fill::before { content: "\f1d7"; }
+.bi-brush::before { content: "\f1d8"; }
+.bi-bucket-fill::before { content: "\f1d9"; }
+.bi-bucket::before { content: "\f1da"; }
+.bi-bug-fill::before { content: "\f1db"; }
+.bi-bug::before { content: "\f1dc"; }
+.bi-building::before { content: "\f1dd"; }
+.bi-bullseye::before { content: "\f1de"; }
+.bi-calculator-fill::before { content: "\f1df"; }
+.bi-calculator::before { content: "\f1e0"; }
+.bi-calendar-check-fill::before { content: "\f1e1"; }
+.bi-calendar-check::before { content: "\f1e2"; }
+.bi-calendar-date-fill::before { content: "\f1e3"; }
+.bi-calendar-date::before { content: "\f1e4"; }
+.bi-calendar-day-fill::before { content: "\f1e5"; }
+.bi-calendar-day::before { content: "\f1e6"; }
+.bi-calendar-event-fill::before { content: "\f1e7"; }
+.bi-calendar-event::before { content: "\f1e8"; }
+.bi-calendar-fill::before { content: "\f1e9"; }
+.bi-calendar-minus-fill::before { content: "\f1ea"; }
+.bi-calendar-minus::before { content: "\f1eb"; }
+.bi-calendar-month-fill::before { content: "\f1ec"; }
+.bi-calendar-month::before { content: "\f1ed"; }
+.bi-calendar-plus-fill::before { content: "\f1ee"; }
+.bi-calendar-plus::before { content: "\f1ef"; }
+.bi-calendar-range-fill::before { content: "\f1f0"; }
+.bi-calendar-range::before { content: "\f1f1"; }
+.bi-calendar-week-fill::before { content: "\f1f2"; }
+.bi-calendar-week::before { content: "\f1f3"; }
+.bi-calendar-x-fill::before { content: "\f1f4"; }
+.bi-calendar-x::before { content: "\f1f5"; }
+.bi-calendar::before { content: "\f1f6"; }
+.bi-calendar2-check-fill::before { content: "\f1f7"; }
+.bi-calendar2-check::before { content: "\f1f8"; }
+.bi-calendar2-date-fill::before { content: "\f1f9"; }
+.bi-calendar2-date::before { content: "\f1fa"; }
+.bi-calendar2-day-fill::before { content: "\f1fb"; }
+.bi-calendar2-day::before { content: "\f1fc"; }
+.bi-calendar2-event-fill::before { content: "\f1fd"; }
+.bi-calendar2-event::before { content: "\f1fe"; }
+.bi-calendar2-fill::before { content: "\f1ff"; }
+.bi-calendar2-minus-fill::before { content: "\f200"; }
+.bi-calendar2-minus::before { content: "\f201"; }
+.bi-calendar2-month-fill::before { content: "\f202"; }
+.bi-calendar2-month::before { content: "\f203"; }
+.bi-calendar2-plus-fill::before { content: "\f204"; }
+.bi-calendar2-plus::before { content: "\f205"; }
+.bi-calendar2-range-fill::before { content: "\f206"; }
+.bi-calendar2-range::before { content: "\f207"; }
+.bi-calendar2-week-fill::before { content: "\f208"; }
+.bi-calendar2-week::before { content: "\f209"; }
+.bi-calendar2-x-fill::before { content: "\f20a"; }
+.bi-calendar2-x::before { content: "\f20b"; }
+.bi-calendar2::before { content: "\f20c"; }
+.bi-calendar3-event-fill::before { content: "\f20d"; }
+.bi-calendar3-event::before { content: "\f20e"; }
+.bi-calendar3-fill::before { content: "\f20f"; }
+.bi-calendar3-range-fill::before { content: "\f210"; }
+.bi-calendar3-range::before { content: "\f211"; }
+.bi-calendar3-week-fill::before { content: "\f212"; }
+.bi-calendar3-week::before { content: "\f213"; }
+.bi-calendar3::before { content: "\f214"; }
+.bi-calendar4-event::before { content: "\f215"; }
+.bi-calendar4-range::before { content: "\f216"; }
+.bi-calendar4-week::before { content: "\f217"; }
+.bi-calendar4::before { content: "\f218"; }
+.bi-camera-fill::before { content: "\f219"; }
+.bi-camera-reels-fill::before { content: "\f21a"; }
+.bi-camera-reels::before { content: "\f21b"; }
+.bi-camera-video-fill::before { content: "\f21c"; }
+.bi-camera-video-off-fill::before { content: "\f21d"; }
+.bi-camera-video-off::before { content: "\f21e"; }
+.bi-camera-video::before { content: "\f21f"; }
+.bi-camera::before { content: "\f220"; }
+.bi-camera2::before { content: "\f221"; }
+.bi-capslock-fill::before { content: "\f222"; }
+.bi-capslock::before { content: "\f223"; }
+.bi-card-checklist::before { content: "\f224"; }
+.bi-card-heading::before { content: "\f225"; }
+.bi-card-image::before { content: "\f226"; }
+.bi-card-list::before { content: "\f227"; }
+.bi-card-text::before { content: "\f228"; }
+.bi-caret-down-fill::before { content: "\f229"; }
+.bi-caret-down-square-fill::before { content: "\f22a"; }
+.bi-caret-down-square::before { content: "\f22b"; }
+.bi-caret-down::before { content: "\f22c"; }
+.bi-caret-left-fill::before { content: "\f22d"; }
+.bi-caret-left-square-fill::before { content: "\f22e"; }
+.bi-caret-left-square::before { content: "\f22f"; }
+.bi-caret-left::before { content: "\f230"; }
+.bi-caret-right-fill::before { content: "\f231"; }
+.bi-caret-right-square-fill::before { content: "\f232"; }
+.bi-caret-right-square::before { content: "\f233"; }
+.bi-caret-right::before { content: "\f234"; }
+.bi-caret-up-fill::before { content: "\f235"; }
+.bi-caret-up-square-fill::before { content: "\f236"; }
+.bi-caret-up-square::before { content: "\f237"; }
+.bi-caret-up::before { content: "\f238"; }
+.bi-cart-check-fill::before { content: "\f239"; }
+.bi-cart-check::before { content: "\f23a"; }
+.bi-cart-dash-fill::before { content: "\f23b"; }
+.bi-cart-dash::before { content: "\f23c"; }
+.bi-cart-fill::before { content: "\f23d"; }
+.bi-cart-plus-fill::before { content: "\f23e"; }
+.bi-cart-plus::before { content: "\f23f"; }
+.bi-cart-x-fill::before { content: "\f240"; }
+.bi-cart-x::before { content: "\f241"; }
+.bi-cart::before { content: "\f242"; }
+.bi-cart2::before { content: "\f243"; }
+.bi-cart3::before { content: "\f244"; }
+.bi-cart4::before { content: "\f245"; }
+.bi-cash-stack::before { content: "\f246"; }
+.bi-cash::before { content: "\f247"; }
+.bi-cast::before { content: "\f248"; }
+.bi-chat-dots-fill::before { content: "\f249"; }
+.bi-chat-dots::before { content: "\f24a"; }
+.bi-chat-fill::before { content: "\f24b"; }
+.bi-chat-left-dots-fill::before { content: "\f24c"; }
+.bi-chat-left-dots::before { content: "\f24d"; }
+.bi-chat-left-fill::before { content: "\f24e"; }
+.bi-chat-left-quote-fill::before { content: "\f24f"; }
+.bi-chat-left-quote::before { content: "\f250"; }
+.bi-chat-left-text-fill::before { content: "\f251"; }
+.bi-chat-left-text::before { content: "\f252"; }
+.bi-chat-left::before { content: "\f253"; }
+.bi-chat-quote-fill::before { content: "\f254"; }
+.bi-chat-quote::before { content: "\f255"; }
+.bi-chat-right-dots-fill::before { content: "\f256"; }
+.bi-chat-right-dots::before { content: "\f257"; }
+.bi-chat-right-fill::before { content: "\f258"; }
+.bi-chat-right-quote-fill::before { content: "\f259"; }
+.bi-chat-right-quote::before { content: "\f25a"; }
+.bi-chat-right-text-fill::before { content: "\f25b"; }
+.bi-chat-right-text::before { content: "\f25c"; }
+.bi-chat-right::before { content: "\f25d"; }
+.bi-chat-square-dots-fill::before { content: "\f25e"; }
+.bi-chat-square-dots::before { content: "\f25f"; }
+.bi-chat-square-fill::before { content: "\f260"; }
+.bi-chat-square-quote-fill::before { content: "\f261"; }
+.bi-chat-square-quote::before { content: "\f262"; }
+.bi-chat-square-text-fill::before { content: "\f263"; }
+.bi-chat-square-text::before { content: "\f264"; }
+.bi-chat-square::before { content: "\f265"; }
+.bi-chat-text-fill::before { content: "\f266"; }
+.bi-chat-text::before { content: "\f267"; }
+.bi-chat::before { content: "\f268"; }
+.bi-check-all::before { content: "\f269"; }
+.bi-check-circle-fill::before { content: "\f26a"; }
+.bi-check-circle::before { content: "\f26b"; }
+.bi-check-square-fill::before { content: "\f26c"; }
+.bi-check-square::before { content: "\f26d"; }
+.bi-check::before { content: "\f26e"; }
+.bi-check2-all::before { content: "\f26f"; }
+.bi-check2-circle::before { content: "\f270"; }
+.bi-check2-square::before { content: "\f271"; }
+.bi-check2::before { content: "\f272"; }
+.bi-chevron-bar-contract::before { content: "\f273"; }
+.bi-chevron-bar-down::before { content: "\f274"; }
+.bi-chevron-bar-expand::before { content: "\f275"; }
+.bi-chevron-bar-left::before { content: "\f276"; }
+.bi-chevron-bar-right::before { content: "\f277"; }
+.bi-chevron-bar-up::before { content: "\f278"; }
+.bi-chevron-compact-down::before { content: "\f279"; }
+.bi-chevron-compact-left::before { content: "\f27a"; }
+.bi-chevron-compact-right::before { content: "\f27b"; }
+.bi-chevron-compact-up::before { content: "\f27c"; }
+.bi-chevron-contract::before { content: "\f27d"; }
+.bi-chevron-double-down::before { content: "\f27e"; }
+.bi-chevron-double-left::before { content: "\f27f"; }
+.bi-chevron-double-right::before { content: "\f280"; }
+.bi-chevron-double-up::before { content: "\f281"; }
+.bi-chevron-down::before { content: "\f282"; }
+.bi-chevron-expand::before { content: "\f283"; }
+.bi-chevron-left::before { content: "\f284"; }
+.bi-chevron-right::before { content: "\f285"; }
+.bi-chevron-up::before { content: "\f286"; }
+.bi-circle-fill::before { content: "\f287"; }
+.bi-circle-half::before { content: "\f288"; }
+.bi-circle-square::before { content: "\f289"; }
+.bi-circle::before { content: "\f28a"; }
+.bi-clipboard-check::before { content: "\f28b"; }
+.bi-clipboard-data::before { content: "\f28c"; }
+.bi-clipboard-minus::before { content: "\f28d"; }
+.bi-clipboard-plus::before { content: "\f28e"; }
+.bi-clipboard-x::before { content: "\f28f"; }
+.bi-clipboard::before { content: "\f290"; }
+.bi-clock-fill::before { content: "\f291"; }
+.bi-clock-history::before { content: "\f292"; }
+.bi-clock::before { content: "\f293"; }
+.bi-cloud-arrow-down-fill::before { content: "\f294"; }
+.bi-cloud-arrow-down::before { content: "\f295"; }
+.bi-cloud-arrow-up-fill::before { content: "\f296"; }
+.bi-cloud-arrow-up::before { content: "\f297"; }
+.bi-cloud-check-fill::before { content: "\f298"; }
+.bi-cloud-check::before { content: "\f299"; }
+.bi-cloud-download-fill::before { content: "\f29a"; }
+.bi-cloud-download::before { content: "\f29b"; }
+.bi-cloud-drizzle-fill::before { content: "\f29c"; }
+.bi-cloud-drizzle::before { content: "\f29d"; }
+.bi-cloud-fill::before { content: "\f29e"; }
+.bi-cloud-fog-fill::before { content: "\f29f"; }
+.bi-cloud-fog::before { content: "\f2a0"; }
+.bi-cloud-fog2-fill::before { content: "\f2a1"; }
+.bi-cloud-fog2::before { content: "\f2a2"; }
+.bi-cloud-hail-fill::before { content: "\f2a3"; }
+.bi-cloud-hail::before { content: "\f2a4"; }
+.bi-cloud-haze-1::before { content: "\f2a5"; }
+.bi-cloud-haze-fill::before { content: "\f2a6"; }
+.bi-cloud-haze::before { content: "\f2a7"; }
+.bi-cloud-haze2-fill::before { content: "\f2a8"; }
+.bi-cloud-lightning-fill::before { content: "\f2a9"; }
+.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; }
+.bi-cloud-lightning-rain::before { content: "\f2ab"; }
+.bi-cloud-lightning::before { content: "\f2ac"; }
+.bi-cloud-minus-fill::before { content: "\f2ad"; }
+.bi-cloud-minus::before { content: "\f2ae"; }
+.bi-cloud-moon-fill::before { content: "\f2af"; }
+.bi-cloud-moon::before { content: "\f2b0"; }
+.bi-cloud-plus-fill::before { content: "\f2b1"; }
+.bi-cloud-plus::before { content: "\f2b2"; }
+.bi-cloud-rain-fill::before { content: "\f2b3"; }
+.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; }
+.bi-cloud-rain-heavy::before { content: "\f2b5"; }
+.bi-cloud-rain::before { content: "\f2b6"; }
+.bi-cloud-slash-fill::before { content: "\f2b7"; }
+.bi-cloud-slash::before { content: "\f2b8"; }
+.bi-cloud-sleet-fill::before { content: "\f2b9"; }
+.bi-cloud-sleet::before { content: "\f2ba"; }
+.bi-cloud-snow-fill::before { content: "\f2bb"; }
+.bi-cloud-snow::before { content: "\f2bc"; }
+.bi-cloud-sun-fill::before { content: "\f2bd"; }
+.bi-cloud-sun::before { content: "\f2be"; }
+.bi-cloud-upload-fill::before { content: "\f2bf"; }
+.bi-cloud-upload::before { content: "\f2c0"; }
+.bi-cloud::before { content: "\f2c1"; }
+.bi-clouds-fill::before { content: "\f2c2"; }
+.bi-clouds::before { content: "\f2c3"; }
+.bi-cloudy-fill::before { content: "\f2c4"; }
+.bi-cloudy::before { content: "\f2c5"; }
+.bi-code-slash::before { content: "\f2c6"; }
+.bi-code-square::before { content: "\f2c7"; }
+.bi-code::before { content: "\f2c8"; }
+.bi-collection-fill::before { content: "\f2c9"; }
+.bi-collection-play-fill::before { content: "\f2ca"; }
+.bi-collection-play::before { content: "\f2cb"; }
+.bi-collection::before { content: "\f2cc"; }
+.bi-columns-gap::before { content: "\f2cd"; }
+.bi-columns::before { content: "\f2ce"; }
+.bi-command::before { content: "\f2cf"; }
+.bi-compass-fill::before { content: "\f2d0"; }
+.bi-compass::before { content: "\f2d1"; }
+.bi-cone-striped::before { content: "\f2d2"; }
+.bi-cone::before { content: "\f2d3"; }
+.bi-controller::before { content: "\f2d4"; }
+.bi-cpu-fill::before { content: "\f2d5"; }
+.bi-cpu::before { content: "\f2d6"; }
+.bi-credit-card-2-back-fill::before { content: "\f2d7"; }
+.bi-credit-card-2-back::before { content: "\f2d8"; }
+.bi-credit-card-2-front-fill::before { content: "\f2d9"; }
+.bi-credit-card-2-front::before { content: "\f2da"; }
+.bi-credit-card-fill::before { content: "\f2db"; }
+.bi-credit-card::before { content: "\f2dc"; }
+.bi-crop::before { content: "\f2dd"; }
+.bi-cup-fill::before { content: "\f2de"; }
+.bi-cup-straw::before { content: "\f2df"; }
+.bi-cup::before { content: "\f2e0"; }
+.bi-cursor-fill::before { content: "\f2e1"; }
+.bi-cursor-text::before { content: "\f2e2"; }
+.bi-cursor::before { content: "\f2e3"; }
+.bi-dash-circle-dotted::before { content: "\f2e4"; }
+.bi-dash-circle-fill::before { content: "\f2e5"; }
+.bi-dash-circle::before { content: "\f2e6"; }
+.bi-dash-square-dotted::before { content: "\f2e7"; }
+.bi-dash-square-fill::before { content: "\f2e8"; }
+.bi-dash-square::before { content: "\f2e9"; }
+.bi-dash::before { content: "\f2ea"; }
+.bi-diagram-2-fill::before { content: "\f2eb"; }
+.bi-diagram-2::before { content: "\f2ec"; }
+.bi-diagram-3-fill::before { content: "\f2ed"; }
+.bi-diagram-3::before { content: "\f2ee"; }
+.bi-diamond-fill::before { content: "\f2ef"; }
+.bi-diamond-half::before { content: "\f2f0"; }
+.bi-diamond::before { content: "\f2f1"; }
+.bi-dice-1-fill::before { content: "\f2f2"; }
+.bi-dice-1::before { content: "\f2f3"; }
+.bi-dice-2-fill::before { content: "\f2f4"; }
+.bi-dice-2::before { content: "\f2f5"; }
+.bi-dice-3-fill::before { content: "\f2f6"; }
+.bi-dice-3::before { content: "\f2f7"; }
+.bi-dice-4-fill::before { content: "\f2f8"; }
+.bi-dice-4::before { content: "\f2f9"; }
+.bi-dice-5-fill::before { content: "\f2fa"; }
+.bi-dice-5::before { content: "\f2fb"; }
+.bi-dice-6-fill::before { content: "\f2fc"; }
+.bi-dice-6::before { content: "\f2fd"; }
+.bi-disc-fill::before { content: "\f2fe"; }
+.bi-disc::before { content: "\f2ff"; }
+.bi-discord::before { content: "\f300"; }
+.bi-display-fill::before { content: "\f301"; }
+.bi-display::before { content: "\f302"; }
+.bi-distribute-horizontal::before { content: "\f303"; }
+.bi-distribute-vertical::before { content: "\f304"; }
+.bi-door-closed-fill::before { content: "\f305"; }
+.bi-door-closed::before { content: "\f306"; }
+.bi-door-open-fill::before { content: "\f307"; }
+.bi-door-open::before { content: "\f308"; }
+.bi-dot::before { content: "\f309"; }
+.bi-download::before { content: "\f30a"; }
+.bi-droplet-fill::before { content: "\f30b"; }
+.bi-droplet-half::before { content: "\f30c"; }
+.bi-droplet::before { content: "\f30d"; }
+.bi-earbuds::before { content: "\f30e"; }
+.bi-easel-fill::before { content: "\f30f"; }
+.bi-easel::before { content: "\f310"; }
+.bi-egg-fill::before { content: "\f311"; }
+.bi-egg-fried::before { content: "\f312"; }
+.bi-egg::before { content: "\f313"; }
+.bi-eject-fill::before { content: "\f314"; }
+.bi-eject::before { content: "\f315"; }
+.bi-emoji-angry-fill::before { content: "\f316"; }
+.bi-emoji-angry::before { content: "\f317"; }
+.bi-emoji-dizzy-fill::before { content: "\f318"; }
+.bi-emoji-dizzy::before { content: "\f319"; }
+.bi-emoji-expressionless-fill::before { content: "\f31a"; }
+.bi-emoji-expressionless::before { content: "\f31b"; }
+.bi-emoji-frown-fill::before { content: "\f31c"; }
+.bi-emoji-frown::before { content: "\f31d"; }
+.bi-emoji-heart-eyes-fill::before { content: "\f31e"; }
+.bi-emoji-heart-eyes::before { content: "\f31f"; }
+.bi-emoji-laughing-fill::before { content: "\f320"; }
+.bi-emoji-laughing::before { content: "\f321"; }
+.bi-emoji-neutral-fill::before { content: "\f322"; }
+.bi-emoji-neutral::before { content: "\f323"; }
+.bi-emoji-smile-fill::before { content: "\f324"; }
+.bi-emoji-smile-upside-down-fill::before { content: "\f325"; }
+.bi-emoji-smile-upside-down::before { content: "\f326"; }
+.bi-emoji-smile::before { content: "\f327"; }
+.bi-emoji-sunglasses-fill::before { content: "\f328"; }
+.bi-emoji-sunglasses::before { content: "\f329"; }
+.bi-emoji-wink-fill::before { content: "\f32a"; }
+.bi-emoji-wink::before { content: "\f32b"; }
+.bi-envelope-fill::before { content: "\f32c"; }
+.bi-envelope-open-fill::before { content: "\f32d"; }
+.bi-envelope-open::before { content: "\f32e"; }
+.bi-envelope::before { content: "\f32f"; }
+.bi-eraser-fill::before { content: "\f330"; }
+.bi-eraser::before { content: "\f331"; }
+.bi-exclamation-circle-fill::before { content: "\f332"; }
+.bi-exclamation-circle::before { content: "\f333"; }
+.bi-exclamation-diamond-fill::before { content: "\f334"; }
+.bi-exclamation-diamond::before { content: "\f335"; }
+.bi-exclamation-octagon-fill::before { content: "\f336"; }
+.bi-exclamation-octagon::before { content: "\f337"; }
+.bi-exclamation-square-fill::before { content: "\f338"; }
+.bi-exclamation-square::before { content: "\f339"; }
+.bi-exclamation-triangle-fill::before { content: "\f33a"; }
+.bi-exclamation-triangle::before { content: "\f33b"; }
+.bi-exclamation::before { content: "\f33c"; }
+.bi-exclude::before { content: "\f33d"; }
+.bi-eye-fill::before { content: "\f33e"; }
+.bi-eye-slash-fill::before { content: "\f33f"; }
+.bi-eye-slash::before { content: "\f340"; }
+.bi-eye::before { content: "\f341"; }
+.bi-eyedropper::before { content: "\f342"; }
+.bi-eyeglasses::before { content: "\f343"; }
+.bi-facebook::before { content: "\f344"; }
+.bi-file-arrow-down-fill::before { content: "\f345"; }
+.bi-file-arrow-down::before { content: "\f346"; }
+.bi-file-arrow-up-fill::before { content: "\f347"; }
+.bi-file-arrow-up::before { content: "\f348"; }
+.bi-file-bar-graph-fill::before { content: "\f349"; }
+.bi-file-bar-graph::before { content: "\f34a"; }
+.bi-file-binary-fill::before { content: "\f34b"; }
+.bi-file-binary::before { content: "\f34c"; }
+.bi-file-break-fill::before { content: "\f34d"; }
+.bi-file-break::before { content: "\f34e"; }
+.bi-file-check-fill::before { content: "\f34f"; }
+.bi-file-check::before { content: "\f350"; }
+.bi-file-code-fill::before { content: "\f351"; }
+.bi-file-code::before { content: "\f352"; }
+.bi-file-diff-fill::before { content: "\f353"; }
+.bi-file-diff::before { content: "\f354"; }
+.bi-file-earmark-arrow-down-fill::before { content: "\f355"; }
+.bi-file-earmark-arrow-down::before { content: "\f356"; }
+.bi-file-earmark-arrow-up-fill::before { content: "\f357"; }
+.bi-file-earmark-arrow-up::before { content: "\f358"; }
+.bi-file-earmark-bar-graph-fill::before { content: "\f359"; }
+.bi-file-earmark-bar-graph::before { content: "\f35a"; }
+.bi-file-earmark-binary-fill::before { content: "\f35b"; }
+.bi-file-earmark-binary::before { content: "\f35c"; }
+.bi-file-earmark-break-fill::before { content: "\f35d"; }
+.bi-file-earmark-break::before { content: "\f35e"; }
+.bi-file-earmark-check-fill::before { content: "\f35f"; }
+.bi-file-earmark-check::before { content: "\f360"; }
+.bi-file-earmark-code-fill::before { content: "\f361"; }
+.bi-file-earmark-code::before { content: "\f362"; }
+.bi-file-earmark-diff-fill::before { content: "\f363"; }
+.bi-file-earmark-diff::before { content: "\f364"; }
+.bi-file-earmark-easel-fill::before { content: "\f365"; }
+.bi-file-earmark-easel::before { content: "\f366"; }
+.bi-file-earmark-excel-fill::before { content: "\f367"; }
+.bi-file-earmark-excel::before { content: "\f368"; }
+.bi-file-earmark-fill::before { content: "\f369"; }
+.bi-file-earmark-font-fill::before { content: "\f36a"; }
+.bi-file-earmark-font::before { content: "\f36b"; }
+.bi-file-earmark-image-fill::before { content: "\f36c"; }
+.bi-file-earmark-image::before { content: "\f36d"; }
+.bi-file-earmark-lock-fill::before { content: "\f36e"; }
+.bi-file-earmark-lock::before { content: "\f36f"; }
+.bi-file-earmark-lock2-fill::before { content: "\f370"; }
+.bi-file-earmark-lock2::before { content: "\f371"; }
+.bi-file-earmark-medical-fill::before { content: "\f372"; }
+.bi-file-earmark-medical::before { content: "\f373"; }
+.bi-file-earmark-minus-fill::before { content: "\f374"; }
+.bi-file-earmark-minus::before { content: "\f375"; }
+.bi-file-earmark-music-fill::before { content: "\f376"; }
+.bi-file-earmark-music::before { content: "\f377"; }
+.bi-file-earmark-person-fill::before { content: "\f378"; }
+.bi-file-earmark-person::before { content: "\f379"; }
+.bi-file-earmark-play-fill::before { content: "\f37a"; }
+.bi-file-earmark-play::before { content: "\f37b"; }
+.bi-file-earmark-plus-fill::before { content: "\f37c"; }
+.bi-file-earmark-plus::before { content: "\f37d"; }
+.bi-file-earmark-post-fill::before { content: "\f37e"; }
+.bi-file-earmark-post::before { content: "\f37f"; }
+.bi-file-earmark-ppt-fill::before { content: "\f380"; }
+.bi-file-earmark-ppt::before { content: "\f381"; }
+.bi-file-earmark-richtext-fill::before { content: "\f382"; }
+.bi-file-earmark-richtext::before { content: "\f383"; }
+.bi-file-earmark-ruled-fill::before { content: "\f384"; }
+.bi-file-earmark-ruled::before { content: "\f385"; }
+.bi-file-earmark-slides-fill::before { content: "\f386"; }
+.bi-file-earmark-slides::before { content: "\f387"; }
+.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; }
+.bi-file-earmark-spreadsheet::before { content: "\f389"; }
+.bi-file-earmark-text-fill::before { content: "\f38a"; }
+.bi-file-earmark-text::before { content: "\f38b"; }
+.bi-file-earmark-word-fill::before { content: "\f38c"; }
+.bi-file-earmark-word::before { content: "\f38d"; }
+.bi-file-earmark-x-fill::before { content: "\f38e"; }
+.bi-file-earmark-x::before { content: "\f38f"; }
+.bi-file-earmark-zip-fill::before { content: "\f390"; }
+.bi-file-earmark-zip::before { content: "\f391"; }
+.bi-file-earmark::before { content: "\f392"; }
+.bi-file-easel-fill::before { content: "\f393"; }
+.bi-file-easel::before { content: "\f394"; }
+.bi-file-excel-fill::before { content: "\f395"; }
+.bi-file-excel::before { content: "\f396"; }
+.bi-file-fill::before { content: "\f397"; }
+.bi-file-font-fill::before { content: "\f398"; }
+.bi-file-font::before { content: "\f399"; }
+.bi-file-image-fill::before { content: "\f39a"; }
+.bi-file-image::before { content: "\f39b"; }
+.bi-file-lock-fill::before { content: "\f39c"; }
+.bi-file-lock::before { content: "\f39d"; }
+.bi-file-lock2-fill::before { content: "\f39e"; }
+.bi-file-lock2::before { content: "\f39f"; }
+.bi-file-medical-fill::before { content: "\f3a0"; }
+.bi-file-medical::before { content: "\f3a1"; }
+.bi-file-minus-fill::before { content: "\f3a2"; }
+.bi-file-minus::before { content: "\f3a3"; }
+.bi-file-music-fill::before { content: "\f3a4"; }
+.bi-file-music::before { content: "\f3a5"; }
+.bi-file-person-fill::before { content: "\f3a6"; }
+.bi-file-person::before { content: "\f3a7"; }
+.bi-file-play-fill::before { content: "\f3a8"; }
+.bi-file-play::before { content: "\f3a9"; }
+.bi-file-plus-fill::before { content: "\f3aa"; }
+.bi-file-plus::before { content: "\f3ab"; }
+.bi-file-post-fill::before { content: "\f3ac"; }
+.bi-file-post::before { content: "\f3ad"; }
+.bi-file-ppt-fill::before { content: "\f3ae"; }
+.bi-file-ppt::before { content: "\f3af"; }
+.bi-file-richtext-fill::before { content: "\f3b0"; }
+.bi-file-richtext::before { content: "\f3b1"; }
+.bi-file-ruled-fill::before { content: "\f3b2"; }
+.bi-file-ruled::before { content: "\f3b3"; }
+.bi-file-slides-fill::before { content: "\f3b4"; }
+.bi-file-slides::before { content: "\f3b5"; }
+.bi-file-spreadsheet-fill::before { content: "\f3b6"; }
+.bi-file-spreadsheet::before { content: "\f3b7"; }
+.bi-file-text-fill::before { content: "\f3b8"; }
+.bi-file-text::before { content: "\f3b9"; }
+.bi-file-word-fill::before { content: "\f3ba"; }
+.bi-file-word::before { content: "\f3bb"; }
+.bi-file-x-fill::before { content: "\f3bc"; }
+.bi-file-x::before { content: "\f3bd"; }
+.bi-file-zip-fill::before { content: "\f3be"; }
+.bi-file-zip::before { content: "\f3bf"; }
+.bi-file::before { content: "\f3c0"; }
+.bi-files-alt::before { content: "\f3c1"; }
+.bi-files::before { content: "\f3c2"; }
+.bi-film::before { content: "\f3c3"; }
+.bi-filter-circle-fill::before { content: "\f3c4"; }
+.bi-filter-circle::before { content: "\f3c5"; }
+.bi-filter-left::before { content: "\f3c6"; }
+.bi-filter-right::before { content: "\f3c7"; }
+.bi-filter-square-fill::before { content: "\f3c8"; }
+.bi-filter-square::before { content: "\f3c9"; }
+.bi-filter::before { content: "\f3ca"; }
+.bi-flag-fill::before { content: "\f3cb"; }
+.bi-flag::before { content: "\f3cc"; }
+.bi-flower1::before { content: "\f3cd"; }
+.bi-flower2::before { content: "\f3ce"; }
+.bi-flower3::before { content: "\f3cf"; }
+.bi-folder-check::before { content: "\f3d0"; }
+.bi-folder-fill::before { content: "\f3d1"; }
+.bi-folder-minus::before { content: "\f3d2"; }
+.bi-folder-plus::before { content: "\f3d3"; }
+.bi-folder-symlink-fill::before { content: "\f3d4"; }
+.bi-folder-symlink::before { content: "\f3d5"; }
+.bi-folder-x::before { content: "\f3d6"; }
+.bi-folder::before { content: "\f3d7"; }
+.bi-folder2-open::before { content: "\f3d8"; }
+.bi-folder2::before { content: "\f3d9"; }
+.bi-fonts::before { content: "\f3da"; }
+.bi-forward-fill::before { content: "\f3db"; }
+.bi-forward::before { content: "\f3dc"; }
+.bi-front::before { content: "\f3dd"; }
+.bi-fullscreen-exit::before { content: "\f3de"; }
+.bi-fullscreen::before { content: "\f3df"; }
+.bi-funnel-fill::before { content: "\f3e0"; }
+.bi-funnel::before { content: "\f3e1"; }
+.bi-gear-fill::before { content: "\f3e2"; }
+.bi-gear-wide-connected::before { content: "\f3e3"; }
+.bi-gear-wide::before { content: "\f3e4"; }
+.bi-gear::before { content: "\f3e5"; }
+.bi-gem::before { content: "\f3e6"; }
+.bi-geo-alt-fill::before { content: "\f3e7"; }
+.bi-geo-alt::before { content: "\f3e8"; }
+.bi-geo-fill::before { content: "\f3e9"; }
+.bi-geo::before { content: "\f3ea"; }
+.bi-gift-fill::before { content: "\f3eb"; }
+.bi-gift::before { content: "\f3ec"; }
+.bi-github::before { content: "\f3ed"; }
+.bi-globe::before { content: "\f3ee"; }
+.bi-globe2::before { content: "\f3ef"; }
+.bi-google::before { content: "\f3f0"; }
+.bi-graph-down::before { content: "\f3f1"; }
+.bi-graph-up::before { content: "\f3f2"; }
+.bi-grid-1x2-fill::before { content: "\f3f3"; }
+.bi-grid-1x2::before { content: "\f3f4"; }
+.bi-grid-3x2-gap-fill::before { content: "\f3f5"; }
+.bi-grid-3x2-gap::before { content: "\f3f6"; }
+.bi-grid-3x2::before { content: "\f3f7"; }
+.bi-grid-3x3-gap-fill::before { content: "\f3f8"; }
+.bi-grid-3x3-gap::before { content: "\f3f9"; }
+.bi-grid-3x3::before { content: "\f3fa"; }
+.bi-grid-fill::before { content: "\f3fb"; }
+.bi-grid::before { content: "\f3fc"; }
+.bi-grip-horizontal::before { content: "\f3fd"; }
+.bi-grip-vertical::before { content: "\f3fe"; }
+.bi-hammer::before { content: "\f3ff"; }
+.bi-hand-index-fill::before { content: "\f400"; }
+.bi-hand-index-thumb-fill::before { content: "\f401"; }
+.bi-hand-index-thumb::before { content: "\f402"; }
+.bi-hand-index::before { content: "\f403"; }
+.bi-hand-thumbs-down-fill::before { content: "\f404"; }
+.bi-hand-thumbs-down::before { content: "\f405"; }
+.bi-hand-thumbs-up-fill::before { content: "\f406"; }
+.bi-hand-thumbs-up::before { content: "\f407"; }
+.bi-handbag-fill::before { content: "\f408"; }
+.bi-handbag::before { content: "\f409"; }
+.bi-hash::before { content: "\f40a"; }
+.bi-hdd-fill::before { content: "\f40b"; }
+.bi-hdd-network-fill::before { content: "\f40c"; }
+.bi-hdd-network::before { content: "\f40d"; }
+.bi-hdd-rack-fill::before { content: "\f40e"; }
+.bi-hdd-rack::before { content: "\f40f"; }
+.bi-hdd-stack-fill::before { content: "\f410"; }
+.bi-hdd-stack::before { content: "\f411"; }
+.bi-hdd::before { content: "\f412"; }
+.bi-headphones::before { content: "\f413"; }
+.bi-headset::before { content: "\f414"; }
+.bi-heart-fill::before { content: "\f415"; }
+.bi-heart-half::before { content: "\f416"; }
+.bi-heart::before { content: "\f417"; }
+.bi-heptagon-fill::before { content: "\f418"; }
+.bi-heptagon-half::before { content: "\f419"; }
+.bi-heptagon::before { content: "\f41a"; }
+.bi-hexagon-fill::before { content: "\f41b"; }
+.bi-hexagon-half::before { content: "\f41c"; }
+.bi-hexagon::before { content: "\f41d"; }
+.bi-hourglass-bottom::before { content: "\f41e"; }
+.bi-hourglass-split::before { content: "\f41f"; }
+.bi-hourglass-top::before { content: "\f420"; }
+.bi-hourglass::before { content: "\f421"; }
+.bi-house-door-fill::before { content: "\f422"; }
+.bi-house-door::before { content: "\f423"; }
+.bi-house-fill::before { content: "\f424"; }
+.bi-house::before { content: "\f425"; }
+.bi-hr::before { content: "\f426"; }
+.bi-hurricane::before { content: "\f427"; }
+.bi-image-alt::before { content: "\f428"; }
+.bi-image-fill::before { content: "\f429"; }
+.bi-image::before { content: "\f42a"; }
+.bi-images::before { content: "\f42b"; }
+.bi-inbox-fill::before { content: "\f42c"; }
+.bi-inbox::before { content: "\f42d"; }
+.bi-inboxes-fill::before { content: "\f42e"; }
+.bi-inboxes::before { content: "\f42f"; }
+.bi-info-circle-fill::before { content: "\f430"; }
+.bi-info-circle::before { content: "\f431"; }
+.bi-info-square-fill::before { content: "\f432"; }
+.bi-info-square::before { content: "\f433"; }
+.bi-info::before { content: "\f434"; }
+.bi-input-cursor-text::before { content: "\f435"; }
+.bi-input-cursor::before { content: "\f436"; }
+.bi-instagram::before { content: "\f437"; }
+.bi-intersect::before { content: "\f438"; }
+.bi-journal-album::before { content: "\f439"; }
+.bi-journal-arrow-down::before { content: "\f43a"; }
+.bi-journal-arrow-up::before { content: "\f43b"; }
+.bi-journal-bookmark-fill::before { content: "\f43c"; }
+.bi-journal-bookmark::before { content: "\f43d"; }
+.bi-journal-check::before { content: "\f43e"; }
+.bi-journal-code::before { content: "\f43f"; }
+.bi-journal-medical::before { content: "\f440"; }
+.bi-journal-minus::before { content: "\f441"; }
+.bi-journal-plus::before { content: "\f442"; }
+.bi-journal-richtext::before { content: "\f443"; }
+.bi-journal-text::before { content: "\f444"; }
+.bi-journal-x::before { content: "\f445"; }
+.bi-journal::before { content: "\f446"; }
+.bi-journals::before { content: "\f447"; }
+.bi-joystick::before { content: "\f448"; }
+.bi-justify-left::before { content: "\f449"; }
+.bi-justify-right::before { content: "\f44a"; }
+.bi-justify::before { content: "\f44b"; }
+.bi-kanban-fill::before { content: "\f44c"; }
+.bi-kanban::before { content: "\f44d"; }
+.bi-key-fill::before { content: "\f44e"; }
+.bi-key::before { content: "\f44f"; }
+.bi-keyboard-fill::before { content: "\f450"; }
+.bi-keyboard::before { content: "\f451"; }
+.bi-ladder::before { content: "\f452"; }
+.bi-lamp-fill::before { content: "\f453"; }
+.bi-lamp::before { content: "\f454"; }
+.bi-laptop-fill::before { content: "\f455"; }
+.bi-laptop::before { content: "\f456"; }
+.bi-layer-backward::before { content: "\f457"; }
+.bi-layer-forward::before { content: "\f458"; }
+.bi-layers-fill::before { content: "\f459"; }
+.bi-layers-half::before { content: "\f45a"; }
+.bi-layers::before { content: "\f45b"; }
+.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; }
+.bi-layout-sidebar-inset::before { content: "\f45d"; }
+.bi-layout-sidebar-reverse::before { content: "\f45e"; }
+.bi-layout-sidebar::before { content: "\f45f"; }
+.bi-layout-split::before { content: "\f460"; }
+.bi-layout-text-sidebar-reverse::before { content: "\f461"; }
+.bi-layout-text-sidebar::before { content: "\f462"; }
+.bi-layout-text-window-reverse::before { content: "\f463"; }
+.bi-layout-text-window::before { content: "\f464"; }
+.bi-layout-three-columns::before { content: "\f465"; }
+.bi-layout-wtf::before { content: "\f466"; }
+.bi-life-preserver::before { content: "\f467"; }
+.bi-lightbulb-fill::before { content: "\f468"; }
+.bi-lightbulb-off-fill::before { content: "\f469"; }
+.bi-lightbulb-off::before { content: "\f46a"; }
+.bi-lightbulb::before { content: "\f46b"; }
+.bi-lightning-charge-fill::before { content: "\f46c"; }
+.bi-lightning-charge::before { content: "\f46d"; }
+.bi-lightning-fill::before { content: "\f46e"; }
+.bi-lightning::before { content: "\f46f"; }
+.bi-link-45deg::before { content: "\f470"; }
+.bi-link::before { content: "\f471"; }
+.bi-linkedin::before { content: "\f472"; }
+.bi-list-check::before { content: "\f473"; }
+.bi-list-nested::before { content: "\f474"; }
+.bi-list-ol::before { content: "\f475"; }
+.bi-list-stars::before { content: "\f476"; }
+.bi-list-task::before { content: "\f477"; }
+.bi-list-ul::before { content: "\f478"; }
+.bi-list::before { content: "\f479"; }
+.bi-lock-fill::before { content: "\f47a"; }
+.bi-lock::before { content: "\f47b"; }
+.bi-mailbox::before { content: "\f47c"; }
+.bi-mailbox2::before { content: "\f47d"; }
+.bi-map-fill::before { content: "\f47e"; }
+.bi-map::before { content: "\f47f"; }
+.bi-markdown-fill::before { content: "\f480"; }
+.bi-markdown::before { content: "\f481"; }
+.bi-mask::before { content: "\f482"; }
+.bi-megaphone-fill::before { content: "\f483"; }
+.bi-megaphone::before { content: "\f484"; }
+.bi-menu-app-fill::before { content: "\f485"; }
+.bi-menu-app::before { content: "\f486"; }
+.bi-menu-button-fill::before { content: "\f487"; }
+.bi-menu-button-wide-fill::before { content: "\f488"; }
+.bi-menu-button-wide::before { content: "\f489"; }
+.bi-menu-button::before { content: "\f48a"; }
+.bi-menu-down::before { content: "\f48b"; }
+.bi-menu-up::before { content: "\f48c"; }
+.bi-mic-fill::before { content: "\f48d"; }
+.bi-mic-mute-fill::before { content: "\f48e"; }
+.bi-mic-mute::before { content: "\f48f"; }
+.bi-mic::before { content: "\f490"; }
+.bi-minecart-loaded::before { content: "\f491"; }
+.bi-minecart::before { content: "\f492"; }
+.bi-moisture::before { content: "\f493"; }
+.bi-moon-fill::before { content: "\f494"; }
+.bi-moon-stars-fill::before { content: "\f495"; }
+.bi-moon-stars::before { content: "\f496"; }
+.bi-moon::before { content: "\f497"; }
+.bi-mouse-fill::before { content: "\f498"; }
+.bi-mouse::before { content: "\f499"; }
+.bi-mouse2-fill::before { content: "\f49a"; }
+.bi-mouse2::before { content: "\f49b"; }
+.bi-mouse3-fill::before { content: "\f49c"; }
+.bi-mouse3::before { content: "\f49d"; }
+.bi-music-note-beamed::before { content: "\f49e"; }
+.bi-music-note-list::before { content: "\f49f"; }
+.bi-music-note::before { content: "\f4a0"; }
+.bi-music-player-fill::before { content: "\f4a1"; }
+.bi-music-player::before { content: "\f4a2"; }
+.bi-newspaper::before { content: "\f4a3"; }
+.bi-node-minus-fill::before { content: "\f4a4"; }
+.bi-node-minus::before { content: "\f4a5"; }
+.bi-node-plus-fill::before { content: "\f4a6"; }
+.bi-node-plus::before { content: "\f4a7"; }
+.bi-nut-fill::before { content: "\f4a8"; }
+.bi-nut::before { content: "\f4a9"; }
+.bi-octagon-fill::before { content: "\f4aa"; }
+.bi-octagon-half::before { content: "\f4ab"; }
+.bi-octagon::before { content: "\f4ac"; }
+.bi-option::before { content: "\f4ad"; }
+.bi-outlet::before { content: "\f4ae"; }
+.bi-paint-bucket::before { content: "\f4af"; }
+.bi-palette-fill::before { content: "\f4b0"; }
+.bi-palette::before { content: "\f4b1"; }
+.bi-palette2::before { content: "\f4b2"; }
+.bi-paperclip::before { content: "\f4b3"; }
+.bi-paragraph::before { content: "\f4b4"; }
+.bi-patch-check-fill::before { content: "\f4b5"; }
+.bi-patch-check::before { content: "\f4b6"; }
+.bi-patch-exclamation-fill::before { content: "\f4b7"; }
+.bi-patch-exclamation::before { content: "\f4b8"; }
+.bi-patch-minus-fill::before { content: "\f4b9"; }
+.bi-patch-minus::before { content: "\f4ba"; }
+.bi-patch-plus-fill::before { content: "\f4bb"; }
+.bi-patch-plus::before { content: "\f4bc"; }
+.bi-patch-question-fill::before { content: "\f4bd"; }
+.bi-patch-question::before { content: "\f4be"; }
+.bi-pause-btn-fill::before { content: "\f4bf"; }
+.bi-pause-btn::before { content: "\f4c0"; }
+.bi-pause-circle-fill::before { content: "\f4c1"; }
+.bi-pause-circle::before { content: "\f4c2"; }
+.bi-pause-fill::before { content: "\f4c3"; }
+.bi-pause::before { content: "\f4c4"; }
+.bi-peace-fill::before { content: "\f4c5"; }
+.bi-peace::before { content: "\f4c6"; }
+.bi-pen-fill::before { content: "\f4c7"; }
+.bi-pen::before { content: "\f4c8"; }
+.bi-pencil-fill::before { content: "\f4c9"; }
+.bi-pencil-square::before { content: "\f4ca"; }
+.bi-pencil::before { content: "\f4cb"; }
+.bi-pentagon-fill::before { content: "\f4cc"; }
+.bi-pentagon-half::before { content: "\f4cd"; }
+.bi-pentagon::before { content: "\f4ce"; }
+.bi-people-fill::before { content: "\f4cf"; }
+.bi-people::before { content: "\f4d0"; }
+.bi-percent::before { content: "\f4d1"; }
+.bi-person-badge-fill::before { content: "\f4d2"; }
+.bi-person-badge::before { content: "\f4d3"; }
+.bi-person-bounding-box::before { content: "\f4d4"; }
+.bi-person-check-fill::before { content: "\f4d5"; }
+.bi-person-check::before { content: "\f4d6"; }
+.bi-person-circle::before { content: "\f4d7"; }
+.bi-person-dash-fill::before { content: "\f4d8"; }
+.bi-person-dash::before { content: "\f4d9"; }
+.bi-person-fill::before { content: "\f4da"; }
+.bi-person-lines-fill::before { content: "\f4db"; }
+.bi-person-plus-fill::before { content: "\f4dc"; }
+.bi-person-plus::before { content: "\f4dd"; }
+.bi-person-square::before { content: "\f4de"; }
+.bi-person-x-fill::before { content: "\f4df"; }
+.bi-person-x::before { content: "\f4e0"; }
+.bi-person::before { content: "\f4e1"; }
+.bi-phone-fill::before { content: "\f4e2"; }
+.bi-phone-landscape-fill::before { content: "\f4e3"; }
+.bi-phone-landscape::before { content: "\f4e4"; }
+.bi-phone-vibrate-fill::before { content: "\f4e5"; }
+.bi-phone-vibrate::before { content: "\f4e6"; }
+.bi-phone::before { content: "\f4e7"; }
+.bi-pie-chart-fill::before { content: "\f4e8"; }
+.bi-pie-chart::before { content: "\f4e9"; }
+.bi-pin-angle-fill::before { content: "\f4ea"; }
+.bi-pin-angle::before { content: "\f4eb"; }
+.bi-pin-fill::before { content: "\f4ec"; }
+.bi-pin::before { content: "\f4ed"; }
+.bi-pip-fill::before { content: "\f4ee"; }
+.bi-pip::before { content: "\f4ef"; }
+.bi-play-btn-fill::before { content: "\f4f0"; }
+.bi-play-btn::before { content: "\f4f1"; }
+.bi-play-circle-fill::before { content: "\f4f2"; }
+.bi-play-circle::before { content: "\f4f3"; }
+.bi-play-fill::before { content: "\f4f4"; }
+.bi-play::before { content: "\f4f5"; }
+.bi-plug-fill::before { content: "\f4f6"; }
+.bi-plug::before { content: "\f4f7"; }
+.bi-plus-circle-dotted::before { content: "\f4f8"; }
+.bi-plus-circle-fill::before { content: "\f4f9"; }
+.bi-plus-circle::before { content: "\f4fa"; }
+.bi-plus-square-dotted::before { content: "\f4fb"; }
+.bi-plus-square-fill::before { content: "\f4fc"; }
+.bi-plus-square::before { content: "\f4fd"; }
+.bi-plus::before { content: "\f4fe"; }
+.bi-power::before { content: "\f4ff"; }
+.bi-printer-fill::before { content: "\f500"; }
+.bi-printer::before { content: "\f501"; }
+.bi-puzzle-fill::before { content: "\f502"; }
+.bi-puzzle::before { content: "\f503"; }
+.bi-question-circle-fill::before { content: "\f504"; }
+.bi-question-circle::before { content: "\f505"; }
+.bi-question-diamond-fill::before { content: "\f506"; }
+.bi-question-diamond::before { content: "\f507"; }
+.bi-question-octagon-fill::before { content: "\f508"; }
+.bi-question-octagon::before { content: "\f509"; }
+.bi-question-square-fill::before { content: "\f50a"; }
+.bi-question-square::before { content: "\f50b"; }
+.bi-question::before { content: "\f50c"; }
+.bi-rainbow::before { content: "\f50d"; }
+.bi-receipt-cutoff::before { content: "\f50e"; }
+.bi-receipt::before { content: "\f50f"; }
+.bi-reception-0::before { content: "\f510"; }
+.bi-reception-1::before { content: "\f511"; }
+.bi-reception-2::before { content: "\f512"; }
+.bi-reception-3::before { content: "\f513"; }
+.bi-reception-4::before { content: "\f514"; }
+.bi-record-btn-fill::before { content: "\f515"; }
+.bi-record-btn::before { content: "\f516"; }
+.bi-record-circle-fill::before { content: "\f517"; }
+.bi-record-circle::before { content: "\f518"; }
+.bi-record-fill::before { content: "\f519"; }
+.bi-record::before { content: "\f51a"; }
+.bi-record2-fill::before { content: "\f51b"; }
+.bi-record2::before { content: "\f51c"; }
+.bi-reply-all-fill::before { content: "\f51d"; }
+.bi-reply-all::before { content: "\f51e"; }
+.bi-reply-fill::before { content: "\f51f"; }
+.bi-reply::before { content: "\f520"; }
+.bi-rss-fill::before { content: "\f521"; }
+.bi-rss::before { content: "\f522"; }
+.bi-rulers::before { content: "\f523"; }
+.bi-save-fill::before { content: "\f524"; }
+.bi-save::before { content: "\f525"; }
+.bi-save2-fill::before { content: "\f526"; }
+.bi-save2::before { content: "\f527"; }
+.bi-scissors::before { content: "\f528"; }
+.bi-screwdriver::before { content: "\f529"; }
+.bi-search::before { content: "\f52a"; }
+.bi-segmented-nav::before { content: "\f52b"; }
+.bi-server::before { content: "\f52c"; }
+.bi-share-fill::before { content: "\f52d"; }
+.bi-share::before { content: "\f52e"; }
+.bi-shield-check::before { content: "\f52f"; }
+.bi-shield-exclamation::before { content: "\f530"; }
+.bi-shield-fill-check::before { content: "\f531"; }
+.bi-shield-fill-exclamation::before { content: "\f532"; }
+.bi-shield-fill-minus::before { content: "\f533"; }
+.bi-shield-fill-plus::before { content: "\f534"; }
+.bi-shield-fill-x::before { content: "\f535"; }
+.bi-shield-fill::before { content: "\f536"; }
+.bi-shield-lock-fill::before { content: "\f537"; }
+.bi-shield-lock::before { content: "\f538"; }
+.bi-shield-minus::before { content: "\f539"; }
+.bi-shield-plus::before { content: "\f53a"; }
+.bi-shield-shaded::before { content: "\f53b"; }
+.bi-shield-slash-fill::before { content: "\f53c"; }
+.bi-shield-slash::before { content: "\f53d"; }
+.bi-shield-x::before { content: "\f53e"; }
+.bi-shield::before { content: "\f53f"; }
+.bi-shift-fill::before { content: "\f540"; }
+.bi-shift::before { content: "\f541"; }
+.bi-shop-window::before { content: "\f542"; }
+.bi-shop::before { content: "\f543"; }
+.bi-shuffle::before { content: "\f544"; }
+.bi-signpost-2-fill::before { content: "\f545"; }
+.bi-signpost-2::before { content: "\f546"; }
+.bi-signpost-fill::before { content: "\f547"; }
+.bi-signpost-split-fill::before { content: "\f548"; }
+.bi-signpost-split::before { content: "\f549"; }
+.bi-signpost::before { content: "\f54a"; }
+.bi-sim-fill::before { content: "\f54b"; }
+.bi-sim::before { content: "\f54c"; }
+.bi-skip-backward-btn-fill::before { content: "\f54d"; }
+.bi-skip-backward-btn::before { content: "\f54e"; }
+.bi-skip-backward-circle-fill::before { content: "\f54f"; }
+.bi-skip-backward-circle::before { content: "\f550"; }
+.bi-skip-backward-fill::before { content: "\f551"; }
+.bi-skip-backward::before { content: "\f552"; }
+.bi-skip-end-btn-fill::before { content: "\f553"; }
+.bi-skip-end-btn::before { content: "\f554"; }
+.bi-skip-end-circle-fill::before { content: "\f555"; }
+.bi-skip-end-circle::before { content: "\f556"; }
+.bi-skip-end-fill::before { content: "\f557"; }
+.bi-skip-end::before { content: "\f558"; }
+.bi-skip-forward-btn-fill::before { content: "\f559"; }
+.bi-skip-forward-btn::before { content: "\f55a"; }
+.bi-skip-forward-circle-fill::before { content: "\f55b"; }
+.bi-skip-forward-circle::before { content: "\f55c"; }
+.bi-skip-forward-fill::before { content: "\f55d"; }
+.bi-skip-forward::before { content: "\f55e"; }
+.bi-skip-start-btn-fill::before { content: "\f55f"; }
+.bi-skip-start-btn::before { content: "\f560"; }
+.bi-skip-start-circle-fill::before { content: "\f561"; }
+.bi-skip-start-circle::before { content: "\f562"; }
+.bi-skip-start-fill::before { content: "\f563"; }
+.bi-skip-start::before { content: "\f564"; }
+.bi-slack::before { content: "\f565"; }
+.bi-slash-circle-fill::before { content: "\f566"; }
+.bi-slash-circle::before { content: "\f567"; }
+.bi-slash-square-fill::before { content: "\f568"; }
+.bi-slash-square::before { content: "\f569"; }
+.bi-slash::before { content: "\f56a"; }
+.bi-sliders::before { content: "\f56b"; }
+.bi-smartwatch::before { content: "\f56c"; }
+.bi-snow::before { content: "\f56d"; }
+.bi-snow2::before { content: "\f56e"; }
+.bi-snow3::before { content: "\f56f"; }
+.bi-sort-alpha-down-alt::before { content: "\f570"; }
+.bi-sort-alpha-down::before { content: "\f571"; }
+.bi-sort-alpha-up-alt::before { content: "\f572"; }
+.bi-sort-alpha-up::before { content: "\f573"; }
+.bi-sort-down-alt::before { content: "\f574"; }
+.bi-sort-down::before { content: "\f575"; }
+.bi-sort-numeric-down-alt::before { content: "\f576"; }
+.bi-sort-numeric-down::before { content: "\f577"; }
+.bi-sort-numeric-up-alt::before { content: "\f578"; }
+.bi-sort-numeric-up::before { content: "\f579"; }
+.bi-sort-up-alt::before { content: "\f57a"; }
+.bi-sort-up::before { content: "\f57b"; }
+.bi-soundwave::before { content: "\f57c"; }
+.bi-speaker-fill::before { content: "\f57d"; }
+.bi-speaker::before { content: "\f57e"; }
+.bi-speedometer::before { content: "\f57f"; }
+.bi-speedometer2::before { content: "\f580"; }
+.bi-spellcheck::before { content: "\f581"; }
+.bi-square-fill::before { content: "\f582"; }
+.bi-square-half::before { content: "\f583"; }
+.bi-square::before { content: "\f584"; }
+.bi-stack::before { content: "\f585"; }
+.bi-star-fill::before { content: "\f586"; }
+.bi-star-half::before { content: "\f587"; }
+.bi-star::before { content: "\f588"; }
+.bi-stars::before { content: "\f589"; }
+.bi-stickies-fill::before { content: "\f58a"; }
+.bi-stickies::before { content: "\f58b"; }
+.bi-sticky-fill::before { content: "\f58c"; }
+.bi-sticky::before { content: "\f58d"; }
+.bi-stop-btn-fill::before { content: "\f58e"; }
+.bi-stop-btn::before { content: "\f58f"; }
+.bi-stop-circle-fill::before { content: "\f590"; }
+.bi-stop-circle::before { content: "\f591"; }
+.bi-stop-fill::before { content: "\f592"; }
+.bi-stop::before { content: "\f593"; }
+.bi-stoplights-fill::before { content: "\f594"; }
+.bi-stoplights::before { content: "\f595"; }
+.bi-stopwatch-fill::before { content: "\f596"; }
+.bi-stopwatch::before { content: "\f597"; }
+.bi-subtract::before { content: "\f598"; }
+.bi-suit-club-fill::before { content: "\f599"; }
+.bi-suit-club::before { content: "\f59a"; }
+.bi-suit-diamond-fill::before { content: "\f59b"; }
+.bi-suit-diamond::before { content: "\f59c"; }
+.bi-suit-heart-fill::before { content: "\f59d"; }
+.bi-suit-heart::before { content: "\f59e"; }
+.bi-suit-spade-fill::before { content: "\f59f"; }
+.bi-suit-spade::before { content: "\f5a0"; }
+.bi-sun-fill::before { content: "\f5a1"; }
+.bi-sun::before { content: "\f5a2"; }
+.bi-sunglasses::before { content: "\f5a3"; }
+.bi-sunrise-fill::before { content: "\f5a4"; }
+.bi-sunrise::before { content: "\f5a5"; }
+.bi-sunset-fill::before { content: "\f5a6"; }
+.bi-sunset::before { content: "\f5a7"; }
+.bi-symmetry-horizontal::before { content: "\f5a8"; }
+.bi-symmetry-vertical::before { content: "\f5a9"; }
+.bi-table::before { content: "\f5aa"; }
+.bi-tablet-fill::before { content: "\f5ab"; }
+.bi-tablet-landscape-fill::before { content: "\f5ac"; }
+.bi-tablet-landscape::before { content: "\f5ad"; }
+.bi-tablet::before { content: "\f5ae"; }
+.bi-tag-fill::before { content: "\f5af"; }
+.bi-tag::before { content: "\f5b0"; }
+.bi-tags-fill::before { content: "\f5b1"; }
+.bi-tags::before { content: "\f5b2"; }
+.bi-telegram::before { content: "\f5b3"; }
+.bi-telephone-fill::before { content: "\f5b4"; }
+.bi-telephone-forward-fill::before { content: "\f5b5"; }
+.bi-telephone-forward::before { content: "\f5b6"; }
+.bi-telephone-inbound-fill::before { content: "\f5b7"; }
+.bi-telephone-inbound::before { content: "\f5b8"; }
+.bi-telephone-minus-fill::before { content: "\f5b9"; }
+.bi-telephone-minus::before { content: "\f5ba"; }
+.bi-telephone-outbound-fill::before { content: "\f5bb"; }
+.bi-telephone-outbound::before { content: "\f5bc"; }
+.bi-telephone-plus-fill::before { content: "\f5bd"; }
+.bi-telephone-plus::before { content: "\f5be"; }
+.bi-telephone-x-fill::before { content: "\f5bf"; }
+.bi-telephone-x::before { content: "\f5c0"; }
+.bi-telephone::before { content: "\f5c1"; }
+.bi-terminal-fill::before { content: "\f5c2"; }
+.bi-terminal::before { content: "\f5c3"; }
+.bi-text-center::before { content: "\f5c4"; }
+.bi-text-indent-left::before { content: "\f5c5"; }
+.bi-text-indent-right::before { content: "\f5c6"; }
+.bi-text-left::before { content: "\f5c7"; }
+.bi-text-paragraph::before { content: "\f5c8"; }
+.bi-text-right::before { content: "\f5c9"; }
+.bi-textarea-resize::before { content: "\f5ca"; }
+.bi-textarea-t::before { content: "\f5cb"; }
+.bi-textarea::before { content: "\f5cc"; }
+.bi-thermometer-half::before { content: "\f5cd"; }
+.bi-thermometer-high::before { content: "\f5ce"; }
+.bi-thermometer-low::before { content: "\f5cf"; }
+.bi-thermometer-snow::before { content: "\f5d0"; }
+.bi-thermometer-sun::before { content: "\f5d1"; }
+.bi-thermometer::before { content: "\f5d2"; }
+.bi-three-dots-vertical::before { content: "\f5d3"; }
+.bi-three-dots::before { content: "\f5d4"; }
+.bi-toggle-off::before { content: "\f5d5"; }
+.bi-toggle-on::before { content: "\f5d6"; }
+.bi-toggle2-off::before { content: "\f5d7"; }
+.bi-toggle2-on::before { content: "\f5d8"; }
+.bi-toggles::before { content: "\f5d9"; }
+.bi-toggles2::before { content: "\f5da"; }
+.bi-tools::before { content: "\f5db"; }
+.bi-tornado::before { content: "\f5dc"; }
+.bi-trash-fill::before { content: "\f5dd"; }
+.bi-trash::before { content: "\f5de"; }
+.bi-trash2-fill::before { content: "\f5df"; }
+.bi-trash2::before { content: "\f5e0"; }
+.bi-tree-fill::before { content: "\f5e1"; }
+.bi-tree::before { content: "\f5e2"; }
+.bi-triangle-fill::before { content: "\f5e3"; }
+.bi-triangle-half::before { content: "\f5e4"; }
+.bi-triangle::before { content: "\f5e5"; }
+.bi-trophy-fill::before { content: "\f5e6"; }
+.bi-trophy::before { content: "\f5e7"; }
+.bi-tropical-storm::before { content: "\f5e8"; }
+.bi-truck-flatbed::before { content: "\f5e9"; }
+.bi-truck::before { content: "\f5ea"; }
+.bi-tsunami::before { content: "\f5eb"; }
+.bi-tv-fill::before { content: "\f5ec"; }
+.bi-tv::before { content: "\f5ed"; }
+.bi-twitch::before { content: "\f5ee"; }
+.bi-twitter::before { content: "\f5ef"; }
+.bi-type-bold::before { content: "\f5f0"; }
+.bi-type-h1::before { content: "\f5f1"; }
+.bi-type-h2::before { content: "\f5f2"; }
+.bi-type-h3::before { content: "\f5f3"; }
+.bi-type-italic::before { content: "\f5f4"; }
+.bi-type-strikethrough::before { content: "\f5f5"; }
+.bi-type-underline::before { content: "\f5f6"; }
+.bi-type::before { content: "\f5f7"; }
+.bi-ui-checks-grid::before { content: "\f5f8"; }
+.bi-ui-checks::before { content: "\f5f9"; }
+.bi-ui-radios-grid::before { content: "\f5fa"; }
+.bi-ui-radios::before { content: "\f5fb"; }
+.bi-umbrella-fill::before { content: "\f5fc"; }
+.bi-umbrella::before { content: "\f5fd"; }
+.bi-union::before { content: "\f5fe"; }
+.bi-unlock-fill::before { content: "\f5ff"; }
+.bi-unlock::before { content: "\f600"; }
+.bi-upc-scan::before { content: "\f601"; }
+.bi-upc::before { content: "\f602"; }
+.bi-upload::before { content: "\f603"; }
+.bi-vector-pen::before { content: "\f604"; }
+.bi-view-list::before { content: "\f605"; }
+.bi-view-stacked::before { content: "\f606"; }
+.bi-vinyl-fill::before { content: "\f607"; }
+.bi-vinyl::before { content: "\f608"; }
+.bi-voicemail::before { content: "\f609"; }
+.bi-volume-down-fill::before { content: "\f60a"; }
+.bi-volume-down::before { content: "\f60b"; }
+.bi-volume-mute-fill::before { content: "\f60c"; }
+.bi-volume-mute::before { content: "\f60d"; }
+.bi-volume-off-fill::before { content: "\f60e"; }
+.bi-volume-off::before { content: "\f60f"; }
+.bi-volume-up-fill::before { content: "\f610"; }
+.bi-volume-up::before { content: "\f611"; }
+.bi-vr::before { content: "\f612"; }
+.bi-wallet-fill::before { content: "\f613"; }
+.bi-wallet::before { content: "\f614"; }
+.bi-wallet2::before { content: "\f615"; }
+.bi-watch::before { content: "\f616"; }
+.bi-water::before { content: "\f617"; }
+.bi-whatsapp::before { content: "\f618"; }
+.bi-wifi-1::before { content: "\f619"; }
+.bi-wifi-2::before { content: "\f61a"; }
+.bi-wifi-off::before { content: "\f61b"; }
+.bi-wifi::before { content: "\f61c"; }
+.bi-wind::before { content: "\f61d"; }
+.bi-window-dock::before { content: "\f61e"; }
+.bi-window-sidebar::before { content: "\f61f"; }
+.bi-window::before { content: "\f620"; }
+.bi-wrench::before { content: "\f621"; }
+.bi-x-circle-fill::before { content: "\f622"; }
+.bi-x-circle::before { content: "\f623"; }
+.bi-x-diamond-fill::before { content: "\f624"; }
+.bi-x-diamond::before { content: "\f625"; }
+.bi-x-octagon-fill::before { content: "\f626"; }
+.bi-x-octagon::before { content: "\f627"; }
+.bi-x-square-fill::before { content: "\f628"; }
+.bi-x-square::before { content: "\f629"; }
+.bi-x::before { content: "\f62a"; }
+.bi-youtube::before { content: "\f62b"; }
+.bi-zoom-in::before { content: "\f62c"; }
+.bi-zoom-out::before { content: "\f62d"; }
+.bi-bank::before { content: "\f62e"; }
+.bi-bank2::before { content: "\f62f"; }
+.bi-bell-slash-fill::before { content: "\f630"; }
+.bi-bell-slash::before { content: "\f631"; }
+.bi-cash-coin::before { content: "\f632"; }
+.bi-check-lg::before { content: "\f633"; }
+.bi-coin::before { content: "\f634"; }
+.bi-currency-bitcoin::before { content: "\f635"; }
+.bi-currency-dollar::before { content: "\f636"; }
+.bi-currency-euro::before { content: "\f637"; }
+.bi-currency-exchange::before { content: "\f638"; }
+.bi-currency-pound::before { content: "\f639"; }
+.bi-currency-yen::before { content: "\f63a"; }
+.bi-dash-lg::before { content: "\f63b"; }
+.bi-exclamation-lg::before { content: "\f63c"; }
+.bi-file-earmark-pdf-fill::before { content: "\f63d"; }
+.bi-file-earmark-pdf::before { content: "\f63e"; }
+.bi-file-pdf-fill::before { content: "\f63f"; }
+.bi-file-pdf::before { content: "\f640"; }
+.bi-gender-ambiguous::before { content: "\f641"; }
+.bi-gender-female::before { content: "\f642"; }
+.bi-gender-male::before { content: "\f643"; }
+.bi-gender-trans::before { content: "\f644"; }
+.bi-headset-vr::before { content: "\f645"; }
+.bi-info-lg::before { content: "\f646"; }
+.bi-mastodon::before { content: "\f647"; }
+.bi-messenger::before { content: "\f648"; }
+.bi-piggy-bank-fill::before { content: "\f649"; }
+.bi-piggy-bank::before { content: "\f64a"; }
+.bi-pin-map-fill::before { content: "\f64b"; }
+.bi-pin-map::before { content: "\f64c"; }
+.bi-plus-lg::before { content: "\f64d"; }
+.bi-question-lg::before { content: "\f64e"; }
+.bi-recycle::before { content: "\f64f"; }
+.bi-reddit::before { content: "\f650"; }
+.bi-safe-fill::before { content: "\f651"; }
+.bi-safe2-fill::before { content: "\f652"; }
+.bi-safe2::before { content: "\f653"; }
+.bi-sd-card-fill::before { content: "\f654"; }
+.bi-sd-card::before { content: "\f655"; }
+.bi-skype::before { content: "\f656"; }
+.bi-slash-lg::before { content: "\f657"; }
+.bi-translate::before { content: "\f658"; }
+.bi-x-lg::before { content: "\f659"; }
+.bi-safe::before { content: "\f65a"; }

File diff suppressed because it is too large
+ 4 - 0
src/baby/frontend/static/css/vendor/bootstrap.css


二進制
src/baby/frontend/static/css/vendor/fonts/bootstrap-icons.woff


二進制
src/baby/frontend/static/css/vendor/fonts/bootstrap-icons.woff2


File diff suppressed because it is too large
+ 5 - 0
src/baby/frontend/static/js/vendor/bootstrap.js


File diff suppressed because it is too large
+ 1 - 0
src/baby/frontend/static/js/vendor/jquery.js


+ 120 - 0
src/baby/frontend/templates/calendar.html

@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Baby - {{context.data.month_word}} {{context.data.year}}</title>
+    <!-- Bootstrap CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap.css">
+    <!-- Bootstrap Font Icon CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap-icons.css">
+
+    <link rel="stylesheet" href="static/css/main/main.css">
+    <link rel="stylesheet" href="static/css/calendar/calendar.css">
+</head>
+<body>
+
+    {% include "nav.html" %}
+    <main class="container">
+
+        <div class="calendar">
+
+            <div class="calendar-date-selector">
+                <form class="calendar-date-selector-form" action="/calendar">
+                    <div class="input-group">
+                        <select class="custom-select form-control calendar-date-month" name="month">
+                          <option value="01">Janvier</option>
+                          <option value="02">Février</option>
+                          <option value="03">Mars</option>
+                          <option value="04">Avril</option>
+                          <option value="05">Mai</option>
+                          <option value="06">Juin</option>
+                          <option value="07">Juillet</option>
+                          <option value="08">Août</option>
+                          <option value="09">Septembre</option>
+                          <option value="10">Octobre</option>
+                          <option value="11">Novembre</option>
+                          <option value="12">Décembre</option>
+                        </select>
+                        <select class="custom-select form-control calendar-date-year" name="year">
+                          <option value="2023">2023</option>
+                          <option value="2024">2024</option>
+                          <option value="2025">2025</option>
+                          <option value="2026">2026</option>
+                          <option value="2027">2027</option>
+                          <option value="2028">2028</option>
+                          <option value="2029">2029</option>
+                          <option value="2030">2030</option>
+                          <option value="2031">2031</option>
+                          <option value="2032">2032</option>
+                          <option value="2033">2033</option>
+                        </select>
+                        <div class="input-group-append calendar-date-submit">
+                          <button class="btn btn-outline-secondary" type="submit">Valider</button>
+                        </div>
+                      </div>
+                </form>
+            </div>
+            <div class="header-month">
+                <a class="header-month-prev_month" onclick="location.search='?date={{context.data.prev}}'">
+                    <i class="bi-chevron-left"></i>
+                </a>
+                <span class="header-month-current_month">
+                    {{context.data.month_word}} {{context.data.year}}
+                </span>
+                <a class="header-month-next_month" onclick="location.search='?date={{context.data.next}}'">
+                    <i class="bi-chevron-right"></i>
+                </a>
+            </div>
+            <table class="calendar-content">
+                <thead>
+                    <tr class="calendar-content-header-row">
+                        <th>Lu</th>
+                        <th>Ma</th>
+                        <th>Me</th>
+                        <th>Je</th>
+                        <th>Ve</th>
+                        <th>Sa</th>
+                        <th>Di</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    {% for week in context.data.calendar %}
+                    <tr class="calendar-content-row">
+                        {% for day in week %}
+                        <td class="calendar-day {{day.classes}}" tooltip="{{day.classes}}">
+                            {{day.day}}
+                        </td>
+                        {% endfor %}
+                    </tr>
+                    {% endfor %}
+
+
+
+                </tbody>
+            </table>
+            <div>
+                <span class="lengend tag-debut">Debut</span>
+                <span class="lengend tag-fertilite">Fertilité</span>
+                <span class="lengend tag-ovulation">Ovulation</span>
+            </div>
+
+        </div>
+
+    </main>
+
+    <!-- Bootstrap JS Bundle with Popper -->
+
+    <script src="static/js/vendor/jquery.js"></script>
+    <script src="static/js/vendor/bootstrap.js"></script>
+    <script>
+        $( document ).ready(function() {
+            const month = "{{context.data.month}}".padStart(2, '0');
+            const year = "{{context.data.year}}".padStart(4, '0');
+            console.log("---> month ", month)
+            $(".calendar-date-month").val(month)
+            $(".calendar-date-year").val(year)
+        });
+    </script>
+</body>
+</html>

+ 90 - 0
src/baby/frontend/templates/edit.html

@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Baby - Editer les cycles</title>
+    <!-- Bootstrap CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap.css">
+    <!-- Bootstrap Font Icon CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap-icons.css">
+
+    <link rel="stylesheet" href="static/css/main/main.css">
+    <link rel="stylesheet" href="static/css/edit/edit.css">
+</head>
+<body>
+    {% include "nav.html" %}
+    <main class="container">
+        <div class="row tile-holder">
+            <div class="header-month">
+                <a class="header-month-prev_month" onclick="location.search='?year={{context.data.prev}}'">
+                    <i class="bi-chevron-left"></i>
+                </a>
+                <span class="header-month-current_month">
+                    {{context.data.year}}
+                </span>
+                <a class="header-month-next_month" onclick="location.search='?year={{context.data.next}}'">
+                    <i class="bi-chevron-right"></i>
+                </a>
+            </div>
+            <div class="regle-host">
+                {% if context.data.count < 1  %}
+                <div class="no-regle">
+                    Aucune date enregistrée en {{context.data.year}}
+                </div>
+                {% endif %}
+                {% for regle in context.data.dates %}
+                    <div class="regle-wrapper">
+                        <div class="regle">
+                            <span class="regle-content">
+                                {{ regle.date }}
+                            </span>
+                            <a class="regle-action">
+                                <i class="bi-trash" onclick="remove_regle('{{regle.date}}','{{regle.id}}')"></i>
+                            </a>
+                        </div>
+                    </div>
+                {% endfor %}
+            </div>
+        </div>
+          
+        
+
+        <div class="modal fade" id="staticBackdrop" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
+            <div class="modal-dialog">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h5 class="modal-title" id="staticBackdropLabel">Supprimer le cycle</h5>
+                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+                    </div>
+                    <div class="modal-body">
+                        Êtes vous sur de vouloir supprimer la règle du <span id="regle-date"></span>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
+                        <button type="button" class="btn btn-primary" onclick="on_remove_regle()">Valider</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+          
+    </main>
+
+
+    <script src="static/js/vendor/jquery.js"></script>
+    <script src="static/js/vendor/bootstrap.js"></script>
+    <script>
+        var dest_date = null;
+        function remove_regle(date, id){
+            $("#regle-date").html(date)
+            dest_date = date;
+            $("#staticBackdrop").modal("show")
+        }
+
+        function on_remove_regle(){
+            location.href = "/delete_regle?date="+dest_date;
+        }
+
+    </script>
+</body>
+</html>

+ 102 - 0
src/baby/frontend/templates/index.html

@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Including Bootstrap Icons in HTML</title>
+    <!-- Bootstrap CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap.css">
+    <!-- Bootstrap Font Icon CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap-icons.css">
+
+    <link rel="stylesheet" href="static/css/main/main.css">
+    <link rel="stylesheet" href="static/css/accueil/accueil.css">
+</head>
+<body>
+    {% include "nav.html" %}
+    <main class="container">
+        <div class="row tile-holder">
+            {% if context.data.value %}
+            <!--  Nombre de jour -->
+            <div class="col tile" onclick="location.pathname = '/stats'">
+                <div class="tile-content-transparent">
+                    <div class="tile-title-text">
+                        {{context.data.offset}}
+                    </div>
+                    <a class="btn tile-title-info">
+                        jours depuis les dernières règless
+                    </a>
+                </div>
+            </div>
+
+            <!--  Prochaines regles -->
+            <div class="col tile" onclick="location.pathname = '/stats'">
+                <div class="tile-content-transparent">
+                    <div class="tile-title-text">
+                        {{context.data.end}}
+                    </div>
+                    <a class="btn tile-title-info">
+                        Prochaines règles
+                    </a>
+                </div>
+            </div>
+            {% endif %}
+        </div>
+
+        <div class="row tile-holder">
+            <!--  Nouveau cycle -->
+            <div class="col-6 col-xl-4 tile" onclick="location.pathname = '/new_cycle'">
+                <div class="tile-content">
+                    <center>
+                        <i class="bi-check-lg tile-img" ></i>
+                    </center>
+                    <a class="btn tile-title">
+                        Nouveau Cycle
+                    </a>
+                </div>
+            </div>
+
+            <!--  Calendrier -->
+            <div class="col-6 col-xl-4 tile" onclick="location.pathname = '/calendar'">
+                <div class="tile-content">
+                    <center>
+                        <i class="bi bi-calendar3 tile-img" ></i>
+                    </center>
+                    <a class="btn tile-title">
+                        Calendrier
+                    </a>
+                </div>
+            </div>
+
+            <!--  Editer cycles -->
+            <div class="col-6 col-xl-4 tile" onclick="location.pathname = '/edit'">
+                <div class="tile-content">
+                    <center>
+                        <i class="bi bi-pencil tile-img" ></i>
+                    </center>
+                    <a class="btn tile-title">
+                        Editer cycles
+                    </a>
+                </div>
+            </div>
+
+            <!--  Parametres -->
+            <div class="col-6 col-xl-4 tile" onclick="location.pathname = '/parameters'">
+                <div class="tile-content">
+                    <center>
+                        <i class="bi bi-gear tile-img" ></i>
+                    </center>
+                    <a class="btn tile-title">
+                        Paramètres
+                    </a>
+                </div>
+            </div>
+        </div>
+    </main>
+
+    <!-- Bootstrap JS Bundle with Popper -->
+
+    <script src="static/js/vendor/jquery.js"></script>
+    <script src="static/js/vendor/bootstrap.js"></script>
+</body>
+</html>

+ 44 - 0
src/baby/frontend/templates/login.html

@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Baby - Login</title>
+    <!-- Bootstrap CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap.css">
+    <!-- Bootstrap Font Icon CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap-icons.css">
+
+    <link rel="stylesheet" href="static/css/main/main.css">
+    <link rel="stylesheet" href="static/css/login/login.css">
+</head>
+<body>
+    {% include "nav.html" %}
+    <main class="container">
+        <div class="card">
+            <div class="card-body">
+                <form class="stats-period-selector-form" action="/auth" method="post">
+                    <h5 class="card-title">Login</h5>
+                    <p class="card-text">Entrez votre login et mot de passe</p>
+                        <div class="input-group mb-3">
+                            <span class="input-group-text">Nom d'utilisateur</span>
+                            <input type="text" class="form-control"  name="login"
+                                    name="Sizing example input">
+                        </div>
+                        <div class="input-group mb-3">
+                            <span class="input-group-text">Mot de passe</span>
+                            <input type="password" class="form-control"  name="password"
+                                    name="Sizing example input">
+                        </div>
+                    <button type="submit" class="btn btn-login">Valider</button>
+                </form>
+            </div>
+        </div>
+    </main>
+
+    <!-- Bootstrap JS Bundle with Popper -->
+
+    <script src="static/js/vendor/jquery.js"></script>
+    <script src="static/js/vendor/bootstrap.js"></script>
+</body>
+</html>

+ 39 - 0
src/baby/frontend/templates/nav.html

@@ -0,0 +1,39 @@
+<nav class="navbar navbar-expand-lg">
+    
+    <div class="container-fluid">
+        <a class="navbar-brand nav-home" href="/"><i class="bi-house"></i></a>
+        <a class="nav-titre navbar-brand">{{context.data.title}}</a>
+        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
+            <span class="navbar-toggler-icon"></span>
+        </button>
+        <div class="collapse navbar-collapse" id="navbarSupportedContent">
+        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
+            <!--<li class="nav-item">
+                <a class="nav-link active" aria-current="page" href="#">Home</a>
+            </li>
+            <li class="nav-item">
+                <a class="nav-link" href="#">Link</a>
+            </li> -->
+            {% if context.user.authenticated  %}
+                <li class="nav-item"><a class="nav-link" href="/new_cycle">Valider cycle</a></li>
+                <li class="nav-item"><a class="nav-link" href="/calendar">Calendrier</a></li>
+                <li class="nav-item"><a class="nav-link" href="/parameters">Paramètres</a></li>
+                <li class="nav-item"><a class="nav-link" href="/stats">Stats</a></li>
+                <li class="nav-item">
+                    <a class="nav-link" href="/disconnect">Déconnexion</a>
+                </li>
+            {% else  %}
+            {% endif %}
+        </ul>
+        </div>
+    </div>
+</nav>
+
+<script>
+    const False = false;
+    const True = true;
+    const None = null;
+    console.log('Context'); 
+    console.log('{{context|safe}}'); 
+    var Context = {{context|safe}};
+</script>

+ 72 - 0
src/baby/frontend/templates/new_cycle.html

@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Baby - Nouveau cycle</title>
+    <!-- Bootstrap CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap.css">
+    <!-- Bootstrap Font Icon CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap-icons.css">
+
+    <link rel="stylesheet" href="static/css/main/main.css">
+    <link rel="stylesheet" href="static/css/accueil/accueil.css">
+</head>
+<body>
+    {% include "nav.html" %}
+    <main class="container">
+
+        <!--  Nouveau cycle -->
+        <div class="row tile-holder">
+
+            <!--  Démarrer aujourd'hui -->
+            <div class="col-6 col-xl-4 tile" onclick="location.pathname = '/validate_cycle'">
+                <div class="tile-content">
+                    <center>
+                        <i class="bi bi-check tile-img" ></i>
+                    </center>
+                    <a class="btn tile-title">
+                        Démarrer aujourd'hui
+                    </a>
+                </div>
+            </div>
+
+            <!--  Annuler dernier cycle -->
+            <div class="col-6 col-xl-4 tile">
+                <div class="tile-content">
+                    <div class="btn tile-title">
+                        <form action="/validate_cycle">
+                            <div class="validate_cycle-label">
+                                Choisir une date
+                            </div>
+                            <div class="validate_cycle-input">
+                                <input type="date" name="date" />
+                            </div>
+                            <div class="validate_cycle-submit">
+                                <button class="btn btn-validate-cycle" action="submit">Valider</button>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+            </div>
+
+            <!--  Retour -->
+            <div class="col-6 col-xl-4 tile" onclick="location.pathname = '/'">
+                <div class="tile-content">
+                    <center>
+                        <i class="bi bi-arrow-left tile-img" ></i>
+                    </center>
+                    <a class="btn tile-title">
+                        Retour
+                    </a>
+                </div>
+            </div>
+        </div>
+    </main>
+
+    <!-- Bootstrap JS Bundle with Popper -->
+
+    <script src="static/js/vendor/jquery.js"></script>
+    <script src="static/js/vendor/bootstrap.js"></script>
+</body>
+</html>

+ 109 - 0
src/baby/frontend/templates/parameters.html

@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Baby - Editer les cycles</title>
+    <!-- Bootstrap CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap.css">
+    <!-- Bootstrap Font Icon CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap-icons.css">
+
+    <link rel="stylesheet" href="static/css/main/main.css">
+    <link rel="stylesheet" href="static/css/parameters/parameters.css">
+</head>
+<body>
+    {% include "nav.html" %}
+    <main class="container">
+        <form method="post" action="/parameters">
+            {% if context.data.is_post %}
+            {% if context.data.status %}
+                <div class="post-success" onclick="remove_message()">
+                    Modification effectué avec success
+                </div>
+            {% else %}
+                <div class="post-fail"  onclick="remove_message()">
+                    Des erreurs ont eu lieu, la modification n'a pas eu lieu
+                </div>
+                
+            {% endif %}
+                
+            {% endif %}
+            {{context.data|safe}}
+
+            <h3>Interface graphique</h3>
+    
+            <div class="input-group mb-3">
+                <span class="input-group-text" >Thème</span>
+                <select class="form-select" name="ui.theme">
+                    <option value="dark">Sombre</option>
+                    <option value="light">Clair</option>
+                </select>
+                {% if context.data.errors.ui_theme %}
+                <div class="invalid-feedback"> {{context.data.errors.ui_theme}}</div>
+                {% endif %}
+            </div>
+
+
+            <h3>Calcul des règles</h3>
+
+            <div class="input-group mb-3">
+                <span class="input-group-text">Durée min d'un cycle (jours)</span>
+                <input type="number" class="form-control" name="regle.min_period"
+                    name="Sizing example input" aria-describedby="cycle-min" 
+                    value="{{context.user.preferences.main.regle.min_period}}">
+                    {% if context.data.errors.regle_min_period %}
+                    <div class="invalid-feedback"> {{context.data.errors.regle_min_period}}</div>
+                    {% endif %}
+            </div>
+    
+            <div class="input-group mb-3">
+                <span class="input-group-text">Durée max d'un cycle (jours)</span>
+                <input type="number" class="form-control"  name="regle.max_period"
+                    name="Sizing example input"
+                    value="{{context.user.preferences.main.regle.max_period}}">
+                    {% if context.data.errors.regle_max_period %}
+                    <div class="invalid-feedback"> {{context.data.errors.regle_max_period}}</div>
+                    {% endif %}
+            </div>
+    
+            <div class="input-group mb-3">
+                <span class="input-group-text" >Durée en mois de calcul de moyenne</span>
+                <input type="number" class="form-control"  name="regle.average_period_window"
+                    name="Sizing example input"
+                    value="{{context.user.preferences.main.regle.average_period_window}}">
+                {% if context.data.errors.regle_average_period_window %}
+                <div class="invalid-feedback"> {{context.data.errors.regle_average_period_window}}</div>
+                {% endif %}
+            </div>
+    
+            <div class="input-group mb-3">
+                <span class="input-group-text" >Méthode de calcul</span>
+                <select class="form-select" name="regle.method">
+                    <option value="contraception">Contraception</option>
+                    <option value="procreation">Procréation</option>
+                </select>
+                {% if context.data.errors.regle_method %}
+                <div class="invalid-feedback"> {{context.data.errors.regle_method}}</div>
+                {% endif %}
+            </div>
+
+            <button type="submit" class="btn  input-group btn-submit">Valider</button>
+        </form>
+
+    </main>
+
+
+    <script src="static/js/vendor/jquery.js"></script>
+    <script src="static/js/vendor/bootstrap.js"></script>
+    <script>
+        $("select[name='regle.method']").val('{{context.user.preferences.main.regle.method}}')
+        $("select[name='ui.theme']").val('{{context.user.preferences.main.ui.theme}}')
+
+        function  remove_message(){
+            $(".post-fail").remove();
+            $(".post-success").remove();
+        }   $(".invalid-feedback").show()
+    </script>
+    </body>
+</html>

+ 110 - 0
src/baby/frontend/templates/stats.html

@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Baby - Editer les cycles</title>
+    <!-- Bootstrap CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap.css">
+    <!-- Bootstrap Font Icon CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap-icons.css">
+
+    <link rel="stylesheet" href="static/css/main/main.css">
+    <link rel="stylesheet" href="static/css/stats/stats.css">
+</head>
+<body>
+    {% include "nav.html" %}
+    <main class="container">
+            <div class="stats-period-selector">
+                <form class="stats-period-selector-form" action="/stats">
+                    <div class="input-group">
+                        <select class="custom-select form-control stats-period" name="period">
+                            <option value="custom">Période de calcul</option>
+                            <option value="6">6 mois</option>
+                            <option value="12">1 an</option>
+                            <option value="all">Tous le temps</option>
+                        </select>
+                        <div class="input-group-append stats-period-submit">
+                            <button class="btn btn-outline-secondary" type="submit">Valider</button>
+                        </div>
+                    </div>
+                </form>
+            </div>
+
+            <div class="row tile-holder">
+            {% if context.data.value %}
+            <!--  Nombre de jour -->
+            <div class="col-6 col-xl-4 tile">
+                <div class="tile-content-transparent">
+                    <div class="tile-title-text">
+                        {{context.data.offset}}
+                    </div>
+                    <a class="btn tile-title-info">
+                        jours depuis les dernières règles
+                    </a>
+                </div>
+            </div>
+
+            <!--  Prochaines regles -->
+            <div class="col-6 col-xl-4 tile">
+                <div class="tile-content-transparent">
+                    <div class="tile-title-text">
+                        {{context.data.end}}
+                    </div>
+                    <a class="btn tile-title-info">
+                        Prochaines règles
+                    </a>
+                </div>
+            </div>
+
+            <!--  Durée min regles -->
+            <div class="col-6 col-xl-4 tile">
+                <div class="tile-content-transparent">
+                    <div class="tile-title-text">
+                        {{context.data.min}}
+                    </div>
+                    <a class="btn tile-title-info">
+                        Cylce le plus court
+                    </a>
+                </div>
+            </div>
+
+
+            <!--  Durée min regles -->
+            <div class="col-6 col-xl-4 tile">
+                <div class="tile-content-transparent">
+                    <div class="tile-title-text">
+                        {{context.data.max}}
+                    </div>
+                    <a class="btn tile-title-info">
+                        Cylce le plus long
+                    </a>
+                </div>
+            </div>
+
+
+            <!--  Durée min regles -->
+            <div class="col-6 col-xl-4 tile">
+                <div class="tile-content-transparent">
+                    <div class="tile-title-text">
+                        {{context.data.average}}
+                    </div>
+                    <a class="btn tile-title-info">
+                        Cylce moyen 
+                    </a>
+                </div>
+            </div>
+            {% else %}
+                Rien à afficher
+            {% endif %}
+        </div>
+          
+    </main>
+
+
+    <script src="static/js/vendor/jquery.js"></script>
+    <script src="static/js/vendor/bootstrap.js"></script>
+    <script>
+    </script>
+</body>
+</html>

+ 40 - 0
src/baby/frontend/templates/validate_cycle.html

@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Including Bootstrap Icons in HTML</title>
+    <!-- Bootstrap CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap.css">
+    <!-- Bootstrap Font Icon CSS -->
+    <link rel="stylesheet" href="static/css/vendor/bootstrap-icons.css">
+
+    <link rel="stylesheet" href="static/css/main/main.css">
+</head>
+<body>
+
+    {% include "nav.html" %}
+    <main class="container">
+        <h1></h1>
+
+        <!--  Nouveau cycle -->
+        <div class="row tile-holder">
+            {% if context.data.status %}
+                <div>
+                    {{ context.data.message }}
+                </div>
+            {% else %}
+                <div>
+                    {{ context.data.message }}
+                </div>
+
+            {% endif %}
+        </div>
+    </main>
+
+    <!-- Bootstrap JS Bundle with Popper -->
+
+    <script src="static/js/vendor/jquery.js"></script>
+    <script src="static/js/vendor/bootstrap.js"></script>
+</body>
+</html>

+ 0 - 0
src/baby/scripts/__init__.py


+ 0 - 0
src/baby/scripts/commands/__init__.py


+ 11 - 0
src/baby/scripts/djangotools

@@ -0,0 +1,11 @@
+#!/bin/env python
+
+import sys, os
+
+from djangotools.cmdline import ArgsParser
+
+
+settings_module = os.environ.get("DJANGO_SETTINGS_MODULE", "baby.settings.prod")
+
+ARGS = sys.argv[1:]
+ArgsParser("baby.scripts.commands").parse(ARGS, settings_module=settings_module)

+ 70 - 0
src/baby/settings/__init__.py

@@ -0,0 +1,70 @@
+"""
+Django settings for baby project.
+
+Generated by 'django-admin startproject' using Django 4.2.5.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/4.2/ref/settings/
+"""
+
+from pathlib import Path
+
+from django.core.management.utils import get_random_secret_key
+from djangotools.config import load_settings
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+DEBUG = True
+
+
+
+# Application definition
+INSTALLED_APPS = [
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    "baby.app",
+    "djangotools"
+]
+
+MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'corsheaders.middleware.CorsMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+
+
+WSGI_APPLICATION = 'baby.wsgi.application'
+
+
+
+# djangotools
+COMMANDS_MODULES=["djangotools.cmdline.commands", "baby.scripts.commands"]
+CONTEXT_MODULES=["baby.app.context"]
+VIEWS_MODULES=["djangotools.views", "baby.app.views"]
+ALLOW_AUTO_LOGIN=False
+LOGIN_URL="login"
+LOGOUT_URL="disconnect"
+AUTH_URL="auth"
+CONTEXT_URL="context/<str:name>"
+LOGIN_TEMPLATE="login.html"
+LOGIN_CONTEXT="login"
+app_dir = BASE_DIR.parent.parent / "data"
+STATICFILES_DIRS = ["frontend/static"]
+TEMPLATES_DIR = ["frontend/templates"]
+ROOT_TEMPLATE="index.html"
+
+CONFIG = load_settings(globals())

+ 37 - 0
src/baby/settings/prod.py

@@ -0,0 +1,37 @@
+"""
+Django settings for baby project.
+
+Generated by 'django-admin startproject' using Django 4.2.5.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/4.2/ref/settings/
+"""
+from djangotools.config import NoConfigLoader
+
+with NoConfigLoader():
+    from baby.settings import *
+
+DEBUG=False
+
+CRON = [
+    # {
+    #     "name" : "backup",
+    #     "timers" : [
+    #     ]
+    # },
+    # {
+    #     "name" : "update",
+    #     "timers" : [
+    #         ("daily", 0),
+    #         ("daily", 6),
+    #         ("daily", 12),
+    #     ]
+    # }
+]
+
+HOSTNAME="localhost:8000"
+
+CONFIG = load_settings(globals())

+ 22 - 0
src/baby/urls.py

@@ -0,0 +1,22 @@
+"""
+URL configuration for baby project.
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/4.2/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.urls import include, path
+    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path
+
+urlpatterns = [
+    path('admin/', admin.site.urls),
+]

+ 16 - 0
src/baby/wsgi.py

@@ -0,0 +1,16 @@
+"""
+WSGI config for baby project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'baby.settings')
+
+application = get_wsgi_application()

+ 0 - 0
tests/__init__.py


+ 10 - 0
tests/settings.py

@@ -0,0 +1,10 @@
+
+
+from baby.settings import *
+
+DATABASES = {
+    "default" : {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': ':memory:',
+    }
+}

+ 49 - 0
tests/test_a.py

@@ -0,0 +1,49 @@
+import datetime
+
+import pytest
+from django.contrib.auth.models import User
+
+from baby.app.models.regle import Regle, Cycle
+
+
+@pytest.mark.django_db
+class Test:
+
+
+    def test_cycle(self):
+        today = datetime.date.today()
+        user = User.objects.create_user("user")
+        times = [
+            -29 - 26 -26,
+            -26 -26,
+            -26
+        ]
+        for i, x in enumerate(times, start=1):
+            Regle.objects.create(user=user, date=today+datetime.timedelta(days=x))
+
+        cycle = Cycle.from_day(user, today)
+        print(i)
+
+
+
+    def test_cycle2(self):
+        today = datetime.date.today()
+        user = User.objects.create_user("user")
+        ref = datetime.date(2023, 9, 2)
+
+        times = [
+            -28 - 25 -26,
+            -26 -25,
+            -25,
+            0
+        ]
+        for i, x in enumerate(times, start=1):
+            Regle.objects.create(user=user, date=ref+datetime.timedelta(days=x))
+
+        #cycle = Cycle.from_date_range(user, ref+datetime.timedelta(days=30), ref+datetime.timedelta(days=390))
+        cycle = Cycle.from_month(user, 11, 2023)
+        print(cycle)
+        print(i)
+
+
+

+ 30 - 0
tests/test_calendar.py

@@ -0,0 +1,30 @@
+import datetime
+
+import pytest
+from django.contrib.auth.models import User
+
+from baby.app.common.calendar import MonthCalendar
+from baby.app.models.regle import Regle
+
+
+@pytest.mark.django_db
+class TestCalendar:
+
+    def test_nominal_case(self):
+        user = User.objects.create_user("user")
+        ref = datetime.date(2023, 9, 2)
+
+        times = [
+            -28 - 25 - 26,
+            -26 - 25,
+            -25,
+            0
+        ]
+        for i, x in enumerate(times, start=1):
+            Regle.objects.create(user=user, date=ref + datetime.timedelta(days=x))
+
+
+        cal = MonthCalendar(user, 11, 23)
+        data = cal.get_calendar_dict()
+        js = cal.json
+        print(data)

+ 0 - 0
tests/util.py


+ 2 - 0
todo

@@ -0,0 +1,2 @@
+- Retour pour toutes les pages
+- Paramètres

Some files were not shown because too many files changed in this diff