Skip to content

Commit

Permalink
feat: Initial implementation of the accordion XBlock
Browse files Browse the repository at this point in the history
Initial working Accordion XBlock

# Conflicts:
#	setup.py

diff --git a/.github/workflows/ci-frontend.yml b/.github/workflows/ci-frontend.yml
new file mode 100644
index 0000000..741569b
--- /dev/null
+++ b/.github/workflows/ci-frontend.yml
@@ -0,0 +1,35 @@
+name: Frontend CI
+
+on:
+  push:
+    branches:
+    - main
+  pull_request:
+
+jobs:
+  tests:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: ./frontend
+    strategy:
+      matrix:
+        npm-test:
+        - lint
+        - test
+    steps:
+    - uses: actions/checkout@v4
+    - name: Setup Nodejs Env
+      run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
+    - uses: actions/setup-node@v2
+      with:
+        node-version: ${{ env.NODE_VER }}
+    - run: npm ci
+    - run: npm run lint
+    - run: npm run coverage
+    - name: upload coverage
+      uses: codecov/codecov-action@v4
+      with:
+        token: ${{ secrets.CODECOV_TOKEN }}
+        fail_ci_if_error: false
+        flags: frontend
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 18cde08..80f4f04 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,11 +15,11 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-20.04]
-        python-version: ['3.8']
-        toxenv: [quality, docs, django32, django40]
+        python-version: ['3.11', '3.12']
+        toxenv: [quality, docs, django42]

     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - name: setup python
       uses: actions/setup-python@v2
       with:
@@ -37,9 +37,9 @@ jobs:
       run: tox

     - name: Run coverage
-      if: matrix.python-version == '3.8' && matrix.toxenv == 'django32'
+      if: matrix.python-version == '3.11' && matrix.toxenv == 'django42'
       uses: codecov/codecov-action@v4
       with:
         token: ${{ secrets.CODECOV_TOKEN }}
-        flags: unittests
-        fail_ci_if_error: true
+        flags: python
+        fail_ci_if_error: false
diff --git a/.gitignore b/.gitignore
index 51c2174..6e66cea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,7 @@ pip-log.txt
 .tox
 coverage.xml
 htmlcov/
+coverage/

 # Virtual environments
 /venv/
diff --git a/Dockerfile b/Dockerfile
index 68ed148..194f19b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,7 +2,7 @@ FROM openedx/xblock-sdk
 RUN mkdir -p /usr/local/src/xblock-accordion
 VOLUME ["/usr/local/src/xblock-accordion"]
 RUN apt-get update && apt-get install -y gettext
-RUN echo "pip install -r /usr/local/src/xblock-accordion/requirements.txt" >> /usr/local/src/xblock-sdk/install_and_run_xblock.sh
+RUN echo "pip install -r /usr/local/src/xblock-accordion/requirements/dev.txt" >> /usr/local/src/xblock-sdk/install_and_run_xblock.sh
 RUN echo "pip install -e /usr/local/src/xblock-accordion" >> /usr/local/src/xblock-sdk/install_and_run_xblock.sh
 RUN echo "cd /usr/local/src/xblock-accordion && make compile_translations && cd /usr/local/src/xblock-sdk" >> /usr/local/src/xblock-sdk/install_and_run_xblock.sh
 RUN echo "exec python /usr/local/src/xblock-sdk/manage.py \"\$@\"" >> /usr/local/src/xblock-sdk/install_and_run_xblock.sh
diff --git a/Makefile b/Makefile
index 3634e23..5376497 100644
--- a/Makefile
+++ b/Makefile
@@ -49,7 +49,7 @@ dev.build:
 	docker build -t $(REPO_NAME)-dev $(CURDIR)

 dev.run: dev.clean dev.build ## Clean, build and run test image
-	docker run -p 8000:8000 -v $(CURDIR):/usr/local/src/$(REPO_NAME) --name $(REPO_NAME)-dev $(REPO_NAME)-dev
+	docker run -p 8200:8000 -v $(CURDIR):/usr/local/src/$(REPO_NAME) --name $(REPO_NAME)-dev $(REPO_NAME)-dev

 ## Localization targets

diff --git a/accordion/__init__.py b/accordion/__init__.py
index 24b005f..8bd8871 100644
--- a/accordion/__init__.py
+++ b/accordion/__init__.py
@@ -4,4 +4,4 @@ Init for the AccordionXBlock package.

 from .accordion import AccordionXBlock

-__version__ = '0.1.0'
+__version__ = "0.1.0"
diff --git a/accordion/accordion.py b/accordion/accordion.py
index 024d203..c4d9a58 100644
--- a/accordion/accordion.py
+++ b/accordion/accordion.py
@@ -1,107 +1,93 @@
-"""TO-DO: Write a description of what this XBlock is."""
+"""
+An XBlock for creating and accordion component with multiple panels with rich content.
+"""
+
+from importlib import resources

-import pkg_resources
 from django.utils import translation
 from web_fragments.fragment import Fragment
 from xblock.core import XBlock
-from xblock.fields import Integer, Scope
-from xblockutils.resources import ResourceLoader
+from xblock.fields import Dict, List, Scope, String

 class AccordionXBlock(XBlock):
     """
-    TO-DO: document what your XBlock does.
+    Accordion XBlock.
     """

-    # Fields are defined on the class.  You can access them in your code as
-    # self.<fieldname>.
-
-    # TO-DO: delete count, and define your own fields.
-    count = Integer(
-        default=0, scope=Scope.user_state,
-        help="A simple counter, to show something happening",
+    display_name = String(default=translation.gettext_noop("Accordion"))
+    panels = List(help="Accordion entries", default=[], scope=Scope.content)
+    styling = Dict(help="Accordion styling", default=[], scope=Scope.content)
+    border_style = String(
+        help="Accordion border style", default="", scope=Scope.content
     )

     def resource_string(self, path):
         """Handy helper for getting resources from our kit."""
-        data = pkg_resources.resource_string(__name__, path)
-        return data.decode("utf8")
+        data = resources.files("accordion").joinpath(path).read_text("utf8")
+        return data

-    # TO-DO: change this view to display your data your own way.
-    def student_view(self, context=None):
+    def student_view(self, context=None):  # pylint: disable=unused-argument
         """
         Create primary view of the AccordionXBlock, shown to students when viewing courses.
         """
-        if context:
-            pass  # TO-DO: do something based on the context.
-        html = self.resource_string("static/html/accordion.html")
-        frag = Fragment(html.format(self=self))
-        frag.add_css(self.resource_string("static/css/accordion.css"))
-
-        # Add i18n js
-        statici18n_js_url = self._get_statici18n_js_url()
-        if statici18n_js_url:
-            frag.add_javascript_url(self.runtime.local_resource_url(self, statici18n_js_url))
-
-        frag.add_javascript(self.resource_string("static/js/src/accordion.js"))
-        frag.initialize_js('AccordionXBlock')
+        frag = Fragment()
+        frag.add_javascript(self.resource_string("static/student.js"))
+        frag.add_css_url(self.runtime.local_resource_url(self, "public/student-ui.css"))
+        frag.initialize_js(
+            "AccordionBlock",
+            {
+                "url": self.runtime.local_resource_url(self, "public/student-ui.js"),
+                "panels": self.panels,
+                "styling": self.styling,
+            },
+        )
         return frag

-    # TO-DO: change this handler to perform your own actions.  You may need more
-    # than one handler, or you may not need any handlers at all.
     @XBlock.json_handler
-    def increment_count(self, data, suffix=''):
-        """
-        Increments data. An example handler.
-        """
-        if suffix:
-            pass  # TO-DO: Use the suffix when storing data.
-        # Just to show data coming in...
-        assert data['hello'] == 'world'
+    def studio_save(self, data, suffix=""):  # pylint: disable=unused-argument
+        """Save config and data based on data received at this API endpoint."""
+        panels = data.get("panels", None)
+        styling = data.get("styling", None)
+        if panels:
+            self.panels = panels
+        if styling:
+            self.styling = styling

-        self.count += 1
-        return {"count": self.count}
+    def studio_view(self, context=None):  # pylint: disable=unused-argument
+        """
+        Create primary view of the AccordionXBlock, shown to students when viewing courses.
+        """
+        html = self.resource_string("static/html/accordion.html")
+        frag = Fragment(html)
+        frag.add_javascript(self.resource_string("static/studio.js"))
+        frag.add_css_url(self.runtime.local_resource_url(self, "public/studio-ui.css"))
+        frag.initialize_js(
+            "AccordionEditor",
+            {
+                "url": self.runtime.local_resource_url(self, "public/studio-ui.js"),
+                "panels": self.panels,
+                "styling": self.styling,
+            },
+        )
+        return frag

-    # TO-DO: change this to create the scenarios you'd like to see in the
-    # workbench while developing your XBlock.
     @staticmethod
     def workbench_scenarios():
         """Create canned scenario for display in the workbench."""
         return [
-            ("AccordionXBlock",
-             """<accordion/>
-             """),
-            ("Multiple AccordionXBlock",
-             """<vertical_demo>
+            (
+                "AccordionXBlock",
+                """<accordion/>
+             """,
+            ),
+            (
+                "Multiple AccordionXBlock",
+                """<vertical_demo>
                 <accordion/>
                 <accordion/>
                 <accordion/>
                 </vertical_demo>
-             """),
+             """,
+            ),
         ]
-
-    @staticmethod
-    def _get_statici18n_js_url():
-        """
-        Return the Javascript translation file for the currently selected language, if any.
-
-        Defaults to English if available.
-        """
-        locale_code = translation.get_language()
-        if locale_code is None:
-            return None
-        text_js = 'public/js/translations/{locale_code}/text.js'
-        lang_code = locale_code.split('-')[0]
-        for code in (locale_code, lang_code, 'en'):
-            loader = ResourceLoader(__name__)
-            if pkg_resources.resource_exists(
-                    loader.module_name, text_js.format(locale_code=code)):
-                return text_js.format(locale_code=code)
-        return None
-
-    @staticmethod
-    def get_dummy():
-        """
-        Generate initial i18n with dummy method.
-        """
-        return translation.gettext_noop('Dummy')
diff --git a/accordion/public/student-ui.css b/accordion/public/student-ui.css
new file mode 100644
index 0000000..702ff35
--- /dev/null
+++ b/accordion/public/student-ui.css
@@ -0,0 +1 @@
+:export{xs:0;sm:576px;md:768px;lg:992px;xl:1200px;xxl:1400px}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover{z-index:1}.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.dropdown-toggle-split:after,.dropup .dropdown-toggle-split:after,.dropright .dropdown-toggle-split:after{margin-left:0}.dropleft .dropdown-toggle-split:before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.9375rem;padding-left:.9375rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn{display:inline-flex;align-items:center;justify-content:center;font-weight:400;color:#454545;text-align:center;vertical-align:middle;-webkit-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.5625rem 1rem;font-size:1.125rem;line-height:1.3333;border-radius:.375rem}.btn:hover{color:#454545;text-decoration:none}.btn.disabled,.btn:disabled{opacity:.65}.btn .btn-icon-before{margin-inline-end:.5rem;margin-left:-.25em}[dir=rtl] .btn .btn-icon-before{margin-right:-.25em;margin-left:.5rem}.btn .btn-icon-after{margin-inline-start:.5rem;margin-right:-.25em}[dir=rtl] .btn .btn-icon-after{margin-right:.5rem;margin-left:-.25em}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#0a3055;border-color:#0a3055}.btn-primary:hover{color:#fff;background-color:#082644;border-color:#082644}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0a3055;border-color:#0a3055}.btn-primary:not(:disabled):not(.disabled):active,.btn-primary:not(:disabled):not(.disabled).active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#07223c;border-color:#07223c}.btn-primary.focus,.btn-primary:focus{position:relative;outline:0;box-shadow:none}.btn-primary.focus:before,.btn-primary:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #0A3055;border-radius:calc(.375rem + 4px)}.btn-primary.focus.btn-lg:before,.btn-group-lg>.btn-primary.focus.btn:before,.btn-primary:focus.btn-lg:before,.btn-group-lg>.btn-primary.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-primary.focus.btn-sm:before,.btn-group-sm>.btn-primary.focus.btn:before,.btn-primary:focus.btn-sm:before,.btn-group-sm>.btn-primary.btn:focus:before{border-radius:.375rem}.btn-primary.focus:active:before,.btn-primary.focus.active:before,.btn-primary:focus:active:before,.btn-primary:focus.active:before{opacity:.75}.btn-primary.focus:disabled:before,.btn-primary.focus.disabled:before,.btn-primary:focus:disabled:before,.btn-primary:focus.disabled:before{display:none}.btn-outline-primary{color:#0a3055;border-color:#0a3055}.btn-outline-primary:hover{color:#082644;background-color:#f0f3f5;border-color:#07223c}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0a3055;background-color:transparent;border-color:#0a3055}.btn-outline-primary:not(:disabled):not(.disabled):active,.btn-outline-primary:not(:disabled):not(.disabled).active,.show>.btn-outline-primary.dropdown-toggle{color:#454545;background-color:#f0f3f5;border-color:#07223c}.btn-outline-primary.focus,.btn-outline-primary:focus{position:relative;outline:0;box-shadow:none}.btn-outline-primary.focus:before,.btn-outline-primary:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #0A3055;border-radius:calc(.375rem + 4px)}.btn-outline-primary.focus.btn-lg:before,.btn-group-lg>.btn-outline-primary.focus.btn:before,.btn-outline-primary:focus.btn-lg:before,.btn-group-lg>.btn-outline-primary.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-outline-primary.focus.btn-sm:before,.btn-group-sm>.btn-outline-primary.focus.btn:before,.btn-outline-primary:focus.btn-sm:before,.btn-group-sm>.btn-outline-primary.btn:focus:before{border-radius:.375rem}.btn-outline-primary.focus:active:before,.btn-outline-primary.focus.active:before,.btn-outline-primary:focus:active:before,.btn-outline-primary:focus.active:before{opacity:.75}.btn-outline-primary.focus:disabled:before,.btn-outline-primary.focus.disabled:before,.btn-outline-primary:focus:disabled:before,.btn-outline-primary:focus.disabled:before{display:none}.btn-inverse-primary{color:#0a3055;border-color:transparent;background-color:#fff}.btn-inverse-primary:not(:disabled):not(.disabled):hover{color:#061d33;background-color:#ececec;border-color:transparent}.btn-inverse-primary.disabled,.btn-inverse-primary:disabled{color:#0a3055;background-color:#fff}.btn-inverse-primary:not(:disabled):not(.disabled):active,.btn-inverse-primary:not(:disabled):not(.disabled).active,.show>.btn-inverse-primary.dropdown-toggle{color:#051627;background:#eee}.btn-inverse-primary.focus,.btn-inverse-primary:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-primary.focus:before,.btn-inverse-primary:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-primary.focus.btn-lg:before,.btn-group-lg>.btn-inverse-primary.focus.btn:before,.btn-inverse-primary:focus.btn-lg:before,.btn-group-lg>.btn-inverse-primary.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-primary.focus.btn-sm:before,.btn-group-sm>.btn-inverse-primary.focus.btn:before,.btn-inverse-primary:focus.btn-sm:before,.btn-group-sm>.btn-inverse-primary.btn:focus:before{border-radius:.375rem}.btn-inverse-primary.focus:active:before,.btn-inverse-primary.focus.active:before,.btn-inverse-primary:focus:active:before,.btn-inverse-primary:focus.active:before{opacity:.75}.btn-inverse-primary.focus:disabled:before,.btn-inverse-primary.focus.disabled:before,.btn-inverse-primary:focus:disabled:before,.btn-inverse-primary:focus.disabled:before{display:none}.btn-inverse-outline-primary{color:#fff;border-color:#fff}.btn-inverse-outline-primary:hover{color:#082644;background-color:#f0f3f5;border-color:transparent}.btn-inverse-outline-primary.disabled,.btn-inverse-outline-primary:disabled{color:#fff;background-color:transparent;border-color:#fff}.btn-inverse-outline-primary:not(:disabled):not(.disabled):active,.btn-inverse-outline-primary:not(:disabled):not(.disabled).active,.show>.btn-inverse-outline-primary.dropdown-toggle{color:#454545;background-color:#f0f3f5;border-color:transparent}.btn-inverse-outline-primary.focus,.btn-inverse-outline-primary:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-outline-primary.focus:before,.btn-inverse-outline-primary:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-outline-primary.focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-primary.focus.btn:before,.btn-inverse-outline-primary:focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-primary.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-outline-primary.focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-primary.focus.btn:before,.btn-inverse-outline-primary:focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-primary.btn:focus:before{border-radius:.375rem}.btn-inverse-outline-primary.focus:active:before,.btn-inverse-outline-primary.focus.active:before,.btn-inverse-outline-primary:focus:active:before,.btn-inverse-outline-primary:focus.active:before{opacity:.75}.btn-inverse-outline-primary.focus:disabled:before,.btn-inverse-outline-primary.focus.disabled:before,.btn-inverse-outline-primary:focus:disabled:before,.btn-inverse-outline-primary:focus.disabled:before{display:none}.btn-secondary{color:#fff;background-color:#454545;border-color:#454545}.btn-secondary:hover{color:#fff;background-color:#373737;border-color:#373737}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#454545;border-color:#454545}.btn-secondary:not(:disabled):not(.disabled):active,.btn-secondary:not(:disabled):not(.disabled).active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#303030;border-color:#303030}.btn-secondary.focus,.btn-secondary:focus{position:relative;outline:0;box-shadow:none}.btn-secondary.focus:before,.btn-secondary:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #454545;border-radius:calc(.375rem + 4px)}.btn-secondary.focus.btn-lg:before,.btn-group-lg>.btn-secondary.focus.btn:before,.btn-secondary:focus.btn-lg:before,.btn-group-lg>.btn-secondary.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-secondary.focus.btn-sm:before,.btn-group-sm>.btn-secondary.focus.btn:before,.btn-secondary:focus.btn-sm:before,.btn-group-sm>.btn-secondary.btn:focus:before{border-radius:.375rem}.btn-secondary.focus:active:before,.btn-secondary.focus.active:before,.btn-secondary:focus:active:before,.btn-secondary:focus.active:before{opacity:.75}.btn-secondary.focus:disabled:before,.btn-secondary.focus.disabled:before,.btn-secondary:focus:disabled:before,.btn-secondary:focus.disabled:before{display:none}.btn-outline-secondary{color:#454545;border-color:#454545}.btn-outline-secondary:hover{color:#373737;background-color:#f4f4f4;border-color:#303030}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#454545;background-color:transparent;border-color:#454545}.btn-outline-secondary:not(:disabled):not(.disabled):active,.btn-outline-secondary:not(:disabled):not(.disabled).active,.show>.btn-outline-secondary.dropdown-toggle{color:#454545;background-color:#f4f4f4;border-color:#303030}.btn-outline-secondary.focus,.btn-outline-secondary:focus{position:relative;outline:0;box-shadow:none}.btn-outline-secondary.focus:before,.btn-outline-secondary:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #454545;border-radius:calc(.375rem + 4px)}.btn-outline-secondary.focus.btn-lg:before,.btn-group-lg>.btn-outline-secondary.focus.btn:before,.btn-outline-secondary:focus.btn-lg:before,.btn-group-lg>.btn-outline-secondary.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-outline-secondary.focus.btn-sm:before,.btn-group-sm>.btn-outline-secondary.focus.btn:before,.btn-outline-secondary:focus.btn-sm:before,.btn-group-sm>.btn-outline-secondary.btn:focus:before{border-radius:.375rem}.btn-outline-secondary.focus:active:before,.btn-outline-secondary.focus.active:before,.btn-outline-secondary:focus:active:before,.btn-outline-secondary:focus.active:before{opacity:.75}.btn-outline-secondary.focus:disabled:before,.btn-outline-secondary.focus.disabled:before,.btn-outline-secondary:focus:disabled:before,.btn-outline-secondary:focus.disabled:before{display:none}.btn-inverse-secondary{color:#454545;border-color:transparent;background-color:#fff}.btn-inverse-secondary:not(:disabled):not(.disabled):hover{color:#323232;background-color:#ececec;border-color:transparent}.btn-inverse-secondary.disabled,.btn-inverse-secondary:disabled{color:#454545;background-color:#fff}.btn-inverse-secondary:not(:disabled):not(.disabled):active,.btn-inverse-secondary:not(:disabled):not(.disabled).active,.show>.btn-inverse-secondary.dropdown-toggle{color:#2c2c2c;background:#eee}.btn-inverse-secondary.focus,.btn-inverse-secondary:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-secondary.focus:before,.btn-inverse-secondary:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-secondary.focus.btn-lg:before,.btn-group-lg>.btn-inverse-secondary.focus.btn:before,.btn-inverse-secondary:focus.btn-lg:before,.btn-group-lg>.btn-inverse-secondary.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-secondary.focus.btn-sm:before,.btn-group-sm>.btn-inverse-secondary.focus.btn:before,.btn-inverse-secondary:focus.btn-sm:before,.btn-group-sm>.btn-inverse-secondary.btn:focus:before{border-radius:.375rem}.btn-inverse-secondary.focus:active:before,.btn-inverse-secondary.focus.active:before,.btn-inverse-secondary:focus:active:before,.btn-inverse-secondary:focus.active:before{opacity:.75}.btn-inverse-secondary.focus:disabled:before,.btn-inverse-secondary.focus.disabled:before,.btn-inverse-secondary:focus:disabled:before,.btn-inverse-secondary:focus.disabled:before{display:none}.btn-inverse-outline-secondary{color:#fff;border-color:#fff}.btn-inverse-outline-secondary:hover{color:#373737;background-color:#f4f4f4;border-color:transparent}.btn-inverse-outline-secondary.disabled,.btn-inverse-outline-secondary:disabled{color:#fff;background-color:transparent;border-color:#fff}.btn-inverse-outline-secondary:not(:disabled):not(.disabled):active,.btn-inverse-outline-secondary:not(:disabled):not(.disabled).active,.show>.btn-inverse-outline-secondary.dropdown-toggle{color:#454545;background-color:#f4f4f4;border-color:transparent}.btn-inverse-outline-secondary.focus,.btn-inverse-outline-secondary:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-outline-secondary.focus:before,.btn-inverse-outline-secondary:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-outline-secondary.focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-secondary.focus.btn:before,.btn-inverse-outline-secondary:focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-secondary.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-outline-secondary.focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-secondary.focus.btn:before,.btn-inverse-outline-secondary:focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-secondary.btn:focus:before{border-radius:.375rem}.btn-inverse-outline-secondary.focus:active:before,.btn-inverse-outline-secondary.focus.active:before,.btn-inverse-outline-secondary:focus:active:before,.btn-inverse-outline-secondary:focus.active:before{opacity:.75}.btn-inverse-outline-secondary.focus:disabled:before,.btn-inverse-outline-secondary.focus.disabled:before,.btn-inverse-outline-secondary:focus:disabled:before,.btn-inverse-outline-secondary:focus.disabled:before{display:none}.btn-brand{color:#fff;background-color:#9d0054;border-color:#9d0054}.btn-brand:hover{color:#fff;background-color:#7e0043;border-color:#7e0043}.btn-brand.disabled,.btn-brand:disabled{color:#fff;background-color:#9d0054;border-color:#9d0054}.btn-brand:not(:disabled):not(.disabled):active,.btn-brand:not(:disabled):not(.disabled).active,.show>.btn-brand.dropdown-toggle{color:#fff;background-color:#6e003b;border-color:#6e003b}.btn-brand.focus,.btn-brand:focus{position:relative;outline:0;box-shadow:none}.btn-brand.focus:before,.btn-brand:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #9D0054;border-radius:calc(.375rem + 4px)}.btn-brand.focus.btn-lg:before,.btn-group-lg>.btn-brand.focus.btn:before,.btn-brand:focus.btn-lg:before,.btn-group-lg>.btn-brand.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-brand.focus.btn-sm:before,.btn-group-sm>.btn-brand.focus.btn:before,.btn-brand:focus.btn-sm:before,.btn-group-sm>.btn-brand.btn:focus:before{border-radius:.375rem}.btn-brand.focus:active:before,.btn-brand.focus.active:before,.btn-brand:focus:active:before,.btn-brand:focus.active:before{opacity:.75}.btn-brand.focus:disabled:before,.btn-brand.focus.disabled:before,.btn-brand:focus:disabled:before,.btn-brand:focus.disabled:before{display:none}.btn-outline-brand{color:#9d0054;border-color:#9d0054}.btn-outline-brand:hover{color:#7e0043;background-color:#f9f0f5;border-color:#6e003b}.btn-outline-brand.disabled,.btn-outline-brand:disabled{color:#9d0054;background-color:transparent;border-color:#9d0054}.btn-outline-brand:not(:disabled):not(.disabled):active,.btn-outline-brand:not(:disabled):not(.disabled).active,.show>.btn-outline-brand.dropdown-toggle{color:#454545;background-color:#f9f0f5;border-color:#6e003b}.btn-outline-brand.focus,.btn-outline-brand:focus{position:relative;outline:0;box-shadow:none}.btn-outline-brand.focus:before,.btn-outline-brand:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #9D0054;border-radius:calc(.375rem + 4px)}.btn-outline-brand.focus.btn-lg:before,.btn-group-lg>.btn-outline-brand.focus.btn:before,.btn-outline-brand:focus.btn-lg:before,.btn-group-lg>.btn-outline-brand.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-outline-brand.focus.btn-sm:before,.btn-group-sm>.btn-outline-brand.focus.btn:before,.btn-outline-brand:focus.btn-sm:before,.btn-group-sm>.btn-outline-brand.btn:focus:before{border-radius:.375rem}.btn-outline-brand.focus:active:before,.btn-outline-brand.focus.active:before,.btn-outline-brand:focus:active:before,.btn-outline-brand:focus.active:before{opacity:.75}.btn-outline-brand.focus:disabled:before,.btn-outline-brand.focus.disabled:before,.btn-outline-brand:focus:disabled:before,.btn-outline-brand:focus.disabled:before{display:none}.btn-inverse-brand{color:#9d0054;border-color:transparent;background-color:#fff}.btn-inverse-brand:not(:disabled):not(.disabled):hover{color:#770040;background-color:#ececec;border-color:transparent}.btn-inverse-brand.disabled,.btn-inverse-brand:disabled{color:#9d0054;background-color:#fff}.btn-inverse-brand:not(:disabled):not(.disabled):active,.btn-inverse-brand:not(:disabled):not(.disabled).active,.show>.btn-inverse-brand.dropdown-toggle{color:#6a0039;background:#eee}.btn-inverse-brand.focus,.btn-inverse-brand:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-brand.focus:before,.btn-inverse-brand:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-brand.focus.btn-lg:before,.btn-group-lg>.btn-inverse-brand.focus.btn:before,.btn-inverse-brand:focus.btn-lg:before,.btn-group-lg>.btn-inverse-brand.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-brand.focus.btn-sm:before,.btn-group-sm>.btn-inverse-brand.focus.btn:before,.btn-inverse-brand:focus.btn-sm:before,.btn-group-sm>.btn-inverse-brand.btn:focus:before{border-radius:.375rem}.btn-inverse-brand.focus:active:before,.btn-inverse-brand.focus.active:before,.btn-inverse-brand:focus:active:before,.btn-inverse-brand:focus.active:before{opacity:.75}.btn-inverse-brand.focus:disabled:before,.btn-inverse-brand.focus.disabled:before,.btn-inverse-brand:focus:disabled:before,.btn-inverse-brand:focus.disabled:before{display:none}.btn-inverse-outline-brand{color:#fff;border-color:#fff}.btn-inverse-outline-brand:hover{color:#7e0043;background-color:#f9f0f5;border-color:transparent}.btn-inverse-outline-brand.disabled,.btn-inverse-outline-brand:disabled{color:#fff;background-color:transparent;border-color:#fff}.btn-inverse-outline-brand:not(:disabled):not(.disabled):active,.btn-inverse-outline-brand:not(:disabled):not(.disabled).active,.show>.btn-inverse-outline-brand.dropdown-toggle{color:#454545;background-color:#f9f0f5;border-color:transparent}.btn-inverse-outline-brand.focus,.btn-inverse-outline-brand:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-outline-brand.focus:before,.btn-inverse-outline-brand:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-outline-brand.focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-brand.focus.btn:before,.btn-inverse-outline-brand:focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-brand.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-outline-brand.focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-brand.focus.btn:before,.btn-inverse-outline-brand:focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-brand.btn:focus:before{border-radius:.375rem}.btn-inverse-outline-brand.focus:active:before,.btn-inverse-outline-brand.focus.active:before,.btn-inverse-outline-brand:focus:active:before,.btn-inverse-outline-brand:focus.active:before{opacity:.75}.btn-inverse-outline-brand.focus:disabled:before,.btn-inverse-outline-brand.focus.disabled:before,.btn-inverse-outline-brand:focus:disabled:before,.btn-inverse-outline-brand:focus.disabled:before{display:none}.btn-success{color:#fff;background-color:#178253;border-color:#178253}.btn-success:hover{color:#fff;background-color:#126842;border-color:#126842}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#178253;border-color:#178253}.btn-success:not(:disabled):not(.disabled):active,.btn-success:not(:disabled):not(.disabled).active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#105b3a;border-color:#105b3a}.btn-success.focus,.btn-success:focus{position:relative;outline:0;box-shadow:none}.btn-success.focus:before,.btn-success:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #178253;border-radius:calc(.375rem + 4px)}.btn-success.focus.btn-lg:before,.btn-group-lg>.btn-success.focus.btn:before,.btn-success:focus.btn-lg:before,.btn-group-lg>.btn-success.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-success.focus.btn-sm:before,.btn-group-sm>.btn-success.focus.btn:before,.btn-success:focus.btn-sm:before,.btn-group-sm>.btn-success.btn:focus:before{border-radius:.375rem}.btn-success.focus:active:before,.btn-success.focus.active:before,.btn-success:focus:active:before,.btn-success:focus.active:before{opacity:.75}.btn-success.focus:disabled:before,.btn-success.focus.disabled:before,.btn-success:focus:disabled:before,.btn-success:focus.disabled:before{display:none}.btn-outline-success{color:#178253;border-color:#178253}.btn-outline-success:hover{color:#126842;background-color:#f1f8f5;border-color:#105b3a}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#178253;background-color:transparent;border-color:#178253}.btn-outline-success:not(:disabled):not(.disabled):active,.btn-outline-success:not(:disabled):not(.disabled).active,.show>.btn-outline-success.dropdown-toggle{color:#454545;background-color:#f1f8f5;border-color:#105b3a}.btn-outline-success.focus,.btn-outline-success:focus{position:relative;outline:0;box-shadow:none}.btn-outline-success.focus:before,.btn-outline-success:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #178253;border-radius:calc(.375rem + 4px)}.btn-outline-success.focus.btn-lg:before,.btn-group-lg>.btn-outline-success.focus.btn:before,.btn-outline-success:focus.btn-lg:before,.btn-group-lg>.btn-outline-success.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-outline-success.focus.btn-sm:before,.btn-group-sm>.btn-outline-success.focus.btn:before,.btn-outline-success:focus.btn-sm:before,.btn-group-sm>.btn-outline-success.btn:focus:before{border-radius:.375rem}.btn-outline-success.focus:active:before,.btn-outline-success.focus.active:before,.btn-outline-success:focus:active:before,.btn-outline-success:focus.active:before{opacity:.75}.btn-outline-success.focus:disabled:before,.btn-outline-success.focus.disabled:before,.btn-outline-success:focus:disabled:before,.btn-outline-success:focus.disabled:before{display:none}.btn-inverse-success{color:#178253;border-color:transparent;background-color:#fff}.btn-inverse-success:not(:disabled):not(.disabled):hover{color:#11623e;background-color:#ececec;border-color:transparent}.btn-inverse-success.disabled,.btn-inverse-success:disabled{color:#178253;background-color:#fff}.btn-inverse-success:not(:disabled):not(.disabled):active,.btn-inverse-success:not(:disabled):not(.disabled).active,.show>.btn-inverse-success.dropdown-toggle{color:#0f5737;background:#eee}.btn-inverse-success.focus,.btn-inverse-success:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-success.focus:before,.btn-inverse-success:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-success.focus.btn-lg:before,.btn-group-lg>.btn-inverse-success.focus.btn:before,.btn-inverse-success:focus.btn-lg:before,.btn-group-lg>.btn-inverse-success.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-success.focus.btn-sm:before,.btn-group-sm>.btn-inverse-success.focus.btn:before,.btn-inverse-success:focus.btn-sm:before,.btn-group-sm>.btn-inverse-success.btn:focus:before{border-radius:.375rem}.btn-inverse-success.focus:active:before,.btn-inverse-success.focus.active:before,.btn-inverse-success:focus:active:before,.btn-inverse-success:focus.active:before{opacity:.75}.btn-inverse-success.focus:disabled:before,.btn-inverse-success.focus.disabled:before,.btn-inverse-success:focus:disabled:before,.btn-inverse-success:focus.disabled:before{display:none}.btn-inverse-outline-success{color:#fff;border-color:#fff}.btn-inverse-outline-success:hover{color:#126842;background-color:#f1f8f5;border-color:transparent}.btn-inverse-outline-success.disabled,.btn-inverse-outline-success:disabled{color:#fff;background-color:transparent;border-color:#fff}.btn-inverse-outline-success:not(:disabled):not(.disabled):active,.btn-inverse-outline-success:not(:disabled):not(.disabled).active,.show>.btn-inverse-outline-success.dropdown-toggle{color:#454545;background-color:#f1f8f5;border-color:transparent}.btn-inverse-outline-success.focus,.btn-inverse-outline-success:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-outline-success.focus:before,.btn-inverse-outline-success:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-outline-success.focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-success.focus.btn:before,.btn-inverse-outline-success:focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-success.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-outline-success.focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-success.focus.btn:before,.btn-inverse-outline-success:focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-success.btn:focus:before{border-radius:.375rem}.btn-inverse-outline-success.focus:active:before,.btn-inverse-outline-success.focus.active:before,.btn-inverse-outline-success:focus:active:before,.btn-inverse-outline-success:focus.active:before{opacity:.75}.btn-inverse-outline-success.focus:disabled:before,.btn-inverse-outline-success.focus.disabled:before,.btn-inverse-outline-success:focus:disabled:before,.btn-inverse-outline-success:focus.disabled:before{display:none}.btn-info{color:#fff;background-color:#006daa;border-color:#006daa}.btn-info:hover{color:#fff;background-color:#005788;border-color:#005788}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#006daa;border-color:#006daa}.btn-info:not(:disabled):not(.disabled):active,.btn-info:not(:disabled):not(.disabled).active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#004c77;border-color:#004c77}.btn-info.focus,.btn-info:focus{position:relative;outline:0;box-shadow:none}.btn-info.focus:before,.btn-info:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #006DAA;border-radius:calc(.375rem + 4px)}.btn-info.focus.btn-lg:before,.btn-group-lg>.btn-info.focus.btn:before,.btn-info:focus.btn-lg:before,.btn-group-lg>.btn-info.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-info.focus.btn-sm:before,.btn-group-sm>.btn-info.focus.btn:before,.btn-info:focus.btn-sm:before,.btn-group-sm>.btn-info.btn:focus:before{border-radius:.375rem}.btn-info.focus:active:before,.btn-info.focus.active:before,.btn-info:focus:active:before,.btn-info:focus.active:before{opacity:.75}.btn-info.focus:disabled:before,.btn-info.focus.disabled:before,.btn-info:focus:disabled:before,.btn-info:focus.disabled:before{display:none}.btn-outline-info{color:#006daa;border-color:#006daa}.btn-outline-info:hover{color:#005788;background-color:#f0f6fa;border-color:#004c77}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#006daa;background-color:transparent;border-color:#006daa}.btn-outline-info:not(:disabled):not(.disabled):active,.btn-outline-info:not(:disabled):not(.disabled).active,.show>.btn-outline-info.dropdown-toggle{color:#454545;background-color:#f0f6fa;border-color:#004c77}.btn-outline-info.focus,.btn-outline-info:focus{position:relative;outline:0;box-shadow:none}.btn-outline-info.focus:before,.btn-outline-info:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #006DAA;border-radius:calc(.375rem + 4px)}.btn-outline-info.focus.btn-lg:before,.btn-group-lg>.btn-outline-info.focus.btn:before,.btn-outline-info:focus.btn-lg:before,.btn-group-lg>.btn-outline-info.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-outline-info.focus.btn-sm:before,.btn-group-sm>.btn-outline-info.focus.btn:before,.btn-outline-info:focus.btn-sm:before,.btn-group-sm>.btn-outline-info.btn:focus:before{border-radius:.375rem}.btn-outline-info.focus:active:before,.btn-outline-info.focus.active:before,.btn-outline-info:focus:active:before,.btn-outline-info:focus.active:before{opacity:.75}.btn-outline-info.focus:disabled:before,.btn-outline-info.focus.disabled:before,.btn-outline-info:focus:disabled:before,.btn-outline-info:focus.disabled:before{display:none}.btn-inverse-info{color:#006daa;border-color:transparent;background-color:#fff}.btn-inverse-info:not(:disabled):not(.disabled):hover{color:#005484;background-color:#ececec;border-color:transparent}.btn-inverse-info.disabled,.btn-inverse-info:disabled{color:#006daa;background-color:#fff}.btn-inverse-info:not(:disabled):not(.disabled):active,.btn-inverse-info:not(:disabled):not(.disabled).active,.show>.btn-inverse-info.dropdown-toggle{color:#004c77;background:#eee}.btn-inverse-info.focus,.btn-inverse-info:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-info.focus:before,.btn-inverse-info:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-info.focus.btn-lg:before,.btn-group-lg>.btn-inverse-info.focus.btn:before,.btn-inverse-info:focus.btn-lg:before,.btn-group-lg>.btn-inverse-info.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-info.focus.btn-sm:before,.btn-group-sm>.btn-inverse-info.focus.btn:before,.btn-inverse-info:focus.btn-sm:before,.btn-group-sm>.btn-inverse-info.btn:focus:before{border-radius:.375rem}.btn-inverse-info.focus:active:before,.btn-inverse-info.focus.active:before,.btn-inverse-info:focus:active:before,.btn-inverse-info:focus.active:before{opacity:.75}.btn-inverse-info.focus:disabled:before,.btn-inverse-info.focus.disabled:before,.btn-inverse-info:focus:disabled:before,.btn-inverse-info:focus.disabled:before{display:none}.btn-inverse-outline-info{color:#fff;border-color:#fff}.btn-inverse-outline-info:hover{color:#005788;background-color:#f0f6fa;border-color:transparent}.btn-inverse-outline-info.disabled,.btn-inverse-outline-info:disabled{color:#fff;background-color:transparent;border-color:#fff}.btn-inverse-outline-info:not(:disabled):not(.disabled):active,.btn-inverse-outline-info:not(:disabled):not(.disabled).active,.show>.btn-inverse-outline-info.dropdown-toggle{color:#454545;background-color:#f0f6fa;border-color:transparent}.btn-inverse-outline-info.focus,.btn-inverse-outline-info:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-outline-info.focus:before,.btn-inverse-outline-info:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-outline-info.focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-info.focus.btn:before,.btn-inverse-outline-info:focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-info.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-outline-info.focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-info.focus.btn:before,.btn-inverse-outline-info:focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-info.btn:focus:before{border-radius:.375rem}.btn-inverse-outline-info.focus:active:before,.btn-inverse-outline-info.focus.active:before,.btn-inverse-outline-info:focus:active:before,.btn-inverse-outline-info:focus.active:before{opacity:.75}.btn-inverse-outline-info.focus:disabled:before,.btn-inverse-outline-info.focus.disabled:before,.btn-inverse-outline-info:focus:disabled:before,.btn-inverse-outline-info:focus.disabled:before{display:none}.btn-warning{color:#454545;background-color:#ffd900;border-color:#ffd900}.btn-warning:hover{color:#454545;background-color:#ccae00;border-color:#ccae00}.btn-warning.disabled,.btn-warning:disabled{color:#454545;background-color:#ffd900;border-color:#ffd900}.btn-warning:not(:disabled):not(.disabled):active,.btn-warning:not(:disabled):not(.disabled).active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#b39800;border-color:#b39800}.btn-warning.focus,.btn-warning:focus{position:relative;outline:0;box-shadow:none}.btn-warning.focus:before,.btn-warning:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFD900;border-radius:calc(.375rem + 4px)}.btn-warning.focus.btn-lg:before,.btn-group-lg>.btn-warning.focus.btn:before,.btn-warning:focus.btn-lg:before,.btn-group-lg>.btn-warning.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-warning.focus.btn-sm:before,.btn-group-sm>.btn-warning.focus.btn:before,.btn-warning:focus.btn-sm:before,.btn-group-sm>.btn-warning.btn:focus:before{border-radius:.375rem}.btn-warning.focus:active:before,.btn-warning.focus.active:before,.btn-warning:focus:active:before,.btn-warning:focus.active:before{opacity:.75}.btn-warning.focus:disabled:before,.btn-warning.focus.disabled:before,.btn-warning:focus:disabled:before,.btn-warning:focus.disabled:before{display:none}.btn-outline-warning{color:#ffd900;border-color:#ffd900}.btn-outline-warning:hover{color:#ccae00;background-color:#fffdf0;border-color:#b39800}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffd900;background-color:transparent;border-color:#ffd900}.btn-outline-warning:not(:disabled):not(.disabled):active,.btn-outline-warning:not(:disabled):not(.disabled).active,.show>.btn-outline-warning.dropdown-toggle{color:#454545;background-color:#fffdf0;border-color:#b39800}.btn-outline-warning.focus,.btn-outline-warning:focus{position:relative;outline:0;box-shadow:none}.btn-outline-warning.focus:before,.btn-outline-warning:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFD900;border-radius:calc(.375rem + 4px)}.btn-outline-warning.focus.btn-lg:before,.btn-group-lg>.btn-outline-warning.focus.btn:before,.btn-outline-warning:focus.btn-lg:before,.btn-group-lg>.btn-outline-warning.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-outline-warning.focus.btn-sm:before,.btn-group-sm>.btn-outline-warning.focus.btn:before,.btn-outline-warning:focus.btn-sm:before,.btn-group-sm>.btn-outline-warning.btn:focus:before{border-radius:.375rem}.btn-outline-warning.focus:active:before,.btn-outline-warning.focus.active:before,.btn-outline-warning:focus:active:before,.btn-outline-warning:focus.active:before{opacity:.75}.btn-outline-warning.focus:disabled:before,.btn-outline-warning.focus.disabled:before,.btn-outline-warning:focus:disabled:before,.btn-outline-warning:focus.disabled:before{display:none}.btn-inverse-warning{color:#ffd900;border-color:transparent;background-color:#454545}.btn-inverse-warning:not(:disabled):not(.disabled):hover{color:#d9b800;background-color:#323232;border-color:transparent}.btn-inverse-warning.disabled,.btn-inverse-warning:disabled{color:#ffd900;background-color:#454545}.btn-inverse-warning:not(:disabled):not(.disabled):active,.btn-inverse-warning:not(:disabled):not(.disabled).active,.show>.btn-inverse-warning.dropdown-toggle{color:#ccae00;background:#eee}.btn-inverse-warning.focus,.btn-inverse-warning:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-warning.focus:before,.btn-inverse-warning:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-warning.focus.btn-lg:before,.btn-group-lg>.btn-inverse-warning.focus.btn:before,.btn-inverse-warning:focus.btn-lg:before,.btn-group-lg>.btn-inverse-warning.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-warning.focus.btn-sm:before,.btn-group-sm>.btn-inverse-warning.focus.btn:before,.btn-inverse-warning:focus.btn-sm:before,.btn-group-sm>.btn-inverse-warning.btn:focus:before{border-radius:.375rem}.btn-inverse-warning.focus:active:before,.btn-inverse-warning.focus.active:before,.btn-inverse-warning:focus:active:before,.btn-inverse-warning:focus.active:before{opacity:.75}.btn-inverse-warning.focus:disabled:before,.btn-inverse-warning.focus.disabled:before,.btn-inverse-warning:focus:disabled:before,.btn-inverse-warning:focus.disabled:before{display:none}.btn-inverse-outline-warning{color:#fff;border-color:#fff}.btn-inverse-outline-warning:hover{color:#ccae00;background-color:#fffdf0;border-color:transparent}.btn-inverse-outline-warning.disabled,.btn-inverse-outline-warning:disabled{color:#fff;background-color:transparent;border-color:#fff}.btn-inverse-outline-warning:not(:disabled):not(.disabled):active,.btn-inverse-outline-warning:not(:disabled):not(.disabled).active,.show>.btn-inverse-outline-warning.dropdown-toggle{color:#454545;background-color:#fffdf0;border-color:transparent}.btn-inverse-outline-warning.focus,.btn-inverse-outline-warning:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-outline-warning.focus:before,.btn-inverse-outline-warning:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-outline-warning.focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-warning.focus.btn:before,.btn-inverse-outline-warning:focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-warning.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-outline-warning.focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-warning.focus.btn:before,.btn-inverse-outline-warning:focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-warning.btn:focus:before{border-radius:.375rem}.btn-inverse-outline-warning.focus:active:before,.btn-inverse-outline-warning.focus.active:before,.btn-inverse-outline-warning:focus:active:before,.btn-inverse-outline-warning:focus.active:before{opacity:.75}.btn-inverse-outline-warning.focus:disabled:before,.btn-inverse-outline-warning.focus.disabled:before,.btn-inverse-outline-warning:focus:disabled:before,.btn-inverse-outline-warning:focus.disabled:before{display:none}.btn-danger{color:#fff;background-color:#c32d3a;border-color:#c32d3a}.btn-danger:hover{color:#fff;background-color:#9c242e;border-color:#9c242e}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#c32d3a;border-color:#c32d3a}.btn-danger:not(:disabled):not(.disabled):active,.btn-danger:not(:disabled):not(.disabled).active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#892029;border-color:#892029}.btn-danger.focus,.btn-danger:focus{position:relative;outline:0;box-shadow:none}.btn-danger.focus:before,.btn-danger:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #C32D3A;border-radius:calc(.375rem + 4px)}.btn-danger.focus.btn-lg:before,.btn-group-lg>.btn-danger.focus.btn:before,.btn-danger:focus.btn-lg:before,.btn-group-lg>.btn-danger.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-danger.focus.btn-sm:before,.btn-group-sm>.btn-danger.focus.btn:before,.btn-danger:focus.btn-sm:before,.btn-group-sm>.btn-danger.btn:focus:before{border-radius:.375rem}.btn-danger.focus:active:before,.btn-danger.focus.active:before,.btn-danger:focus:active:before,.btn-danger:focus.active:before{opacity:.75}.btn-danger.focus:disabled:before,.btn-danger.focus.disabled:before,.btn-danger:focus:disabled:before,.btn-danger:focus.disabled:before{display:none}.btn-outline-danger{color:#c32d3a;border-color:#c32d3a}.btn-outline-danger:hover{color:#9c242e;background-color:#fbf2f3;border-color:#892029}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#c32d3a;background-color:transparent;border-color:#c32d3a}.btn-outline-danger:not(:disabled):not(.disabled):active,.btn-outline-danger:not(:disabled):not(.disabled).active,.show>.btn-outline-danger.dropdown-toggle{color:#454545;background-color:#fbf2f3;border-color:#892029}.btn-outline-danger.focus,.btn-outline-danger:focus{position:relative;outline:0;box-shadow:none}.btn-outline-danger.focus:before,.btn-outline-danger:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #C32D3A;border-radius:calc(.375rem + 4px)}.btn-outline-danger.focus.btn-lg:before,.btn-group-lg>.btn-outline-danger.focus.btn:before,.btn-outline-danger:focus.btn-lg:before,.btn-group-lg>.btn-outline-danger.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-outline-danger.focus.btn-sm:before,.btn-group-sm>.btn-outline-danger.focus.btn:before,.btn-outline-danger:focus.btn-sm:before,.btn-group-sm>.btn-outline-danger.btn:focus:before{border-radius:.375rem}.btn-outline-danger.focus:active:before,.btn-outline-danger.focus.active:before,.btn-outline-danger:focus:active:before,.btn-outline-danger:focus.active:before{opacity:.75}.btn-outline-danger.focus:disabled:before,.btn-outline-danger.focus.disabled:before,.btn-outline-danger:focus:disabled:before,.btn-outline-danger:focus.disabled:before{display:none}.btn-inverse-danger{color:#c32d3a;border-color:transparent;background-color:#fff}.btn-inverse-danger:not(:disabled):not(.disabled):hover{color:#a42631;background-color:#ececec;border-color:transparent}.btn-inverse-danger.disabled,.btn-inverse-danger:disabled{color:#c32d3a;background-color:#fff}.btn-inverse-danger:not(:disabled):not(.disabled):active,.btn-inverse-danger:not(:disabled):not(.disabled).active,.show>.btn-inverse-danger.dropdown-toggle{color:#9a232e;background:#eee}.btn-inverse-danger.focus,.btn-inverse-danger:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-danger.focus:before,.btn-inverse-danger:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-danger.focus.btn-lg:before,.btn-group-lg>.btn-inverse-danger.focus.btn:before,.btn-inverse-danger:focus.btn-lg:before,.btn-group-lg>.btn-inverse-danger.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-danger.focus.btn-sm:before,.btn-group-sm>.btn-inverse-danger.focus.btn:before,.btn-inverse-danger:focus.btn-sm:before,.btn-group-sm>.btn-inverse-danger.btn:focus:before{border-radius:.375rem}.btn-inverse-danger.focus:active:before,.btn-inverse-danger.focus.active:before,.btn-inverse-danger:focus:active:before,.btn-inverse-danger:focus.active:before{opacity:.75}.btn-inverse-danger.focus:disabled:before,.btn-inverse-danger.focus.disabled:before,.btn-inverse-danger:focus:disabled:before,.btn-inverse-danger:focus.disabled:before{display:none}.btn-inverse-outline-danger{color:#fff;border-color:#fff}.btn-inverse-outline-danger:hover{color:#9c242e;background-color:#fbf2f3;border-color:transparent}.btn-inverse-outline-danger.disabled,.btn-inverse-outline-danger:disabled{color:#fff;background-color:transparent;border-color:#fff}.btn-inverse-outline-danger:not(:disabled):not(.disabled):active,.btn-inverse-outline-danger:not(:disabled):not(.disabled).active,.show>.btn-inverse-outline-danger.dropdown-toggle{color:#454545;background-color:#fbf2f3;border-color:transparent}.btn-inverse-outline-danger.focus,.btn-inverse-outline-danger:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-outline-danger.focus:before,.btn-inverse-outline-danger:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-outline-danger.focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-danger.focus.btn:before,.btn-inverse-outline-danger:focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-danger.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-outline-danger.focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-danger.focus.btn:before,.btn-inverse-outline-danger:focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-danger.btn:focus:before{border-radius:.375rem}.btn-inverse-outline-danger.focus:active:before,.btn-inverse-outline-danger.focus.active:before,.btn-inverse-outline-danger:focus:active:before,.btn-inverse-outline-danger:focus.active:before{opacity:.75}.btn-inverse-outline-danger.focus:disabled:before,.btn-inverse-outline-danger.focus.disabled:before,.btn-inverse-outline-danger:focus:disabled:before,.btn-inverse-outline-danger:focus.disabled:before{display:none}.btn-light{color:#454545;background-color:#e1dddb;border-color:#e1dddb}.btn-light:hover{color:#454545;background-color:#b4b1af;border-color:#b4b1af}.btn-light.disabled,.btn-light:disabled{color:#454545;background-color:#e1dddb;border-color:#e1dddb}.btn-light:not(:disabled):not(.disabled):active,.btn-light:not(:disabled):not(.disabled).active,.show>.btn-light.dropdown-toggle{color:#454545;background-color:#9e9b99;border-color:#9e9b99}.btn-light.focus,.btn-light:focus{position:relative;outline:0;box-shadow:none}.btn-light.focus:before,.btn-light:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #E1DDDB;border-radius:calc(.375rem + 4px)}.btn-light.focus.btn-lg:before,.btn-group-lg>.btn-light.focus.btn:before,.btn-light:focus.btn-lg:before,.btn-group-lg>.btn-light.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-light.focus.btn-sm:before,.btn-group-sm>.btn-light.focus.btn:before,.btn-light:focus.btn-sm:before,.btn-group-sm>.btn-light.btn:focus:before{border-radius:.375rem}.btn-light.focus:active:before,.btn-light.focus.active:before,.btn-light:focus:active:before,.btn-light:focus.active:before{opacity:.75}.btn-light.focus:disabled:before,.btn-light.focus.disabled:before,.btn-light:focus:disabled:before,.btn-light:focus.disabled:before{display:none}.btn-outline-light{color:#e1dddb;border-color:#e1dddb}.btn-outline-light:hover{color:#b4b1af;background-color:#fdfdfd;border-color:#9e9b99}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#e1dddb;background-color:transparent;border-color:#e1dddb}.btn-outline-light:not(:disabled):not(.disabled):active,.btn-outline-light:not(:disabled):not(.disabled).active,.show>.btn-outline-light.dropdown-toggle{color:#454545;background-color:#fdfdfd;border-color:#9e9b99}.btn-outline-light.focus,.btn-outline-light:focus{position:relative;outline:0;box-shadow:none}.btn-outline-light.focus:before,.btn-outline-light:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #E1DDDB;border-radius:calc(.375rem + 4px)}.btn-outline-light.focus.btn-lg:before,.btn-group-lg>.btn-outline-light.focus.btn:before,.btn-outline-light:focus.btn-lg:before,.btn-group-lg>.btn-outline-light.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-outline-light.focus.btn-sm:before,.btn-group-sm>.btn-outline-light.focus.btn:before,.btn-outline-light:focus.btn-sm:before,.btn-group-sm>.btn-outline-light.btn:focus:before{border-radius:.375rem}.btn-outline-light.focus:active:before,.btn-outline-light.focus.active:before,.btn-outline-light:focus:active:before,.btn-outline-light:focus.active:before{opacity:.75}.btn-outline-light.focus:disabled:before,.btn-outline-light.focus.disabled:before,.btn-outline-light:focus:disabled:before,.btn-outline-light:focus.disabled:before{display:none}.btn-inverse-light{color:#e1dddb;border-color:transparent;background-color:#454545}.btn-inverse-light:not(:disabled):not(.disabled):hover{color:#d0c9c6;background-color:#323232;border-color:transparent}.btn-inverse-light.disabled,.btn-inverse-light:disabled{color:#e1dddb;background-color:#454545}.btn-inverse-light:not(:disabled):not(.disabled):active,.btn-inverse-light:not(:disabled):not(.disabled).active,.show>.btn-inverse-light.dropdown-toggle{color:#cac3bf;background:#eee}.btn-inverse-light.focus,.btn-inverse-light:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-light.focus:before,.btn-inverse-light:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-light.focus.btn-lg:before,.btn-group-lg>.btn-inverse-light.focus.btn:before,.btn-inverse-light:focus.btn-lg:before,.btn-group-lg>.btn-inverse-light.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-light.focus.btn-sm:before,.btn-group-sm>.btn-inverse-light.focus.btn:before,.btn-inverse-light:focus.btn-sm:before,.btn-group-sm>.btn-inverse-light.btn:focus:before{border-radius:.375rem}.btn-inverse-light.focus:active:before,.btn-inverse-light.focus.active:before,.btn-inverse-light:focus:active:before,.btn-inverse-light:focus.active:before{opacity:.75}.btn-inverse-light.focus:disabled:before,.btn-inverse-light.focus.disabled:before,.btn-inverse-light:focus:disabled:before,.btn-inverse-light:focus.disabled:before{display:none}.btn-inverse-outline-light{color:#fff;border-color:#fff}.btn-inverse-outline-light:hover{color:#b4b1af;background-color:#fdfdfd;border-color:transparent}.btn-inverse-outline-light.disabled,.btn-inverse-outline-light:disabled{color:#fff;background-color:transparent;border-color:#fff}.btn-inverse-outline-light:not(:disabled):not(.disabled):active,.btn-inverse-outline-light:not(:disabled):not(.disabled).active,.show>.btn-inverse-outline-light.dropdown-toggle{color:#454545;background-color:#fdfdfd;border-color:transparent}.btn-inverse-outline-light.focus,.btn-inverse-outline-light:focus{position:relative;outline:0;box-shadow:none}.btn-inverse-outline-light.focus:before,.btn-inverse-outline-light:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #FFFFFF;border-radius:calc(.375rem + 4px)}.btn-inverse-outline-light.focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-light.focus.btn:before,.btn-inverse-outline-light:focus.btn-lg:before,.btn-group-lg>.btn-inverse-outline-light.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-inverse-outline-light.focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-light.focus.btn:before,.btn-inverse-outline-light:focus.btn-sm:before,.btn-group-sm>.btn-inverse-outline-light.btn:focus:before{border-radius:.375rem}.btn-inverse-outline-light.focus:active:before,.btn-inverse-outline-light.focus.active:before,.btn-inverse-outline-light:focus:active:before,.btn-inverse-outline-light:focus.active:before{opacity:.75}.btn-inverse-outline-light.focus:disabled:before,.btn-inverse-outline-light.focus.disabled:before,.btn-inverse-outline-light:focus:disabled:before,.btn-inverse-outline-light:focus.disabled:before{display:none}.btn-dark{color:#fff;background-color:#273f2f;border-color:#273f2f}.btn-dark:hover{color:#fff;background-color:#1f3226;border-color:#1f3226}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#273f2f;border-color:#273f2f}.btn-dark:not(:disabled):not(.disabled):active,.btn-dark:not(:disabled):not(.disabled).active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1b2c21;border-color:#1b2c21}.btn-dark.focus,.btn-dark:focus{position:relative;outline:0;box-shadow:none}.btn-dark.focus:before,.btn-dark:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #273F2F;border-radius:calc(.375rem + 4px)}.btn-dark.focus.btn-lg:before,.btn-group-lg>.btn-dark.focus.btn:before,.btn-dark:focus.btn-lg:before,.btn-group-lg>.btn-dark.btn:focus:before{border-radius:calc(.375rem + 4px)}.btn-dark.focus.btn-sm:before,.btn-group-sm>.btn-dark.focus.btn:before,.btn-dark:focus.btn-sm:before,.btn-group-sm>.btn-dark.btn:focus:before{border-radius:.375rem}.btn-dark.focus:active:before,.btn-dark.focus.active:before,.btn-dark:focus:active:before,.btn-dark:focus.active:before{opacity:.75}.btn-dark.focus:disabled:before,.btn-dark.focus.disabled:before,.btn-dark:focus:disabled:before,.btn-dark:focus.disabled:before{display:none}.btn-outline-dark{color:#273f2f;border-color:#273f2f}.btn-outline-dark:hover{color:#1f3226;background-color:#f2f3f3;border-color:#1b2c21}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#273f2f;background-color:transparent;border-color:#273f2f}.btn-outline-dark:not(:disabled):not(.disabled):active,.btn-outline-dark:not(:disabled):not(.disabled).active,.show>.btn-outline-dark.dropdown-toggle{color:#454545;background-color:#f2f3f3;border-color:#1b2c21}.btn-outline-dark.focus,.btn-outline-dark:focus{position:relative;outline:0;box-shadow:none}.btn-outline-dark.focus:before,.btn-outline-dark:focus:before{content:"";position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;border:solid 2px #273F2F;border-radius:calc(.375rem + 4px)}.btn-outline-dark.focus.btn-lg:before,.btn-group-lg>.btn-outline-dark.focus.btn:before,.btn-outline-dark:focus.btn-lg…
  • Loading branch information
xitij2000 committed Dec 9, 2024
1 parent e58573b commit a989fa9
Show file tree
Hide file tree
Showing 69 changed files with 99,031 additions and 618 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/ci-frontend.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Frontend CI

on:
push:
branches:
- main
pull_request:

jobs:
tests:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./frontend
strategy:
matrix:
npm_script:
- lint
- coverage
- check-build
steps:
- uses: actions/checkout@v4
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VER }}
- run: npm ci
- run: npm run ${{ matrix.npm_script }}
- name: upload coverage
uses: codecov/codecov-action@v4
if: matrix.npm_script == 'coverage'
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
flags: frontend
18 changes: 10 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@ jobs:
strategy:
matrix:
os: [ubuntu-20.04]
python-version: ['3.8']
toxenv: [quality, docs, django32, django40]
toxenv: [quality, docs, py38-django32, py311-django42, py312-django42]

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: setup python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
python-version: |
3.8
3.11
3.12
- name: Install pip
run: pip install -r requirements/pip.txt
Expand All @@ -37,9 +39,9 @@ jobs:
run: tox

- name: Run coverage
if: matrix.python-version == '3.8' && matrix.toxenv == 'django32'
if: matrix.toxenv == 'py38-django32'
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: unittests
fail_ci_if_error: true
flags: python
fail_ci_if_error: false
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pip-log.txt
.tox
coverage.xml
htmlcov/
coverage/

# Virtual environments
/venv/
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ FROM openedx/xblock-sdk
RUN mkdir -p /usr/local/src/xblock-accordion
VOLUME ["/usr/local/src/xblock-accordion"]
RUN apt-get update && apt-get install -y gettext
RUN echo "pip install -r /usr/local/src/xblock-accordion/requirements.txt" >> /usr/local/src/xblock-sdk/install_and_run_xblock.sh
RUN echo "pip install -r /usr/local/src/xblock-accordion/requirements/dev.txt" >> /usr/local/src/xblock-sdk/install_and_run_xblock.sh
RUN echo "pip install -e /usr/local/src/xblock-accordion" >> /usr/local/src/xblock-sdk/install_and_run_xblock.sh
RUN echo "cd /usr/local/src/xblock-accordion && make compile_translations && cd /usr/local/src/xblock-sdk" >> /usr/local/src/xblock-sdk/install_and_run_xblock.sh
RUN echo "exec python /usr/local/src/xblock-sdk/manage.py \"\$@\"" >> /usr/local/src/xblock-sdk/install_and_run_xblock.sh
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ dev.build:
docker build -t $(REPO_NAME)-dev $(CURDIR)

dev.run: dev.clean dev.build ## Clean, build and run test image
docker run -p 8000:8000 -v $(CURDIR):/usr/local/src/$(REPO_NAME) --name $(REPO_NAME)-dev $(REPO_NAME)-dev
docker run -p 8200:8000 -v $(CURDIR):/usr/local/src/$(REPO_NAME) --name $(REPO_NAME)-dev $(REPO_NAME)-dev

## Localization targets

Expand Down
147 changes: 18 additions & 129 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,142 +10,31 @@ This XBlock comes with a Docker test environment ready to build, based on the xb

The XBlock SDK Workbench, including this XBlock, will be available on the list of XBlocks at http://localhost:8000

Translating
*************

Internationalization (i18n) is when a program is made aware of multiple languages.
Localization (l10n) is adapting a program to local language and cultural habits.

Use the locale directory to provide internationalized strings for your XBlock project.
For more information on how to enable translations, visit the
`Open edX XBlock tutorial on Internationalization <https://edx.readthedocs.org/projects/xblock-tutorial/en/latest/edx_platform/edx_lms.html>`_.

This cookiecutter template uses `django-statici18n <https://django-statici18n.readthedocs.io/en/latest/>`_
to provide translations to static javascript using ``gettext``.

The included Makefile contains targets for extracting, compiling and validating translatable strings.
The general steps to provide multilingual messages for a Python program (or an XBlock) are:

1. Mark translatable strings.
2. Run i18n tools to create raw message catalogs.
3. Create language specific translations for each message in the catalogs.
4. Use ``gettext`` to translate strings.

1. Mark translatable strings
=============================

Mark translatable strings in python::


from django.utils.translation import ugettext as _

# Translators: This comment will appear in the `.po` file.
message = _("This will be marked.")

See `edx-developer-guide <https://edx.readthedocs.io/projects/edx-developer-guide/en/latest/internationalization/i18n.html#python-source-code>`__
for more information.

You can also use ``gettext`` to mark strings in javascript::


// Translators: This comment will appear in the `.po` file.
var message = gettext("Custom message.");

See `edx-developer-guide <https://edx.readthedocs.io/projects/edx-developer-guide/en/latest/internationalization/i18n.html#javascript-files>`__
for more information.

2. Run i18n tools to create Raw message catalogs
=================================================

This cookiecutter template offers multiple make targets which are shortcuts to
use `edx-i18n-tools <https://github.com/openedx/i18n-tools>`_.

After marking strings as translatable we have to create the raw message catalogs.
These catalogs are created in ``.po`` files. For more information see
`GNU PO file documentation <https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html>`_.
These catalogs can be created by running::


$ make extract_translations

The previous command will create the necessary ``.po`` files under
``xblock-accordion/accordion/conf/locale/en/LC_MESSAGES/text.po``.
The ``text.po`` file is created from the ``django-partial.po`` file created by
``django-admin makemessages`` (`makemessages documentation <https://docs.djangoproject.com/en/3.2/topics/i18n/translation/#message-files>`_),
this is why you will not see a ``django-partial.po`` file.

3. Create language specific translations
==============================================

3.1 Add translated strings
---------------------------

After creating the raw message catalogs, all translations should be filled out by the translator.
One or more translators must edit the entries created in the message catalog, i.e. the ``.po`` file(s).
The format of each entry is as follows::

# translator-comments
A. extracted-comments
#: reference…
#, flag…
#| msgid previous-untranslated-string
msgid 'untranslated message'
msgstr 'mensaje traducido (translated message)'

For more information see
`GNU PO file documentation <https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html>`_.

To use translations from transifex use the follow Make target to pull translations::

$ make pull_translations

See `config instructions <https://github.com/openedx/i18n-tools#transifex-commands>`_ for information on how to set up your
transifex credentials.

See `transifex documentation <https://docs.transifex.com/integrations/django>`_ for more details about integrating
django with transiflex.

3.2 Compile translations
-------------------------

Once translations are in place, use the following Make target to compile the translation catalogs ``.po`` into
``.mo`` message files::

$ make compile_translations

The previous command will compile ``.po`` files using
``django-admin compilemessages`` (`compilemessages documentation <https://docs.djangoproject.com/en/3.2/topics/i18n/translation/#compiling-message-files>`_).
After compiling the ``.po`` file(s), ``django-statici18n`` is used to create language specific catalogs. See
``django-statici18n`` `documentation <https://django-statici18n.readthedocs.io/en/latest/>`_ for more information.

To upload translations to transiflex use the follow Make target::

$ make push_translations

See `config instructions <https://github.com/openedx/i18n-tools#transifex-commands>`_ for information on how to set up your
transifex credentials.
Testing frontend
****************

See `transifex documentation <https://docs.transifex.com/integrations/django>`_ for more details about integrating
django with transiflex.
The frontend code in the XBlock can be tested without the backend running for quicker iteration on the UI.
To do this, just run ``npm run dev`` in the `frontend` folder. This will start a dev server with just the
frontend component. You can open the URL in the terminal and browse to the student or studio components.

**Note:** The ``dev.run`` make target will automatically compile any translations.
Installation
************

**Note:** To check if the source translation files (``.po``) are up-to-date run::
You can install the XBlock with the following command:

$ make detect_changed_source_translations
.. code-block:: bash
4. Use ``gettext`` to translate strings
========================================
pip install xblock-accordion@git+https://github.com/open-craft/xblock-accordion.git
Django will automatically use ``gettext`` and the compiled translations to translate strings.
If using tutor, you can add `xblock-accordion@git+https://github.com/open-craft/xblock-accordion.git` to
`OPENEDX_EXTRA_PIP_REQUIREMENTS` to have the xblock installed on deployment.

Troubleshooting
****************

If there are any errors compiling ``.po`` files run the following command to validate your ``.po`` files::
Usage
*****

$ make validate
To use the XBlock in a course, you need to ensure that you have "accordion" in the
**Advanced Modules List** in the Advanced settings page of the course.

See `django's i18n troubleshooting documentation
<https://docs.djangoproject.com/en/3.2/topics/i18n/translation/#troubleshooting-gettext-incorrectly-detects-python-format-in-strings-with-percent-signs>`_
for more information.
Once added there you will be able to add an Accordion block from the **Advanced Modules**
section of blocks.
2 changes: 1 addition & 1 deletion accordion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

from .accordion import AccordionXBlock

__version__ = '0.1.0'
__version__ = "1.0.0"
Loading

0 comments on commit a989fa9

Please sign in to comment.