diff --git a/merge/.git-blame-ignore-revs b/merge/.git-blame-ignore-revs
new file mode 100644
index 000000000..8bb0f1de0
--- /dev/null
+++ b/merge/.git-blame-ignore-revs
@@ -0,0 +1,8 @@
+# Format all python with ruff
+47f713ddce20e3c751e3cbd05a8671dda8f9a054
+# Format all HTML with djLint
+0cd2a6561291dabccf435f19aca3f96323097e8b
+# Format login form with djLint
+07313f9fbb9384489c6aa57885f3fecba358f288
+# Reformat middleware file using ruff
+bc456a556e0df11b0f25420c5c6933c068adef10
diff --git a/merge/.gitattributes b/merge/.gitattributes
new file mode 100644
index 000000000..2accc33da
--- /dev/null
+++ b/merge/.gitattributes
@@ -0,0 +1 @@
+*.bat eol=crlf
diff --git a/merge/.github/workflows/format.yml b/merge/.github/workflows/format.yml
new file mode 100644
index 000000000..f88184ad9
--- /dev/null
+++ b/merge/.github/workflows/format.yml
@@ -0,0 +1,35 @@
+name: Verify formatting
+
+on:
+ push:
+ branches: main
+ pull_request:
+
+jobs:
+ ruff:
+ runs-on: ubuntu-latest
+ name: Verify Python formatting
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: astral-sh/ruff-action@v1
+ with:
+ args: "format --check"
+ changed-files: 'true'
+
+ djlint:
+ runs-on: ubuntu-latest
+ name: Check HTML formatting
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+
+ - name: Install dependencies
+ run: |
+ pip install -U pip
+ pip install djlint
+
+ - name: Format HTML
+ run: djlint . --check
diff --git a/merge/.github/workflows/lint.yml b/merge/.github/workflows/lint.yml
new file mode 100644
index 000000000..c36d946e8
--- /dev/null
+++ b/merge/.github/workflows/lint.yml
@@ -0,0 +1,33 @@
+name: Lint codebase
+
+on:
+ push:
+ branches: main
+ pull_request:
+
+jobs:
+ ruff:
+ runs-on: ubuntu-latest
+ name: Lint Python
+ steps:
+ - uses: actions/checkout@v4
+ - uses: astral-sh/ruff-action@v1
+ with:
+ changed-files: 'true'
+
+ djlint:
+ runs-on: ubuntu-latest
+ name: Lint HTML
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+
+ - name: Install dependencies
+ run: |
+ pip install -U pip
+ pip install djlint
+
+ - name: Lint
+ run: djlint --lint .
diff --git a/merge/.gitignore b/merge/.gitignore
new file mode 100644
index 000000000..532f6fa4e
--- /dev/null
+++ b/merge/.gitignore
@@ -0,0 +1,23 @@
+.*
+
+*.egg
+*.egg-info
+*.py[cio]
+*.sw?
+
+__pycache__/
+build/
+dist/
+
+# Tailwind CSS
+src/argus_htmx/static/styles.css
+# Autogenerated via management command
+src/argus_htmx/tailwindtheme/tailwind.config.js
+# OS specific
+src/argus_htmx/tailwindtheme/tailwindcss
+
+!.git-blame-ignore-revs
+!.gitattributes
+!.gitignore
+!.pre-commit-config.yaml
+!.github
diff --git a/merge/.pre-commit-config.yaml b/merge/.pre-commit-config.yaml
new file mode 100644
index 000000000..232a38d96
--- /dev/null
+++ b/merge/.pre-commit-config.yaml
@@ -0,0 +1,20 @@
+repos:
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v3.2.0
+ hooks:
+ - id: trailing-whitespace
+ - id: mixed-line-ending
+ - id: end-of-file-fixer
+ - id: debug-statements
+- repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.7.2
+ hooks:
+ # Run the linter.
+ - id: ruff
+ # Run the formatter.
+ - id: ruff-format
+- repo: https://github.com/djlint/djLint
+ rev: v1.35.4
+ hooks:
+ - id: djlint-reformat-django
+ - id: djlint-django
diff --git a/merge/Makefile b/merge/Makefile
new file mode 100644
index 000000000..e48391485
--- /dev/null
+++ b/merge/Makefile
@@ -0,0 +1,28 @@
+.PHONY: clean testclean distclean coverageclean nuke tailwind
+
+TAILWINDDIR=src/argus_htmx/tailwindtheme
+STATICDIR=src/argus_htmx/static
+
+clean:
+ -find . -name __pycache__ -print0 | xargs -0 rm -rf
+ -find . -name "*.pyc" -print0 | xargs -0 rm -rf
+ -find . -name "*.egg-info" -print0 | xargs -0 rm -rf
+ -find . -name ".ruff_cache" -print0 | xargs -0 rm -rf
+
+distclean:
+ -rm -rf ./dist
+ -rm -rf ./build
+
+coverageclean:
+ -rm .coverage
+ -rm .coverage.*
+ -rm coverage.xml
+ -rm -rf htmlcov
+
+testclean: coverageclean clean
+ -rm -rf .tox
+
+nuke: clean distclean testclean
+
+tailwind:
+ tailwindcss -c $(TAILWINDDIR)/tailwind.config.js -i $(TAILWINDDIR)/styles.css -o $(STATICDIR)/styles.css
diff --git a/merge/README.rst b/merge/README.rst
new file mode 100644
index 000000000..ecb7e6647
--- /dev/null
+++ b/merge/README.rst
@@ -0,0 +1,320 @@
+===================
+argus-HTMx-frontend
+===================
+
+Experimental frontend for `argus-server`_ as a django app.
+
+Will possibly cease to exist as a separate app if the experiment is deemed
+successful.
+
+See `argus-server`_ for more about argus.
+
+Imports `django-htmx`_. See the `documentation for django-htmx`_
+for details.
+
+How to play
+===========
+
+Install
+-------
+
+To make sure you do not accidentally work on an old argus-server, do the following:
+
+1. Use/make a venv, for instance: create a new one with ``python -m venv argus-htmx``
+2. Check out argus-server code
+3. Install argus-server dynamically into the venv: ``pip install -e .``
+4. Check out this repo
+5. Install this app dynamically into the venv: ``pip install -e .``
+
+It is now safe to remove argus-server from the venv if you feel like it.
+
+Configure
+---------
+
+Do this in your workdir, which could be the checked out `argus-server`_ repo.
+
+This assumes that you have a local settings file (we recommend calling it
+"localsettings.py" since that is hidden by .gitignore) as a sibling of
+``src/``.
+
+At the top of this local settings file, copy the contents of
+``argus.htmx.settings``. This will base the settings-file on
+``argus.site.settings.backend`` and automatically use
+``argus.site.utils.update_settings`` with
+``argus_htmx.app_config.APP_SETTINGS`` to set/overwrite some settings and
+mutate others. Note the usage of ``globals()``; due to this, inheriting from
+``argus.htmx.settings`` will probably not work as expected.
+
+While developing you will probably prefer to swap out
+``argus.site.settings.backend`` with ``argus.site.settings.dev``, as the former
+is almost production-ready while the latter is tuned for development and
+depends on the optional dependencies you can install via ``pip install
+argus-server[dev]``.
+
+The ``argus.site.utils.update_settings`` function will add or change the settings
+
+* INSTALLED_APPS
+* LOGIN_REDIRECT_URL
+* LOGIN_URL
+* LOGOUT_REDIRECT_URL
+* LOGOUT_URL
+* MIDDLEWARE
+* PUBLIC_URLS
+* ROOT_URLCONF
+* TEMPLATES
+
+See ``argus_htmx.appconfig._app_settings`` for what is being set. The
+management command ``printsettings`` (which depends on the app
+``django-extensions``, a ``dev``-dependency) will print out the complete
+settings used.
+
+Customizing
+-----------
+
+If you add more pages and endpoints you will have to write your own root
+urls.py and set ROOT_URLCONF appropriately.
+
+If you have some other apps you want installed and configured, you could either
+add the necessary settings to your ``localsettings.py`` or use the extra-apps
+machinery. The later is especially useful during the development phase when you
+haven't settled on which apps to use yet.
+
+With extra-apps machinery
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You make a JSON-file which is read into your settings via one of two
+environment variables.
+
+In order to add apps and settings that *extend* ``argus-server`` and this
+``app`` you use the environment variable ``ARGUS_EXTRA_APPS``::
+
+ export ARGUS_EXTRA_APPS=`cat extra.json`
+
+If you want to *override* existing apps the environment variable to use is
+``ARGUS_OVERRIDING_APPS``::
+
+ export ARGUS_OVERRIDING_APPS=`cat overriding.json`
+
+Have a look at the contents of ``argus_htmx.appconfig._app_settings`` for an
+example of what you can set this way.
+
+You can merge your urlpatterns with the apps' urlpatterns via the
+``argus.site.utils.get_urlpatterns`` function, see ``argus.htmx.urls`` for an
+example.
+
+Optional authentication backend settings
+----------------------------------------
+
+If using ``django.contrib.auth.backends.RemoteUserBackend`` (which depends on
+the middleware ``django.contrib.auth.middleware.RemoteUserMiddleware``) there's
+an optional setting ``ARGUS_REMOTE_USER_METHOD_NAME`` to choose what to show on
+the button.
+
+If using ``social_core.backends.open_id_connect.OpenIdConnectAuth`` there's an
+optional setting ``ARGUS_OIDC_METHOD_NAME`` to choose what to show on the
+button.
+
+Both can be set via environment variables.
+
+Update
+======
+
+On every new version, reinstall the dependencies since there might be new ones.
+
+Themes and styling
+==================
+
+To try out daisyUI themes use the context processor
+``argus_htmx.context_processor.theme_via_session`` instead of
+``argus_htmx.context_processor.theme_via_GET``.
+
+Default included themes are: `light`, `dark` and `argus`.
+
+This project supports Tailwind CSS utility classes and daisyUI components for styling.
+Below is an overview of the stack, installation and build instructions, and configuration details for themes and styles.
+
+Overview
+--------
+* Tailwind CSS: A utility-first CSS framework for rapidly building custom user interfaces.
+* daisyUI: A component library for Tailwind CSS that provides a set of ready-to-use components as well as color themes.
+
+Installation and build instructions
+-----------------------------------
+Recommended but open for tweaks and adaptations steps:
+
+1. Get Tailwind standalone CLI bundled with daisyUI from
+ https://github.com/dobicinaitis/tailwind-cli-extra
+
+ Most linux::
+
+ $ curl -sL https://github.com/dobicinaitis/tailwind-cli-extra/releases/latest/download/tailwindcss-extra-linux-x64 -o /tmp/tailwindcss
+ $ chmod +x /tmp/tailwindcss
+
+ For other OSes see
+ https://github.com/dobicinaitis/tailwind-cli-extra/releases/latest/ and
+ update the bit after ``download/`` accordingly.
+
+ Optionally you can compile tailwind+daisyUI standalone cli bundle yourself as described here:
+ https://github.com/tailwindlabs/tailwindcss/discussions/12294#discussioncomment-8268378.
+2. (Linux/OsX) Move the tailwindcss file to your $PATH, for instance to ``~/bin/`` or ``.local/bin``.
+3. Go to the repo directory (parent of ``src/``)
+4. Build main stylesheet file using ``tailwindcss`` executable from step 1 and
+ pointing to the included config file:
+
+ Manually::
+
+ tailwindcss -c src/argus_htmx/tailwindtheme/tailwind.config.js -i src/argus_htmx/tailwindtheme/styles.css --output src/argus_htmx/static/styles.css
+
+ Running with the ``--watch`` flag for automatic update on change seems
+ error-prone so we've made it very easy to run the command, with ``make`` or ``tox``::
+
+ make tailwind
+ tox -e tailwind
+
+ Either will rebuild the styles for you.
+
+
+Customization
+-------------
+
+How to customize the look:
+
+* Override Argus' Tailwind CSS theme defaults and/or choose which daisyUI
+ color themes to include. You can do so by updating the default
+ ``TAILWIND_THEME_OVERRIDE`` and ``DAISYUI_THEMES`` values respectively
+ before running a ``tailwind_config`` management command:
+
+ Via environment variables, for example::
+
+ TAILWIND_THEME_OVERRIDE = '
+ {
+ "borderWidth": {
+ "DEFAULT": "1px"
+ },
+ "extend": {
+ "borderRadius": {
+ "4xl": "2rem"
+ }
+ }
+ }
+ '
+ DAISYUI_THEMES = '
+ [
+ "light",
+ "dark",
+ "cyberpunk",
+ "dim",
+ "autumn",
+ { "mytheme": {
+ "primary": "#009eb6",
+ "primary-content": "#00090c",
+ "secondary": "#00ac00",
+ "secondary-content": "#000b00",
+ "accent": "#ff0000",
+ "accent-content": "#160000",
+ "neutral": "#262c0e",
+ "neutral-content": "#cfd1ca",
+ "base-100": "#292129",
+ "base-200": "#221b22",
+ "base-300": "#1c161c",
+ "base-content": "#d0cdd0",
+ "info": "#00feff",
+ "info-content": "#001616",
+ "success": "#b1ea50",
+ "success-content": "#0c1302",
+ "warning": "#d86d00",
+ "warning-content": "#110400",
+ "error": "#ff6280",
+ "error-content": "#160306"
+ }
+ }
+ ]
+ '
+
+ Or by providing corresponding values in your local settings that star-imports from an `argus-server`_ settings file::
+
+ TAILWIND_THEME_OVERRIDE = {...}
+ DAISYUI_THEMES = [...]
+
+ Some links that may be relevant for the customization values mentioned above:
+ * `daisyUI themes`_
+ * `list of daisyUI color names`_
+ * `Tailwind CSS theme customization`_
+
+* Override the default main stylesheet path by setting
+ ``ARGUS_STYLESHEET_PATH`` in the environment. The path is under
+ ``STATIC_URL``. This depends on the context processor
+ ``argus_htmx.context_processors.path_to_stylesheet``.
+* Include additional styles/stylesheets using the ``head`` block in your templates.
+* Generate a Tailwind config file by running the ``tailwind_config`` management
+ command. By default the generated file will be based on
+ ``src/argus_htmx/tailwindtheme/tailwind.config.template.js`` and expected
+ values will be injected with reasonable defaults.
+
+UI Settings
+===========
+
+Incident table column customization
+-----------------------------------
+You can customize which columns are shown in the incidents listing table by overriding the
+``INCIDENT_TABLE_COLUMNS`` setting. This setting takes a list of ``str`` or
+``argus_htmx.incidents.customization.IncidentTableColumn`` instances. when given a ``str``, this
+key must be available in the ``argus_htmx.incidents.customization.BUILTIN_COLUMNS`` dictionary. For
+example::
+
+ from argus_htmx.incidents.customization import BUILTIN_COLUMNS, IncidentTableColumn
+
+ INCIDENT_TABLE_COLUMNS = [
+ "id",
+ "start_time",
+ BUILTIN_COLUMNS["description"], # equivalent to just "description"
+ IncidentTableColumn( # a new column definition
+ name="name",
+ label="Custom"
+ cell_template="/path/to/template.html"
+ context={
+ "additional": "value"
+ }
+ ),
+
+ ]
+
+For inbuilt support for other types of columns see the howtos in `the local docs `_.
+
+
+.. _django-htmx: https://github.com/adamchainz/django-htmx
+.. _argus-server: https://github.com/Uninett/Argus
+.. _documentation for django-htmx: https://django-htmx.readthedocs.io/en/latest/
+.. _daisyUI themes: https://daisyui.com/docs/themes/
+.. _list of daisyUI color names: https://daisyui.com/docs/colors/#-2
+.. _tailwind-cli-extra: https://github.com/dobicinaitis/tailwind-cli-extra
+.. _Tailwind CSS theme customization: https://tailwindcss.com/docs/theme
+
+Custom widget
+-------------
+
+Argus supports showing an extra widget next to the menubar in the incidents listing. This box can
+take the width of 1/3 of the window. You can add the widget by creating a context processor that
+injects an ``incidents_extra_widget`` variable that points to an html template::
+
+ def extra_widget(request):
+ return {
+ "incidents_extra_widget": "path/to/_extra_widget.html",
+ }
+
+*note* Don't forget to include the context processor in your settings
+
+You could then create ``path/to/_extra_widget.html`` as following::
+
+
+ My custom widget
+
+
+
+Page size
+---------
+
+By default, incidents are shown with a page size of ``10`` (ie. 10 rows at a time), and the user can
+select a different page size from ``[10, 20, 50, 100]``. It possible to override these settings by
+setting the ``ARGUS_INCIDENTS_DEFAULT_PAGE_SIZE`` and ``ARGUS_INCIDENTS_PAGE_SIZES`` setting
+respectively.
diff --git a/merge/docs/Makefile b/merge/docs/Makefile
new file mode 100644
index 000000000..d4bb2cbb9
--- /dev/null
+++ b/merge/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/merge/docs/conf.py b/merge/docs/conf.py
new file mode 100644
index 000000000..4cd01f8d8
--- /dev/null
+++ b/merge/docs/conf.py
@@ -0,0 +1,26 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+project = "argus-htmx-frontend"
+copyright = "2024, Geant+Sikt"
+author = "Geant+Sikt"
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+extensions = []
+
+templates_path = ["_templates"]
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+html_theme = "alabaster"
+html_static_path = ["_static"]
diff --git a/merge/docs/development/dependencies.rst b/merge/docs/development/dependencies.rst
new file mode 100644
index 000000000..f36f4053e
--- /dev/null
+++ b/merge/docs/development/dependencies.rst
@@ -0,0 +1,30 @@
+Upgrading HTMX to a new version
+===============================
+
+To upgrade the HTMX to a new version, follow these steps:
+
+1. **Backup Current File**:
+ Ensure you have a backup of the current `htmx-*.min.js` file located in the `static` directory.
+
+2. **Download the Latest Version**:
+ Obtain the latest version of minified HTMX from the `UNPKG`_, for example:
+ `https://unpkg.com/htmx.org@2.0.2/dist/htmx.min.js`
+
+3. **Replace Old File**:
+ Replace the old HTMX file in your project with the new file from the latest version. This involves updating the `htmx-*.min.js` file in the `static` directory. Make sure to replace the `*` in the file name with the new version number.
+
+4. **Update References**:
+ Ensure that all references to HTMX in your project point to the new version. This includes updating the script tag in ``htmx_base.html``::
+
+ <-- HTMX -->
+
+
+
+Upgrading Hyperscript to a new version
+======================================
+
+To upgrade Hyperscript to a new version, use the same steps as for upgrading HTMX but for the
+latest version of Hyperscript on `UNPKG`_, for example:
+`https://unpkg.com/hyperscript.org@0.9.13/dist/_hyperscript.min.js`
+
+.. _UNPKG: https://unpkg.com
diff --git a/merge/docs/howtos/column_for_specific_ack_group.rst b/merge/docs/howtos/column_for_specific_ack_group.rst
new file mode 100644
index 000000000..752cf8d50
--- /dev/null
+++ b/merge/docs/howtos/column_for_specific_ack_group.rst
@@ -0,0 +1,21 @@
+==================================================
+How to make a column for an ack with a known group
+==================================================
+
+You need a template for the cell, in ``src/argus_htmx/templates/htmx/incidents/_incident_ack_by_group.html``::
+
+ {% load argus_htmx %}
+ {% if incident|is_acked_by:column.context.group %}X{% endif %}
+
+You also need to add the column to the column config::
+
+ INCIDENT_TABLE_COLUMNS = [
+ ..
+ IncidentTableColumn(
+ "mygroup_ack",
+ "MYGROUP",
+ "htmx/incidents/_incident_ack_by_group.html",
+ context={"group": "MYGROUP"},
+ )
+ ..
+ ]
diff --git a/merge/docs/howtos/column_for_specific_tag.rst b/merge/docs/howtos/column_for_specific_tag.rst
new file mode 100644
index 000000000..72e443dca
--- /dev/null
+++ b/merge/docs/howtos/column_for_specific_tag.rst
@@ -0,0 +1,16 @@
+=======================================
+How to make a column for a specific tag
+=======================================
+
+You only need to add the column to the column config::
+
+ INCIDENT_TABLE_COLUMNS = [
+ ..
+ IncidentTableColumn(
+ "mytag",
+ "MYTAG",
+ "htmx/incidents/_incident_tag.html",
+ context={"tag": "MYTAG"},
+ )
+ ..
+ ]
diff --git a/merge/docs/index.rst b/merge/docs/index.rst
new file mode 100644
index 000000000..8da1daba3
--- /dev/null
+++ b/merge/docs/index.rst
@@ -0,0 +1,18 @@
+.. argus-htmx-frontend documentation master file, created by
+ sphinx-quickstart on Wed Jul 24 11:15:56 2024.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+argus-htmx-frontend documentation
+=================================
+
+Add your content using ``reStructuredText`` syntax. See the
+`reStructuredText `_
+documentation for details.
+
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents:
+
+ howtos/*
diff --git a/merge/docs/make.bat b/merge/docs/make.bat
new file mode 100644
index 000000000..954237b9b
--- /dev/null
+++ b/merge/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.https://www.sphinx-doc.org/
+ exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/merge/manage.py b/merge/manage.py
new file mode 100644
index 000000000..286534b29
--- /dev/null
+++ b/merge/manage.py
@@ -0,0 +1,22 @@
+#! /usr/bin/env python3
+
+import django
+
+from django.conf import settings
+from django.core.management import call_command
+
+settings.configure(
+ DEBUG=True,
+ INSTALLED_APPS=(
+ "django.contrib.contenttypes",
+ "django.contrib.auth",
+ "argus.auth",
+ "argus.incident",
+ "argus.notificationprofile",
+ "argus_htmx",
+ ),
+ MEDIA_PLUGINS=(),
+)
+
+django.setup()
+call_command("makemigrations", "argus_htmx")
diff --git a/merge/pyproject.toml b/merge/pyproject.toml
new file mode 100644
index 000000000..b2a491540
--- /dev/null
+++ b/merge/pyproject.toml
@@ -0,0 +1,45 @@
+[build-system]
+requires = ["flit_core >=3.2,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+description = "HTTP Frontend as a django app for argus-server using HTMx"
+name = "argus-htmx-frontend"
+readme = "README.rst"
+authors = [{name = "Hanne Moa", email = "hanne.moa@sikt.no"}]
+requires-python = ">=3.9"
+classifiers = [
+ "Development Status :: 2 - Pre-Alpha",
+ "Framework :: Django",
+ "Framework :: Django :: 4.2",
+ "Framework :: Django :: 5.0",
+ "Framework :: Django :: 5.1",
+ "Programming Language :: Python :: 3 :: Only",
+ "Environment :: Web Environment",
+]
+dynamic = ["version"]
+dependencies = [
+ "argus-server>=1.27.0",
+ "django-htmx",
+ "django-widget-tweaks==1.5.0",
+ "social-auth-core>=4.1",
+ "social-auth-app-django>=5.0",
+]
+
+[project.urls]
+Github = "https://github.com/Uninett/argus-htmx-frontend"
+
+[project.optional-dependencies]
+docs = ["sphinx"]
+
+[tool.flit.module]
+name = "argus_htmx"
+
+[tool.ruff]
+line-length = 120
+output-format = "full"
+
+[tool.djlint]
+profile="django"
+indent = 2
+ignore="H006"
diff --git a/merge/runtests.py b/merge/runtests.py
new file mode 100644
index 000000000..9d8e8de46
--- /dev/null
+++ b/merge/runtests.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+import os
+import sys
+
+import django
+from django.conf import settings
+from django.test.utils import get_runner
+
+if __name__ == "__main__":
+ os.environ["DJANGO_SETTINGS_MODULE"] = "tests.settings"
+ django.setup()
+ TestRunner = get_runner(settings)
+ test_runner = TestRunner()
+ failures = test_runner.run_tests(["tests"])
+ sys.exit(bool(failures))
diff --git a/merge/src/argus_htmx/__init__.py b/merge/src/argus_htmx/__init__.py
new file mode 100644
index 000000000..718d00032
--- /dev/null
+++ b/merge/src/argus_htmx/__init__.py
@@ -0,0 +1 @@
+__version__ = "0.17"
diff --git a/merge/src/argus_htmx/appconfig.py b/merge/src/argus_htmx/appconfig.py
new file mode 100644
index 000000000..534e60156
--- /dev/null
+++ b/merge/src/argus_htmx/appconfig.py
@@ -0,0 +1,46 @@
+from argus.site.settings._serializers import ListAppSetting
+
+
+__all__ = ["APP_SETTINGS"]
+
+_app_settings = [
+ {
+ "settings": {
+ "LOGIN_URL": "/accounts/login/",
+ "LOGOUT_URL": "/accounts/logout/",
+ "LOGIN_REDIRECT_URL": "/incidents/",
+ "LOGOUT_REDIRECT_URL": "/incidents/",
+ "PUBLIC_URLS": [
+ "/accounts/login/",
+ "/api/",
+ "/oidc/",
+ ],
+ },
+ },
+ {
+ "app_name": "argus_htmx",
+ "urls": {
+ "path": "",
+ "urlpatterns_module": "argus_htmx.urls",
+ },
+ "context_processors": [
+ "argus.auth.context_processors.preferences",
+ "argus_htmx.context_processors.path_to_stylesheet",
+ ],
+ "middleware": {
+ "argus_htmx.middleware.LoginRequiredMiddleware": "end",
+ "argus_htmx.middleware.HtmxMessageMiddleware": "end",
+ },
+ },
+ {
+ "app_name": "django_htmx",
+ "middleware": {
+ "django_htmx.middleware.HtmxMiddleware": "end",
+ },
+ },
+ {
+ "app_name": "widget_tweaks", # indent for readability
+ },
+]
+
+APP_SETTINGS = ListAppSetting(_app_settings).root
diff --git a/merge/src/argus_htmx/apps.py b/merge/src/argus_htmx/apps.py
new file mode 100644
index 000000000..cc6169b59
--- /dev/null
+++ b/merge/src/argus_htmx/apps.py
@@ -0,0 +1,16 @@
+import pathlib
+
+from django.apps import AppConfig
+
+
+class HtmxFrontendConfig(AppConfig):
+ default_auto_field = "django.db.models.BigAutoField"
+ label = "argus_htmx"
+ name = "argus_htmx"
+
+ def tailwind_css_files(self):
+ yield from pathlib.Path(__file__).parent.glob("tailwindtheme/snippets/*.css")
+
+ def ready(self):
+ # Register checks
+ from .checks import check_for_valid_themes_list # noqa: F401
diff --git a/merge/src/argus_htmx/auth/__init__.py b/merge/src/argus_htmx/auth/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/merge/src/argus_htmx/auth/views.py b/merge/src/argus_htmx/auth/views.py
new file mode 100644
index 000000000..09a62baa0
--- /dev/null
+++ b/merge/src/argus_htmx/auth/views.py
@@ -0,0 +1,53 @@
+from django.conf import settings
+from django.contrib.auth.views import LoginView as DjangoLoginView
+from django.urls import reverse
+
+from argus.auth.utils import (
+ has_model_backend,
+ has_remote_user_backend,
+ get_psa_authentication_backends,
+ get_authentication_backend_classes,
+)
+
+
+REMOTE_USER_METHOD_NAME = getattr(settings, "ARGUS_REMOTE_USER_METHOD_NAME", "REMOTE_USER")
+OIDC_METHOD_NAME = getattr(settings, "ARGUS_OIDC_METHOD_NAME", "OIDC")
+
+
+def get_htmx_authentication_backend_name_and_type():
+ # Needed for HTMX LoginView
+ backends = get_authentication_backend_classes()
+
+ data = {}
+ if has_model_backend(backends):
+ data["local"] = {
+ "url": reverse("htmx:login"),
+ "display_name": "Log In",
+ }
+
+ if has_remote_user_backend(backends):
+ remote_user_data = {
+ "url": "/", # Should probably also be a setting
+ "display_name": REMOTE_USER_METHOD_NAME,
+ }
+ data.setdefault("external", []).append(remote_user_data)
+
+ for backend in get_psa_authentication_backends(backends):
+ display_name = backend.name
+ if backend.name == "oidc":
+ display_name = OIDC_METHOD_NAME
+ psa_backend_data = {
+ "url": reverse("social:begin", kwargs={"backend": backend.name}),
+ "display_name": display_name,
+ }
+ data.setdefault("external", []).append(psa_backend_data)
+
+ return data
+
+
+class LoginView(DjangoLoginView):
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ backends = get_htmx_authentication_backend_name_and_type()
+ context["backends"] = backends
+ return context
diff --git a/merge/src/argus_htmx/checks.py b/merge/src/argus_htmx/checks.py
new file mode 100644
index 000000000..6e55ad10d
--- /dev/null
+++ b/merge/src/argus_htmx/checks.py
@@ -0,0 +1,29 @@
+from django.core.checks import Error, Warning, register
+from django.core.exceptions import ImproperlyConfigured
+
+from .themes.utils import get_theme_names, get_stylesheet_path
+
+
+@register
+def check_for_valid_themes_list(app_configs, **kwargs):
+ styles_path = get_stylesheet_path()
+ themes = []
+ try:
+ themes = get_theme_names(quiet=False)
+ except ImproperlyConfigured as e:
+ return [
+ Warning(
+ str(e),
+ hint=f"Regenerate {styles_path}",
+ id="argus_htmx.T001",
+ )
+ ]
+ if not themes:
+ return [
+ Error(
+ "no themes installed",
+ hint=f'Check the settings "DAISYUI_THEMES" and "TAILWIND_THEME_OVERRIDE" and regenerate {styles_path}',
+ id="argus_htmx.T002",
+ )
+ ]
+ return []
diff --git a/merge/src/argus_htmx/constants.py b/merge/src/argus_htmx/constants.py
new file mode 100644
index 000000000..24fc8dae1
--- /dev/null
+++ b/merge/src/argus_htmx/constants.py
@@ -0,0 +1,16 @@
+from argus_htmx.dateformat.constants import DATETIME_DEFAULT, DATETIME_FORMATS, DATETIME_CHOICES
+from argus_htmx.incidents.constants import DEFAULT_PAGE_SIZE, ALLOWED_PAGE_SIZES, PAGE_SIZE_CHOICES
+from argus_htmx.themes.constants import THEME_CHOICES, THEME_NAMES, THEME_DEFAULT
+
+
+__all__ = [
+ "ALLOWED_PAGE_SIZES",
+ "DATETIME_CHOICES",
+ "DATETIME_DEFAULT",
+ "DATETIME_FORMATS",
+ "DEFAULT_PAGE_SIZE",
+ "PAGE_SIZE_CHOICES",
+ "THEME_CHOICES",
+ "THEME_DEFAULT",
+ "THEME_NAMES",
+]
diff --git a/merge/src/argus_htmx/context_processors.py b/merge/src/argus_htmx/context_processors.py
new file mode 100644
index 000000000..3f2b002e9
--- /dev/null
+++ b/merge/src/argus_htmx/context_processors.py
@@ -0,0 +1,14 @@
+"""
+How to use:
+
+Append the "context_processors" list for the TEMPLATES-backend
+``django.template.backends.django.DjangoTemplates`` with the full dotted path.
+
+See django settings for ``TEMPLATES``.
+"""
+
+from .settings import STYLESHEET_PATH
+
+
+def path_to_stylesheet(request):
+ return {"path_to_stylesheet": STYLESHEET_PATH}
diff --git a/merge/src/argus_htmx/dateformat/__init__.py b/merge/src/argus_htmx/dateformat/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/merge/src/argus_htmx/dateformat/constants.py b/merge/src/argus_htmx/dateformat/constants.py
new file mode 100644
index 000000000..67de6361f
--- /dev/null
+++ b/merge/src/argus_htmx/dateformat/constants.py
@@ -0,0 +1,24 @@
+from django.conf import settings
+
+__all__ = [
+ "DATETIME_FALLBACK",
+ "DATETIME_FORMATS",
+ "DATETIME_DEFAULT",
+ "DATETIME_CHOICES",
+]
+
+
+DATETIME_FALLBACK = "LOCALE"
+DATETIME_FORMATS = {
+ "LOCALE": "DATETIME_FORMAT", # default
+ "ISO": "Y-m-d H:i:s",
+ "RFC5322": "r",
+ "EPOCH": "U",
+}
+DATETIME_CHOICES = tuple((format, format) for format in DATETIME_FORMATS)
+
+_datetime_setting = getattr(settings, "ARGUS_FRONTEND_DATETIME_FORMAT", DATETIME_FALLBACK)
+
+DATETIME_DEFAULT = DATETIME_FALLBACK
+if _datetime_setting in DATETIME_FORMATS:
+ DATETIME_DEFAULT = _datetime_setting
diff --git a/merge/src/argus_htmx/dateformat/urls.py b/merge/src/argus_htmx/dateformat/urls.py
new file mode 100644
index 000000000..f8cdf7773
--- /dev/null
+++ b/merge/src/argus_htmx/dateformat/urls.py
@@ -0,0 +1,10 @@
+from django.urls import path
+
+from . import views
+
+
+app_name = "htmx"
+urlpatterns = [
+ path("names/", views.dateformat_names, name="dateformat-names"),
+ path("change/", views.change_dateformat, name="change-dateformat"),
+]
diff --git a/merge/src/argus_htmx/dateformat/views.py b/merge/src/argus_htmx/dateformat/views.py
new file mode 100644
index 000000000..966c30f5e
--- /dev/null
+++ b/merge/src/argus_htmx/dateformat/views.py
@@ -0,0 +1,26 @@
+import logging
+
+from django.shortcuts import render
+
+from django.views.decorators.http import require_GET, require_POST
+from django.http import HttpResponse
+from django_htmx.http import HttpResponseClientRefresh
+
+from argus.auth.utils import save_preference
+
+from argus_htmx.incidents.views import HtmxHttpRequest
+from .constants import DATETIME_FORMATS
+
+LOG = logging.getLogger(__name__)
+
+
+@require_GET
+def dateformat_names(request: HtmxHttpRequest) -> HttpResponse:
+ datetime_formats = DATETIME_FORMATS.keys()
+ return render(request, "htmx/dateformat/_dateformat_list.html", {"datetime_formats": datetime_formats})
+
+
+@require_POST
+def change_dateformat(request: HtmxHttpRequest) -> HttpResponse:
+ save_preference(request, request.POST, "argus_htmx", "datetime_format_name")
+ return HttpResponseClientRefresh()
diff --git a/merge/src/argus_htmx/destinations/__init__.py b/merge/src/argus_htmx/destinations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/merge/src/argus_htmx/destinations/urls.py b/merge/src/argus_htmx/destinations/urls.py
new file mode 100644
index 000000000..377fae3ed
--- /dev/null
+++ b/merge/src/argus_htmx/destinations/urls.py
@@ -0,0 +1,21 @@
+from django.http import HttpResponse
+from django.template import Template, RequestContext
+from django.urls import path
+
+
+def placeholder(request):
+ template = Template(
+ """{% extends "htmx/base.html" %}
+ {% block main %}
+ DESTINATION PLACEHOLDER
+ {% endblock main %}
+ """
+ )
+ context = RequestContext(request)
+ return HttpResponse(template.render(context))
+
+
+app_name = "htmx"
+urlpatterns = [
+ path("", placeholder, name="destination-placeholder"),
+]
diff --git a/merge/src/argus_htmx/incidents/__init__.py b/merge/src/argus_htmx/incidents/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/merge/src/argus_htmx/incidents/constants.py b/merge/src/argus_htmx/incidents/constants.py
new file mode 100644
index 000000000..cd2208ea7
--- /dev/null
+++ b/merge/src/argus_htmx/incidents/constants.py
@@ -0,0 +1,13 @@
+from django.conf import settings
+
+
+__all__ = [
+ "DEFAULT_PAGE_SIZE",
+ "ALLOWED_PAGE_SIZES",
+ "PAGE_SIZE_CHOICES",
+]
+
+
+DEFAULT_PAGE_SIZE = getattr(settings, "ARGUS_INCIDENTS_DEFAULT_PAGE_SIZE", 10)
+ALLOWED_PAGE_SIZES = getattr(settings, "ARGUS_INCIDENTS_PAGE_SIZES", [10, 20, 50, 100])
+PAGE_SIZE_CHOICES = tuple((ps, ps) for ps in ALLOWED_PAGE_SIZES)
diff --git a/merge/src/argus_htmx/incidents/customization.py b/merge/src/argus_htmx/incidents/customization.py
new file mode 100644
index 000000000..34603497f
--- /dev/null
+++ b/merge/src/argus_htmx/incidents/customization.py
@@ -0,0 +1,71 @@
+"""
+Definitions and defaults for UI customization on the incidents page.
+
+Currently customizable UI elements:
+- table columns: configure what columns to show in the incidents listing
+"""
+
+from dataclasses import dataclass
+from typing import List, Optional, Union
+
+from django.conf import settings
+
+from argus_htmx import settings as argus_htmx_settings
+
+
+@dataclass
+class IncidentTableColumn:
+ """Class for defining a column in the incidents table.
+
+ :param name: identifier for this column
+ :param label: what to show as the column header
+ :param cell_template: template to use when rendering a cell for this column
+ :param context: additional context to pass to the rendering cell. Will be made
+ available as ``cell_context`` in the cell template
+ """
+
+ name: str # identifier
+ label: str # display value
+ cell_template: str
+ header_template: Optional[str] = None
+ context: Optional[dict] = None
+
+
+_BUILTIN_COLUMN_LIST = [
+ IncidentTableColumn(
+ "row_select",
+ "Selected",
+ "htmx/incidents/_incident_checkbox.html",
+ "htmx/incidents/_selected_incidents_header.html",
+ ),
+ IncidentTableColumn("id", "ID", "htmx/incidents/_incident_pk.html"),
+ IncidentTableColumn(
+ "start_time",
+ "Timestamp",
+ "htmx/incidents/_incident_start_time.html",
+ "htmx/incidents/_incident_start_time_header.html",
+ ),
+ IncidentTableColumn("status", "Status", "htmx/incidents/_incident_status.html"),
+ IncidentTableColumn("level", "Severity level", "htmx/incidents/_incident_level.html"),
+ IncidentTableColumn("source", "Source", "htmx/incidents/_incident_source.html"),
+ IncidentTableColumn("description", "Description", "htmx/incidents/_incident_description.html"),
+ IncidentTableColumn("ack", "Ack", "htmx/incidents/_incident_ack.html"),
+ IncidentTableColumn("combined_status", "Status", "htmx/incidents/_incident_combined_status.html"),
+ IncidentTableColumn("ticket", "Ticket", "htmx/incidents/_incident_ticket.html"),
+ IncidentTableColumn("links", "Actions", "htmx/incidents/_incident_actions.html"),
+]
+BUILTIN_COLUMNS = {col.name: col for col in _BUILTIN_COLUMN_LIST}
+
+
+def get_incident_table_columns() -> List[IncidentTableColumn]:
+ columns = getattr(settings, "INCIDENT_TABLE_COLUMNS", argus_htmx_settings.INCIDENT_TABLE_COLUMNS)
+ return [_resolve_column(col) for col in columns]
+
+
+def _resolve_column(col: Union[str, IncidentTableColumn]):
+ if isinstance(col, str):
+ try:
+ col = BUILTIN_COLUMNS[col]
+ except KeyError:
+ raise ValueError(f"Column '{col}' is not defined")
+ return col
diff --git a/merge/src/argus_htmx/incidents/filter.py b/merge/src/argus_htmx/incidents/filter.py
new file mode 100644
index 000000000..1b4e27895
--- /dev/null
+++ b/merge/src/argus_htmx/incidents/filter.py
@@ -0,0 +1,79 @@
+from django import forms
+
+from argus.filter import get_filter_backend
+from argus.incident.models import SourceSystem
+from argus.incident.constants import Level
+
+
+filter_backend = get_filter_backend()
+QuerySetFilter = filter_backend.QuerySetFilter
+
+
+class DropdownMultiSelect(forms.CheckboxSelectMultiple):
+ template_name = "htmx/incidents/_incident_source_select.html"
+ option_template_name = "htmx/forms/checkbox_select_multiple.html"
+
+
+class IncidentFilterForm(forms.Form):
+ open = forms.BooleanField(required=False)
+ closed = forms.BooleanField(required=False)
+ acked = forms.BooleanField(required=False)
+ unacked = forms.BooleanField(required=False)
+ source = forms.MultipleChoiceField(
+ widget=DropdownMultiSelect(attrs={"placeholder": "select sources..."}),
+ choices=tuple(SourceSystem.objects.values_list("id", "name")),
+ required=False,
+ label="Sources",
+ )
+ maxlevel = forms.IntegerField(
+ widget=forms.NumberInput(
+ attrs={"type": "range", "step": "1", "min": min(Level).value, "max": max(Level).value}
+ ),
+ label="Level <=",
+ initial=max(Level).value,
+ required=False,
+ )
+
+ def _tristate(self, onkey, offkey):
+ on = self.cleaned_data.get(onkey, None)
+ off = self.cleaned_data.get(offkey, None)
+ if on == off:
+ return None
+ if on and not off:
+ return True
+ if off and not on:
+ return False
+
+ def to_filterblob(self):
+ if not self.is_valid():
+ return {}
+
+ filterblob = {}
+
+ open = self._tristate("open", "closed")
+ if open is not None:
+ filterblob["open"] = open
+
+ acked = self._tristate("acked", "unacked")
+ if acked is not None:
+ filterblob["acked"] = acked
+
+ source = self.cleaned_data.get("source", [])
+ if source:
+ filterblob["sourceSystemIds"] = source
+
+ maxlevel = self.cleaned_data.get("maxlevel", 0)
+ if maxlevel:
+ filterblob["maxlevel"] = maxlevel
+
+ return filterblob
+
+
+def incident_list_filter(request, qs):
+ # TODO: initialize with chosen Filter.filter if any
+ form = IncidentFilterForm(request.GET or None)
+
+ if form.is_valid():
+ filterblob = form.to_filterblob()
+ qs = QuerySetFilter.filtered_incidents(filterblob, qs)
+ return form, qs
diff --git a/merge/src/argus_htmx/incidents/forms.py b/merge/src/argus_htmx/incidents/forms.py
new file mode 100644
index 000000000..f536c33af
--- /dev/null
+++ b/merge/src/argus_htmx/incidents/forms.py
@@ -0,0 +1,20 @@
+from django import forms
+
+
+class AckForm(forms.Form):
+ description = forms.CharField()
+ expiration = forms.DateTimeField(required=False)
+
+
+class DescriptionOptionalForm(forms.Form):
+ "For closing/reopening"
+
+ description = forms.CharField(required=False)
+
+
+class EditTicketUrlForm(forms.Form):
+ ticket_url = forms.URLField(required=False)
+
+
+class AddTicketUrlForm(forms.Form):
+ ticket_url = forms.URLField(required=True)
diff --git a/merge/src/argus_htmx/incidents/urls.py b/merge/src/argus_htmx/incidents/urls.py
new file mode 100644
index 000000000..338e5c069
--- /dev/null
+++ b/merge/src/argus_htmx/incidents/urls.py
@@ -0,0 +1,12 @@
+from django.urls import path
+
+from . import views
+
+
+app_name = "htmx"
+urlpatterns = [
+ path("", views.incident_list, name="incident-list"),
+ path("/", views.incident_detail, name="incident-detail"),
+ path("update//", views.incidents_update, name="incidents-update"),
+ path("filter/", views.filter_form, name="incidents-filter"),
+]
diff --git a/merge/src/argus_htmx/incidents/utils.py b/merge/src/argus_htmx/incidents/utils.py
new file mode 100644
index 000000000..fe39d9a9c
--- /dev/null
+++ b/merge/src/argus_htmx/incidents/utils.py
@@ -0,0 +1,15 @@
+import importlib
+
+from django.conf import settings
+
+FUNCTION_NAME = "incident_list_filter"
+DEFAULT_MODULE = "argus_htmx.incidents.filter"
+
+
+def get_filter_function():
+ dotted_path = getattr(settings, "ARGUS_HTMX_FILTER_FUNCTION", DEFAULT_MODULE)
+ module = importlib.import_module(dotted_path)
+ function = getattr(module, FUNCTION_NAME, None)
+ if function:
+ return function
+ raise ImportError(f"Could not import {FUNCTION_NAME} from {dotted_path}")
diff --git a/merge/src/argus_htmx/incidents/views.py b/merge/src/argus_htmx/incidents/views.py
new file mode 100644
index 000000000..422316397
--- /dev/null
+++ b/merge/src/argus_htmx/incidents/views.py
@@ -0,0 +1,146 @@
+from __future__ import annotations
+
+import logging
+from datetime import datetime
+
+from django import forms
+from django.contrib.auth import get_user_model
+from django.shortcuts import render, get_object_or_404
+
+from django.views.decorators.http import require_POST, require_GET
+from django.core.paginator import Paginator
+from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest
+from django_htmx.middleware import HtmxDetails
+from django_htmx.http import HttpResponseClientRefresh
+
+from argus.auth.utils import get_preference, save_preference
+from argus.incident.models import Incident
+from argus.util.datetime_utils import make_aware
+
+from .constants import ALLOWED_PAGE_SIZES
+from .customization import get_incident_table_columns
+from .utils import get_filter_function
+from .forms import AckForm, DescriptionOptionalForm, EditTicketUrlForm, AddTicketUrlForm
+from ..utils import (
+ bulk_change_incidents,
+ bulk_ack_queryset,
+ bulk_close_queryset,
+ bulk_reopen_queryset,
+ bulk_change_ticket_url_queryset,
+)
+
+User = get_user_model()
+LOG = logging.getLogger(__name__)
+
+
+# Map request trigger to parameters for incidents update
+INCIDENT_UPDATE_ACTIONS = {
+ "ack": (AckForm, bulk_ack_queryset),
+ "close": (DescriptionOptionalForm, bulk_close_queryset),
+ "reopen": (DescriptionOptionalForm, bulk_reopen_queryset),
+ "update-ticket": (EditTicketUrlForm, bulk_change_ticket_url_queryset),
+ "add-ticket": (AddTicketUrlForm, bulk_change_ticket_url_queryset),
+}
+
+
+def prefetch_incident_daughters():
+ return Incident.objects.select_related("source").prefetch_related(
+ "incident_tag_relations",
+ "incident_tag_relations__tag",
+ "events",
+ "events__ack",
+ )
+
+
+class HtmxHttpRequest(HttpRequest):
+ htmx: HtmxDetails
+
+
+# fetch with htmx
+def incident_detail(request, pk: int):
+ incident = get_object_or_404(Incident, id=pk)
+ context = {
+ "incident": incident,
+ "page_title": str(incident),
+ }
+ return render(request, "htmx/incidents/incident_detail.html", context=context)
+
+
+def get_form_data(request, formclass: forms.Form):
+ formdata = request.POST or None
+ incident_ids = []
+ cleaned_form = None
+ if formdata:
+ incident_ids = request.POST.getlist("incident_ids", [])
+ form = formclass(formdata)
+ if form.is_valid():
+ cleaned_form = form.cleaned_data
+ return cleaned_form, incident_ids
+
+
+@require_POST
+def incidents_update(request: HtmxHttpRequest, action: str):
+ try:
+ formclass, callback_func = INCIDENT_UPDATE_ACTIONS[action]
+ except KeyError:
+ LOG.error("Unrecognized action name %s when updating incidents.", action)
+ return HttpResponseBadRequest("Invalid update action")
+ formdata, incident_ids = get_form_data(request, formclass)
+ if formdata:
+ bulk_change_incidents(request.user, incident_ids, formdata, callback_func)
+ return HttpResponseClientRefresh()
+
+
+@require_GET
+def filter_form(request: HtmxHttpRequest):
+ incident_list_filter = get_filter_function()
+ filter_form, _ = incident_list_filter(request, None)
+ context = {"filter_form": filter_form}
+ return render(request, "htmx/incidents/_incident_filterbox.html", context=context)
+
+
+def incident_list(request: HtmxHttpRequest) -> HttpResponse:
+ columns = get_incident_table_columns()
+
+ # Load incidents
+ qs = prefetch_incident_daughters().order_by("-start_time")
+ total_count = qs.count()
+ last_refreshed = make_aware(datetime.now())
+
+ params = dict(request.GET.items())
+
+ incident_list_filter = get_filter_function()
+ filter_form, qs = incident_list_filter(request, qs)
+ filtered_count = qs.count()
+
+ # Standard Django pagination
+
+ page_size = get_preference(request, "argus_htmx", "page_size")
+ success = save_preference(request, request.GET, "argus_htmx", "page_size")
+ if success:
+ page_size = get_preference(request, "argus_htmx", "page_size")
+ paginator = Paginator(object_list=qs, per_page=page_size)
+ page_num = params.pop("page", "1")
+ page = paginator.get_page(page_num)
+
+ # The htmx magic - use a different, minimal base template for htmx
+ # requests, allowing us to skip rendering the unchanging parts of the
+ # template.
+ if request.htmx:
+ base_template = "htmx/incidents/responses/_incidents_table_refresh.html"
+ else:
+ base_template = "htmx/incidents/_base.html"
+ context = {
+ "columns": columns,
+ "filtered_count": filtered_count,
+ "count": total_count,
+ "filter_form": filter_form,
+ "page_title": "Incidents",
+ "base": base_template,
+ "page": page,
+ "last_refreshed": last_refreshed,
+ "update_interval": 30,
+ "all_page_sizes": ALLOWED_PAGE_SIZES,
+ }
+
+ return render(request, "htmx/incidents/incident_list.html", context=context)
diff --git a/merge/src/argus_htmx/management/commands/__init__.py b/merge/src/argus_htmx/management/commands/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/merge/src/argus_htmx/management/commands/tailwind_config.py b/merge/src/argus_htmx/management/commands/tailwind_config.py
new file mode 100644
index 000000000..a01d1408d
--- /dev/null
+++ b/merge/src/argus_htmx/management/commands/tailwind_config.py
@@ -0,0 +1,120 @@
+import json
+import pathlib
+import textwrap
+from django.apps import apps
+from django.core.management.base import BaseCommand
+from django.conf import settings
+from django.template import engines
+from django.template.context import make_context
+from django.template.loader import get_template
+
+from argus_htmx.themes.utils import get_raw_themes_setting
+from argus_htmx import settings as argus_htmx_settings
+
+
+# Copied from https://github.com/GEANT/geant-argus/pull/15 with minor modifications
+class Command(BaseCommand):
+ help = """
+ Uses the template specified in the TAILWIND_CONFIG_TEMPLATE setting
+ (default: tailwind/tailwind.config.js) to dynamically build a tailwind.config.js.
+ This file must be in an app's templates directory. The template may contain:
+
+ - a '{{ projectpaths }} section without square brackets that will be
+ popuplated with auto discovered template dirs of installed apps
+ - a '{{ daisyuithemes }}' section without square brackets that will be
+ popuplated by the daisyUI theme list specified in the DAISYUI_THEMES
+ setting (default: ["dark", "light", {"argus"": {...}} ])
+ - a '{{ themeoverride }}' section that will be popuplated by a dict containing
+ tailwind theme options specified in TAILWIND_THEME_OVERRIDE setting
+ (default: {})
+
+ This command also generates a `styles.css` that contains the base css file for
+ tailwind to create its final css file. It uses the template specified by the
+ `TAILWIND_CSS_TEMPLATE` setting (default: tailwind/styles.css). This file should
+ also be in an app's templates directory
+
+ The `styles.css` template may iterate over a `cssfiles` context variable that
+ contains css files/snippets that should be included in the final css file. Apps
+ may define a `tailwind_css_files` method that gives the location (as a str or
+ pathlib.Path object) for every css file from this app to include. (see
+ argus_htmx.apps.HtmxFrontendConfig for an example)
+
+ Additional settings that govern the functionality of this command are:
+
+ - TAILWIND_CONFIG_TARGET: the target location for writing the tailwind.config.js
+ - TAILWIND_CSS_TARGET: override the base tailwind css file location
+
+ These target locations are relative to the working directory where this command is
+ executed
+ """
+ DEFAULT_CONFIG_TEMPLATE_NAME = "tailwind/tailwind.config.js"
+ DEFAULT_CSS_TEMPLATE_NAME = "tailwind/styles.css"
+ DEFAULT_CONFIG_TARGET = "src/argus_htmx/tailwindtheme/tailwind.config.js"
+ DEFAULT_CSS_TARGET = "src/argus_htmx/tailwindtheme/styles.css"
+
+ def handle(self, *args, **options):
+ config_template_name = getattr(settings, "TAILWIND_CONFIG_TEMPLATE", self.DEFAULT_CONFIG_TEMPLATE_NAME)
+ config_target_path = pathlib.Path(getattr(settings, "TAILWIND_CONFIG_TARGET", self.DEFAULT_CONFIG_TARGET))
+ css_template_name = getattr(settings, "TAILWIND_CSS_TEMPLATE", self.DEFAULT_CSS_TEMPLATE_NAME)
+ css_target_path = pathlib.Path(getattr(settings, "TAILWIND_CSS_TARGET", self.DEFAULT_CSS_TARGET))
+ self.write_file(
+ config_template_name,
+ config_target_path,
+ context=self.get_context(target_dir=config_target_path.parent),
+ name="tailwind config",
+ )
+ self.write_file(
+ css_template_name,
+ css_target_path,
+ context=self.get_context(target_dir=css_target_path.parent),
+ name="tailwind base css",
+ )
+
+ def get_context(self, target_dir: pathlib.Path):
+ return {
+ "themeoverride": getattr(
+ settings,
+ "TAILWIND_THEME_OVERRIDE",
+ argus_htmx_settings.TAILWIND_THEME_OVERRIDE,
+ ),
+ "daisyuithemes": textwrap.indent(
+ json.dumps(get_raw_themes_setting(), indent=2),
+ prefix=10 * " ",
+ predicate=lambda line: line != "[\n", # this is kinda hacky, but eh
+ ),
+ "projectpaths": "\n".join(f" '{d}/**/*.html'," for d in self.get_template_dirs()),
+ "cssfiles": self.get_css_files(target_dir),
+ }
+
+ def write_file(self, template_name, target_path, context, name):
+ pathlib.Path(target_path).write_text(self.render(template_name=template_name, context=context))
+
+ self.stdout.write(f"Wrote {name} to '{target_path}'")
+
+ @staticmethod
+ def render(template_name: str, context):
+ template = get_template(template_name)
+ return template.template.render(make_context(context, autoescape=False))
+
+ @staticmethod
+ def get_template_dirs():
+ for engine in engines.all():
+ yield from getattr(engine, "template_dirs", [])
+
+ @classmethod
+ def get_css_files(cls, target_dir: pathlib.Path):
+ css_files = sorted(cls._iter_css_files(), key=lambda p: p.stem)
+ return (cls.make_relative(p, target_dir) for p in css_files)
+
+ @staticmethod
+ def _iter_css_files():
+ for app in apps.get_app_configs():
+ if callable(css_files := getattr(app, "tailwind_css_files", None)):
+ yield from (pathlib.Path(p) for p in css_files())
+
+ @staticmethod
+ def make_relative(path: pathlib.Path, base_path: pathlib.Path):
+ try:
+ return path.relative_to(base_path.absolute())
+ except ValueError:
+ return path
diff --git a/merge/src/argus_htmx/middleware.py b/merge/src/argus_htmx/middleware.py
new file mode 100644
index 000000000..ad352fb10
--- /dev/null
+++ b/merge/src/argus_htmx/middleware.py
@@ -0,0 +1,69 @@
+from django.conf import settings
+from django.contrib.auth.views import redirect_to_login
+from django.http import HttpRequest, HttpResponse
+from django.template import loader
+from django.utils.deprecation import MiddlewareMixin
+from django.utils.encoding import force_str
+
+
+class LoginRequiredMiddleware:
+ def __init__(self, get_response):
+ self.public_urls = getattr(settings, "PUBLIC_URLS", ())
+ self.login_url = force_str(settings.LOGIN_URL)
+ self.get_response = get_response
+
+ def __call__(self, request):
+ response = self.get_response(request)
+ return response
+
+ def process_view(self, request, view_func, _view_args, _view_kwargs):
+ assert hasattr(request, "user"), (
+ "The LoginRequiredMiddleware requires authentication middleware "
+ "to be installed. Edit your MIDDLEWARE%s setting to insert "
+ "'django.contrib.auth.middleware.AuthenticationMiddleware' "
+ "before this middleware." % ("_CLASSES" if settings.MIDDLEWARE is None else "")
+ )
+
+ # If path is public, allow
+ for url in self.public_urls:
+ if request.path.startswith(url):
+ return None
+
+ # If CBV has the attribute login_required == False, allow
+ view_class = getattr(view_func, "view_class", None)
+ if view_class and not getattr(view_class, "login_required", True):
+ return None
+
+ # If view_func.login_required == False, allow
+ if not getattr(view_func, "login_required", True):
+ return None
+
+ # Allow authenticated users
+ if request.user.is_authenticated:
+ return None
+
+ # Redirect unauthenticated users to login page
+ return redirect_to_login(request.get_full_path(), self.login_url, "next")
+
+
+class HtmxMessageMiddleware(MiddlewareMixin):
+ """
+ For htmx requests, adds messages to the #notification-messages div defined in
+ `templates/messages/_notification_messages.html` using htmx's hx-swap-oob feature
+ """
+
+ TEMPLATE = "messages/_notification_messages_htmx_append.html"
+
+ def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse:
+ if not request.htmx:
+ return response
+
+ # Ignore redirections because HTMX cannot read the headers
+ if 300 <= response.status_code < 400:
+ return response
+
+ if not response.writable():
+ return response
+
+ response.write(loader.render_to_string(self.TEMPLATE, request=request))
+ return response
diff --git a/merge/src/argus_htmx/migrations/0001_initial.py b/merge/src/argus_htmx/migrations/0001_initial.py
new file mode 100644
index 000000000..98b9837c3
--- /dev/null
+++ b/merge/src/argus_htmx/migrations/0001_initial.py
@@ -0,0 +1,24 @@
+# Generated by Django 4.2.16 on 2024-11-07 11:02
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ initial = True
+
+ dependencies = [
+ ("argus_auth", "0004_add_preferences_model"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="ArgusHtmxPreferences",
+ fields=[],
+ options={
+ "proxy": True,
+ "indexes": [],
+ "constraints": [],
+ },
+ bases=("argus_auth.preferences",),
+ ),
+ ]
diff --git a/merge/src/argus_htmx/migrations/__init__.py b/merge/src/argus_htmx/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/merge/src/argus_htmx/models.py b/merge/src/argus_htmx/models.py
new file mode 100644
index 000000000..1054aca22
--- /dev/null
+++ b/merge/src/argus_htmx/models.py
@@ -0,0 +1,8 @@
+from django.db import models # noqa: F401 - unused-import
+
+from argus_htmx.user.preferences.models import ArgusHtmxPreferences
+
+
+__all__ = [
+ "ArgusHtmxPreferences",
+]
diff --git a/merge/src/argus_htmx/notificationprofiles/__init__.py b/merge/src/argus_htmx/notificationprofiles/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/merge/src/argus_htmx/notificationprofiles/urls.py b/merge/src/argus_htmx/notificationprofiles/urls.py
new file mode 100644
index 000000000..e3d8ab649
--- /dev/null
+++ b/merge/src/argus_htmx/notificationprofiles/urls.py
@@ -0,0 +1,21 @@
+from django.http import HttpResponse
+from django.template import Template, RequestContext
+from django.urls import path
+
+
+def placeholder(request):
+ template = Template(
+ """{% extends "htmx/base.html" %}
+ {% block main %}
+ NOTIFICATION PROFILES PLACEHOLDER
+ {% endblock main %}
+ """
+ )
+ context = RequestContext(request)
+ return HttpResponse(template.render(context))
+
+
+app_name = "htmx"
+urlpatterns = [
+ path("", placeholder, name="notificationprofile-placeholder"),
+]
diff --git a/merge/src/argus_htmx/settings.py b/merge/src/argus_htmx/settings.py
new file mode 100644
index 000000000..00f14bb02
--- /dev/null
+++ b/merge/src/argus_htmx/settings.py
@@ -0,0 +1,61 @@
+# customize the displayed columns in the incident table
+# items in INCIDENT_TABLE_COLUMNS can be either a `str` referring to a key in
+# argus_htmx.incidents.customization.BUILTIN_COLUMNS or an instance of
+# argus_htmx.incidents.customization.IncidentTableColumn
+from argus.site.settings import get_json_env, get_str_env
+
+INCIDENT_TABLE_COLUMNS = [
+ "row_select",
+ "id",
+ "start_time",
+ "combined_status",
+ "level",
+ "source",
+ "description",
+ "ticket",
+]
+
+# These templates are auto-discovered by the templating engine and are relative
+# to the argus_htmx's `templates/` directory
+TAILWIND_CONFIG_TEMPLATE = "tailwind/tailwind.config.js"
+TAILWIND_CSS_TEMPLATE = "tailwind/styles.css"
+
+# These targets are relative to the working directory when running the `tailwind_config`
+# management command, which should be done from the repostitory root
+TAILWIND_CONFIG_TARGET = "src/argus_htmx/tailwindtheme/tailwind.config.js"
+TAILWIND_CSS_TARGET = "src/argus_htmx/tailwindtheme/styles.css"
+
+STYLESHEET_PATH_DEFAULT = "styles.css"
+DEFAULT_THEMES = [
+ "dark",
+ "light",
+ {
+ "argus": {
+ "primary": "#006d91",
+ "primary-content": "#d1e1e9",
+ "secondary": "#f3b61f",
+ "secondary-content": "#140c00",
+ "accent": "#c84700",
+ "accent-content": "#f8dbd1",
+ "neutral": "#006d91",
+ "neutral-content": "#d1e1e9",
+ "base-100": "#edfaff",
+ "base-200": "#ced9de",
+ "base-300": "#b0babd",
+ "base-content": "#141516",
+ "info": "#0073e5",
+ "info-content": "#000512",
+ "success": "#008700",
+ "success-content": "#d3e7d1",
+ "warning": "#ee4900",
+ "warning-content": "#140200",
+ "error": "#e5545a",
+ "error-content": "#120203",
+ }
+ },
+]
+DAISYUI_THEMES = get_json_env("DAISYUI_THEMES", DEFAULT_THEMES, quiet=True)
+THEME_DEFAULT = get_str_env("ARGUS_THEME_DEFAULT", "argus")
+DEFAULT_THEME_OVERRIDE = {}
+TAILWIND_THEME_OVERRIDE = get_json_env("TAILWIND_THEME_OVERRIDE", DEFAULT_THEME_OVERRIDE, quiet=True)
+STYLESHEET_PATH = get_str_env("ARGUS_STYLESHEET_PATH", STYLESHEET_PATH_DEFAULT)
diff --git a/merge/src/argus_htmx/static/htmx-2.0.2.min.js b/merge/src/argus_htmx/static/htmx-2.0.2.min.js
new file mode 100644
index 000000000..c11fbbdf4
--- /dev/null
+++ b/merge/src/argus_htmx/static/htmx-2.0.2.min.js
@@ -0,0 +1 @@
+var htmx=function(){"use strict";const Q={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(e,t){const n=cn(e,t||"post");return n.values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:true,ignoreTitle:false,scrollIntoViewOnBoost:true,triggerSpecsCache:null,disableInheritance:false,responseHandling:[{code:"204",swap:false},{code:"[23]..",swap:true},{code:"[45]..",swap:false,error:true}],allowNestedOobSwaps:true},parseInterval:null,_:null,version:"2.0.2"};Q.onLoad=$;Q.process=Dt;Q.on=be;Q.off=we;Q.trigger=de;Q.ajax=Hn;Q.find=r;Q.findAll=p;Q.closest=g;Q.remove=K;Q.addClass=Y;Q.removeClass=o;Q.toggleClass=W;Q.takeClass=ge;Q.swap=ze;Q.defineExtension=Bn;Q.removeExtension=Un;Q.logAll=z;Q.logNone=J;Q.parseInterval=h;Q._=_;const n={addTriggerHandler:Et,bodyContains:le,canAccessLocalStorage:j,findThisElement:Ee,filterValues:hn,swap:ze,hasAttribute:s,getAttributeValue:te,getClosestAttributeValue:re,getClosestMatch:T,getExpressionVars:Cn,getHeaders:dn,getInputValues:cn,getInternalData:ie,getSwapSpecification:pn,getTriggerSpecs:lt,getTarget:Ce,makeFragment:D,mergeObjects:ue,makeSettleInfo:xn,oobSwap:Te,querySelectorExt:ae,settleImmediately:Gt,shouldCancel:ht,triggerEvent:de,triggerErrorEvent:fe,withExtensions:Bt};const v=["get","post","put","delete","patch"];const O=v.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");const R=e("head");function e(e,t=false){return new RegExp(`<${e}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${e}>`,t?"gim":"im")}function h(e){if(e==undefined){return undefined}let t=NaN;if(e.slice(-2)=="ms"){t=parseFloat(e.slice(0,-2))}else if(e.slice(-1)=="s"){t=parseFloat(e.slice(0,-1))*1e3}else if(e.slice(-1)=="m"){t=parseFloat(e.slice(0,-1))*1e3*60}else{t=parseFloat(e)}return isNaN(t)?undefined:t}function ee(e,t){return e instanceof Element&&e.getAttribute(t)}function s(e,t){return!!e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function te(e,t){return ee(e,t)||ee(e,"data-"+t)}function u(e){const t=e.parentElement;if(!t&&e.parentNode instanceof ShadowRoot)return e.parentNode;return t}function ne(){return document}function H(e,t){return e.getRootNode?e.getRootNode({composed:t}):ne()}function T(e,t){while(e&&!t(e)){e=u(e)}return e||null}function q(e,t,n){const r=te(t,n);const o=te(t,"hx-disinherit");var i=te(t,"hx-inherit");if(e!==t){if(Q.config.disableInheritance){if(i&&(i==="*"||i.split(" ").indexOf(n)>=0)){return r}else{return null}}if(o&&(o==="*"||o.split(" ").indexOf(n)>=0)){return"unset"}}return r}function re(t,n){let r=null;T(t,function(e){return!!(r=q(t,ce(e),n))});if(r!=="unset"){return r}}function f(e,t){const n=e instanceof Element&&(e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector);return!!n&&n.call(e,t)}function L(e){const t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;const n=t.exec(e);if(n){return n[1].toLowerCase()}else{return""}}function N(e){const t=new DOMParser;return t.parseFromString(e,"text/html")}function A(e,t){while(t.childNodes.length>0){e.append(t.childNodes[0])}}function I(e){const t=ne().createElement("script");se(e.attributes,function(e){t.setAttribute(e.name,e.value)});t.textContent=e.textContent;t.async=false;if(Q.config.inlineScriptNonce){t.nonce=Q.config.inlineScriptNonce}return t}function P(e){return e.matches("script")&&(e.type==="text/javascript"||e.type==="module"||e.type==="")}function k(e){Array.from(e.querySelectorAll("script")).forEach(e=>{if(P(e)){const t=I(e);const n=e.parentNode;try{n.insertBefore(t,e)}catch(e){w(e)}finally{e.remove()}}})}function D(e){const t=e.replace(R,"");const n=L(t);let r;if(n==="html"){r=new DocumentFragment;const i=N(e);A(r,i.body);r.title=i.title}else if(n==="body"){r=new DocumentFragment;const i=N(t);A(r,i.body);r.title=i.title}else{const i=N(''+t+" ");r=i.querySelector("template").content;r.title=i.title;var o=r.querySelector("title");if(o&&o.parentNode===r){o.remove();r.title=o.innerText}}if(r){if(Q.config.allowScriptTags){k(r)}else{r.querySelectorAll("script").forEach(e=>e.remove())}}return r}function oe(e){if(e){e()}}function t(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function M(e){return typeof e==="function"}function X(e){return t(e,"Object")}function ie(e){const t="htmx-internal-data";let n=e[t];if(!n){n=e[t]={}}return n}function F(t){const n=[];if(t){for(let e=0;e=0}function le(e){const t=e.getRootNode&&e.getRootNode();if(t&&t instanceof window.ShadowRoot){return ne().body.contains(t.host)}else{return ne().body.contains(e)}}function U(e){return e.trim().split(/\s+/)}function ue(e,t){for(const n in t){if(t.hasOwnProperty(n)){e[n]=t[n]}}return e}function S(e){try{return JSON.parse(e)}catch(e){w(e);return null}}function j(){const e="htmx:localStorageTest";try{localStorage.setItem(e,e);localStorage.removeItem(e);return true}catch(e){return false}}function V(t){try{const e=new URL(t);if(e){t=e.pathname+e.search}if(!/^\/$/.test(t)){t=t.replace(/\/+$/,"")}return t}catch(e){return t}}function _(e){return vn(ne().body,function(){return eval(e)})}function $(t){const e=Q.on("htmx:load",function(e){t(e.detail.elt)});return e}function z(){Q.logger=function(e,t,n){if(console){console.log(t,e,n)}}}function J(){Q.logger=null}function r(e,t){if(typeof e!=="string"){return e.querySelector(t)}else{return r(ne(),e)}}function p(e,t){if(typeof e!=="string"){return e.querySelectorAll(t)}else{return p(ne(),e)}}function E(){return window}function K(e,t){e=y(e);if(t){E().setTimeout(function(){K(e);e=null},t)}else{u(e).removeChild(e)}}function ce(e){return e instanceof Element?e:null}function G(e){return e instanceof HTMLElement?e:null}function Z(e){return typeof e==="string"?e:null}function d(e){return e instanceof Element||e instanceof Document||e instanceof DocumentFragment?e:null}function Y(e,t,n){e=ce(y(e));if(!e){return}if(n){E().setTimeout(function(){Y(e,t);e=null},n)}else{e.classList&&e.classList.add(t)}}function o(e,t,n){let r=ce(y(e));if(!r){return}if(n){E().setTimeout(function(){o(r,t);r=null},n)}else{if(r.classList){r.classList.remove(t);if(r.classList.length===0){r.removeAttribute("class")}}}}function W(e,t){e=y(e);e.classList.toggle(t)}function ge(e,t){e=y(e);se(e.parentElement.children,function(e){o(e,t)});Y(ce(e),t)}function g(e,t){e=ce(y(e));if(e&&e.closest){return e.closest(t)}else{do{if(e==null||f(e,t)){return e}}while(e=e&&ce(u(e)));return null}}function l(e,t){return e.substring(0,t.length)===t}function pe(e,t){return e.substring(e.length-t.length)===t}function i(e){const t=e.trim();if(l(t,"<")&&pe(t,"/>")){return t.substring(1,t.length-2)}else{return t}}function m(e,t,n){e=y(e);if(t.indexOf("closest ")===0){return[g(ce(e),i(t.substr(8)))]}else if(t.indexOf("find ")===0){return[r(d(e),i(t.substr(5)))]}else if(t==="next"){return[ce(e).nextElementSibling]}else if(t.indexOf("next ")===0){return[me(e,i(t.substr(5)),!!n)]}else if(t==="previous"){return[ce(e).previousElementSibling]}else if(t.indexOf("previous ")===0){return[ye(e,i(t.substr(9)),!!n)]}else if(t==="document"){return[document]}else if(t==="window"){return[window]}else if(t==="body"){return[document.body]}else if(t==="root"){return[H(e,!!n)]}else if(t.indexOf("global ")===0){return m(e,t.slice(7),true)}else{return F(d(H(e,!!n)).querySelectorAll(i(t)))}}var me=function(t,e,n){const r=d(H(t,n)).querySelectorAll(e);for(let e=0;e=0;e--){const o=r[e];if(o.compareDocumentPosition(t)===Node.DOCUMENT_POSITION_FOLLOWING){return o}}};function ae(e,t){if(typeof e!=="string"){return m(e,t)[0]}else{return m(ne().body,e)[0]}}function y(e,t){if(typeof e==="string"){return r(d(t)||document,e)}else{return e}}function xe(e,t,n){if(M(t)){return{target:ne().body,event:Z(e),listener:t}}else{return{target:y(e),event:Z(t),listener:n}}}function be(t,n,r){_n(function(){const e=xe(t,n,r);e.target.addEventListener(e.event,e.listener)});const e=M(n);return e?n:r}function we(t,n,r){_n(function(){const e=xe(t,n,r);e.target.removeEventListener(e.event,e.listener)});return M(n)?n:r}const ve=ne().createElement("output");function Se(e,t){const n=re(e,t);if(n){if(n==="this"){return[Ee(e,t)]}else{const r=m(e,n);if(r.length===0){w('The selector "'+n+'" on '+t+" returned no matches!");return[ve]}else{return r}}}}function Ee(e,t){return ce(T(e,function(e){return te(ce(e),t)!=null}))}function Ce(e){const t=re(e,"hx-target");if(t){if(t==="this"){return Ee(e,"hx-target")}else{return ae(e,t)}}else{const n=ie(e);if(n.boosted){return ne().body}else{return e}}}function Oe(t){const n=Q.config.attributesToSettle;for(let e=0;e0){s=e.substr(0,e.indexOf(":"));t=e.substr(e.indexOf(":")+1,e.length)}else{s=e}const n=ne().querySelectorAll(t);if(n){se(n,function(e){let t;const n=o.cloneNode(true);t=ne().createDocumentFragment();t.appendChild(n);if(!He(s,e)){t=d(n)}const r={shouldSwap:true,target:e,fragment:t};if(!de(e,"htmx:oobBeforeSwap",r))return;e=r.target;if(r.shouldSwap){_e(s,e,e,t,i)}se(i.elts,function(e){de(e,"htmx:oobAfterSwap",r)})});o.parentNode.removeChild(o)}else{o.parentNode.removeChild(o);fe(ne().body,"htmx:oobErrorNoTarget",{content:o})}return e}function qe(e){se(p(e,"[hx-preserve], [data-hx-preserve]"),function(e){const t=te(e,"id");const n=ne().getElementById(t);if(n!=null){e.parentNode.replaceChild(n,e)}})}function Le(l,e,u){se(e.querySelectorAll("[id]"),function(t){const n=ee(t,"id");if(n&&n.length>0){const r=n.replace("'","\\'");const o=t.tagName.replace(":","\\:");const e=d(l);const i=e&&e.querySelector(o+"[id='"+r+"']");if(i&&i!==e){const s=t.cloneNode();Re(t,i);u.tasks.push(function(){Re(t,s)})}}})}function Ne(e){return function(){o(e,Q.config.addedClass);Dt(ce(e));Ae(d(e));de(e,"htmx:load")}}function Ae(e){const t="[autofocus]";const n=G(f(e,t)?e:e.querySelector(t));if(n!=null){n.focus()}}function c(e,t,n,r){Le(e,n,r);while(n.childNodes.length>0){const o=n.firstChild;Y(ce(o),Q.config.addedClass);e.insertBefore(o,t);if(o.nodeType!==Node.TEXT_NODE&&o.nodeType!==Node.COMMENT_NODE){r.tasks.push(Ne(o))}}}function Ie(e,t){let n=0;while(n0}function ze(e,t,r,o){if(!o){o={}}e=y(e);const n=document.activeElement;let i={};try{i={elt:n,start:n?n.selectionStart:null,end:n?n.selectionEnd:null}}catch(e){}const s=xn(e);if(r.swapStyle==="textContent"){e.textContent=t}else{let n=D(t);s.title=n.title;if(o.selectOOB){const u=o.selectOOB.split(",");for(let t=0;t0){E().setTimeout(l,r.settleDelay)}else{l()}}function Je(e,t,n){const r=e.getResponseHeader(t);if(r.indexOf("{")===0){const o=S(r);for(const i in o){if(o.hasOwnProperty(i)){let e=o[i];if(X(e)){n=e.target!==undefined?e.target:n}else{e={value:e}}de(n,i,e)}}}else{const s=r.split(",");for(let e=0;e0){const s=o[0];if(s==="]"){e--;if(e===0){if(n===null){t=t+"true"}o.shift();t+=")})";try{const l=vn(r,function(){return Function(t)()},function(){return true});l.source=t;return l}catch(e){fe(ne().body,"htmx:syntax:error",{error:e,source:t});return null}}}else if(s==="["){e++}if(nt(s,n,i)){t+="(("+i+"."+s+") ? ("+i+"."+s+") : (window."+s+"))"}else{t=t+s}n=o.shift()}}}function b(e,t){let n="";while(e.length>0&&!t.test(e[0])){n+=e.shift()}return n}function ot(e){let t;if(e.length>0&&Qe.test(e[0])){e.shift();t=b(e,et).trim();e.shift()}else{t=b(e,x)}return t}const it="input, textarea, select";function st(e,t,n){const r=[];const o=tt(t);do{b(o,We);const l=o.length;const u=b(o,/[,\[\s]/);if(u!==""){if(u==="every"){const c={trigger:"every"};b(o,We);c.pollInterval=h(b(o,/[,\[\s]/));b(o,We);var i=rt(e,o,"event");if(i){c.eventFilter=i}r.push(c)}else{const a={trigger:u};var i=rt(e,o,"event");if(i){a.eventFilter=i}while(o.length>0&&o[0]!==","){b(o,We);const f=o.shift();if(f==="changed"){a.changed=true}else if(f==="once"){a.once=true}else if(f==="consume"){a.consume=true}else if(f==="delay"&&o[0]===":"){o.shift();a.delay=h(b(o,x))}else if(f==="from"&&o[0]===":"){o.shift();if(Qe.test(o[0])){var s=ot(o)}else{var s=b(o,x);if(s==="closest"||s==="find"||s==="next"||s==="previous"){o.shift();const d=ot(o);if(d.length>0){s+=" "+d}}}a.from=s}else if(f==="target"&&o[0]===":"){o.shift();a.target=ot(o)}else if(f==="throttle"&&o[0]===":"){o.shift();a.throttle=h(b(o,x))}else if(f==="queue"&&o[0]===":"){o.shift();a.queue=b(o,x)}else if(f==="root"&&o[0]===":"){o.shift();a[f]=ot(o)}else if(f==="threshold"&&o[0]===":"){o.shift();a[f]=b(o,x)}else{fe(e,"htmx:syntax:error",{token:o.shift()})}}r.push(a)}}if(o.length===l){fe(e,"htmx:syntax:error",{token:o.shift()})}b(o,We)}while(o[0]===","&&o.shift());if(n){n[t]=r}return r}function lt(e){const t=te(e,"hx-trigger");let n=[];if(t){const r=Q.config.triggerSpecsCache;n=r&&r[t]||st(e,t,r)}if(n.length>0){return n}else if(f(e,"form")){return[{trigger:"submit"}]}else if(f(e,'input[type="button"], input[type="submit"]')){return[{trigger:"click"}]}else if(f(e,it)){return[{trigger:"change"}]}else{return[{trigger:"click"}]}}function ut(e){ie(e).cancelled=true}function ct(e,t,n){const r=ie(e);r.timeout=E().setTimeout(function(){if(le(e)&&r.cancelled!==true){if(!pt(n,e,Xt("hx:poll:trigger",{triggerSpec:n,target:e}))){t(e)}ct(e,t,n)}},n.pollInterval)}function at(e){return location.hostname===e.hostname&&ee(e,"href")&&ee(e,"href").indexOf("#")!==0}function ft(e){return g(e,Q.config.disableSelector)}function dt(t,n,e){if(t instanceof HTMLAnchorElement&&at(t)&&(t.target===""||t.target==="_self")||t.tagName==="FORM"&&String(ee(t,"method")).toLowerCase()!=="dialog"){n.boosted=true;let r,o;if(t.tagName==="A"){r="get";o=ee(t,"href")}else{const i=ee(t,"method");r=i?i.toLowerCase():"get";if(r==="get"){}o=ee(t,"action")}e.forEach(function(e){mt(t,function(e,t){const n=ce(e);if(ft(n)){a(n);return}he(r,o,n,t)},n,e,true)})}}function ht(e,t){const n=ce(t);if(!n){return false}if(e.type==="submit"||e.type==="click"){if(n.tagName==="FORM"){return true}if(f(n,'input[type="submit"], button')&&g(n,"form")!==null){return true}if(n instanceof HTMLAnchorElement&&n.href&&(n.getAttribute("href")==="#"||n.getAttribute("href").indexOf("#")!==0)){return true}}return false}function gt(e,t){return ie(e).boosted&&e instanceof HTMLAnchorElement&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function pt(e,t,n){const r=e.eventFilter;if(r){try{return r.call(t,n)!==true}catch(e){const o=r.source;fe(ne().body,"htmx:eventFilter:error",{error:e,source:o});return true}}return false}function mt(s,l,e,u,c){const a=ie(s);let t;if(u.from){t=m(s,u.from)}else{t=[s]}if(u.changed){t.forEach(function(e){const t=ie(e);t.lastValue=e.value})}se(t,function(o){const i=function(e){if(!le(s)){o.removeEventListener(u.trigger,i);return}if(gt(s,e)){return}if(c||ht(e,s)){e.preventDefault()}if(pt(u,s,e)){return}const t=ie(e);t.triggerSpec=u;if(t.handledFor==null){t.handledFor=[]}if(t.handledFor.indexOf(s)<0){t.handledFor.push(s);if(u.consume){e.stopPropagation()}if(u.target&&e.target){if(!f(ce(e.target),u.target)){return}}if(u.once){if(a.triggeredOnce){return}else{a.triggeredOnce=true}}if(u.changed){const n=ie(o);const r=o.value;if(n.lastValue===r){return}n.lastValue=r}if(a.delayed){clearTimeout(a.delayed)}if(a.throttle){return}if(u.throttle>0){if(!a.throttle){de(s,"htmx:trigger");l(s,e);a.throttle=E().setTimeout(function(){a.throttle=null},u.throttle)}}else if(u.delay>0){a.delayed=E().setTimeout(function(){de(s,"htmx:trigger");l(s,e)},u.delay)}else{de(s,"htmx:trigger");l(s,e)}}};if(e.listenerInfos==null){e.listenerInfos=[]}e.listenerInfos.push({trigger:u.trigger,listener:i,on:o});o.addEventListener(u.trigger,i)})}let yt=false;let xt=null;function bt(){if(!xt){xt=function(){yt=true};window.addEventListener("scroll",xt);setInterval(function(){if(yt){yt=false;se(ne().querySelectorAll("[hx-trigger*='revealed'],[data-hx-trigger*='revealed']"),function(e){wt(e)})}},200)}}function wt(e){if(!s(e,"data-hx-revealed")&&B(e)){e.setAttribute("data-hx-revealed","true");const t=ie(e);if(t.initHash){de(e,"revealed")}else{e.addEventListener("htmx:afterProcessNode",function(){de(e,"revealed")},{once:true})}}}function vt(e,t,n,r){const o=function(){if(!n.loaded){n.loaded=true;t(e)}};if(r>0){E().setTimeout(o,r)}else{o()}}function St(t,n,e){let i=false;se(v,function(r){if(s(t,"hx-"+r)){const o=te(t,"hx-"+r);i=true;n.path=o;n.verb=r;e.forEach(function(e){Et(t,e,n,function(e,t){const n=ce(e);if(g(n,Q.config.disableSelector)){a(n);return}he(r,o,n,t)})})}});return i}function Et(r,e,t,n){if(e.trigger==="revealed"){bt();mt(r,n,t,e);wt(ce(r))}else if(e.trigger==="intersect"){const o={};if(e.root){o.root=ae(r,e.root)}if(e.threshold){o.threshold=parseFloat(e.threshold)}const i=new IntersectionObserver(function(t){for(let e=0;e0){t.polling=true;ct(ce(r),n,e)}else{mt(r,n,t,e)}}function Ct(e){const t=ce(e);if(!t){return false}const n=t.attributes;for(let e=0;e", "+e).join(""));return o}else{return[]}}function qt(e){const t=g(ce(e.target),"button, input[type='submit']");const n=Nt(e);if(n){n.lastButtonClicked=t}}function Lt(e){const t=Nt(e);if(t){t.lastButtonClicked=null}}function Nt(e){const t=g(ce(e.target),"button, input[type='submit']");if(!t){return}const n=y("#"+ee(t,"form"),t.getRootNode())||g(t,"form");if(!n){return}return ie(n)}function At(e){e.addEventListener("click",qt);e.addEventListener("focusin",qt);e.addEventListener("focusout",Lt)}function It(t,e,n){const r=ie(t);if(!Array.isArray(r.onHandlers)){r.onHandlers=[]}let o;const i=function(e){vn(t,function(){if(ft(t)){return}if(!o){o=new Function("event",n)}o.call(t,e)})};t.addEventListener(e,i);r.onHandlers.push({event:e,listener:i})}function Pt(t){ke(t);for(let e=0;eQ.config.historyCacheSize){i.shift()}while(i.length>0){try{localStorage.setItem("htmx-history-cache",JSON.stringify(i));break}catch(e){fe(ne().body,"htmx:historyCacheError",{cause:e,cache:i});i.shift()}}}function _t(t){if(!j()){return null}t=V(t);const n=S(localStorage.getItem("htmx-history-cache"))||[];for(let e=0;e=200&&this.status<400){de(ne().body,"htmx:historyCacheMissLoad",i);const e=D(this.response);const t=e.querySelector("[hx-history-elt],[data-hx-history-elt]")||e;const n=jt();const r=xn(n);Dn(e.title);Ve(n,t,r);Gt(r.tasks);Ut=o;de(ne().body,"htmx:historyRestore",{path:o,cacheMiss:true,serverResponse:this.response})}else{fe(ne().body,"htmx:historyCacheMissLoadError",i)}};e.send()}function Yt(e){zt();e=e||location.pathname+location.search;const t=_t(e);if(t){const n=D(t.content);const r=jt();const o=xn(r);Dn(n.title);Ve(r,n,o);Gt(o.tasks);E().setTimeout(function(){window.scrollTo(0,t.scroll)},0);Ut=e;de(ne().body,"htmx:historyRestore",{path:e,item:t})}else{if(Q.config.refreshOnHistoryMiss){window.location.reload(true)}else{Zt(e)}}}function Wt(e){let t=Se(e,"hx-indicator");if(t==null){t=[e]}se(t,function(e){const t=ie(e);t.requestCount=(t.requestCount||0)+1;e.classList.add.call(e.classList,Q.config.requestClass)});return t}function Qt(e){let t=Se(e,"hx-disabled-elt");if(t==null){t=[]}se(t,function(e){const t=ie(e);t.requestCount=(t.requestCount||0)+1;e.setAttribute("disabled","");e.setAttribute("data-disabled-by-htmx","")});return t}function en(e,t){se(e,function(e){const t=ie(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.classList.remove.call(e.classList,Q.config.requestClass)}});se(t,function(e){const t=ie(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.removeAttribute("disabled");e.removeAttribute("data-disabled-by-htmx")}})}function tn(t,n){for(let e=0;en.indexOf(e)<0)}else{e=e.filter(e=>e!==n)}r.delete(t);se(e,e=>r.append(t,e))}}function sn(t,n,r,o,i){if(o==null||tn(t,o)){return}else{t.push(o)}if(nn(o)){const s=ee(o,"name");let e=o.value;if(o instanceof HTMLSelectElement&&o.multiple){e=F(o.querySelectorAll("option:checked")).map(function(e){return e.value})}if(o instanceof HTMLInputElement&&o.files){e=F(o.files)}rn(s,e,n);if(i){ln(o,r)}}if(o instanceof HTMLFormElement){se(o.elements,function(e){if(t.indexOf(e)>=0){on(e.name,e.value,n)}else{t.push(e)}if(i){ln(e,r)}});new FormData(o).forEach(function(e,t){if(e instanceof File&&e.name===""){return}rn(t,e,n)})}}function ln(e,t){const n=e;if(n.willValidate){de(n,"htmx:validation:validate");if(!n.checkValidity()){t.push({elt:n,message:n.validationMessage,validity:n.validity});de(n,"htmx:validation:failed",{message:n.validationMessage,validity:n.validity})}}}function un(n,e){for(const t of e.keys()){n.delete(t)}e.forEach(function(e,t){n.append(t,e)});return n}function cn(e,t){const n=[];const r=new FormData;const o=new FormData;const i=[];const s=ie(e);if(s.lastButtonClicked&&!le(s.lastButtonClicked)){s.lastButtonClicked=null}let l=e instanceof HTMLFormElement&&e.noValidate!==true||te(e,"hx-validate")==="true";if(s.lastButtonClicked){l=l&&s.lastButtonClicked.formNoValidate!==true}if(t!=="get"){sn(n,o,i,g(e,"form"),l)}sn(n,r,i,e,l);if(s.lastButtonClicked||e.tagName==="BUTTON"||e.tagName==="INPUT"&&ee(e,"type")==="submit"){const c=s.lastButtonClicked||e;const a=ee(c,"name");rn(a,c.value,o)}const u=Se(e,"hx-include");se(u,function(e){sn(n,r,i,ce(e),l);if(!f(e,"form")){se(d(e).querySelectorAll(it),function(e){sn(n,r,i,e,l)})}});un(r,o);return{errors:i,formData:r,values:An(r)}}function an(e,t,n){if(e!==""){e+="&"}if(String(n)==="[object Object]"){n=JSON.stringify(n)}const r=encodeURIComponent(n);e+=encodeURIComponent(t)+"="+r;return e}function fn(e){e=Ln(e);let n="";e.forEach(function(e,t){n=an(n,t,e)});return n}function dn(e,t,n){const r={"HX-Request":"true","HX-Trigger":ee(e,"id"),"HX-Trigger-Name":ee(e,"name"),"HX-Target":te(t,"id"),"HX-Current-URL":ne().location.href};wn(e,"hx-headers",false,r);if(n!==undefined){r["HX-Prompt"]=n}if(ie(e).boosted){r["HX-Boosted"]="true"}return r}function hn(n,e){const t=re(e,"hx-params");if(t){if(t==="none"){return new FormData}else if(t==="*"){return n}else if(t.indexOf("not ")===0){se(t.substr(4).split(","),function(e){e=e.trim();n.delete(e)});return n}else{const r=new FormData;se(t.split(","),function(t){t=t.trim();if(n.has(t)){n.getAll(t).forEach(function(e){r.append(t,e)})}});return r}}else{return n}}function gn(e){return!!ee(e,"href")&&ee(e,"href").indexOf("#")>=0}function pn(e,t){const n=t||re(e,"hx-swap");const r={swapStyle:ie(e).boosted?"innerHTML":Q.config.defaultSwapStyle,swapDelay:Q.config.defaultSwapDelay,settleDelay:Q.config.defaultSettleDelay};if(Q.config.scrollIntoViewOnBoost&&ie(e).boosted&&!gn(e)){r.show="top"}if(n){const s=U(n);if(s.length>0){for(let e=0;e0?o.join(":"):null;r.scroll=c;r.scrollTarget=i}else if(l.indexOf("show:")===0){const a=l.substr(5);var o=a.split(":");const f=o.pop();var i=o.length>0?o.join(":"):null;r.show=f;r.showTarget=i}else if(l.indexOf("focus-scroll:")===0){const d=l.substr("focus-scroll:".length);r.focusScroll=d=="true"}else if(e==0){r.swapStyle=l}else{w("Unknown modifier in hx-swap: "+l)}}}}return r}function mn(e){return re(e,"hx-encoding")==="multipart/form-data"||f(e,"form")&&ee(e,"enctype")==="multipart/form-data"}function yn(t,n,r){let o=null;Bt(n,function(e){if(o==null){o=e.encodeParameters(t,r,n)}});if(o!=null){return o}else{if(mn(n)){return un(new FormData,Ln(r))}else{return fn(r)}}}function xn(e){return{tasks:[],elts:[e]}}function bn(e,t){const n=e[0];const r=e[e.length-1];if(t.scroll){var o=null;if(t.scrollTarget){o=ce(ae(n,t.scrollTarget))}if(t.scroll==="top"&&(n||o)){o=o||n;o.scrollTop=0}if(t.scroll==="bottom"&&(r||o)){o=o||r;o.scrollTop=o.scrollHeight}}if(t.show){var o=null;if(t.showTarget){let e=t.showTarget;if(t.showTarget==="window"){e="body"}o=ce(ae(n,e))}if(t.show==="top"&&(n||o)){o=o||n;o.scrollIntoView({block:"start",behavior:Q.config.scrollBehavior})}if(t.show==="bottom"&&(r||o)){o=o||r;o.scrollIntoView({block:"end",behavior:Q.config.scrollBehavior})}}}function wn(r,e,o,i){if(i==null){i={}}if(r==null){return i}const s=te(r,e);if(s){let e=s.trim();let t=o;if(e==="unset"){return null}if(e.indexOf("javascript:")===0){e=e.substr(11);t=true}else if(e.indexOf("js:")===0){e=e.substr(3);t=true}if(e.indexOf("{")!==0){e="{"+e+"}"}let n;if(t){n=vn(r,function(){return Function("return ("+e+")")()},{})}else{n=S(e)}for(const l in n){if(n.hasOwnProperty(l)){if(i[l]==null){i[l]=n[l]}}}}return wn(ce(u(r)),e,o,i)}function vn(e,t,n){if(Q.config.allowEval){return t()}else{fe(e,"htmx:evalDisallowedError");return n}}function Sn(e,t){return wn(e,"hx-vars",true,t)}function En(e,t){return wn(e,"hx-vals",false,t)}function Cn(e){return ue(Sn(e),En(e))}function On(t,n,r){if(r!==null){try{t.setRequestHeader(n,r)}catch(e){t.setRequestHeader(n,encodeURIComponent(r));t.setRequestHeader(n+"-URI-AutoEncoded","true")}}}function Rn(t){if(t.responseURL&&typeof URL!=="undefined"){try{const e=new URL(t.responseURL);return e.pathname+e.search}catch(e){fe(ne().body,"htmx:badResponseUrl",{url:t.responseURL})}}}function C(e,t){return t.test(e.getAllResponseHeaders())}function Hn(e,t,n){e=e.toLowerCase();if(n){if(n instanceof Element||typeof n==="string"){return he(e,t,null,null,{targetOverride:y(n),returnPromise:true})}else{return he(e,t,y(n.source),n.event,{handler:n.handler,headers:n.headers,values:n.values,targetOverride:y(n.target),swapOverride:n.swap,select:n.select,returnPromise:true})}}else{return he(e,t,null,null,{returnPromise:true})}}function Tn(e){const t=[];while(e){t.push(e);e=e.parentElement}return t}function qn(e,t,n){let r;let o;if(typeof URL==="function"){o=new URL(t,document.location.href);const i=document.location.origin;r=i===o.origin}else{o=t;r=l(t,document.location.origin)}if(Q.config.selfRequestsOnly){if(!r){return false}}return de(e,"htmx:validateUrl",ue({url:o,sameHost:r},n))}function Ln(e){if(e instanceof FormData)return e;const t=new FormData;for(const n in e){if(e.hasOwnProperty(n)){if(typeof e[n].forEach==="function"){e[n].forEach(function(e){t.append(n,e)})}else if(typeof e[n]==="object"&&!(e[n]instanceof Blob)){t.append(n,JSON.stringify(e[n]))}else{t.append(n,e[n])}}}return t}function Nn(r,o,e){return new Proxy(e,{get:function(t,e){if(typeof e==="number")return t[e];if(e==="length")return t.length;if(e==="push"){return function(e){t.push(e);r.append(o,e)}}if(typeof t[e]==="function"){return function(){t[e].apply(t,arguments);r.delete(o);t.forEach(function(e){r.append(o,e)})}}if(t[e]&&t[e].length===1){return t[e][0]}else{return t[e]}},set:function(e,t,n){e[t]=n;r.delete(o);e.forEach(function(e){r.append(o,e)});return true}})}function An(r){return new Proxy(r,{get:function(e,t){if(typeof t==="symbol"){return Reflect.get(e,t)}if(t==="toJSON"){return()=>Object.fromEntries(r)}if(t in e){if(typeof e[t]==="function"){return function(){return r[t].apply(r,arguments)}}else{return e[t]}}const n=r.getAll(t);if(n.length===0){return undefined}else if(n.length===1){return n[0]}else{return Nn(e,t,n)}},set:function(t,n,e){if(typeof n!=="string"){return false}t.delete(n);if(typeof e.forEach==="function"){e.forEach(function(e){t.append(n,e)})}else if(typeof e==="object"&&!(e instanceof Blob)){t.append(n,JSON.stringify(e))}else{t.append(n,e)}return true},deleteProperty:function(e,t){if(typeof t==="string"){e.delete(t)}return true},ownKeys:function(e){return Reflect.ownKeys(Object.fromEntries(e))},getOwnPropertyDescriptor:function(e,t){return Reflect.getOwnPropertyDescriptor(Object.fromEntries(e),t)}})}function he(t,n,r,o,i,D){let s=null;let l=null;i=i!=null?i:{};if(i.returnPromise&&typeof Promise!=="undefined"){var e=new Promise(function(e,t){s=e;l=t})}if(r==null){r=ne().body}const M=i.handler||Mn;const X=i.select||null;if(!le(r)){oe(s);return e}const u=i.targetOverride||ce(Ce(r));if(u==null||u==ve){fe(r,"htmx:targetError",{target:te(r,"hx-target")});oe(l);return e}let c=ie(r);const a=c.lastButtonClicked;if(a){const L=ee(a,"formaction");if(L!=null){n=L}const N=ee(a,"formmethod");if(N!=null){if(N.toLowerCase()!=="dialog"){t=N}}}const f=re(r,"hx-confirm");if(D===undefined){const K=function(e){return he(t,n,r,o,i,!!e)};const G={target:u,elt:r,path:n,verb:t,triggeringEvent:o,etc:i,issueRequest:K,question:f};if(de(r,"htmx:confirm",G)===false){oe(s);return e}}let d=r;let h=re(r,"hx-sync");let g=null;let F=false;if(h){const A=h.split(":");const I=A[0].trim();if(I==="this"){d=Ee(r,"hx-sync")}else{d=ce(ae(r,I))}h=(A[1]||"drop").trim();c=ie(d);if(h==="drop"&&c.xhr&&c.abortable!==true){oe(s);return e}else if(h==="abort"){if(c.xhr){oe(s);return e}else{F=true}}else if(h==="replace"){de(d,"htmx:abort")}else if(h.indexOf("queue")===0){const Z=h.split(" ");g=(Z[1]||"last").trim()}}if(c.xhr){if(c.abortable){de(d,"htmx:abort")}else{if(g==null){if(o){const P=ie(o);if(P&&P.triggerSpec&&P.triggerSpec.queue){g=P.triggerSpec.queue}}if(g==null){g="last"}}if(c.queuedRequests==null){c.queuedRequests=[]}if(g==="first"&&c.queuedRequests.length===0){c.queuedRequests.push(function(){he(t,n,r,o,i)})}else if(g==="all"){c.queuedRequests.push(function(){he(t,n,r,o,i)})}else if(g==="last"){c.queuedRequests=[];c.queuedRequests.push(function(){he(t,n,r,o,i)})}oe(s);return e}}const p=new XMLHttpRequest;c.xhr=p;c.abortable=F;const m=function(){c.xhr=null;c.abortable=false;if(c.queuedRequests!=null&&c.queuedRequests.length>0){const e=c.queuedRequests.shift();e()}};const B=re(r,"hx-prompt");if(B){var y=prompt(B);if(y===null||!de(r,"htmx:prompt",{prompt:y,target:u})){oe(s);m();return e}}if(f&&!D){if(!confirm(f)){oe(s);m();return e}}let x=dn(r,u,y);if(t!=="get"&&!mn(r)){x["Content-Type"]="application/x-www-form-urlencoded"}if(i.headers){x=ue(x,i.headers)}const U=cn(r,t);let b=U.errors;const j=U.formData;if(i.values){un(j,Ln(i.values))}const V=Ln(Cn(r));const w=un(j,V);let v=hn(w,r);if(Q.config.getCacheBusterParam&&t==="get"){v.set("org.htmx.cache-buster",ee(u,"id")||"true")}if(n==null||n===""){n=ne().location.href}const S=wn(r,"hx-request");const _=ie(r).boosted;let E=Q.config.methodsThatUseUrlParams.indexOf(t)>=0;const C={boosted:_,useUrlParams:E,formData:v,parameters:An(v),unfilteredFormData:w,unfilteredParameters:An(w),headers:x,target:u,verb:t,errors:b,withCredentials:i.credentials||S.credentials||Q.config.withCredentials,timeout:i.timeout||S.timeout||Q.config.timeout,path:n,triggeringEvent:o};if(!de(r,"htmx:configRequest",C)){oe(s);m();return e}n=C.path;t=C.verb;x=C.headers;v=Ln(C.parameters);b=C.errors;E=C.useUrlParams;if(b&&b.length>0){de(r,"htmx:validation:halted",C);oe(s);m();return e}const $=n.split("#");const z=$[0];const O=$[1];let R=n;if(E){R=z;const Y=!v.keys().next().done;if(Y){if(R.indexOf("?")<0){R+="?"}else{R+="&"}R+=fn(v);if(O){R+="#"+O}}}if(!qn(r,R,C)){fe(r,"htmx:invalidPath",C);oe(l);return e}p.open(t.toUpperCase(),R,true);p.overrideMimeType("text/html");p.withCredentials=C.withCredentials;p.timeout=C.timeout;if(S.noHeaders){}else{for(const k in x){if(x.hasOwnProperty(k)){const W=x[k];On(p,k,W)}}}const H={xhr:p,target:u,requestConfig:C,etc:i,boosted:_,select:X,pathInfo:{requestPath:n,finalRequestPath:R,responsePath:null,anchor:O}};p.onload=function(){try{const t=Tn(r);H.pathInfo.responsePath=Rn(p);M(r,H);if(H.keepIndicators!==true){en(T,q)}de(r,"htmx:afterRequest",H);de(r,"htmx:afterOnLoad",H);if(!le(r)){let e=null;while(t.length>0&&e==null){const n=t.shift();if(le(n)){e=n}}if(e){de(e,"htmx:afterRequest",H);de(e,"htmx:afterOnLoad",H)}}oe(s);m()}catch(e){fe(r,"htmx:onLoadError",ue({error:e},H));throw e}};p.onerror=function(){en(T,q);fe(r,"htmx:afterRequest",H);fe(r,"htmx:sendError",H);oe(l);m()};p.onabort=function(){en(T,q);fe(r,"htmx:afterRequest",H);fe(r,"htmx:sendAbort",H);oe(l);m()};p.ontimeout=function(){en(T,q);fe(r,"htmx:afterRequest",H);fe(r,"htmx:timeout",H);oe(l);m()};if(!de(r,"htmx:beforeRequest",H)){oe(s);m();return e}var T=Wt(r);var q=Qt(r);se(["loadstart","loadend","progress","abort"],function(t){se([p,p.upload],function(e){e.addEventListener(t,function(e){de(r,"htmx:xhr:"+t,{lengthComputable:e.lengthComputable,loaded:e.loaded,total:e.total})})})});de(r,"htmx:beforeSend",H);const J=E?null:yn(p,r,v);p.send(J);return e}function In(e,t){const n=t.xhr;let r=null;let o=null;if(C(n,/HX-Push:/i)){r=n.getResponseHeader("HX-Push");o="push"}else if(C(n,/HX-Push-Url:/i)){r=n.getResponseHeader("HX-Push-Url");o="push"}else if(C(n,/HX-Replace-Url:/i)){r=n.getResponseHeader("HX-Replace-Url");o="replace"}if(r){if(r==="false"){return{}}else{return{type:o,path:r}}}const i=t.pathInfo.finalRequestPath;const s=t.pathInfo.responsePath;const l=re(e,"hx-push-url");const u=re(e,"hx-replace-url");const c=ie(e).boosted;let a=null;let f=null;if(l){a="push";f=l}else if(u){a="replace";f=u}else if(c){a="push";f=s||i}if(f){if(f==="false"){return{}}if(f==="true"){f=s||i}if(t.pathInfo.anchor&&f.indexOf("#")===-1){f=f+"#"+t.pathInfo.anchor}return{type:a,path:f}}else{return{}}}function Pn(e,t){var n=new RegExp(e.code);return n.test(t.toString(10))}function kn(e){for(var t=0;t0){E().setTimeout(e,y.swapDelay)}else{e()}}if(f){fe(o,"htmx:responseError",ue({error:"Response Status Error Code "+s.status+" from "+i.pathInfo.requestPath},i))}}const Xn={};function Fn(){return{init:function(e){return null},getSelectors:function(){return null},onEvent:function(e,t){return true},transformResponse:function(e,t,n){return e},isInlineSwap:function(e){return false},handleSwap:function(e,t,n,r){return false},encodeParameters:function(e,t,n){return null}}}function Bn(e,t){if(t.init){t.init(n)}Xn[e]=ue(Fn(),t)}function Un(e){delete Xn[e]}function jn(e,n,r){if(n==undefined){n=[]}if(e==undefined){return n}if(r==undefined){r=[]}const t=te(e,"hx-ext");if(t){se(t.split(","),function(e){e=e.replace(/ /g,"");if(e.slice(0,7)=="ignore:"){r.push(e.slice(7));return}if(r.indexOf(e)<0){const t=Xn[e];if(t&&n.indexOf(t)<0){n.push(t)}}})}return jn(ce(u(e)),n,r)}var Vn=false;ne().addEventListener("DOMContentLoaded",function(){Vn=true});function _n(e){if(Vn||ne().readyState==="complete"){e()}else{ne().addEventListener("DOMContentLoaded",e)}}function $n(){if(Q.config.includeIndicatorStyles!==false){const e=Q.config.inlineStyleNonce?` nonce="${Q.config.inlineStyleNonce}"`:"";ne().head.insertAdjacentHTML("beforeend","")}}function zn(){const e=ne().querySelector('meta[name="htmx-config"]');if(e){return S(e.content)}else{return null}}function Jn(){const e=zn();if(e){Q.config=ue(Q.config,e)}}_n(function(){Jn();$n();let e=ne().body;Dt(e);const t=ne().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(e){const t=e.target;const n=ie(t);if(n&&n.xhr){n.xhr.abort()}});const n=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(e){if(e.state&&e.state.htmx){Yt();se(t,function(e){de(e,"htmx:restored",{document:ne(),triggerEvent:de})})}else{if(n){n(e)}}};E().setTimeout(function(){de(e,"htmx:load",{});e=null},0)});return Q}();
\ No newline at end of file
diff --git a/merge/src/argus_htmx/static/hyperscript-0.9.13.min.js b/merge/src/argus_htmx/static/hyperscript-0.9.13.min.js
new file mode 100644
index 000000000..810c119f5
--- /dev/null
+++ b/merge/src/argus_htmx/static/hyperscript-0.9.13.min.js
@@ -0,0 +1 @@
+(function(e,t){const r=t(e);if(typeof exports==="object"&&typeof exports["nodeName"]!=="string"){module.exports=r}else{e["_hyperscript"]=r;if("document"in e)e["_hyperscript"].browserInit()}})(typeof self!=="undefined"?self:this,(e=>{"use strict";const t={dynamicResolvers:[function(e,t){if(e==="Fixed"){return Number(t).toFixed()}else if(e.indexOf("Fixed:")===0){let r=e.split(":")[1];return Number(t).toFixed(parseInt(r))}}],String:function(e){if(e.toString){return e.toString()}else{return""+e}},Int:function(e){return parseInt(e)},Float:function(e){return parseFloat(e)},Number:function(e){return Number(e)},Date:function(e){return new Date(e)},Array:function(e){return Array.from(e)},JSON:function(e){return JSON.stringify(e)},Object:function(e){if(e instanceof String){e=e.toString()}if(typeof e==="string"){return JSON.parse(e)}else{return Object.assign({},e)}}};const r={attributes:"_, script, data-script",defaultTransition:"all 500ms ease-in",disableSelector:"[disable-scripting], [data-disable-scripting]",hideShowStrategies:{},conversions:t};class n{static OP_TABLE={"+":"PLUS","-":"MINUS","*":"MULTIPLY","/":"DIVIDE",".":"PERIOD","..":"ELLIPSIS","\\":"BACKSLASH",":":"COLON","%":"PERCENT","|":"PIPE","!":"EXCLAMATION","?":"QUESTION","#":"POUND","&":"AMPERSAND",$:"DOLLAR",";":"SEMI",",":"COMMA","(":"L_PAREN",")":"R_PAREN","<":"L_ANG",">":"R_ANG","<=":"LTE_ANG",">=":"GTE_ANG","==":"EQ","===":"EQQ","!=":"NEQ","!==":"NEQQ","{":"L_BRACE","}":"R_BRACE","[":"L_BRACKET","]":"R_BRACKET","=":"EQUALS"};static isValidCSSClassChar(e){return n.isAlpha(e)||n.isNumeric(e)||e==="-"||e==="_"||e===":"}static isValidCSSIDChar(e){return n.isAlpha(e)||n.isNumeric(e)||e==="-"||e==="_"||e===":"}static isWhitespace(e){return e===" "||e==="\t"||n.isNewline(e)}static positionString(e){return"[Line: "+e.line+", Column: "+e.column+"]"}static isNewline(e){return e==="\r"||e==="\n"}static isNumeric(e){return e>="0"&&e<="9"}static isAlpha(e){return e>="a"&&e<="z"||e>="A"&&e<="Z"}static isIdentifierChar(e,t){return e==="_"||e==="$"}static isReservedChar(e){return e==="`"||e==="^"}static isValidSingleQuoteStringStart(e){if(e.length>0){var t=e[e.length-1];if(t.type==="IDENTIFIER"||t.type==="CLASS_REF"||t.type==="ID_REF"){return false}if(t.op&&(t.value===">"||t.value===")")){return false}}return true}static tokenize(e,t){var r=[];var a=e;var o=0;var s=0;var u=1;var l="";var c=0;function f(){return t&&c===0}while(o=0){return this.consumeToken()}}requireToken(e,t){var r=this.matchToken(e,t);if(r){return r}else{this.raiseError(this,"Expected '"+e+"' but found '"+this.currentToken().value+"'")}}peekToken(e,t,r){t=t||0;r=r||"IDENTIFIER";if(this.tokens[t]&&this.tokens[t].value===e&&this.tokens[t].type===r){return this.tokens[t]}}matchToken(e,t){if(this.follows.indexOf(e)!==-1){return}t=t||"IDENTIFIER";if(this.currentToken()&&this.currentToken().value===e&&this.currentToken().type===t){return this.consumeToken()}}consumeToken(){var e=this.tokens.shift();this.consumed.push(e);this._lastConsumed=e;this.consumeWhitespace();return e}consumeUntil(e,t){var r=[];var n=this.token(0,true);while((t==null||n.type!==t)&&(e==null||n.value!==e)&&n.type!=="EOF"){var i=this.tokens.shift();this.consumed.push(i);r.push(n);n=this.token(0,true)}this.consumeWhitespace();return r}lastWhitespace(){if(this.consumed[this.consumed.length-1]&&this.consumed[this.consumed.length-1].type==="WHITESPACE"){return this.consumed[this.consumed.length-1].value}else{return""}}consumeUntilWhitespace(){return this.consumeUntil(null,"WHITESPACE")}hasMore(){return this.tokens.length>0}token(e,t){var r;var n=0;do{if(!t){while(this.tokens[n]&&this.tokens[n].type==="WHITESPACE"){n++}}r=this.tokens[n];e--;n++}while(e>-1);if(r){return r}else{return{type:"EOF",value:"<<>>"}}}currentToken(){return this.token(0)}lastMatch(){return this._lastConsumed}static sourceFor=function(){return this.programSource.substring(this.startToken.start,this.endToken.end)};static lineFor=function(){return this.programSource.split("\n")[this.startToken.line-1]};follows=[];pushFollow(e){this.follows.push(e)}popFollow(){this.follows.pop()}clearFollows(){var e=this.follows;this.follows=[];return e}restoreFollows(e){this.follows=e}}class a{constructor(e){this.runtime=e;this.possessivesDisabled=false;this.addGrammarElement("feature",(function(e,t,r){if(r.matchOpToken("(")){var n=e.requireElement("feature",r);r.requireOpToken(")");return n}var i=e.FEATURES[r.currentToken().value||""];if(i){return i(e,t,r)}}));this.addGrammarElement("command",(function(e,t,r){if(r.matchOpToken("(")){const t=e.requireElement("command",r);r.requireOpToken(")");return t}var n=e.COMMANDS[r.currentToken().value||""];let i;if(n){i=n(e,t,r)}else if(r.currentToken().type==="IDENTIFIER"){i=e.parseElement("pseudoCommand",r)}if(i){return e.parseElement("indirectStatement",r,i)}return i}));this.addGrammarElement("commandList",(function(e,t,r){if(r.hasMore()){var n=e.parseElement("command",r);if(n){r.matchToken("then");const t=e.parseElement("commandList",r);if(t)n.next=t;return n}}return{type:"emptyCommandListCommand",op:function(e){return t.findNext(this,e)},execute:function(e){return t.unifiedExec(this,e)}}}));this.addGrammarElement("leaf",(function(e,t,r){var n=e.parseAnyOf(e.LEAF_EXPRESSIONS,r);if(n==null){return e.parseElement("symbol",r)}return n}));this.addGrammarElement("indirectExpression",(function(e,t,r,n){for(var i=0;i{this.unifiedExec(e,t)})).catch((e=>{this.unifiedExec({op:function(){throw e}},t)}));return}else if(r===o.HALT){if(t.meta.finallyHandler&&!t.meta.handlingFinally){t.meta.handlingFinally=true;e=t.meta.finallyHandler}else{if(t.meta.onHalt){t.meta.onHalt()}if(t.meta.currentException){if(t.meta.reject){t.meta.reject(t.meta.currentException);return}else{throw t.meta.currentException}}else{return}}}else{e=r}}}unifiedEval(e,t){var r=[t];var n=false;var i=false;if(e.args){for(var a=0;a{r=this.wrapArrays(r);Promise.all(r).then((function(r){if(i){this.unwrapAsyncs(r)}try{var a=e.op.apply(e,r);t(a)}catch(e){n(e)}})).catch((function(e){n(e)}))}))}else{if(i){this.unwrapAsyncs(r)}return e.op.apply(e,r)}}_scriptAttrs=null;getScriptAttributes(){if(this._scriptAttrs==null){this._scriptAttrs=r.attributes.replace(/ /g,"").split(",")}return this._scriptAttrs}getScript(e){for(var t=0;t{this.initElement(e,e instanceof HTMLScriptElement&&e.type==="text/hyperscript"?document.body:e)}))}}initElement(e,t){if(e.closest&&e.closest(r.disableSelector)){return}var n=this.getInternalData(e);if(!n.initialized){var i=this.getScript(e);if(i){try{n.initialized=true;n.script=i;const r=this.lexer,s=this.parser;var a=r.tokenize(i);var o=s.parseHyperScript(a);if(!o)return;o.apply(t||e,e);setTimeout((()=>{this.triggerEvent(t||e,"load",{hyperscript:true})}),1)}catch(t){this.triggerEvent(e,"exception",{error:t});console.error("hyperscript errors were found on the following element:",e,"\n\n",t.message,t.stack)}}}}internalDataMap=new WeakMap;getInternalData(e){var t=this.internalDataMap.get(e);if(typeof t==="undefined"){this.internalDataMap.set(e,t={})}return t}typeCheck(e,t,r){if(e==null&&r){return true}var n=Object.prototype.toString.call(e).slice(8,-1);return n===t}getElementScope(e){var t=e.meta&&e.meta.owner;if(t){var r=this.getInternalData(t);var n="elementScope";if(e.meta.feature&&e.meta.feature.behavior){n=e.meta.feature.behavior+"Scope"}var i=h(r,n);return i}else{return{}}}isReservedWord(e){return["meta","it","result","locals","event","target","detail","sender","body"].includes(e)}isHyperscriptContext(e){return e instanceof f}resolveSymbol(t,r,n){if(t==="me"||t==="my"||t==="I"){return r.me}if(t==="it"||t==="its"||t==="result"){return r.result}if(t==="you"||t==="your"||t==="yourself"){return r.you}else{if(n==="global"){return e[t]}else if(n==="element"){var i=this.getElementScope(r);return i[t]}else if(n==="local"){return r.locals[t]}else{if(r.meta&&r.meta.context){var a=r.meta.context[t];if(typeof a!=="undefined"){return a}if(r.meta.context.detail){a=r.meta.context.detail[t];if(typeof a!=="undefined"){return a}}}if(this.isHyperscriptContext(r)&&!this.isReservedWord(t)){var o=r.locals[t]}else{var o=r[t]}if(typeof o!=="undefined"){return o}else{var i=this.getElementScope(r);o=i[t];if(typeof o!=="undefined"){return o}else{return e[t]}}}}}setSymbol(t,r,n,i){if(n==="global"){e[t]=i}else if(n==="element"){var a=this.getElementScope(r);a[t]=i}else if(n==="local"){r.locals[t]=i}else{if(this.isHyperscriptContext(r)&&!this.isReservedWord(t)&&typeof r.locals[t]!=="undefined"){r.locals[t]=i}else{var a=this.getElementScope(r);var o=a[t];if(typeof o!=="undefined"){a[t]=i}else{if(this.isHyperscriptContext(r)&&!this.isReservedWord(t)){r.locals[t]=i}else{r[t]=i}}}}}findNext(e,t){if(e){if(e.resolveNext){return e.resolveNext(t)}else if(e.next){return e.next}else{return this.findNext(e.parent,t)}}}flatGet(e,t,r){if(e!=null){var n=r(e,t);if(typeof n!=="undefined"){return n}if(this.shouldAutoIterate(e)){var i=[];for(var a of e){var o=r(a,t);i.push(o)}return i}}}resolveProperty(e,t){return this.flatGet(e,t,((e,t)=>e[t]))}resolveAttribute(e,t){return this.flatGet(e,t,((e,t)=>e.getAttribute&&e.getAttribute(t)))}resolveStyle(e,t){return this.flatGet(e,t,((e,t)=>e.style&&e.style[t]))}resolveComputedStyle(e,t){return this.flatGet(e,t,((e,t)=>getComputedStyle(e).getPropertyValue(t)))}assignToNamespace(t,r,n,i){let a;if(typeof document!=="undefined"&&t===document.body){a=e}else{a=this.getHyperscriptFeatures(t)}var o;while((o=r.shift())!==undefined){var s=a[o];if(s==null){s={};a[o]=s}a=s}a[n]=i}getHyperTrace(e,t){var r=[];var n=e;while(n.meta.caller){n=n.meta.caller}if(n.meta.traceMap){return n.meta.traceMap.get(t,r)}}registerHyperTrace(e,t){var r=[];var n=null;while(e!=null){r.push(e);n=e;e=e.meta.caller}if(n.meta.traceMap==null){n.meta.traceMap=new Map}if(!n.meta.traceMap.get(t)){var i={trace:r,print:function(e){e=e||console.error;e("hypertrace /// ");var t=0;for(var n=0;n",i.meta.feature.displayName.padEnd(t+2),"-",i.meta.owner)}}};n.meta.traceMap.set(t,i)}}escapeSelector(e){return e.replace(/:/g,(function(e){return"\\"+e}))}nullCheck(e,t){if(e==null){throw new Error("'"+t.sourceFor()+"' is null")}}isEmpty(e){return e==undefined||e.length===0}doesExist(e){if(e==null){return false}if(this.shouldAutoIterate(e)){for(const t of e){return true}return false}return true}getRootNode(e){if(e&&e instanceof Node){var t=e.getRootNode();if(t instanceof Document||t instanceof ShadowRoot)return t}return document}getEventQueueFor(e,t){let r=this.getInternalData(e);var n=r.eventQueues;if(n==null){n=new Map;r.eventQueues=n}var i=n.get(t);if(i==null){i={queue:[],executing:false};n.set(t,i)}return i}beepValueToConsole(e,t,r){if(this.triggerEvent(e,"hyperscript:beep",{element:e,expression:t,value:r})){var n;if(r){if(r instanceof m){n="ElementCollection"}else if(r.constructor){n=r.constructor.name}else{n="unknown"}}else{n="object (null)"}var a=r;if(n==="String"){a='"'+a+'"'}else if(r instanceof m){a=Array.from(r)}console.log("///_ BEEP! The expression ("+i.sourceFor.call(t).replace("beep! ","")+") evaluates to:",a,"of type "+n)}}hyperscriptUrl="document"in e&&document.currentScript?document.currentScript.src:null}function s(){let e=document.cookie.split("; ").map((e=>{let t=e.split("=");return{name:t[0],value:decodeURIComponent(t[1])}}));return e}function u(e){document.cookie=e+"=;expires=Thu, 01 Jan 1970 00:00:00 GMT"}function l(){for(const e of s()){u(e.name)}}const c=new Proxy({},{get(e,t){if(t==="then"||t==="asyncWrapper"){return null}else if(t==="length"){return s().length}else if(t==="clear"){return u}else if(t==="clearAll"){return l}else if(typeof t==="string"){if(!isNaN(t)){return s()[parseInt(t)]}else{let e=document.cookie.split("; ").find((e=>e.startsWith(t+"=")))?.split("=")[1];if(e){return decodeURIComponent(e)}}}else if(t===Symbol.iterator){return s()[t]}},set(e,t,r){var n=null;if("string"===typeof r){n=encodeURIComponent(r);n+=";samesite=lax"}else{n=encodeURIComponent(r.value);if(r.expires){n+=";expires="+r.maxAge}if(r.maxAge){n+=";max-age="+r.maxAge}if(r.partitioned){n+=";partitioned="+r.partitioned}if(r.path){n+=";path="+r.path}if(r.samesite){n+=";samesite="+r.path}if(r.secure){n+=";secure="+r.path}}document.cookie=t+"="+n;return true}});class f{constructor(t,r,n,i,a){this.meta={parser:a.parser,lexer:a.lexer,runtime:a,owner:t,feature:r,iterators:{},ctx:this};this.locals={cookies:c};this.me=n,this.you=undefined;this.result=undefined;this.event=i;this.target=i?i.target:null;this.detail=i?i.detail:null;this.sender=i?i.detail?i.detail.sender:null:null;this.body="document"in e?document.body:null;a.addFeatures(t,this)}}class m{constructor(e,t,r){this._css=e;this.relativeToElement=t;this.escape=r;this[p]=true}get css(){if(this.escape){return o.prototype.escapeSelector(this._css)}else{return this._css}}get className(){return this._css.substr(1)}get id(){return this.className()}contains(e){for(let t of this){if(t.contains(e)){return true}}return false}get length(){return this.selectMatches().length}[Symbol.iterator](){let e=this.selectMatches();return e[Symbol.iterator]()}selectMatches(){let e=o.prototype.getRootNode(this.relativeToElement).querySelectorAll(this.css);return e}}const p=Symbol();function h(e,t){var r=e[t];if(r){return r}else{var n={};e[t]=n;return n}}function v(e){try{return JSON.parse(e)}catch(e){d(e);return null}}function d(e){if(console.error){console.error(e)}else if(console.log){console.log("ERROR: ",e)}}function E(e,t){return new(e.bind.apply(e,[e].concat(t)))}function T(t){t.addLeafExpression("parenthesized",(function(e,t,r){if(r.matchOpToken("(")){var n=r.clearFollows();try{var i=e.requireElement("expression",r)}finally{r.restoreFollows(n)}r.requireOpToken(")");return i}}));t.addLeafExpression("string",(function(e,t,r){var i=r.matchTokenType("STRING");if(!i)return;var a=i.value;var o;if(i.template){var s=n.tokenize(a,true);o=e.parseStringTemplate(s)}else{o=[]}return{type:"string",token:i,args:o,op:function(e){var t="";for(var r=1;re instanceof Element))}get css(){let e="",t=0;for(const r of this.templateParts){if(r instanceof Element){e+="[data-hs-query-id='"+t+++"']"}else e+=r}return e}[Symbol.iterator](){this.elements.forEach(((e,t)=>e.dataset.hsQueryId=t));const e=super[Symbol.iterator]();this.elements.forEach((e=>e.removeAttribute("data-hs-query-id")));return e}}t.addLeafExpression("queryRef",(function(e,t,i){var a=i.matchOpToken("<");if(!a)return;var o=i.consumeUntil("/");i.requireOpToken("/");i.requireOpToken(">");var s=o.map((function(e){if(e.type==="STRING"){return'"'+e.value+'"'}else{return e.value}})).join("");var u,l,c;if(s.indexOf("$")>=0){u=true;l=n.tokenize(s,true);c=e.parseStringTemplate(l)}return{type:"queryRef",css:s,args:c,op:function(e,...t){if(u){return new r(s,e.me,t)}else{return new m(s,e.me)}},evaluate:function(e){return t.unifiedEval(this,e)}}}));t.addLeafExpression("attributeRef",(function(e,t,r){var n=r.matchTokenType("ATTRIBUTE_REF");if(!n)return;if(!n.value)return;var i=n.value;if(i.indexOf("[")===0){var a=i.substring(2,i.length-1)}else{var a=i.substring(1)}var o="["+a+"]";var s=a.split("=");var u=s[0];var l=s[1];if(l){if(l.indexOf('"')===0){l=l.substring(1,l.length-1)}}return{type:"attributeRef",name:u,css:o,value:l,op:function(e){var t=e.you||e.me;if(t){return t.getAttribute(u)}},evaluate:function(e){return t.unifiedEval(this,e)}}}));t.addLeafExpression("styleRef",(function(e,t,r){var n=r.matchTokenType("STYLE_REF");if(!n)return;if(!n.value)return;var i=n.value.substr(1);if(i.startsWith("computed-")){i=i.substr("computed-".length);return{type:"computedStyleRef",name:i,op:function(e){var r=e.you||e.me;if(r){return t.resolveComputedStyle(r,i)}},evaluate:function(e){return t.unifiedEval(this,e)}}}else{return{type:"styleRef",name:i,op:function(e){var r=e.you||e.me;if(r){return t.resolveStyle(r,i)}},evaluate:function(e){return t.unifiedEval(this,e)}}}}));t.addGrammarElement("objectKey",(function(e,t,r){var n;if(n=r.matchTokenType("STRING")){return{type:"objectKey",key:n.value,evaluate:function(){return n.value}}}else if(r.matchOpToken("[")){var i=e.parseElement("expression",r);r.requireOpToken("]");return{type:"objectKey",expr:i,args:[i],op:function(e,t){return t},evaluate:function(e){return t.unifiedEval(this,e)}}}else{var a="";do{n=r.matchTokenType("IDENTIFIER")||r.matchOpToken("-");if(n)a+=n.value}while(n);return{type:"objectKey",key:a,evaluate:function(){return a}}}}));t.addLeafExpression("objectLiteral",(function(e,t,r){if(!r.matchOpToken("{"))return;var n=[];var i=[];if(!r.matchOpToken("}")){do{var a=e.requireElement("objectKey",r);r.requireOpToken(":");var o=e.requireElement("expression",r);i.push(o);n.push(a)}while(r.matchOpToken(","));r.requireOpToken("}")}return{type:"objectLiteral",args:[n,i],op:function(e,t,r){var n={};for(var i=0;i");var a=e.requireElement("expression",r);return{type:"blockLiteral",args:n,expr:a,evaluate:function(e){var t=function(){for(var t=0;t=0;a--){var o=i[a];if(o.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_FOLLOWING){return o}}if(n){return i[i.length-1]}};var l=function(e,t,r,n){var i=[];o.prototype.forEach(t,(function(t){if(t.matches(r)||t===e){i.push(t)}}));for(var a=0;a","<=",">=","==","===","!=","!==");var a=i?i.value:null;var o=true;var s=false;if(a==null){if(r.matchToken("is")||r.matchToken("am")){if(r.matchToken("not")){if(r.matchToken("in")){a="not in"}else if(r.matchToken("a")){a="not a";s=true}else if(r.matchToken("empty")){a="not empty";o=false}else{if(r.matchToken("really")){a="!=="}else{a="!="}if(r.matchToken("equal")){r.matchToken("to")}}}else if(r.matchToken("in")){a="in"}else if(r.matchToken("a")){a="a";s=true}else if(r.matchToken("empty")){a="empty";o=false}else if(r.matchToken("less")){r.requireToken("than");if(r.matchToken("or")){r.requireToken("equal");r.requireToken("to");a="<="}else{a="<"}}else if(r.matchToken("greater")){r.requireToken("than");if(r.matchToken("or")){r.requireToken("equal");r.requireToken("to");a=">="}else{a=">"}}else{if(r.matchToken("really")){a="==="}else{a="=="}if(r.matchToken("equal")){r.matchToken("to")}}}else if(r.matchToken("equals")){a="=="}else if(r.matchToken("really")){r.requireToken("equals");a="==="}else if(r.matchToken("exist")||r.matchToken("exists")){a="exist";o=false}else if(r.matchToken("matches")||r.matchToken("match")){a="match"}else if(r.matchToken("contains")||r.matchToken("contain")){a="contain"}else if(r.matchToken("includes")||r.matchToken("include")){a="include"}else if(r.matchToken("do")||r.matchToken("does")){r.requireToken("not");if(r.matchToken("matches")||r.matchToken("match")){a="not match"}else if(r.matchToken("contains")||r.matchToken("contain")){a="not contain"}else if(r.matchToken("exist")||r.matchToken("exist")){a="not exist";o=false}else if(r.matchToken("include")){a="not include"}else{e.raiseParseError(r,"Expected matches or contains")}}}if(a){var u,l,c;if(s){u=r.requireTokenType("IDENTIFIER");l=!r.matchOpToken("!")}else if(o){c=e.requireElement("mathExpression",r);if(a==="match"||a==="not match"){c=c.css?c.css:c}}var m=n;n={type:"comparisonOperator",operator:a,typeName:u,nullOk:l,lhs:n,rhs:c,args:[n,c],op:function(e,r,n){if(a==="=="){return r==n}else if(a==="!="){return r!=n}if(a==="==="){return r===n}else if(a==="!=="){return r!==n}if(a==="match"){return r!=null&&p(m,r,n)}if(a==="not match"){return r==null||!p(m,r,n)}if(a==="in"){return n!=null&&f(c,n,r)}if(a==="not in"){return n==null||!f(c,n,r)}if(a==="contain"){return r!=null&&f(m,r,n)}if(a==="not contain"){return r==null||!f(m,r,n)}if(a==="include"){return r!=null&&f(m,r,n)}if(a==="not include"){return r==null||!f(m,r,n)}if(a==="==="){return r===n}else if(a==="!=="){return r!==n}else if(a==="<"){return r"){return r>n}else if(a==="<="){return r<=n}else if(a===">="){return r>=n}else if(a==="empty"){return t.isEmpty(r)}else if(a==="not empty"){return!t.isEmpty(r)}else if(a==="exist"){return t.doesExist(r)}else if(a==="not exist"){return!t.doesExist(r)}else if(a==="a"){return t.typeCheck(r,u.value,l)}else if(a==="not a"){return!t.typeCheck(r,u.value,l)}else{throw"Unknown comparison : "+a}},evaluate:function(e){return t.unifiedEval(this,e)}}}return n}));t.addGrammarElement("comparisonExpression",(function(e,t,r){return e.parseAnyOf(["comparisonOperator","mathExpression"],r)}));t.addGrammarElement("logicalOperator",(function(e,t,r){var n=e.parseElement("comparisonExpression",r);var i,a=null;i=r.matchToken("and")||r.matchToken("or");while(i){a=a||i;if(a.value!==i.value){e.raiseParseError(r,"You must parenthesize logical operations with different operators")}var o=e.requireElement("comparisonExpression",r);const s=i.value;n={type:"logicalOperator",operator:s,lhs:n,rhs:o,args:[n,o],op:function(e,t,r){if(s==="and"){return t&&r}else{return t||r}},evaluate:function(e){return t.unifiedEval(this,e)}};i=r.matchToken("and")||r.matchToken("or")}return n}));t.addGrammarElement("logicalExpression",(function(e,t,r){return e.parseAnyOf(["logicalOperator","mathExpression"],r)}));t.addGrammarElement("asyncExpression",(function(e,t,r){if(r.matchToken("async")){var n=e.requireElement("logicalExpression",r);var i={type:"asyncExpression",value:n,evaluate:function(e){return{asyncWrapper:true,value:this.value.evaluate(e)}}};return i}else{return e.parseElement("logicalExpression",r)}}));t.addGrammarElement("expression",(function(e,t,r){r.matchToken("the");return e.parseElement("asyncExpression",r)}));t.addGrammarElement("assignableExpression",(function(e,t,r){r.matchToken("the");var n=e.parseElement("primaryExpression",r);if(n&&(n.type==="symbol"||n.type==="ofExpression"||n.type==="propertyAccess"||n.type==="attributeRefAccess"||n.type==="attributeRef"||n.type==="styleRef"||n.type==="arrayIndex"||n.type==="possessive")){return n}else{e.raiseParseError(r,"A target expression must be writable. The expression type '"+(n&&n.type)+"' is not.")}return n}));t.addGrammarElement("hyperscript",(function(e,t,r){var n=[];if(r.hasMore()){while(e.featureStart(r.currentToken())||r.currentToken().value==="("){var i=e.requireElement("feature",r);n.push(i);r.matchToken("end")}}return{type:"hyperscript",features:n,apply:function(e,t,r){for(const i of n){i.install(e,t,r)}}}}));var v=function(e){var t=[];if(e.token(0).value==="("&&(e.token(1).value===")"||e.token(2).value===","||e.token(2).value===")")){e.matchOpToken("(");do{t.push(e.requireTokenType("IDENTIFIER"))}while(e.matchOpToken(","));e.requireOpToken(")")}return t};t.addFeature("on",(function(e,t,r){if(!r.matchToken("on"))return;var n=false;if(r.matchToken("every")){n=true}var i=[];var a=null;do{var o=e.requireElement("eventName",r,"Expected event name");var s=o.evaluate();if(a){a=a+" or "+s}else{a="on "+s}var u=v(r);var l=null;if(r.matchOpToken("[")){l=e.requireElement("expression",r);r.requireOpToken("]")}var c,f,m;if(r.currentToken().type==="NUMBER"){var p=r.consumeToken();if(!p.value)return;c=parseInt(p.value);if(r.matchToken("to")){var h=r.consumeToken();if(!h.value)return;f=parseInt(h.value)}else if(r.matchToken("and")){m=true;r.requireToken("on")}}var d,E;if(s==="intersection"){d={};if(r.matchToken("with")){d["with"]=e.requireElement("expression",r).evaluate()}if(r.matchToken("having")){do{if(r.matchToken("margin")){d["rootMargin"]=e.requireElement("stringLike",r).evaluate()}else if(r.matchToken("threshold")){d["threshold"]=e.requireElement("expression",r).evaluate()}else{e.raiseParseError(r,"Unknown intersection config specification")}}while(r.matchToken("and"))}}else if(s==="mutation"){E={};if(r.matchToken("of")){do{if(r.matchToken("anything")){E["attributes"]=true;E["subtree"]=true;E["characterData"]=true;E["childList"]=true}else if(r.matchToken("childList")){E["childList"]=true}else if(r.matchToken("attributes")){E["attributes"]=true;E["attributeOldValue"]=true}else if(r.matchToken("subtree")){E["subtree"]=true}else if(r.matchToken("characterData")){E["characterData"]=true;E["characterDataOldValue"]=true}else if(r.currentToken().type==="ATTRIBUTE_REF"){var T=r.consumeToken();if(E["attributeFilter"]==null){E["attributeFilter"]=[]}if(T.value.indexOf("@")==0){E["attributeFilter"].push(T.value.substring(1))}else{e.raiseParseError(r,"Only shorthand attribute references are allowed here")}}else{e.raiseParseError(r,"Unknown mutation config specification")}}while(r.matchToken("or"))}else{E["attributes"]=true;E["characterData"]=true;E["childList"]=true}}var y=null;var k=false;if(r.matchToken("from")){if(r.matchToken("elsewhere")){k=true}else{r.pushFollow("or");try{y=e.requireElement("expression",r)}finally{r.popFollow()}if(!y){e.raiseParseError(r,'Expected either target value or "elsewhere".')}}}if(y===null&&k===false&&r.matchToken("elsewhere")){k=true}if(r.matchToken("in")){var x=e.parseElement("unaryExpression",r)}if(r.matchToken("debounced")){r.requireToken("at");var g=e.requireElement("unaryExpression",r);var b=g.evaluate({})}else if(r.matchToken("throttled")){r.requireToken("at");var g=e.requireElement("unaryExpression",r);var w=g.evaluate({})}i.push({execCount:0,every:n,on:s,args:u,filter:l,from:y,inExpr:x,elsewhere:k,startCount:c,endCount:f,unbounded:m,debounceTime:b,throttleTime:w,mutationSpec:E,intersectionSpec:d,debounced:undefined,lastExec:undefined})}while(r.matchToken("or"));var S=true;if(!n){if(r.matchToken("queue")){if(r.matchToken("all")){var N=true;var S=false}else if(r.matchToken("first")){var q=true}else if(r.matchToken("none")){var I=true}else{r.requireToken("last")}}}var C=e.requireElement("commandList",r);e.ensureTerminated(C);var R,A;if(r.matchToken("catch")){R=r.requireTokenType("IDENTIFIER").value;A=e.requireElement("commandList",r);e.ensureTerminated(A)}if(r.matchToken("finally")){var L=e.requireElement("commandList",r);e.ensureTerminated(L)}var O={displayName:a,events:i,start:C,every:n,execCount:0,errorHandler:A,errorSymbol:R,execute:function(e){let r=t.getEventQueueFor(e.me,O);if(r.executing&&n===false){if(I||q&&r.queue.length>0){return}if(S){r.queue.length=0}r.queue.push(e);return}O.execCount++;r.executing=true;e.meta.onHalt=function(){r.executing=false;var e=r.queue.shift();if(e){setTimeout((function(){O.execute(e)}),1)}};e.meta.reject=function(r){console.error(r.message?r.message:r);var n=t.getHyperTrace(e,r);if(n){n.print()}t.triggerEvent(e.me,"exception",{error:r})};C.execute(e)},install:function(e,r){for(const r of O.events){var n;if(r.elsewhere){n=[document]}else if(r.from){n=r.from.evaluate(t.makeContext(e,O,e,null))}else{n=[e]}t.implicitLoop(n,(function(n){var i=r.on;if(n==null){console.warn("'%s' feature ignored because target does not exists:",a,e);return}if(r.mutationSpec){i="hyperscript:mutation";const e=new MutationObserver((function(e,r){if(!O.executing){t.triggerEvent(n,i,{mutationList:e,observer:r})}}));e.observe(n,r.mutationSpec)}if(r.intersectionSpec){i="hyperscript:intersection";const e=new IntersectionObserver((function(r){for(const o of r){var a={observer:e};a=Object.assign(a,o);a["intersecting"]=o.isIntersecting;t.triggerEvent(n,i,a)}}),r.intersectionSpec);e.observe(n)}var o=n.addEventListener||n.on;o.call(n,i,(function a(o){if(typeof Node!=="undefined"&&e instanceof Node&&n!==e&&!e.isConnected){n.removeEventListener(i,a);return}var s=t.makeContext(e,O,e,o);if(r.elsewhere&&e.contains(o.target)){return}if(r.from){s.result=n}for(const e of r.args){let t=s.event[e.value];if(t!==undefined){s.locals[e.value]=t}else if("detail"in s.event){s.locals[e.value]=s.event["detail"][e.value]}}s.meta.errorHandler=A;s.meta.errorSymbol=R;s.meta.finallyHandler=L;if(r.filter){var u=s.meta.context;s.meta.context=s.event;try{var l=r.filter.evaluate(s);if(l){}else{return}}finally{s.meta.context=u}}if(r.inExpr){var c=o.target;while(true){if(c.matches&&c.matches(r.inExpr.css)){s.result=c;break}else{c=c.parentElement;if(c==null){return}}}}r.execCount++;if(r.startCount){if(r.endCount){if(r.execCountr.endCount){return}}else if(r.unbounded){if(r.execCount{var a=false;for(const s of i){var o=n=>{e.result=n;if(s.args){for(const t of s.args){e.locals[t.value]=n[t.value]||(n.detail?n.detail[t.value]:null)}}if(!a){a=true;r(t.findNext(this,e))}};if(s.name){n.addEventListener(s.name,o,{once:true})}else if(s.time!=null){setTimeout(o,s.time,s.time)}}}))}};return n}else{var s;if(r.matchToken("a")){r.requireToken("tick");s=0}else{s=e.requireElement("expression",r)}n={type:"waitCmd",time:s,args:[s],op:function(e,r){return new Promise((n=>{setTimeout((()=>{n(t.findNext(this,e))}),r)}))},execute:function(e){return t.unifiedExec(this,e)}};return n}}));t.addGrammarElement("dotOrColonPath",(function(e,t,r){var n=r.matchTokenType("IDENTIFIER");if(n){var i=[n.value];var a=r.matchOpToken(".")||r.matchOpToken(":");if(a){do{i.push(r.requireTokenType("IDENTIFIER","NUMBER").value)}while(r.matchOpToken(a.value))}return{type:"dotOrColonPath",path:i,evaluate:function(){return i.join(a?a.value:"")}}}}));t.addGrammarElement("eventName",(function(e,t,r){var n;if(n=r.matchTokenType("STRING")){return{evaluate:function(){return n.value}}}return e.parseElement("dotOrColonPath",r)}));function d(e,t,r,n){var i=t.requireElement("eventName",n);var a=t.parseElement("namedArgumentList",n);if(e==="send"&&n.matchToken("to")||e==="trigger"&&n.matchToken("on")){var o=t.requireElement("expression",n)}else{var o=t.requireElement("implicitMeTarget",n)}var s={eventName:i,details:a,to:o,args:[o,i,a],op:function(e,t,n,i){r.nullCheck(t,o);r.implicitLoop(t,(function(t){r.triggerEvent(t,n,i,e.me)}));return r.findNext(s,e)}};return s}t.addCommand("trigger",(function(e,t,r){if(r.matchToken("trigger")){return d("trigger",e,t,r)}}));t.addCommand("send",(function(e,t,r){if(r.matchToken("send")){return d("send",e,t,r)}}));var T=function(e,t,r,n){if(n){if(e.commandBoundary(r.currentToken())){e.raiseParseError(r,"'return' commands must return a value. If you do not wish to return a value, use 'exit' instead.")}else{var i=e.requireElement("expression",r)}}var a={value:i,args:[i],op:function(e,r){var n=e.meta.resolve;e.meta.returned=true;e.meta.returnValue=r;if(n){if(r){n(r)}else{n()}}return t.HALT}};return a};t.addCommand("return",(function(e,t,r){if(r.matchToken("return")){return T(e,t,r,true)}}));t.addCommand("exit",(function(e,t,r){if(r.matchToken("exit")){return T(e,t,r,false)}}));t.addCommand("halt",(function(e,t,r){if(r.matchToken("halt")){if(r.matchToken("the")){r.requireToken("event");if(r.matchOpToken("'")){r.requireToken("s")}var n=true}if(r.matchToken("bubbling")){var i=true}else if(r.matchToken("default")){var a=true}var o=T(e,t,r,false);var s={keepExecuting:true,bubbling:i,haltDefault:a,exit:o,op:function(e){if(e.event){if(i){e.event.stopPropagation()}else if(a){e.event.preventDefault()}else{e.event.stopPropagation();e.event.preventDefault()}if(n){return t.findNext(this,e)}else{return o}}}};return s}}));t.addCommand("log",(function(e,t,r){if(!r.matchToken("log"))return;var n=[e.parseElement("expression",r)];while(r.matchOpToken(",")){n.push(e.requireElement("expression",r))}if(r.matchToken("with")){var i=e.requireElement("expression",r)}var a={exprs:n,withExpr:i,args:[i,n],op:function(e,r,n){if(r){r.apply(null,n)}else{console.log.apply(null,n)}return t.findNext(this,e)}};return a}));t.addCommand("beep!",(function(e,t,r){if(!r.matchToken("beep!"))return;var n=[e.parseElement("expression",r)];while(r.matchOpToken(",")){n.push(e.requireElement("expression",r))}var i={exprs:n,args:[n],op:function(e,r){for(let i=0;i{if(!r.matchToken("pick"))return;r.matchToken("the");if(r.matchToken("item")||r.matchToken("items")||r.matchToken("character")||r.matchToken("characters")){const n=g(e,t,r);r.requireToken("from");const i=e.requireElement("expression",r);return{args:[i,n.from,n.to],op(e,r,i,a){if(n.toEnd)a=r.length;if(!n.includeStart)i++;if(n.includeEnd)a++;if(a==null||a==undefined)a=i+1;e.result=r.slice(i,a);return t.findNext(this,e)}}}if(r.matchToken("match")){r.matchToken("of");const n=e.parseElement("expression",r);let i="";if(r.matchOpToken("|")){i=r.requireToken("identifier").value}r.requireToken("from");const a=e.parseElement("expression",r);return{args:[a,n],op(e,r,n){e.result=new RegExp(n,i).exec(r);return t.findNext(this,e)}}}if(r.matchToken("matches")){r.matchToken("of");const n=e.parseElement("expression",r);let i="gu";if(r.matchOpToken("|")){i="g"+r.requireToken("identifier").value.replace("g","")}console.log("flags",i);r.requireToken("from");const a=e.parseElement("expression",r);return{args:[a,n],op(e,r,n){e.result=new w(n,i,r);return t.findNext(this,e)}}}}));t.addCommand("increment",(function(e,t,r){if(!r.matchToken("increment"))return;var n;var i=e.parseElement("assignableExpression",r);if(r.matchToken("by")){n=e.requireElement("expression",r)}var a={type:"implicitIncrementOp",target:i,args:[i,n],op:function(e,t,r){t=t?parseFloat(t):0;r=n?parseFloat(r):1;var i=t+r;e.result=i;return i},evaluate:function(e){return t.unifiedEval(this,e)}};return k(e,t,r,i,a)}));t.addCommand("decrement",(function(e,t,r){if(!r.matchToken("decrement"))return;var n;var i=e.parseElement("assignableExpression",r);if(r.matchToken("by")){n=e.requireElement("expression",r)}var a={type:"implicitDecrementOp",target:i,args:[i,n],op:function(e,t,r){t=t?parseFloat(t):0;r=n?parseFloat(r):1;var i=t-r;e.result=i;return i},evaluate:function(e){return t.unifiedEval(this,e)}};return k(e,t,r,i,a)}));function S(e,t){var r="text";var n;e.matchToken("a")||e.matchToken("an");if(e.matchToken("json")||e.matchToken("Object")){r="json"}else if(e.matchToken("response")){r="response"}else if(e.matchToken("html")){r="html"}else if(e.matchToken("text")){}else{n=t.requireElement("dotOrColonPath",e).evaluate()}return{type:r,conversion:n}}t.addCommand("fetch",(function(e,t,r){if(!r.matchToken("fetch"))return;var n=e.requireElement("stringLike",r);if(r.matchToken("as")){var i=S(r,e)}if(r.matchToken("with")&&r.currentToken().value!=="{"){var a=e.parseElement("nakedNamedArgumentList",r)}else{var a=e.parseElement("objectLiteral",r)}if(i==null&&r.matchToken("as")){i=S(r,e)}var o=i?i.type:"text";var s=i?i.conversion:null;var u={url:n,argExpressions:a,args:[n,a],op:function(e,r,n){var i=n||{};i["sender"]=e.me;i["headers"]=i["headers"]||{};var a=new AbortController;let l=e.me.addEventListener("fetch:abort",(function(){a.abort()}),{once:true});i["signal"]=a.signal;t.triggerEvent(e.me,"hyperscript:beforeFetch",i);t.triggerEvent(e.me,"fetch:beforeRequest",i);n=i;var c=false;if(n.timeout){setTimeout((function(){if(!c){a.abort()}}),n.timeout)}return fetch(r,n).then((function(r){let n={response:r};t.triggerEvent(e.me,"fetch:afterResponse",n);r=n.response;if(o==="response"){e.result=r;t.triggerEvent(e.me,"fetch:afterRequest",{result:r});c=true;return t.findNext(u,e)}if(o==="json"){return r.json().then((function(r){e.result=r;t.triggerEvent(e.me,"fetch:afterRequest",{result:r});c=true;return t.findNext(u,e)}))}return r.text().then((function(r){if(s)r=t.convertValue(r,s);if(o==="html")r=t.convertValue(r,"Fragment");e.result=r;t.triggerEvent(e.me,"fetch:afterRequest",{result:r});c=true;return t.findNext(u,e)}))})).catch((function(r){t.triggerEvent(e.me,"fetch:error",{reason:r});throw r})).finally((function(){e.me.removeEventListener("fetch:abort",l)}))}};return u}))}function y(e){e.addCommand("settle",(function(e,t,r){if(r.matchToken("settle")){if(!e.commandBoundary(r.currentToken())){var n=e.requireElement("expression",r)}else{var n=e.requireElement("implicitMeTarget",r)}var i={type:"settleCmd",args:[n],op:function(e,r){t.nullCheck(r,n);var a=null;var o=false;var s=false;var u=new Promise((function(e){a=e}));r.addEventListener("transitionstart",(function(){s=true}),{once:true});setTimeout((function(){if(!s&&!o){a(t.findNext(i,e))}}),500);r.addEventListener("transitionend",(function(){if(!o){a(t.findNext(i,e))}}),{once:true});return u},execute:function(e){return t.unifiedExec(this,e)}};return i}}));e.addCommand("add",(function(e,t,r){if(r.matchToken("add")){var n=e.parseElement("classRef",r);var i=null;var a=null;if(n==null){i=e.parseElement("attributeRef",r);if(i==null){a=e.parseElement("styleLiteral",r);if(a==null){e.raiseParseError(r,"Expected either a class reference or attribute expression")}}}else{var o=[n];while(n=e.parseElement("classRef",r)){o.push(n)}}if(r.matchToken("to")){var s=e.requireElement("expression",r)}else{var s=e.requireElement("implicitMeTarget",r)}if(r.matchToken("when")){if(a){e.raiseParseError(r,"Only class and properties are supported with a when clause")}var u=e.requireElement("expression",r)}if(o){return{classRefs:o,to:s,args:[s,o],op:function(e,r,n){t.nullCheck(r,s);t.forEach(n,(function(n){t.implicitLoop(r,(function(r){if(u){e.result=r;let i=t.evaluateNoPromise(u,e);if(i){if(r instanceof Element)r.classList.add(n.className)}else{if(r instanceof Element)r.classList.remove(n.className)}e.result=null}else{if(r instanceof Element)r.classList.add(n.className)}}))}));return t.findNext(this,e)}}}else if(i){return{type:"addCmd",attributeRef:i,to:s,args:[s],op:function(e,r,n){t.nullCheck(r,s);t.implicitLoop(r,(function(r){if(u){e.result=r;let n=t.evaluateNoPromise(u,e);if(n){r.setAttribute(i.name,i.value)}else{r.removeAttribute(i.name)}e.result=null}else{r.setAttribute(i.name,i.value)}}));return t.findNext(this,e)},execute:function(e){return t.unifiedExec(this,e)}}}else{return{type:"addCmd",cssDeclaration:a,to:s,args:[s,a],op:function(e,r,n){t.nullCheck(r,s);t.implicitLoop(r,(function(e){e.style.cssText+=n}));return t.findNext(this,e)},execute:function(e){return t.unifiedExec(this,e)}}}}}));e.addGrammarElement("styleLiteral",(function(e,t,r){if(!r.matchOpToken("{"))return;var n=[""];var i=[];while(r.hasMore()){if(r.matchOpToken("\\")){r.consumeToken()}else if(r.matchOpToken("}")){break}else if(r.matchToken("$")){var a=r.matchOpToken("{");var o=e.parseElement("expression",r);if(a)r.requireOpToken("}");i.push(o);n.push("")}else{var s=r.consumeToken();n[n.length-1]+=r.source.substring(s.start,s.end)}n[n.length-1]+=r.lastWhitespace()}return{type:"styleLiteral",args:[i],op:function(e,t){var r="";n.forEach((function(e,n){r+=e;if(n in t)r+=t[n]}));return r},evaluate:function(e){return t.unifiedEval(this,e)}}}));e.addCommand("remove",(function(e,t,r){if(r.matchToken("remove")){var n=e.parseElement("classRef",r);var i=null;var a=null;if(n==null){i=e.parseElement("attributeRef",r);if(i==null){a=e.parseElement("expression",r);if(a==null){e.raiseParseError(r,"Expected either a class reference, attribute expression or value expression")}}}else{var o=[n];while(n=e.parseElement("classRef",r)){o.push(n)}}if(r.matchToken("from")){var s=e.requireElement("expression",r)}else{if(a==null){var s=e.requireElement("implicitMeTarget",r)}}if(a){return{elementExpr:a,from:s,args:[a,s],op:function(e,r,n){t.nullCheck(r,a);t.implicitLoop(r,(function(e){if(e.parentElement&&(n==null||n.contains(e))){e.parentElement.removeChild(e)}}));return t.findNext(this,e)}}}else{return{classRefs:o,attributeRef:i,elementExpr:a,from:s,args:[o,s],op:function(e,r,n){t.nullCheck(n,s);if(r){t.forEach(r,(function(e){t.implicitLoop(n,(function(t){t.classList.remove(e.className)}))}))}else{t.implicitLoop(n,(function(e){e.removeAttribute(i.name)}))}return t.findNext(this,e)}}}}}));e.addCommand("toggle",(function(e,t,r){if(r.matchToken("toggle")){r.matchAnyToken("the","my");if(r.currentToken().type==="STYLE_REF"){let t=r.consumeToken();var n=t.value.substr(1);var a=true;var o=i(e,r,n);if(r.matchToken("of")){r.pushFollow("with");try{var s=e.requireElement("expression",r)}finally{r.popFollow()}}else{var s=e.requireElement("implicitMeTarget",r)}}else if(r.matchToken("between")){var u=true;var l=e.parseElement("classRef",r);r.requireToken("and");var c=e.requireElement("classRef",r)}else{var l=e.parseElement("classRef",r);var f=null;if(l==null){f=e.parseElement("attributeRef",r);if(f==null){e.raiseParseError(r,"Expected either a class reference or attribute expression")}}else{var m=[l];while(l=e.parseElement("classRef",r)){m.push(l)}}}if(a!==true){if(r.matchToken("on")){var s=e.requireElement("expression",r)}else{var s=e.requireElement("implicitMeTarget",r)}}if(r.matchToken("for")){var p=e.requireElement("expression",r)}else if(r.matchToken("until")){var h=e.requireElement("dotOrColonPath",r,"Expected event name");if(r.matchToken("from")){var v=e.requireElement("expression",r)}}var d={classRef:l,classRef2:c,classRefs:m,attributeRef:f,on:s,time:p,evt:h,from:v,toggle:function(e,r,n,i){t.nullCheck(e,s);if(a){t.implicitLoop(e,(function(e){o("toggle",e)}))}else if(u){t.implicitLoop(e,(function(e){if(e.classList.contains(r.className)){e.classList.remove(r.className);e.classList.add(n.className)}else{e.classList.add(r.className);e.classList.remove(n.className)}}))}else if(i){t.forEach(i,(function(r){t.implicitLoop(e,(function(e){e.classList.toggle(r.className)}))}))}else{t.implicitLoop(e,(function(e){if(e.hasAttribute(f.name)){e.removeAttribute(f.name)}else{e.setAttribute(f.name,f.value)}}))}},args:[s,p,h,v,l,c,m],op:function(e,r,n,i,a,o,s,u){if(n){return new Promise((function(i){d.toggle(r,o,s,u);setTimeout((function(){d.toggle(r,o,s,u);i(t.findNext(d,e))}),n)}))}else if(i){return new Promise((function(n){var l=a||e.me;l.addEventListener(i,(function(){d.toggle(r,o,s,u);n(t.findNext(d,e))}),{once:true});d.toggle(r,o,s,u)}))}else{this.toggle(r,o,s,u);return t.findNext(d,e)}}};return d}}));var t={display:function(r,n,i){if(i){n.style.display=i}else if(r==="toggle"){if(getComputedStyle(n).display==="none"){t.display("show",n,i)}else{t.display("hide",n,i)}}else if(r==="hide"){const t=e.runtime.getInternalData(n);if(t.originalDisplay==null){t.originalDisplay=n.style.display}n.style.display="none"}else{const t=e.runtime.getInternalData(n);if(t.originalDisplay&&t.originalDisplay!=="none"){n.style.display=t.originalDisplay}else{n.style.removeProperty("display")}}},visibility:function(e,r,n){if(n){r.style.visibility=n}else if(e==="toggle"){if(getComputedStyle(r).visibility==="hidden"){t.visibility("show",r,n)}else{t.visibility("hide",r,n)}}else if(e==="hide"){r.style.visibility="hidden"}else{r.style.visibility="visible"}},opacity:function(e,r,n){if(n){r.style.opacity=n}else if(e==="toggle"){if(getComputedStyle(r).opacity==="0"){t.opacity("show",r,n)}else{t.opacity("hide",r,n)}}else if(e==="hide"){r.style.opacity="0"}else{r.style.opacity="1"}}};var n=function(e,t,r){var n;var i=r.currentToken();if(i.value==="when"||i.value==="with"||e.commandBoundary(i)){n=e.parseElement("implicitMeTarget",r)}else{n=e.parseElement("expression",r)}return n};var i=function(e,n,i){var a=r.defaultHideShowStrategy;var o=t;if(r.hideShowStrategies){o=Object.assign(o,r.hideShowStrategies)}i=i||a||"display";var s=o[i];if(s==null){e.raiseParseError(n,"Unknown show/hide strategy : "+i)}return s};e.addCommand("hide",(function(e,t,r){if(r.matchToken("hide")){var a=n(e,t,r);var o=null;if(r.matchToken("with")){o=r.requireTokenType("IDENTIFIER","STYLE_REF").value;if(o.indexOf("*")===0){o=o.substr(1)}}var s=i(e,r,o);return{target:a,args:[a],op:function(e,r){t.nullCheck(r,a);t.implicitLoop(r,(function(e){s("hide",e)}));return t.findNext(this,e)}}}}));e.addCommand("show",(function(e,t,r){if(r.matchToken("show")){var a=n(e,t,r);var o=null;if(r.matchToken("with")){o=r.requireTokenType("IDENTIFIER","STYLE_REF").value;if(o.indexOf("*")===0){o=o.substr(1)}}var s=null;if(r.matchOpToken(":")){var u=r.consumeUntilWhitespace();r.matchTokenType("WHITESPACE");s=u.map((function(e){return e.value})).join("")}if(r.matchToken("when")){var l=e.requireElement("expression",r)}var c=i(e,r,o);return{target:a,when:l,args:[a],op:function(e,r){t.nullCheck(r,a);t.implicitLoop(r,(function(r){if(l){e.result=r;let n=t.evaluateNoPromise(l,e);if(n){c("show",r,s)}else{c("hide",r)}e.result=null}else{c("show",r,s)}}));return t.findNext(this,e)}}}}));e.addCommand("take",(function(e,t,r){if(r.matchToken("take")){let u=null;let l=[];while(u=e.parseElement("classRef",r)){l.push(u)}var n=null;var i=null;let c=l.length>0;if(!c){n=e.parseElement("attributeRef",r);if(n==null){e.raiseParseError(r,"Expected either a class reference or attribute expression")}if(r.matchToken("with")){i=e.requireElement("expression",r)}}if(r.matchToken("from")){var a=e.requireElement("expression",r)}if(r.matchToken("for")){var o=e.requireElement("expression",r)}else{var o=e.requireElement("implicitMeTarget",r)}if(c){var s={classRefs:l,from:a,forElt:o,args:[l,a,o],op:function(e,r,n,i){t.nullCheck(i,o);t.implicitLoop(r,(function(e){var r=e.className;if(n){t.implicitLoop(n,(function(e){e.classList.remove(r)}))}else{t.implicitLoop(e,(function(e){e.classList.remove(r)}))}t.implicitLoop(i,(function(e){e.classList.add(r)}))}));return t.findNext(this,e)}};return s}else{var s={attributeRef:n,from:a,forElt:o,args:[a,o,i],op:function(e,r,i,s){t.nullCheck(r,a);t.nullCheck(i,o);t.implicitLoop(r,(function(e){if(!s){e.removeAttribute(n.name)}else{e.setAttribute(n.name,s)}}));t.implicitLoop(i,(function(e){e.setAttribute(n.name,n.value||"")}));return t.findNext(this,e)}};return s}}}));function a(t,r,n,i){if(n!=null){var a=t.resolveSymbol(n,r)}else{var a=r}if(a instanceof Element||a instanceof HTMLDocument){while(a.firstChild)a.removeChild(a.firstChild);a.append(e.runtime.convertValue(i,"Fragment"));t.processNode(a)}else{if(n!=null){t.setSymbol(n,r,null,i)}else{throw"Don't know how to put a value into "+typeof r}}}e.addCommand("put",(function(e,t,r){if(r.matchToken("put")){var n=e.requireElement("expression",r);var i=r.matchAnyToken("into","before","after");if(i==null&&r.matchToken("at")){r.matchToken("the");i=r.matchAnyToken("start","end");r.requireToken("of")}if(i==null){e.raiseParseError(r,"Expected one of 'into', 'before', 'at start of', 'at end of', 'after'")}var o=e.requireElement("expression",r);var s=i.value;var u=false;var l=false;var c=null;var f=null;if(o.type==="arrayIndex"&&s==="into"){u=true;f=o.prop;c=o.root}else if(o.prop&&o.root&&s==="into"){f=o.prop.value;c=o.root}else if(o.type==="symbol"&&s==="into"){l=true;f=o.name}else if(o.type==="attributeRef"&&s==="into"){var m=true;f=o.name;c=e.requireElement("implicitMeTarget",r)}else if(o.type==="styleRef"&&s==="into"){var p=true;f=o.name;c=e.requireElement("implicitMeTarget",r)}else if(o.attribute&&s==="into"){var m=o.attribute.type==="attributeRef";var p=o.attribute.type==="styleRef";f=o.attribute.name;c=o.root}else{c=o}var h={target:o,operation:s,symbolWrite:l,value:n,args:[c,f,n],op:function(e,r,n,i){if(l){a(t,e,n,i)}else{t.nullCheck(r,c);if(s==="into"){if(m){t.implicitLoop(r,(function(e){e.setAttribute(n,i)}))}else if(p){t.implicitLoop(r,(function(e){e.style[n]=i}))}else if(u){r[n]=i}else{t.implicitLoop(r,(function(e){a(t,e,n,i)}))}}else{var o=s==="before"?Element.prototype.before:s==="after"?Element.prototype.after:s==="start"?Element.prototype.prepend:s==="end"?Element.prototype.append:Element.prototype.append;t.implicitLoop(r,(function(e){o.call(e,i instanceof Node?i:t.convertValue(i,"Fragment"));if(e.parentElement){t.processNode(e.parentElement)}else{t.processNode(e)}}))}}return t.findNext(this,e)}};return h}}));function o(e,t,r){var n;if(r.matchToken("the")||r.matchToken("element")||r.matchToken("elements")||r.currentToken().type==="CLASS_REF"||r.currentToken().type==="ID_REF"||r.currentToken().op&&r.currentToken().value==="<"){e.possessivesDisabled=true;try{n=e.parseElement("expression",r)}finally{delete e.possessivesDisabled}if(r.matchOpToken("'")){r.requireToken("s")}}else if(r.currentToken().type==="IDENTIFIER"&&r.currentToken().value==="its"){var i=r.matchToken("its");n={type:"pseudopossessiveIts",token:i,name:i.value,evaluate:function(e){return t.resolveSymbol("it",e)}}}else{r.matchToken("my")||r.matchToken("me");n=e.parseElement("implicitMeTarget",r)}return n}e.addCommand("transition",(function(e,t,n){if(n.matchToken("transition")){var i=o(e,t,n);var a=[];var s=[];var u=[];var l=n.currentToken();while(!e.commandBoundary(l)&&l.value!=="over"&&l.value!=="using"){if(n.currentToken().type==="STYLE_REF"){let e=n.consumeToken();let t=e.value.substr(1);a.push({type:"styleRefValue",evaluate:function(){return t}})}else{a.push(e.requireElement("stringLike",n))}if(n.matchToken("from")){s.push(e.requireElement("expression",n))}else{s.push(null)}n.requireToken("to");if(n.matchToken("initial")){u.push({type:"initial_literal",evaluate:function(){return"initial"}})}else{u.push(e.requireElement("expression",n))}l=n.currentToken()}if(n.matchToken("over")){var c=e.requireElement("expression",n)}else if(n.matchToken("using")){var f=e.requireElement("expression",n)}var m={to:u,args:[i,a,s,u,f,c],op:function(e,n,a,o,s,u,l){t.nullCheck(n,i);var c=[];t.implicitLoop(n,(function(e){var n=new Promise((function(n,i){var c=e.style.transition;if(l){e.style.transition="all "+l+"ms ease-in"}else if(u){e.style.transition=u}else{e.style.transition=r.defaultTransition}var f=t.getInternalData(e);var m=getComputedStyle(e);var p={};for(var h=0;he.forEach((e=>S(e))))).then((()=>n((function(){a();k.processNode(document.documentElement);e.document.addEventListener("htmx:load",(function(e){k.processNode(e.detail.elt)}))}))));function n(e){if(document.readyState!=="loading"){setTimeout(e)}else{document.addEventListener("DOMContentLoaded",e)}}function i(){var e=document.querySelector('meta[name="htmx-config"]');if(e){return v(e.content)}else{return null}}function a(){var e=i();if(e){Object.assign(r,e)}}}const S=Object.assign(b,{config:r,use(e){e(S)},internals:{lexer:x,parser:g,runtime:k,Lexer:n,Tokens:i,Parser:a,Runtime:o},ElementCollection:m,addFeature:g.addFeature.bind(g),addCommand:g.addCommand.bind(g),addLeafExpression:g.addLeafExpression.bind(g),addIndirectExpression:g.addIndirectExpression.bind(g),evaluate:k.evaluate.bind(k),parse:k.parse.bind(k),processNode:k.processNode.bind(k),version:"0.9.13",browserInit:w});return S}));
diff --git a/merge/src/argus_htmx/static/logo_white.svg b/merge/src/argus_htmx/static/logo_white.svg
new file mode 100644
index 000000000..b44680164
--- /dev/null
+++ b/merge/src/argus_htmx/static/logo_white.svg
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/merge/src/argus_htmx/static/styles.css b/merge/src/argus_htmx/static/styles.css
new file mode 100644
index 000000000..b304384f8
--- /dev/null
+++ b/merge/src/argus_htmx/static/styles.css
@@ -0,0 +1,4286 @@
+/*
+! tailwindcss v3.4.10 | MIT License | https://tailwindcss.com
+*/
+
+/*
+1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
+2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
+*/
+
+*,
+::before,
+::after {
+ box-sizing: border-box;
+ /* 1 */
+ border-width: 0;
+ /* 2 */
+ border-style: solid;
+ /* 2 */
+ border-color: #e5e7eb;
+ /* 2 */
+}
+
+::before,
+::after {
+ --tw-content: '';
+}
+
+/*
+1. Use a consistent sensible line-height in all browsers.
+2. Prevent adjustments of font size after orientation changes in iOS.
+3. Use a more readable tab size.
+4. Use the user's configured `sans` font-family by default.
+5. Use the user's configured `sans` font-feature-settings by default.
+6. Use the user's configured `sans` font-variation-settings by default.
+7. Disable tap highlights on iOS
+*/
+
+html,
+:host {
+ line-height: 1.5;
+ /* 1 */
+ -webkit-text-size-adjust: 100%;
+ /* 2 */
+ -moz-tab-size: 4;
+ /* 3 */
+ -o-tab-size: 4;
+ tab-size: 4;
+ /* 3 */
+ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ /* 4 */
+ font-feature-settings: normal;
+ /* 5 */
+ font-variation-settings: normal;
+ /* 6 */
+ -webkit-tap-highlight-color: transparent;
+ /* 7 */
+}
+
+/*
+1. Remove the margin in all browsers.
+2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
+*/
+
+body {
+ margin: 0;
+ /* 1 */
+ line-height: inherit;
+ /* 2 */
+}
+
+/*
+1. Add the correct height in Firefox.
+2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
+3. Ensure horizontal rules are visible by default.
+*/
+
+hr {
+ height: 0;
+ /* 1 */
+ color: inherit;
+ /* 2 */
+ border-top-width: 1px;
+ /* 3 */
+}
+
+/*
+Add the correct text decoration in Chrome, Edge, and Safari.
+*/
+
+abbr:where([title]) {
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+}
+
+/*
+Remove the default font size and weight for headings.
+*/
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-size: inherit;
+ font-weight: inherit;
+}
+
+/*
+Reset links to optimize for opt-in styling instead of opt-out.
+*/
+
+a {
+ color: inherit;
+ text-decoration: inherit;
+}
+
+/*
+Add the correct font weight in Edge and Safari.
+*/
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/*
+1. Use the user's configured `mono` font-family by default.
+2. Use the user's configured `mono` font-feature-settings by default.
+3. Use the user's configured `mono` font-variation-settings by default.
+4. Correct the odd `em` font sizing in all browsers.
+*/
+
+code,
+kbd,
+samp,
+pre {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ /* 1 */
+ font-feature-settings: normal;
+ /* 2 */
+ font-variation-settings: normal;
+ /* 3 */
+ font-size: 1em;
+ /* 4 */
+}
+
+/*
+Add the correct font size in all browsers.
+*/
+
+small {
+ font-size: 80%;
+}
+
+/*
+Prevent `sub` and `sup` elements from affecting the line height in all browsers.
+*/
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/*
+1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
+2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
+3. Remove gaps between table borders by default.
+*/
+
+table {
+ text-indent: 0;
+ /* 1 */
+ border-color: inherit;
+ /* 2 */
+ border-collapse: collapse;
+ /* 3 */
+}
+
+/*
+1. Change the font styles in all browsers.
+2. Remove the margin in Firefox and Safari.
+3. Remove default padding in all browsers.
+*/
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit;
+ /* 1 */
+ font-feature-settings: inherit;
+ /* 1 */
+ font-variation-settings: inherit;
+ /* 1 */
+ font-size: 100%;
+ /* 1 */
+ font-weight: inherit;
+ /* 1 */
+ line-height: inherit;
+ /* 1 */
+ letter-spacing: inherit;
+ /* 1 */
+ color: inherit;
+ /* 1 */
+ margin: 0;
+ /* 2 */
+ padding: 0;
+ /* 3 */
+}
+
+/*
+Remove the inheritance of text transform in Edge and Firefox.
+*/
+
+button,
+select {
+ text-transform: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Remove default button styles.
+*/
+
+button,
+input:where([type='button']),
+input:where([type='reset']),
+input:where([type='submit']) {
+ -webkit-appearance: button;
+ /* 1 */
+ background-color: transparent;
+ /* 2 */
+ background-image: none;
+ /* 2 */
+}
+
+/*
+Use the modern Firefox focus style for all focusable elements.
+*/
+
+:-moz-focusring {
+ outline: auto;
+}
+
+/*
+Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
+*/
+
+:-moz-ui-invalid {
+ box-shadow: none;
+}
+
+/*
+Add the correct vertical alignment in Chrome and Firefox.
+*/
+
+progress {
+ vertical-align: baseline;
+}
+
+/*
+Correct the cursor style of increment and decrement buttons in Safari.
+*/
+
+::-webkit-inner-spin-button,
+::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/*
+1. Correct the odd appearance in Chrome and Safari.
+2. Correct the outline style in Safari.
+*/
+
+[type='search'] {
+ -webkit-appearance: textfield;
+ /* 1 */
+ outline-offset: -2px;
+ /* 2 */
+}
+
+/*
+Remove the inner padding in Chrome and Safari on macOS.
+*/
+
+::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Change font properties to `inherit` in Safari.
+*/
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button;
+ /* 1 */
+ font: inherit;
+ /* 2 */
+}
+
+/*
+Add the correct display in Chrome and Safari.
+*/
+
+summary {
+ display: list-item;
+}
+
+/*
+Removes the default spacing and border for appropriate elements.
+*/
+
+blockquote,
+dl,
+dd,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+figure,
+p,
+pre {
+ margin: 0;
+}
+
+fieldset {
+ margin: 0;
+ padding: 0;
+}
+
+legend {
+ padding: 0;
+}
+
+ol,
+ul,
+menu {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+/*
+Reset default styling for dialogs.
+*/
+
+dialog {
+ padding: 0;
+}
+
+/*
+Prevent resizing textareas horizontally by default.
+*/
+
+textarea {
+ resize: vertical;
+}
+
+/*
+1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
+2. Set the default placeholder color to the user's configured gray 400 color.
+*/
+
+input::-moz-placeholder, textarea::-moz-placeholder {
+ opacity: 1;
+ /* 1 */
+ color: #9ca3af;
+ /* 2 */
+}
+
+input::placeholder,
+textarea::placeholder {
+ opacity: 1;
+ /* 1 */
+ color: #9ca3af;
+ /* 2 */
+}
+
+/*
+Set the default cursor for buttons.
+*/
+
+button,
+[role="button"] {
+ cursor: pointer;
+}
+
+/*
+Make sure disabled buttons don't get the pointer cursor.
+*/
+
+:disabled {
+ cursor: default;
+}
+
+/*
+1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
+2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
+ This can trigger a poorly considered lint error in some tools but is included by design.
+*/
+
+img,
+svg,
+video,
+canvas,
+audio,
+iframe,
+embed,
+object {
+ display: block;
+ /* 1 */
+ vertical-align: middle;
+ /* 2 */
+}
+
+/*
+Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
+*/
+
+img,
+video {
+ max-width: 100%;
+ height: auto;
+}
+
+/* Make elements with the HTML hidden attribute stay hidden by default */
+
+[hidden] {
+ display: none;
+}
+
+:root,
+[data-theme] {
+ background-color: var(--fallback-b1,oklch(var(--b1)/1));
+ color: var(--fallback-bc,oklch(var(--bc)/1));
+}
+
+@supports not (color: oklch(0% 0 0)) {
+ :root {
+ color-scheme: light;
+ --fallback-p: #491eff;
+ --fallback-pc: #d4dbff;
+ --fallback-s: #ff41c7;
+ --fallback-sc: #fff9fc;
+ --fallback-a: #00cfbd;
+ --fallback-ac: #00100d;
+ --fallback-n: #2b3440;
+ --fallback-nc: #d7dde4;
+ --fallback-b1: #ffffff;
+ --fallback-b2: #e5e6e6;
+ --fallback-b3: #e5e6e6;
+ --fallback-bc: #1f2937;
+ --fallback-in: #00b3f0;
+ --fallback-inc: #000000;
+ --fallback-su: #00ca92;
+ --fallback-suc: #000000;
+ --fallback-wa: #ffc22d;
+ --fallback-wac: #000000;
+ --fallback-er: #ff6f70;
+ --fallback-erc: #000000;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ :root {
+ color-scheme: dark;
+ --fallback-p: #7582ff;
+ --fallback-pc: #050617;
+ --fallback-s: #ff71cf;
+ --fallback-sc: #190211;
+ --fallback-a: #00c7b5;
+ --fallback-ac: #000e0c;
+ --fallback-n: #2a323c;
+ --fallback-nc: #a6adbb;
+ --fallback-b1: #1d232a;
+ --fallback-b2: #191e24;
+ --fallback-b3: #15191e;
+ --fallback-bc: #a6adbb;
+ --fallback-in: #00b3f0;
+ --fallback-inc: #000000;
+ --fallback-su: #00ca92;
+ --fallback-suc: #000000;
+ --fallback-wa: #ffc22d;
+ --fallback-wac: #000000;
+ --fallback-er: #ff6f70;
+ --fallback-erc: #000000;
+ }
+ }
+}
+
+html {
+ -webkit-tap-highlight-color: transparent;
+}
+
+* {
+ scrollbar-color: color-mix(in oklch, currentColor 35%, transparent) transparent;
+}
+
+*:hover {
+ scrollbar-color: color-mix(in oklch, currentColor 60%, transparent) transparent;
+}
+
+:root {
+ color-scheme: dark;
+ --in: 72.06% 0.191 231.6;
+ --su: 64.8% 0.150 160;
+ --wa: 84.71% 0.199 83.87;
+ --er: 71.76% 0.221 22.18;
+ --pc: 13.138% 0.0392 275.75;
+ --sc: 14.96% 0.052 342.55;
+ --ac: 14.902% 0.0334 183.61;
+ --inc: 0% 0 0;
+ --suc: 0% 0 0;
+ --wac: 0% 0 0;
+ --erc: 0% 0 0;
+ --rounded-box: 1rem;
+ --rounded-btn: 0.5rem;
+ --rounded-badge: 1.9rem;
+ --animation-btn: 0.25s;
+ --animation-input: .2s;
+ --btn-focus-scale: 0.95;
+ --border-btn: 1px;
+ --tab-border: 1px;
+ --tab-radius: 0.5rem;
+ --p: 65.69% 0.196 275.75;
+ --s: 74.8% 0.26 342.55;
+ --a: 74.51% 0.167 183.61;
+ --n: 31.3815% 0.021108 254.139175;
+ --nc: 74.6477% 0.0216 264.435964;
+ --b1: 25.3267% 0.015896 252.417568;
+ --b2: 23.2607% 0.013807 253.100675;
+ --b3: 21.1484% 0.01165 254.087939;
+ --bc: 74.6477% 0.0216 264.435964;
+}
+
+[data-theme=dark] {
+ color-scheme: dark;
+ --in: 72.06% 0.191 231.6;
+ --su: 64.8% 0.150 160;
+ --wa: 84.71% 0.199 83.87;
+ --er: 71.76% 0.221 22.18;
+ --pc: 13.138% 0.0392 275.75;
+ --sc: 14.96% 0.052 342.55;
+ --ac: 14.902% 0.0334 183.61;
+ --inc: 0% 0 0;
+ --suc: 0% 0 0;
+ --wac: 0% 0 0;
+ --erc: 0% 0 0;
+ --rounded-box: 1rem;
+ --rounded-btn: 0.5rem;
+ --rounded-badge: 1.9rem;
+ --animation-btn: 0.25s;
+ --animation-input: .2s;
+ --btn-focus-scale: 0.95;
+ --border-btn: 1px;
+ --tab-border: 1px;
+ --tab-radius: 0.5rem;
+ --p: 65.69% 0.196 275.75;
+ --s: 74.8% 0.26 342.55;
+ --a: 74.51% 0.167 183.61;
+ --n: 31.3815% 0.021108 254.139175;
+ --nc: 74.6477% 0.0216 264.435964;
+ --b1: 25.3267% 0.015896 252.417568;
+ --b2: 23.2607% 0.013807 253.100675;
+ --b3: 21.1484% 0.01165 254.087939;
+ --bc: 74.6477% 0.0216 264.435964;
+}
+
+:root:has(input.theme-controller[value=dark]:checked) {
+ color-scheme: dark;
+ --in: 72.06% 0.191 231.6;
+ --su: 64.8% 0.150 160;
+ --wa: 84.71% 0.199 83.87;
+ --er: 71.76% 0.221 22.18;
+ --pc: 13.138% 0.0392 275.75;
+ --sc: 14.96% 0.052 342.55;
+ --ac: 14.902% 0.0334 183.61;
+ --inc: 0% 0 0;
+ --suc: 0% 0 0;
+ --wac: 0% 0 0;
+ --erc: 0% 0 0;
+ --rounded-box: 1rem;
+ --rounded-btn: 0.5rem;
+ --rounded-badge: 1.9rem;
+ --animation-btn: 0.25s;
+ --animation-input: .2s;
+ --btn-focus-scale: 0.95;
+ --border-btn: 1px;
+ --tab-border: 1px;
+ --tab-radius: 0.5rem;
+ --p: 65.69% 0.196 275.75;
+ --s: 74.8% 0.26 342.55;
+ --a: 74.51% 0.167 183.61;
+ --n: 31.3815% 0.021108 254.139175;
+ --nc: 74.6477% 0.0216 264.435964;
+ --b1: 25.3267% 0.015896 252.417568;
+ --b2: 23.2607% 0.013807 253.100675;
+ --b3: 21.1484% 0.01165 254.087939;
+ --bc: 74.6477% 0.0216 264.435964;
+}
+
+[data-theme=light] {
+ color-scheme: light;
+ --in: 72.06% 0.191 231.6;
+ --su: 64.8% 0.150 160;
+ --wa: 84.71% 0.199 83.87;
+ --er: 71.76% 0.221 22.18;
+ --pc: 89.824% 0.06192 275.75;
+ --ac: 15.352% 0.0368 183.61;
+ --inc: 0% 0 0;
+ --suc: 0% 0 0;
+ --wac: 0% 0 0;
+ --erc: 0% 0 0;
+ --rounded-box: 1rem;
+ --rounded-btn: 0.5rem;
+ --rounded-badge: 1.9rem;
+ --animation-btn: 0.25s;
+ --animation-input: .2s;
+ --btn-focus-scale: 0.95;
+ --border-btn: 1px;
+ --tab-border: 1px;
+ --tab-radius: 0.5rem;
+ --p: 49.12% 0.3096 275.75;
+ --s: 69.71% 0.329 342.55;
+ --sc: 98.71% 0.0106 342.55;
+ --a: 76.76% 0.184 183.61;
+ --n: 32.1785% 0.02476 255.701624;
+ --nc: 89.4994% 0.011585 252.096176;
+ --b1: 100% 0 0;
+ --b2: 96.1151% 0 0;
+ --b3: 92.4169% 0.00108 197.137559;
+ --bc: 27.8078% 0.029596 256.847952;
+}
+
+:root:has(input.theme-controller[value=light]:checked) {
+ color-scheme: light;
+ --in: 72.06% 0.191 231.6;
+ --su: 64.8% 0.150 160;
+ --wa: 84.71% 0.199 83.87;
+ --er: 71.76% 0.221 22.18;
+ --pc: 89.824% 0.06192 275.75;
+ --ac: 15.352% 0.0368 183.61;
+ --inc: 0% 0 0;
+ --suc: 0% 0 0;
+ --wac: 0% 0 0;
+ --erc: 0% 0 0;
+ --rounded-box: 1rem;
+ --rounded-btn: 0.5rem;
+ --rounded-badge: 1.9rem;
+ --animation-btn: 0.25s;
+ --animation-input: .2s;
+ --btn-focus-scale: 0.95;
+ --border-btn: 1px;
+ --tab-border: 1px;
+ --tab-radius: 0.5rem;
+ --p: 49.12% 0.3096 275.75;
+ --s: 69.71% 0.329 342.55;
+ --sc: 98.71% 0.0106 342.55;
+ --a: 76.76% 0.184 183.61;
+ --n: 32.1785% 0.02476 255.701624;
+ --nc: 89.4994% 0.011585 252.096176;
+ --b1: 100% 0 0;
+ --b2: 96.1151% 0 0;
+ --b3: 92.4169% 0.00108 197.137559;
+ --bc: 27.8078% 0.029596 256.847952;
+}
+
+[data-theme=argus] {
+ --p: 50.0719% 0.099708 229.935495;
+ --rounded-box: 1rem;
+ --rounded-btn: 0.5rem;
+ --rounded-badge: 1.9rem;
+ --animation-btn: 0.25s;
+ --animation-input: .2s;
+ --btn-focus-scale: 0.95;
+ --border-btn: 1px;
+ --tab-border: 1px;
+ --tab-radius: 0.5rem;
+ --pc: 90.0131% 0.020378 229.062151;
+ --s: 81.1089% 0.160737 83.094462;
+ --sc: 16.0976% 0.033017 84.44886;
+ --a: 57.0203% 0.176029 40.82272;
+ --ac: 91.2668% 0.035092 39.983706;
+ --n: 50.0719% 0.099708 229.935495;
+ --nc: 90.0131% 0.020378 229.062151;
+ --b1: 97.6898% 0.015282 222.677002;
+ --b2: 87.8396% 0.013785 226.581051;
+ --b3: 78.1992% 0.011943 217.566813;
+ --bc: 19.5043% 0.002558 247.957994;
+ --in: 56.8673% 0.19351 255.909793;
+ --inc: 11.5624% 0.036526 253.76502;
+ --su: 54.0146% 0.183798 142.495339;
+ --suc: 90.7096% 0.036394 142.726976;
+ --wa: 63.5453% 0.210841 37.389442;
+ --wac: 12.8885% 0.041866 38.356211;
+ --er: 64.1045% 0.179995 21.389979;
+ --erc: 12.5861% 0.036535 17.51583;
+}
+
+:root:has(input.theme-controller[value=argus]:checked) {
+ --p: 50.0719% 0.099708 229.935495;
+ --rounded-box: 1rem;
+ --rounded-btn: 0.5rem;
+ --rounded-badge: 1.9rem;
+ --animation-btn: 0.25s;
+ --animation-input: .2s;
+ --btn-focus-scale: 0.95;
+ --border-btn: 1px;
+ --tab-border: 1px;
+ --tab-radius: 0.5rem;
+ --pc: 90.0131% 0.020378 229.062151;
+ --s: 81.1089% 0.160737 83.094462;
+ --sc: 16.0976% 0.033017 84.44886;
+ --a: 57.0203% 0.176029 40.82272;
+ --ac: 91.2668% 0.035092 39.983706;
+ --n: 50.0719% 0.099708 229.935495;
+ --nc: 90.0131% 0.020378 229.062151;
+ --b1: 97.6898% 0.015282 222.677002;
+ --b2: 87.8396% 0.013785 226.581051;
+ --b3: 78.1992% 0.011943 217.566813;
+ --bc: 19.5043% 0.002558 247.957994;
+ --in: 56.8673% 0.19351 255.909793;
+ --inc: 11.5624% 0.036526 253.76502;
+ --su: 54.0146% 0.183798 142.495339;
+ --suc: 90.7096% 0.036394 142.726976;
+ --wa: 63.5453% 0.210841 37.389442;
+ --wac: 12.8885% 0.041866 38.356211;
+ --er: 64.1045% 0.179995 21.389979;
+ --erc: 12.5861% 0.036535 17.51583;
+}
+
+*, ::before, ::after {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ --tw-contain-size: ;
+ --tw-contain-layout: ;
+ --tw-contain-paint: ;
+ --tw-contain-style: ;
+}
+
+::backdrop {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ --tw-contain-size: ;
+ --tw-contain-layout: ;
+ --tw-contain-paint: ;
+ --tw-contain-style: ;
+}
+
+.alert {
+ display: grid;
+ width: 100%;
+ grid-auto-flow: row;
+ align-content: flex-start;
+ align-items: center;
+ justify-items: center;
+ gap: 1rem;
+ text-align: center;
+ border-radius: var(--rounded-box, 1rem);
+ border-width: 1px;
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
+ padding: 1rem;
+ --tw-text-opacity: 1;
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
+ --alert-bg: var(--fallback-b2,oklch(var(--b2)/1));
+ --alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1));
+ background-color: var(--alert-bg);
+}
+
+@media (min-width: 640px) {
+ .alert {
+ grid-auto-flow: column;
+ grid-template-columns: auto minmax(auto,1fr);
+ justify-items: start;
+ text-align: start;
+ }
+}
+
+.avatar {
+ position: relative;
+ display: inline-flex;
+}
+
+.avatar > div {
+ display: block;
+ aspect-ratio: 1 / 1;
+ overflow: hidden;
+}
+
+.avatar img {
+ height: 100%;
+ width: 100%;
+ -o-object-fit: cover;
+ object-fit: cover;
+}
+
+.avatar.placeholder > div {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ transition-duration: 200ms;
+ height: 1.25rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ width: -moz-fit-content;
+ width: fit-content;
+ padding-left: 0.563rem;
+ padding-right: 0.563rem;
+ border-radius: var(--rounded-badge, 1.9rem);
+ border-width: 1px;
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
+ --tw-text-opacity: 1;
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
+}
+
+@media (hover:hover) {
+ .checkbox-primary:hover {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));
+ }
+
+ .checkbox-accent:hover {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity)));
+ }
+
+ .label a:hover {
+ --tw-text-opacity: 1;
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
+ }
+
+ .menu li > *:not(ul, .menu-title, details, .btn):active,
+.menu li > *:not(ul, .menu-title, details, .btn).active,
+.menu li > details > summary:active {
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
+ --tw-text-opacity: 1;
+ color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
+ }
+
+ .tab:hover {
+ --tw-text-opacity: 1;
+ }
+
+ .table tr.hover:hover,
+ .table tr.hover:nth-child(even):hover {
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
+ }
+
+ .table-zebra tr.hover:hover,
+ .table-zebra tr.hover:nth-child(even):hover {
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
+ }
+}
+
+.btn {
+ display: inline-flex;
+ height: 3rem;
+ min-height: 3rem;
+ flex-shrink: 0;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: center;
+ border-radius: var(--rounded-btn, 0.5rem);
+ border-color: transparent;
+ border-color: oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity));
+ padding-left: 1rem;
+ padding-right: 1rem;
+ text-align: center;
+ font-size: 0.875rem;
+ line-height: 1em;
+ gap: 0.5rem;
+ font-weight: 600;
+ text-decoration-line: none;
+ transition-duration: 200ms;
+ transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ border-width: var(--border-btn, 1px);
+ transition-property: color, background-color, border-color, opacity, box-shadow, transform;
+ --tw-text-opacity: 1;
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
+ --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+ outline-color: var(--fallback-bc,oklch(var(--bc)/1));
+ background-color: oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity));
+ --tw-bg-opacity: 1;
+ --tw-border-opacity: 1;
+}
+
+.btn-disabled,
+ .btn[disabled],
+ .btn:disabled {
+ pointer-events: none;
+}
+
+.btn-circle {
+ height: 3rem;
+ width: 3rem;
+ border-radius: 9999px;
+ padding: 0px;
+}
+
+:where(.btn:is(input[type="checkbox"])),
+:where(.btn:is(input[type="radio"])) {
+ width: auto;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+.btn:is(input[type="checkbox"]):after,
+.btn:is(input[type="radio"]):after {
+ --tw-content: attr(aria-label);
+ content: var(--tw-content);
+}
+
+.card {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ border-radius: var(--rounded-box, 1rem);
+}
+
+.card:focus {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+}
+
+.card-body {
+ display: flex;
+ flex: 1 1 auto;
+ flex-direction: column;
+ padding: var(--padding-card, 2rem);
+ gap: 0.5rem;
+}
+
+.card-body :where(p) {
+ flex-grow: 1;
+}
+
+.card-actions {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: flex-start;
+ gap: 0.5rem;
+}
+
+.card figure {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.card.image-full {
+ display: grid;
+}
+
+.card.image-full:before {
+ position: relative;
+ content: "";
+ z-index: 10;
+ border-radius: var(--rounded-box, 1rem);
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
+ opacity: 0.75;
+}
+
+.card.image-full:before,
+ .card.image-full > * {
+ grid-column-start: 1;
+ grid-row-start: 1;
+}
+
+.card.image-full > figure img {
+ height: 100%;
+ -o-object-fit: cover;
+ object-fit: cover;
+}
+
+.card.image-full > .card-body {
+ position: relative;
+ z-index: 20;
+ --tw-text-opacity: 1;
+ color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
+}
+
+.checkbox {
+ flex-shrink: 0;
+ --chkbg: var(--fallback-bc,oklch(var(--bc)/1));
+ --chkfg: var(--fallback-b1,oklch(var(--b1)/1));
+ height: 1.5rem;
+ width: 1.5rem;
+ cursor: pointer;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ border-radius: var(--rounded-btn, 0.5rem);
+ border-width: 1px;
+ border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
+ --tw-border-opacity: 0.2;
+}
+
+.divider {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ align-self: stretch;
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+ height: 1rem;
+ white-space: nowrap;
+}
+
+.divider:before,
+ .divider:after {
+ height: 0.125rem;
+ width: 100%;
+ flex-grow: 1;
+ --tw-content: '';
+ content: var(--tw-content);
+ background-color: var(--fallback-bc,oklch(var(--bc)/0.1));
+}
+
+.divider-start:before {
+ display: none;
+}
+
+.divider-end:after {
+ display: none;
+}
+
+.dropdown {
+ position: relative;
+ display: inline-block;
+}
+
+.dropdown > *:not(summary):focus {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+}
+
+.dropdown .dropdown-content {
+ position: absolute;
+}
+
+.dropdown:is(:not(details)) .dropdown-content {
+ visibility: hidden;
+ opacity: 0;
+ transform-origin: top;
+ --tw-scale-x: .95;
+ --tw-scale-y: .95;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ transition-duration: 200ms;
+}
+
+.dropdown-end .dropdown-content {
+ inset-inline-end: 0px;
+}
+
+.dropdown-left .dropdown-content {
+ bottom: auto;
+ inset-inline-end: 100%;
+ top: 0px;
+ transform-origin: right;
+}
+
+.dropdown-right .dropdown-content {
+ bottom: auto;
+ inset-inline-start: 100%;
+ top: 0px;
+ transform-origin: left;
+}
+
+.dropdown-bottom .dropdown-content {
+ bottom: auto;
+ top: 100%;
+ transform-origin: top;
+}
+
+.dropdown-top .dropdown-content {
+ bottom: 100%;
+ top: auto;
+ transform-origin: bottom;
+}
+
+.dropdown-end.dropdown-right .dropdown-content {
+ bottom: 0px;
+ top: auto;
+}
+
+.dropdown-end.dropdown-left .dropdown-content {
+ bottom: 0px;
+ top: auto;
+}
+
+.dropdown.dropdown-open .dropdown-content,
+.dropdown:not(.dropdown-hover):focus .dropdown-content,
+.dropdown:focus-within .dropdown-content {
+ visibility: visible;
+ opacity: 1;
+}
+
+@media (hover: hover) {
+ .dropdown.dropdown-hover:hover .dropdown-content {
+ visibility: visible;
+ opacity: 1;
+ }
+
+ .btn:hover {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity)));
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
+ }
+
+ @supports (color: color-mix(in oklab, black, black)) {
+ .btn:hover {
+ background-color: color-mix(
+ in oklab,
+ oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)) 90%,
+ black
+ );
+ border-color: color-mix(
+ in oklab,
+ oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)) 90%,
+ black
+ );
+ }
+ }
+
+ @supports not (color: oklch(0% 0 0)) {
+ .btn:hover {
+ background-color: var(--btn-color, var(--fallback-b2));
+ border-color: var(--btn-color, var(--fallback-b2));
+ }
+ }
+
+ .btn.glass:hover {
+ --glass-opacity: 25%;
+ --glass-border-opacity: 15%;
+ }
+
+ .btn-ghost:hover {
+ border-color: transparent;
+ }
+
+ @supports (color: oklch(0% 0 0)) {
+ .btn-ghost:hover {
+ background-color: var(--fallback-bc,oklch(var(--bc)/0.2));
+ }
+ }
+
+ .btn-outline.btn-primary:hover {
+ --tw-text-opacity: 1;
+ color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
+ }
+
+ @supports (color: color-mix(in oklab, black, black)) {
+ .btn-outline.btn-primary:hover {
+ background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
+ border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
+ }
+ }
+
+ .btn-outline.btn-accent:hover {
+ --tw-text-opacity: 1;
+ color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)));
+ }
+
+ @supports (color: color-mix(in oklab, black, black)) {
+ .btn-outline.btn-accent:hover {
+ background-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black);
+ border-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black);
+ }
+ }
+
+ .btn-disabled:hover,
+ .btn[disabled]:hover,
+ .btn:disabled:hover {
+ --tw-border-opacity: 0;
+ background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
+ --tw-bg-opacity: 0.2;
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
+ --tw-text-opacity: 0.2;
+ }
+
+ @supports (color: color-mix(in oklab, black, black)) {
+ .btn:is(input[type="checkbox"]:checked):hover, .btn:is(input[type="radio"]:checked):hover {
+ background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
+ border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
+ }
+ }
+
+ .dropdown.dropdown-hover:hover .dropdown-content {
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+
+ :where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(.active, .btn):hover, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(.active, .btn):hover {
+ cursor: pointer;
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+ }
+
+ @supports (color: oklch(0% 0 0)) {
+ :where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(.active, .btn):hover, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(.active, .btn):hover {
+ background-color: var(--fallback-bc,oklch(var(--bc)/0.1));
+ }
+ }
+
+ .tab[disabled],
+ .tab[disabled]:hover {
+ cursor: not-allowed;
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
+ --tw-text-opacity: 0.2;
+ }
+}
+
+.dropdown:is(details) summary::-webkit-details-marker {
+ display: none;
+}
+
+.footer {
+ display: grid;
+ width: 100%;
+ grid-auto-flow: row;
+ place-items: start;
+ -moz-column-gap: 1rem;
+ column-gap: 1rem;
+ row-gap: 2.5rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+}
+
+.footer > * {
+ display: grid;
+ place-items: start;
+ gap: 0.5rem;
+}
+
+@media (min-width: 48rem) {
+ .footer {
+ grid-auto-flow: column;
+ }
+
+ .footer-center {
+ grid-auto-flow: row dense;
+ }
+}
+
+.form-control {
+ display: flex;
+ flex-direction: column;
+}
+
+.label {
+ display: flex;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ align-items: center;
+ justify-content: space-between;
+ padding-left: 0.25rem;
+ padding-right: 0.25rem;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+
+.indicator {
+ position: relative;
+ display: inline-flex;
+ width: -moz-max-content;
+ width: max-content;
+}
+
+.indicator :where(.indicator-item) {
+ z-index: 1;
+ position: absolute;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ white-space: nowrap;
+}
+
+.input {
+ flex-shrink: 1;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ height: 3rem;
+ padding-left: 1rem;
+ padding-right: 1rem;
+ font-size: 1rem;
+ line-height: 2;
+ line-height: 1.5rem;
+ border-radius: var(--rounded-btn, 0.5rem);
+ border-width: 1px;
+ border-color: transparent;
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
+}
+
+.input[type="number"]::-webkit-inner-spin-button,
+.input-md[type="number"]::-webkit-inner-spin-button {
+ margin-top: -1rem;
+ margin-bottom: -1rem;
+ margin-inline-end: -1rem;
+}
+
+.join {
+ display: inline-flex;
+ align-items: stretch;
+ border-radius: var(--rounded-btn, 0.5rem);
+}
+
+.join :where(.join-item) {
+ border-start-end-radius: 0;
+ border-end-end-radius: 0;
+ border-end-start-radius: 0;
+ border-start-start-radius: 0;
+}
+
+.join .join-item:not(:first-child):not(:last-child),
+ .join *:not(:first-child):not(:last-child) .join-item {
+ border-start-end-radius: 0;
+ border-end-end-radius: 0;
+ border-end-start-radius: 0;
+ border-start-start-radius: 0;
+}
+
+.join .join-item:first-child:not(:last-child),
+ .join *:first-child:not(:last-child) .join-item {
+ border-start-end-radius: 0;
+ border-end-end-radius: 0;
+}
+
+.join .dropdown .join-item:first-child:not(:last-child),
+ .join *:first-child:not(:last-child) .dropdown .join-item {
+ border-start-end-radius: inherit;
+ border-end-end-radius: inherit;
+}
+
+.join :where(.join-item:first-child:not(:last-child)),
+ .join :where(*:first-child:not(:last-child) .join-item) {
+ border-end-start-radius: inherit;
+ border-start-start-radius: inherit;
+}
+
+.join .join-item:last-child:not(:first-child),
+ .join *:last-child:not(:first-child) .join-item {
+ border-end-start-radius: 0;
+ border-start-start-radius: 0;
+}
+
+.join :where(.join-item:last-child:not(:first-child)),
+ .join :where(*:last-child:not(:first-child) .join-item) {
+ border-start-end-radius: inherit;
+ border-end-end-radius: inherit;
+}
+
+@supports not selector(:has(*)) {
+ :where(.join *) {
+ border-radius: inherit;
+ }
+}
+
+@supports selector(:has(*)) {
+ :where(.join *:has(.join-item)) {
+ border-radius: inherit;
+ }
+}
+
+.link {
+ cursor: pointer;
+ text-decoration-line: underline;
+}
+
+.mask {
+ -webkit-mask-size: contain;
+ mask-size: contain;
+ -webkit-mask-repeat: no-repeat;
+ mask-repeat: no-repeat;
+ -webkit-mask-position: center;
+ mask-position: center;
+}
+
+.menu {
+ display: flex;
+ flex-direction: column;
+ flex-wrap: wrap;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ padding: 0.5rem;
+}
+
+.menu :where(li ul) {
+ position: relative;
+ white-space: nowrap;
+ margin-inline-start: 1rem;
+ padding-inline-start: 0.5rem;
+}
+
+.menu :where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)), .menu :where(li:not(.menu-title) > details > summary:not(.menu-title)) {
+ display: grid;
+ grid-auto-flow: column;
+ align-content: flex-start;
+ align-items: center;
+ gap: 0.5rem;
+ grid-auto-columns: minmax(auto, max-content) auto max-content;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.menu li.disabled {
+ cursor: not-allowed;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ color: var(--fallback-bc,oklch(var(--bc)/0.3));
+}
+
+.menu :where(li > .menu-dropdown:not(.menu-dropdown-show)) {
+ display: none;
+}
+
+:where(.menu li) {
+ position: relative;
+ display: flex;
+ flex-shrink: 0;
+ flex-direction: column;
+ flex-wrap: wrap;
+ align-items: stretch;
+}
+
+:where(.menu li) .badge {
+ justify-self: end;
+}
+
+.modal {
+ pointer-events: none;
+ position: fixed;
+ inset: 0px;
+ margin: 0px;
+ display: grid;
+ height: 100%;
+ max-height: none;
+ width: 100%;
+ max-width: none;
+ justify-items: center;
+ padding: 0px;
+ opacity: 0;
+ overscroll-behavior: contain;
+ z-index: 999;
+ background-color: transparent;
+ color: inherit;
+ transition-duration: 200ms;
+ transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ transition-property: transform, opacity, visibility;
+ overflow-y: hidden;
+}
+
+:where(.modal) {
+ align-items: center;
+}
+
+.modal-box {
+ max-height: calc(100vh - 5em);
+ grid-column-start: 1;
+ grid-row-start: 1;
+ width: 91.666667%;
+ max-width: 32rem;
+ --tw-scale-x: .9;
+ --tw-scale-y: .9;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ border-bottom-right-radius: var(--rounded-box, 1rem);
+ border-bottom-left-radius: var(--rounded-box, 1rem);
+ border-top-left-radius: var(--rounded-box, 1rem);
+ border-top-right-radius: var(--rounded-box, 1rem);
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
+ padding: 1.5rem;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ transition-duration: 200ms;
+ box-shadow: rgba(0, 0, 0, 0.25) 0px 25px 50px -12px;
+ overflow-y: auto;
+ overscroll-behavior: contain;
+}
+
+.modal-open,
+.modal:target,
+.modal-toggle:checked + .modal,
+.modal[open] {
+ pointer-events: auto;
+ visibility: visible;
+ opacity: 1;
+}
+
+.modal-action {
+ display: flex;
+ margin-top: 1.5rem;
+ justify-content: flex-end;
+}
+
+:root:has(:is(.modal-open, .modal:target, .modal-toggle:checked + .modal, .modal[open])) {
+ overflow: hidden;
+ scrollbar-gutter: stable;
+}
+
+.navbar {
+ display: flex;
+ align-items: center;
+ padding: var(--navbar-padding, 0.5rem);
+ min-height: 4rem;
+ width: 100%;
+}
+
+:where(.navbar > *:not(script, style)) {
+ display: inline-flex;
+ align-items: center;
+}
+
+.navbar-start {
+ width: 50%;
+ justify-content: flex-start;
+}
+
+.navbar-center {
+ flex-shrink: 0;
+}
+
+.navbar-end {
+ width: 50%;
+ justify-content: flex-end;
+}
+
+.radio {
+ flex-shrink: 0;
+ --chkbg: var(--bc);
+ height: 1.5rem;
+ width: 1.5rem;
+ cursor: pointer;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ border-radius: 9999px;
+ border-width: 1px;
+ border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
+ --tw-border-opacity: 0.2;
+}
+
+.range {
+ height: 1.5rem;
+ width: 100%;
+ cursor: pointer;
+ -moz-appearance: none;
+ appearance: none;
+ -webkit-appearance: none;
+ --range-shdw: var(--fallback-bc,oklch(var(--bc)/1));
+ overflow: hidden;
+ border-radius: var(--rounded-box, 1rem);
+ background-color: transparent;
+}
+
+.range:focus {
+ outline: none;
+}
+
+.select {
+ display: inline-flex;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ height: 3rem;
+ min-height: 3rem;
+ padding-inline-start: 1rem;
+ padding-inline-end: 2.5rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ line-height: 2;
+ border-radius: var(--rounded-btn, 0.5rem);
+ border-width: 1px;
+ border-color: transparent;
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
+ background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
+ linear-gradient(135deg, currentColor 50%, transparent 50%);
+ background-position: calc(100% - 20px) calc(1px + 50%),
+ calc(100% - 16.1px) calc(1px + 50%);
+ background-size: 4px 4px,
+ 4px 4px;
+ background-repeat: no-repeat;
+}
+
+.select[multiple] {
+ height: auto;
+}
+
+.stats {
+ display: inline-grid;
+ border-radius: var(--rounded-box, 1rem);
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
+ --tw-text-opacity: 1;
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
+}
+
+:where(.stats) {
+ grid-auto-flow: column;
+ overflow-x: auto;
+}
+
+.stat {
+ display: inline-grid;
+ width: 100%;
+ grid-template-columns: repeat(1, 1fr);
+ -moz-column-gap: 1rem;
+ column-gap: 1rem;
+ border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
+ --tw-border-opacity: 0.1;
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ padding-top: 1rem;
+ padding-bottom: 1rem;
+}
+
+.stat-title {
+ grid-column-start: 1;
+ white-space: nowrap;
+ color: var(--fallback-bc,oklch(var(--bc)/0.6));
+}
+
+.stat-value {
+ grid-column-start: 1;
+ white-space: nowrap;
+ font-size: 2.25rem;
+ line-height: 2.5rem;
+ font-weight: 800;
+}
+
+.swap {
+ position: relative;
+ display: inline-grid;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ place-content: center;
+ cursor: pointer;
+}
+
+.swap > * {
+ grid-column-start: 1;
+ grid-row-start: 1;
+ transition-duration: 300ms;
+ transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ transition-property: transform, opacity;
+}
+
+.swap input {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+.swap .swap-on,
+.swap .swap-indeterminate,
+.swap input:indeterminate ~ .swap-on {
+ opacity: 0;
+}
+
+.swap input:checked ~ .swap-off,
+.swap-active .swap-off,
+.swap input:indeterminate ~ .swap-off {
+ opacity: 0;
+}
+
+.swap input:checked ~ .swap-on,
+.swap-active .swap-on,
+.swap input:indeterminate ~ .swap-indeterminate {
+ opacity: 1;
+}
+
+.tabs {
+ display: grid;
+ align-items: flex-end;
+}
+
+.tabs-lifted:has(.tab-content[class^="rounded-"])
+ .tab:first-child:not(:is(.tab-active, [aria-selected="true"])), .tabs-lifted:has(.tab-content[class*=" rounded-"])
+ .tab:first-child:not(:is(.tab-active, [aria-selected="true"])) {
+ border-bottom-color: transparent;
+}
+
+.tab {
+ position: relative;
+ grid-row-start: 1;
+ display: inline-flex;
+ height: 2rem;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ line-height: 2;
+ --tab-padding: 1rem;
+ --tw-text-opacity: 0.5;
+ --tab-color: var(--fallback-bc,oklch(var(--bc)/1));
+ --tab-bg: var(--fallback-b1,oklch(var(--b1)/1));
+ --tab-border-color: var(--fallback-b3,oklch(var(--b3)/1));
+ color: var(--tab-color);
+ padding-inline-start: var(--tab-padding, 1rem);
+ padding-inline-end: var(--tab-padding, 1rem);
+}
+
+.tab:is(input[type="radio"]) {
+ width: auto;
+ border-bottom-right-radius: 0px;
+ border-bottom-left-radius: 0px;
+}
+
+.tab:is(input[type="radio"]):after {
+ --tw-content: attr(aria-label);
+ content: var(--tw-content);
+}
+
+.tab:not(input):empty {
+ cursor: default;
+ grid-column-start: span 9999;
+}
+
+.tab-content {
+ grid-column-start: 1;
+ grid-column-end: span 9999;
+ grid-row-start: 2;
+ margin-top: calc(var(--tab-border) * -1);
+ display: none;
+ border-color: transparent;
+ border-width: var(--tab-border, 0);
+}
+
+:checked + .tab-content:nth-child(2),
+ :is(.tab-active, [aria-selected="true"]) + .tab-content:nth-child(2) {
+ border-start-start-radius: 0px;
+}
+
+input.tab:checked + .tab-content,
+:is(.tab-active, [aria-selected="true"]) + .tab-content {
+ display: block;
+}
+
+.table {
+ position: relative;
+ width: 100%;
+ border-radius: var(--rounded-box, 1rem);
+ text-align: left;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+}
+
+.table :where(.table-pin-rows thead tr) {
+ position: sticky;
+ top: 0px;
+ z-index: 1;
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
+}
+
+.table :where(.table-pin-rows tfoot tr) {
+ position: sticky;
+ bottom: 0px;
+ z-index: 1;
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
+}
+
+.table :where(.table-pin-cols tr th) {
+ position: sticky;
+ left: 0px;
+ right: 0px;
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
+}
+
+.toast {
+ position: fixed;
+ display: flex;
+ min-width: -moz-fit-content;
+ min-width: fit-content;
+ flex-direction: column;
+ white-space: nowrap;
+ gap: 0.5rem;
+ padding: 1rem;
+}
+
+.avatar-group :where(.avatar) {
+ overflow: hidden;
+ border-radius: 9999px;
+ border-width: 4px;
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-border-opacity)));
+}
+
+.badge-neutral {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity)));
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
+ --tw-text-opacity: 1;
+ color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
+}
+
+.badge-primary {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));
+ --tw-text-opacity: 1;
+ color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
+}
+
+.badge-accent {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity)));
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-a,oklch(var(--a)/var(--tw-bg-opacity)));
+ --tw-text-opacity: 1;
+ color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)));
+}
+
+.badge-outline.badge-neutral {
+ --tw-text-opacity: 1;
+ color: var(--fallback-n,oklch(var(--n)/var(--tw-text-opacity)));
+}
+
+.badge-outline.badge-primary {
+ --tw-text-opacity: 1;
+ color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)));
+}
+
+.badge-outline.badge-accent {
+ --tw-text-opacity: 1;
+ color: var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity)));
+}
+
+.btm-nav > * .label {
+ font-size: 1rem;
+ line-height: 1.5rem;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ .btn {
+ animation: button-pop var(--animation-btn, 0.25s) ease-out;
+ }
+}
+
+.btn:active:hover,
+ .btn:active:focus {
+ animation: button-pop 0s ease-out;
+ transform: scale(var(--btn-focus-scale, 0.97));
+}
+
+@supports not (color: oklch(0% 0 0)) {
+ .btn {
+ background-color: var(--btn-color, var(--fallback-b2));
+ border-color: var(--btn-color, var(--fallback-b2));
+ }
+
+ .btn-primary {
+ --btn-color: var(--fallback-p);
+ }
+
+ .btn-accent {
+ --btn-color: var(--fallback-a);
+ }
+
+ .btn-neutral {
+ --btn-color: var(--fallback-n);
+ }
+}
+
+@supports (color: color-mix(in oklab, black, black)) {
+ .btn-active {
+ background-color: color-mix(
+ in oklab,
+ oklch(var(--btn-color, var(--b3)) / var(--tw-bg-opacity, 1)) 90%,
+ black
+ );
+ border-color: color-mix(
+ in oklab,
+ oklch(var(--btn-color, var(--b3)) / var(--tw-border-opacity, 1)) 90%,
+ black
+ );
+ }
+
+ .btn-outline.btn-primary.btn-active {
+ background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
+ border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
+ }
+
+ .btn-outline.btn-secondary.btn-active {
+ background-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black);
+ border-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black);
+ }
+
+ .btn-outline.btn-accent.btn-active {
+ background-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black);
+ border-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black);
+ }
+
+ .btn-outline.btn-success.btn-active {
+ background-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black);
+ border-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black);
+ }
+
+ .btn-outline.btn-info.btn-active {
+ background-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black);
+ border-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black);
+ }
+
+ .btn-outline.btn-warning.btn-active {
+ background-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black);
+ border-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black);
+ }
+
+ .btn-outline.btn-error.btn-active {
+ background-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black);
+ border-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black);
+ }
+}
+
+.btn:focus-visible {
+ outline-style: solid;
+ outline-width: 2px;
+ outline-offset: 2px;
+}
+
+.btn-primary {
+ --tw-text-opacity: 1;
+ color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
+ outline-color: var(--fallback-p,oklch(var(--p)/1));
+}
+
+@supports (color: oklch(0% 0 0)) {
+ .btn-primary {
+ --btn-color: var(--p);
+ }
+
+ .btn-accent {
+ --btn-color: var(--a);
+ }
+
+ .btn-neutral {
+ --btn-color: var(--n);
+ }
+}
+
+.btn-accent {
+ --tw-text-opacity: 1;
+ color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)));
+ outline-color: var(--fallback-a,oklch(var(--a)/1));
+}
+
+.btn-neutral {
+ --tw-text-opacity: 1;
+ color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
+ outline-color: var(--fallback-n,oklch(var(--n)/1));
+}
+
+.btn.glass {
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+ outline-color: currentColor;
+}
+
+.btn.glass.btn-active {
+ --glass-opacity: 25%;
+ --glass-border-opacity: 15%;
+}
+
+.btn-ghost {
+ border-width: 1px;
+ border-color: transparent;
+ background-color: transparent;
+ color: currentColor;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+ outline-color: currentColor;
+}
+
+.btn-ghost.btn-active {
+ border-color: transparent;
+ background-color: var(--fallback-bc,oklch(var(--bc)/0.2));
+}
+
+.btn-link.btn-active {
+ border-color: transparent;
+ background-color: transparent;
+ text-decoration-line: underline;
+}
+
+.btn-outline.btn-active {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
+ --tw-text-opacity: 1;
+ color: var(--fallback-b1,oklch(var(--b1)/var(--tw-text-opacity)));
+}
+
+.btn-outline.btn-primary {
+ --tw-text-opacity: 1;
+ color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)));
+}
+
+.btn-outline.btn-primary.btn-active {
+ --tw-text-opacity: 1;
+ color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
+}
+
+.btn-outline.btn-secondary.btn-active {
+ --tw-text-opacity: 1;
+ color: var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)));
+}
+
+.btn-outline.btn-accent {
+ --tw-text-opacity: 1;
+ color: var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity)));
+}
+
+.btn-outline.btn-accent.btn-active {
+ --tw-text-opacity: 1;
+ color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)));
+}
+
+.btn-outline.btn-success.btn-active {
+ --tw-text-opacity: 1;
+ color: var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)));
+}
+
+.btn-outline.btn-info.btn-active {
+ --tw-text-opacity: 1;
+ color: var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)));
+}
+
+.btn-outline.btn-warning.btn-active {
+ --tw-text-opacity: 1;
+ color: var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)));
+}
+
+.btn-outline.btn-error.btn-active {
+ --tw-text-opacity: 1;
+ color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));
+}
+
+.btn.btn-disabled,
+ .btn[disabled],
+ .btn:disabled {
+ --tw-border-opacity: 0;
+ background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
+ --tw-bg-opacity: 0.2;
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
+ --tw-text-opacity: 0.2;
+}
+
+.btn:is(input[type="checkbox"]:checked),
+.btn:is(input[type="radio"]:checked) {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));
+ --tw-text-opacity: 1;
+ color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
+}
+
+.btn:is(input[type="checkbox"]:checked):focus-visible, .btn:is(input[type="radio"]:checked):focus-visible {
+ outline-color: var(--fallback-p,oklch(var(--p)/1));
+}
+
+@keyframes button-pop {
+ 0% {
+ transform: scale(var(--btn-focus-scale, 0.98));
+ }
+
+ 40% {
+ transform: scale(1.02);
+ }
+
+ 100% {
+ transform: scale(1);
+ }
+}
+
+.card :where(figure:first-child) {
+ overflow: hidden;
+ border-start-start-radius: inherit;
+ border-start-end-radius: inherit;
+ border-end-start-radius: unset;
+ border-end-end-radius: unset;
+}
+
+.card :where(figure:last-child) {
+ overflow: hidden;
+ border-start-start-radius: unset;
+ border-start-end-radius: unset;
+ border-end-start-radius: inherit;
+ border-end-end-radius: inherit;
+}
+
+.card:focus-visible {
+ outline: 2px solid currentColor;
+ outline-offset: 2px;
+}
+
+.card.bordered {
+ border-width: 1px;
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
+}
+
+.card.compact .card-body {
+ padding: 1rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+}
+
+.card-title {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+ font-weight: 600;
+}
+
+.card.image-full :where(figure) {
+ overflow: hidden;
+ border-radius: inherit;
+}
+
+.checkbox:focus {
+ box-shadow: none;
+}
+
+.checkbox:focus-visible {
+ outline-style: solid;
+ outline-width: 2px;
+ outline-offset: 2px;
+ outline-color: var(--fallback-bc,oklch(var(--bc)/1));
+}
+
+.checkbox:disabled {
+ border-width: 0px;
+ cursor: not-allowed;
+ border-color: transparent;
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
+ opacity: 0.2;
+}
+
+.checkbox:checked,
+ .checkbox[aria-checked="true"] {
+ background-repeat: no-repeat;
+ animation: checkmark var(--animation-input, 0.2s) ease-out;
+ background-color: var(--chkbg);
+ background-image: linear-gradient(-45deg, transparent 65%, var(--chkbg) 65.99%),
+ linear-gradient(45deg, transparent 75%, var(--chkbg) 75.99%),
+ linear-gradient(-45deg, var(--chkbg) 40%, transparent 40.99%),
+ linear-gradient(
+ 45deg,
+ var(--chkbg) 30%,
+ var(--chkfg) 30.99%,
+ var(--chkfg) 40%,
+ transparent 40.99%
+ ),
+ linear-gradient(-45deg, var(--chkfg) 50%, var(--chkbg) 50.99%);
+}
+
+.checkbox:indeterminate {
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
+ background-repeat: no-repeat;
+ animation: checkmark var(--animation-input, 0.2s) ease-out;
+ background-image: linear-gradient(90deg, transparent 80%, var(--chkbg) 80%),
+ linear-gradient(-90deg, transparent 80%, var(--chkbg) 80%),
+ linear-gradient(0deg, var(--chkbg) 43%, var(--chkfg) 43%, var(--chkfg) 57%, var(--chkbg) 57%);
+}
+
+.checkbox-primary {
+ --chkbg: var(--fallback-p,oklch(var(--p)/1));
+ --chkfg: var(--fallback-pc,oklch(var(--pc)/1));
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));
+}
+
+.checkbox-primary:focus-visible {
+ outline-color: var(--fallback-p,oklch(var(--p)/1));
+}
+
+.checkbox-primary:checked,
+ .checkbox-primary[aria-checked="true"] {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));
+ --tw-text-opacity: 1;
+ color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
+}
+
+.checkbox-accent {
+ --chkbg: var(--fallback-a,oklch(var(--a)/1));
+ --chkfg: var(--fallback-ac,oklch(var(--ac)/1));
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity)));
+}
+
+.checkbox-accent:focus-visible {
+ outline-color: var(--fallback-a,oklch(var(--a)/1));
+}
+
+.checkbox-accent:checked,
+ .checkbox-accent[aria-checked="true"] {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity)));
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-a,oklch(var(--a)/var(--tw-bg-opacity)));
+ --tw-text-opacity: 1;
+ color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)));
+}
+
+@keyframes checkmark {
+ 0% {
+ background-position-y: 5px;
+ }
+
+ 50% {
+ background-position-y: -2px;
+ }
+
+ 100% {
+ background-position-y: 0;
+ }
+}
+
+.divider:not(:empty) {
+ gap: 1rem;
+}
+
+.divider-secondary:before,
+ .divider-secondary:after {
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-s,oklch(var(--s)/var(--tw-bg-opacity)));
+}
+
+.dropdown.dropdown-open .dropdown-content,
+.dropdown:focus .dropdown-content,
+.dropdown:focus-within .dropdown-content {
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.label-text {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ --tw-text-opacity: 1;
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
+}
+
+.input input {
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));
+ background-color: transparent;
+}
+
+.input input:focus {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+}
+
+.input[list]::-webkit-calendar-picker-indicator {
+ line-height: 1em;
+}
+
+.input-bordered {
+ border-color: var(--fallback-bc,oklch(var(--bc)/0.2));
+}
+
+.input:focus,
+ .input:focus-within {
+ box-shadow: none;
+ border-color: var(--fallback-bc,oklch(var(--bc)/0.2));
+ outline-style: solid;
+ outline-width: 2px;
+ outline-offset: 2px;
+ outline-color: var(--fallback-bc,oklch(var(--bc)/0.2));
+}
+
+.input-accent {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity)));
+}
+
+.input-accent:focus,
+ .input-accent:focus-within {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity)));
+ outline-color: var(--fallback-a,oklch(var(--a)/1));
+}
+
+.input:has(> input[disabled]),
+ .input-disabled,
+ .input:disabled,
+ .input[disabled] {
+ cursor: not-allowed;
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
+ color: var(--fallback-bc,oklch(var(--bc)/0.4));
+}
+
+.input:has(> input[disabled])::-moz-placeholder, .input-disabled::-moz-placeholder, .input:disabled::-moz-placeholder, .input[disabled]::-moz-placeholder {
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
+ --tw-placeholder-opacity: 0.2;
+}
+
+.input:has(> input[disabled])::placeholder,
+ .input-disabled::placeholder,
+ .input:disabled::placeholder,
+ .input[disabled]::placeholder {
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
+ --tw-placeholder-opacity: 0.2;
+}
+
+.input:has(> input[disabled]) > input[disabled] {
+ cursor: not-allowed;
+}
+
+.input::-webkit-date-and-time-value {
+ text-align: inherit;
+}
+
+.join > :where(*:not(:first-child)) {
+ margin-top: 0px;
+ margin-bottom: 0px;
+ margin-inline-start: -1px;
+}
+
+.join > :where(*:not(:first-child)):is(.btn) {
+ margin-inline-start: calc(var(--border-btn) * -1);
+}
+
+.join-item:focus {
+ isolation: isolate;
+}
+
+.link:focus {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+}
+
+.link:focus-visible {
+ outline: 2px solid currentColor;
+ outline-offset: 2px;
+}
+
+.loading {
+ pointer-events: none;
+ display: inline-block;
+ aspect-ratio: 1 / 1;
+ width: 1.5rem;
+ background-color: currentColor;
+ -webkit-mask-size: 100%;
+ mask-size: 100%;
+ -webkit-mask-repeat: no-repeat;
+ mask-repeat: no-repeat;
+ -webkit-mask-position: center;
+ mask-position: center;
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
+ mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
+}
+
+.loading-spinner {
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
+ mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
+}
+
+.loading-lg {
+ width: 2.5rem;
+}
+
+.mask-circle {
+ -webkit-mask-image: url("data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle fill='black' cx='100' cy='100' r='100' fill-rule='evenodd'/%3e%3c/svg%3e");
+ mask-image: url("data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle fill='black' cx='100' cy='100' r='100' fill-rule='evenodd'/%3e%3c/svg%3e");
+}
+
+:where(.menu li:empty) {
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
+ opacity: 0.1;
+ margin: 0.5rem 1rem;
+ height: 1px;
+}
+
+.menu :where(li ul):before {
+ position: absolute;
+ bottom: 0.75rem;
+ inset-inline-start: 0px;
+ top: 0.75rem;
+ width: 1px;
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
+ opacity: 0.1;
+ content: "";
+}
+
+.menu :where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)),
+.menu :where(li:not(.menu-title) > details > summary:not(.menu-title)) {
+ border-radius: var(--rounded-btn, 0.5rem);
+ padding-left: 1rem;
+ padding-right: 1rem;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ text-align: start;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ transition-duration: 200ms;
+ text-wrap: balance;
+}
+
+:where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(summary, .active, .btn).focus, :where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(summary, .active, .btn):focus, :where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):is(summary):not(.active, .btn):focus-visible, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(summary, .active, .btn).focus, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(summary, .active, .btn):focus, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):is(summary):not(.active, .btn):focus-visible {
+ cursor: pointer;
+ background-color: var(--fallback-bc,oklch(var(--bc)/0.1));
+ --tw-text-opacity: 1;
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+}
+
+.menu li > *:not(ul, .menu-title, details, .btn):active,
+.menu li > *:not(ul, .menu-title, details, .btn).active,
+.menu li > details > summary:active {
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
+ --tw-text-opacity: 1;
+ color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
+}
+
+.menu :where(li > details > summary)::-webkit-details-marker {
+ display: none;
+}
+
+.menu :where(li > details > summary):after,
+.menu :where(li > .menu-dropdown-toggle):after {
+ justify-self: end;
+ display: block;
+ margin-top: -0.5rem;
+ height: 0.5rem;
+ width: 0.5rem;
+ transform: rotate(45deg);
+ transition-property: transform, margin-top;
+ transition-duration: 0.3s;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ content: "";
+ transform-origin: 75% 75%;
+ box-shadow: 2px 2px;
+ pointer-events: none;
+}
+
+.menu :where(li > details[open] > summary):after,
+.menu :where(li > .menu-dropdown-toggle.menu-dropdown-show):after {
+ transform: rotate(225deg);
+ margin-top: 0;
+}
+
+.menu-title {
+ padding-left: 1rem;
+ padding-right: 1rem;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 700;
+ color: var(--fallback-bc,oklch(var(--bc)/0.4));
+}
+
+.mockup-browser .mockup-browser-toolbar .input {
+ position: relative;
+ margin-left: auto;
+ margin-right: auto;
+ display: block;
+ height: 1.75rem;
+ width: 24rem;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
+ padding-left: 2rem;
+ direction: ltr;
+}
+
+.mockup-browser .mockup-browser-toolbar .input:before {
+ content: "";
+ position: absolute;
+ left: 0.5rem;
+ top: 50%;
+ aspect-ratio: 1 / 1;
+ height: 0.75rem;
+ --tw-translate-y: -50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ border-radius: 9999px;
+ border-width: 2px;
+ border-color: currentColor;
+ opacity: 0.6;
+}
+
+.mockup-browser .mockup-browser-toolbar .input:after {
+ content: "";
+ position: absolute;
+ left: 1.25rem;
+ top: 50%;
+ height: 0.5rem;
+ --tw-translate-y: 25%;
+ --tw-rotate: -45deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ border-radius: 9999px;
+ border-width: 1px;
+ border-color: currentColor;
+ opacity: 0.6;
+}
+
+.modal:not(dialog:not(.modal-open)),
+ .modal::backdrop {
+ background-color: #0006;
+ animation: modal-pop 0.2s ease-out;
+}
+
+.modal-backdrop {
+ z-index: -1;
+ grid-column-start: 1;
+ grid-row-start: 1;
+ display: grid;
+ align-self: stretch;
+ justify-self: stretch;
+ color: transparent;
+}
+
+.modal-open .modal-box,
+.modal-toggle:checked + .modal .modal-box,
+.modal:target .modal-box,
+.modal[open] .modal-box {
+ --tw-translate-y: 0px;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.modal-action > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-right: calc(0.5rem * var(--tw-space-x-reverse));
+ margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
+@keyframes modal-pop {
+ 0% {
+ opacity: 0;
+ }
+}
+
+@keyframes progress-loading {
+ 50% {
+ background-position-x: -115%;
+ }
+}
+
+.radio:focus {
+ box-shadow: none;
+}
+
+.radio:focus-visible {
+ outline-style: solid;
+ outline-width: 2px;
+ outline-offset: 2px;
+ outline-color: var(--fallback-bc,oklch(var(--bc)/1));
+}
+
+.radio:checked,
+ .radio[aria-checked="true"] {
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
+ background-image: none;
+ animation: radiomark var(--animation-input, 0.2s) ease-out;
+ box-shadow: 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset,
+ 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset;
+}
+
+.radio:disabled {
+ cursor: not-allowed;
+ opacity: 0.2;
+}
+
+@keyframes radiomark {
+ 0% {
+ box-shadow: 0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset,
+ 0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset;
+ }
+
+ 50% {
+ box-shadow: 0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset,
+ 0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset;
+ }
+
+ 100% {
+ box-shadow: 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset,
+ 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset;
+ }
+}
+
+.range:focus-visible::-webkit-slider-thumb {
+ --focus-shadow: 0 0 0 6px var(--fallback-b1,oklch(var(--b1)/1)) inset, 0 0 0 2rem var(--range-shdw) inset;
+}
+
+.range:focus-visible::-moz-range-thumb {
+ --focus-shadow: 0 0 0 6px var(--fallback-b1,oklch(var(--b1)/1)) inset, 0 0 0 2rem var(--range-shdw) inset;
+}
+
+.range::-webkit-slider-runnable-track {
+ height: 0.5rem;
+ width: 100%;
+ border-radius: var(--rounded-box, 1rem);
+ background-color: var(--fallback-bc,oklch(var(--bc)/0.1));
+}
+
+.range::-moz-range-track {
+ height: 0.5rem;
+ width: 100%;
+ border-radius: var(--rounded-box, 1rem);
+ background-color: var(--fallback-bc,oklch(var(--bc)/0.1));
+}
+
+.range::-webkit-slider-thumb {
+ position: relative;
+ height: 1.5rem;
+ width: 1.5rem;
+ border-radius: var(--rounded-box, 1rem);
+ border-style: none;
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
+ appearance: none;
+ -webkit-appearance: none;
+ top: 50%;
+ color: var(--range-shdw);
+ transform: translateY(-50%);
+ --filler-size: 100rem;
+ --filler-offset: 0.6rem;
+ box-shadow: 0 0 0 3px var(--range-shdw) inset,
+ var(--focus-shadow, 0 0),
+ calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size);
+}
+
+.range::-moz-range-thumb {
+ position: relative;
+ height: 1.5rem;
+ width: 1.5rem;
+ border-radius: var(--rounded-box, 1rem);
+ border-style: none;
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
+ top: 50%;
+ color: var(--range-shdw);
+ --filler-size: 100rem;
+ --filler-offset: 0.5rem;
+ box-shadow: 0 0 0 3px var(--range-shdw) inset,
+ var(--focus-shadow, 0 0),
+ calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size);
+}
+
+.range-primary {
+ --range-shdw: var(--fallback-p,oklch(var(--p)/1));
+}
+
+@keyframes rating-pop {
+ 0% {
+ transform: translateY(-0.125em);
+ }
+
+ 40% {
+ transform: translateY(-0.125em);
+ }
+
+ 100% {
+ transform: translateY(0);
+ }
+}
+
+.select:focus {
+ box-shadow: none;
+ border-color: var(--fallback-bc,oklch(var(--bc)/0.2));
+ outline-style: solid;
+ outline-width: 2px;
+ outline-offset: 2px;
+ outline-color: var(--fallback-bc,oklch(var(--bc)/0.2));
+}
+
+.select-accent {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity)));
+}
+
+.select-accent:focus {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity)));
+ outline-color: var(--fallback-a,oklch(var(--a)/1));
+}
+
+.select-disabled,
+ .select:disabled,
+ .select[disabled] {
+ cursor: not-allowed;
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
+ color: var(--fallback-bc,oklch(var(--bc)/0.4));
+}
+
+.select-disabled::-moz-placeholder, .select:disabled::-moz-placeholder, .select[disabled]::-moz-placeholder {
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
+ --tw-placeholder-opacity: 0.2;
+}
+
+.select-disabled::placeholder,
+ .select:disabled::placeholder,
+ .select[disabled]::placeholder {
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
+ --tw-placeholder-opacity: 0.2;
+}
+
+.select-multiple,
+ .select[multiple],
+ .select[size].select:not([size="1"]) {
+ background-image: none;
+ padding-right: 1rem;
+}
+
+[dir="rtl"] .select {
+ background-position: calc(0% + 12px) calc(1px + 50%),
+ calc(0% + 16px) calc(1px + 50%);
+}
+
+@keyframes skeleton {
+ from {
+ background-position: 150%;
+ }
+
+ to {
+ background-position: -50%;
+ }
+}
+
+:where(.stats) > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-x-reverse: 0;
+ border-right-width: calc(1px * var(--tw-divide-x-reverse));
+ border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
+ --tw-divide-y-reverse: 0;
+ border-top-width: calc(0px * calc(1 - var(--tw-divide-y-reverse)));
+ border-bottom-width: calc(0px * var(--tw-divide-y-reverse));
+}
+
+:is([dir="rtl"] .stats > :not([hidden]) ~ :not([hidden])) {
+ --tw-divide-x-reverse: 1;
+}
+
+.tabs-lifted > .tab:focus-visible {
+ border-end-end-radius: 0;
+ border-end-start-radius: 0;
+}
+
+.tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]), .tab:is(input:checked) {
+ border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
+ --tw-border-opacity: 1;
+ --tw-text-opacity: 1;
+}
+
+.tab:focus {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+}
+
+.tab:focus-visible {
+ outline: 2px solid currentColor;
+ outline-offset: -5px;
+}
+
+.tab-disabled,
+ .tab[disabled] {
+ cursor: not-allowed;
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
+ --tw-text-opacity: 0.2;
+}
+
+.tabs-bordered > .tab {
+ border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
+ --tw-border-opacity: 0.2;
+ border-style: solid;
+ border-bottom-width: calc(var(--tab-border, 1px) + 1px);
+}
+
+.tabs-lifted > .tab {
+ border: var(--tab-border, 1px) solid transparent;
+ border-width: 0 0 var(--tab-border, 1px) 0;
+ border-start-start-radius: var(--tab-radius, 0.5rem);
+ border-start-end-radius: var(--tab-radius, 0.5rem);
+ border-bottom-color: var(--tab-border-color);
+ padding-inline-start: var(--tab-padding, 1rem);
+ padding-inline-end: var(--tab-padding, 1rem);
+ padding-top: var(--tab-border, 1px);
+}
+
+.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]), .tabs-lifted > .tab:is(input:checked) {
+ background-color: var(--tab-bg);
+ border-width: var(--tab-border, 1px) var(--tab-border, 1px) 0 var(--tab-border, 1px);
+ border-inline-start-color: var(--tab-border-color);
+ border-inline-end-color: var(--tab-border-color);
+ border-top-color: var(--tab-border-color);
+ padding-inline-start: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px));
+ padding-inline-end: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px));
+ padding-bottom: var(--tab-border, 1px);
+ padding-top: 0;
+}
+
+.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):before, .tabs-lifted > .tab:is(input:checked):before {
+ z-index: 1;
+ content: "";
+ display: block;
+ position: absolute;
+ width: calc(100% + var(--tab-radius, 0.5rem) * 2);
+ height: var(--tab-radius, 0.5rem);
+ bottom: 0;
+ background-size: var(--tab-radius, 0.5rem);
+ background-position: top left,
+ top right;
+ background-repeat: no-repeat;
+ --tab-grad: calc(69% - var(--tab-border, 1px));
+ --radius-start: radial-gradient(
+ circle at top left,
+ transparent var(--tab-grad),
+ var(--tab-border-color) calc(var(--tab-grad) + 0.25px),
+ var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)),
+ var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px)
+ );
+ --radius-end: radial-gradient(
+ circle at top right,
+ transparent var(--tab-grad),
+ var(--tab-border-color) calc(var(--tab-grad) + 0.25px),
+ var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)),
+ var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px)
+ );
+ background-image: var(--radius-start), var(--radius-end);
+}
+
+.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):first-child:before, .tabs-lifted > .tab:is(input:checked):first-child:before {
+ background-image: var(--radius-end);
+ background-position: top right;
+}
+
+[dir="rtl"] .tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):first-child:before, [dir="rtl"] .tabs-lifted > .tab:is(input:checked):first-child:before {
+ background-image: var(--radius-start);
+ background-position: top left;
+}
+
+.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):last-child:before, .tabs-lifted > .tab:is(input:checked):last-child:before {
+ background-image: var(--radius-start);
+ background-position: top left;
+}
+
+[dir="rtl"] .tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):last-child:before, [dir="rtl"] .tabs-lifted > .tab:is(input:checked):last-child:before {
+ background-image: var(--radius-end);
+ background-position: top right;
+}
+
+.tabs-lifted
+ > :is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled])
+ + .tabs-lifted
+ :is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):before, .tabs-lifted > .tab:is(input:checked) + .tabs-lifted .tab:is(input:checked):before {
+ background-image: var(--radius-end);
+ background-position: top right;
+}
+
+.tabs-boxed .tab {
+ border-radius: var(--rounded-btn, 0.5rem);
+}
+
+.table:where([dir="rtl"], [dir="rtl"] *) {
+ text-align: right;
+}
+
+.table :where(th, td) {
+ padding-left: 1rem;
+ padding-right: 1rem;
+ padding-top: 0.75rem;
+ padding-bottom: 0.75rem;
+ vertical-align: middle;
+}
+
+.table tr.active,
+ .table tr.active:nth-child(even),
+ .table-zebra tbody tr:nth-child(even) {
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
+}
+
+.table :where(thead tr, tbody tr:not(:last-child), tbody tr:first-child:last-child) {
+ border-bottom-width: 1px;
+ --tw-border-opacity: 1;
+ border-bottom-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
+}
+
+.table :where(thead, tfoot) {
+ white-space: nowrap;
+ font-size: 0.75rem;
+ line-height: 1rem;
+ font-weight: 700;
+ color: var(--fallback-bc,oklch(var(--bc)/0.6));
+}
+
+.table :where(tfoot) {
+ border-top-width: 1px;
+ --tw-border-opacity: 1;
+ border-top-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
+}
+
+.toast > * {
+ animation: toast-pop 0.25s ease-out;
+}
+
+@keyframes toast-pop {
+ 0% {
+ transform: scale(0.9);
+ opacity: 0;
+ }
+
+ 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+}
+
+.glass,
+ .glass.btn-active {
+ border: none;
+ -webkit-backdrop-filter: blur(var(--glass-blur, 40px));
+ backdrop-filter: blur(var(--glass-blur, 40px));
+ background-color: transparent;
+ background-image: linear-gradient(
+ 135deg,
+ rgb(255 255 255 / var(--glass-opacity, 30%)) 0%,
+ rgb(0 0 0 / 0%) 100%
+ ),
+ linear-gradient(
+ var(--glass-reflex-degree, 100deg),
+ rgb(255 255 255 / var(--glass-reflex-opacity, 10%)) 25%,
+ rgb(0 0 0 / 0%) 25%
+ );
+ box-shadow: 0 0 0 1px rgb(255 255 255 / var(--glass-border-opacity, 10%)) inset,
+ 0 0 0 2px rgb(0 0 0 / 5%);
+ text-shadow: 0 1px rgb(0 0 0 / var(--glass-text-shadow-opacity, 5%));
+}
+
+@media (hover: hover) {
+ .glass.btn-active {
+ border: none;
+ -webkit-backdrop-filter: blur(var(--glass-blur, 40px));
+ backdrop-filter: blur(var(--glass-blur, 40px));
+ background-color: transparent;
+ background-image: linear-gradient(
+ 135deg,
+ rgb(255 255 255 / var(--glass-opacity, 30%)) 0%,
+ rgb(0 0 0 / 0%) 100%
+ ),
+ linear-gradient(
+ var(--glass-reflex-degree, 100deg),
+ rgb(255 255 255 / var(--glass-reflex-opacity, 10%)) 25%,
+ rgb(0 0 0 / 0%) 25%
+ );
+ box-shadow: 0 0 0 1px rgb(255 255 255 / var(--glass-border-opacity, 10%)) inset,
+ 0 0 0 2px rgb(0 0 0 / 5%);
+ text-shadow: 0 1px rgb(0 0 0 / var(--glass-text-shadow-opacity, 5%));
+ }
+}
+
+.btn-sm {
+ height: 2rem;
+ min-height: 2rem;
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+ font-size: 0.875rem;
+}
+
+.btn-block {
+ width: 100%;
+}
+
+.btn-square:where(.btn-sm) {
+ height: 2rem;
+ width: 2rem;
+ padding: 0px;
+}
+
+.btn-circle:where(.btn-xs) {
+ height: 1.5rem;
+ width: 1.5rem;
+ border-radius: 9999px;
+ padding: 0px;
+}
+
+.btn-circle:where(.btn-sm) {
+ height: 2rem;
+ width: 2rem;
+ border-radius: 9999px;
+ padding: 0px;
+}
+
+.btn-circle:where(.btn-md) {
+ height: 3rem;
+ width: 3rem;
+ border-radius: 9999px;
+ padding: 0px;
+}
+
+.btn-circle:where(.btn-lg) {
+ height: 4rem;
+ width: 4rem;
+ border-radius: 9999px;
+ padding: 0px;
+}
+
+[type="checkbox"].checkbox-sm {
+ height: 1.25rem;
+ width: 1.25rem;
+}
+
+.indicator :where(.indicator-item) {
+ bottom: auto;
+ inset-inline-end: 0px;
+ inset-inline-start: auto;
+ top: 0px;
+ --tw-translate-y: -50%;
+ --tw-translate-x: 50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.indicator :where(.indicator-item):where([dir="rtl"], [dir="rtl"] *) {
+ --tw-translate-x: -50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.indicator :where(.indicator-item.indicator-start) {
+ inset-inline-end: auto;
+ inset-inline-start: 0px;
+ --tw-translate-x: -50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.indicator :where(.indicator-item.indicator-start):where([dir="rtl"], [dir="rtl"] *) {
+ --tw-translate-x: 50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.indicator :where(.indicator-item.indicator-center) {
+ inset-inline-end: 50%;
+ inset-inline-start: 50%;
+ --tw-translate-x: -50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.indicator :where(.indicator-item.indicator-center):where([dir="rtl"], [dir="rtl"] *) {
+ --tw-translate-x: 50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.indicator :where(.indicator-item.indicator-end) {
+ inset-inline-end: 0px;
+ inset-inline-start: auto;
+ --tw-translate-x: 50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.indicator :where(.indicator-item.indicator-end):where([dir="rtl"], [dir="rtl"] *) {
+ --tw-translate-x: -50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.indicator :where(.indicator-item.indicator-bottom) {
+ bottom: 0px;
+ top: auto;
+ --tw-translate-y: 50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.indicator :where(.indicator-item.indicator-middle) {
+ bottom: 50%;
+ top: 50%;
+ --tw-translate-y: -50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.indicator :where(.indicator-item.indicator-top) {
+ bottom: auto;
+ top: 0px;
+ --tw-translate-y: -50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.input-md {
+ height: 3rem;
+ padding-left: 1rem;
+ padding-right: 1rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ line-height: 2;
+}
+
+.join.join-vertical {
+ flex-direction: column;
+}
+
+.join.join-vertical .join-item:first-child:not(:last-child),
+ .join.join-vertical *:first-child:not(:last-child) .join-item {
+ border-end-start-radius: 0;
+ border-end-end-radius: 0;
+ border-start-start-radius: inherit;
+ border-start-end-radius: inherit;
+}
+
+.join.join-vertical .join-item:last-child:not(:first-child),
+ .join.join-vertical *:last-child:not(:first-child) .join-item {
+ border-start-start-radius: 0;
+ border-start-end-radius: 0;
+ border-end-start-radius: inherit;
+ border-end-end-radius: inherit;
+}
+
+.join.join-horizontal {
+ flex-direction: row;
+}
+
+.join.join-horizontal .join-item:first-child:not(:last-child),
+ .join.join-horizontal *:first-child:not(:last-child) .join-item {
+ border-end-end-radius: 0;
+ border-start-end-radius: 0;
+ border-end-start-radius: inherit;
+ border-start-start-radius: inherit;
+}
+
+.join.join-horizontal .join-item:last-child:not(:first-child),
+ .join.join-horizontal *:last-child:not(:first-child) .join-item {
+ border-end-start-radius: 0;
+ border-start-start-radius: 0;
+ border-end-end-radius: inherit;
+ border-start-end-radius: inherit;
+}
+
+.menu-horizontal {
+ display: inline-flex;
+ flex-direction: row;
+}
+
+.menu-horizontal > li:not(.menu-title) > details > ul {
+ position: absolute;
+}
+
+.menu-vertical {
+ display: flex;
+ flex-direction: column;
+}
+
+.menu-vertical > li:not(.menu-title) > details > ul {
+ position: relative;
+}
+
+.range-xs {
+ height: 1rem;
+}
+
+.range-xs::-webkit-slider-runnable-track {
+ height: 0.25rem;
+}
+
+.range-xs::-moz-range-track {
+ height: 0.25rem;
+}
+
+.range-xs::-webkit-slider-thumb {
+ height: 1rem;
+ width: 1rem;
+ --filler-offset: 0.4rem;
+}
+
+.range-xs::-moz-range-thumb {
+ height: 1rem;
+ width: 1rem;
+ --filler-offset: 0.4rem;
+}
+
+.select-xs {
+ height: 1.5rem;
+ min-height: 1.5rem;
+ padding-left: 0.5rem;
+ padding-right: 2rem;
+ font-size: 0.75rem;
+ line-height: 1rem;
+ line-height: 1.625;
+}
+
+[dir="rtl"] .select-xs {
+ padding-left: 2rem;
+ padding-right: 0.5rem;
+}
+
+.stats-horizontal {
+ grid-auto-flow: column;
+}
+
+.tabs-md :where(.tab) {
+ height: 2rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ line-height: 2;
+ --tab-padding: 1rem;
+}
+
+.tabs-lg :where(.tab) {
+ height: 3rem;
+ font-size: 1.125rem;
+ line-height: 1.75rem;
+ line-height: 2;
+ --tab-padding: 1.25rem;
+}
+
+.tabs-sm :where(.tab) {
+ height: 1.5rem;
+ font-size: 0.875rem;
+ line-height: .75rem;
+ --tab-padding: 0.75rem;
+}
+
+.tabs-xs :where(.tab) {
+ height: 1.25rem;
+ font-size: 0.75rem;
+ line-height: .75rem;
+ --tab-padding: 0.5rem;
+}
+
+:where(.toast) {
+ bottom: 0px;
+ inset-inline-end: 0px;
+ inset-inline-start: auto;
+ top: auto;
+ --tw-translate-x: 0px;
+ --tw-translate-y: 0px;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.toast:where(.toast-start) {
+ inset-inline-end: auto;
+ inset-inline-start: 0px;
+ --tw-translate-x: 0px;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.toast:where(.toast-center) {
+ inset-inline-end: 50%;
+ inset-inline-start: 50%;
+ --tw-translate-x: -50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.toast:where(.toast-center):where([dir="rtl"], [dir="rtl"] *) {
+ --tw-translate-x: 50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.toast:where(.toast-end) {
+ inset-inline-end: 0px;
+ inset-inline-start: auto;
+ --tw-translate-x: 0px;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.toast:where(.toast-bottom) {
+ bottom: 0px;
+ top: auto;
+ --tw-translate-y: 0px;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.toast:where(.toast-middle) {
+ bottom: auto;
+ top: 50%;
+ --tw-translate-y: -50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.toast:where(.toast-top) {
+ bottom: auto;
+ top: 0px;
+ --tw-translate-y: 0px;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.avatar.online:before {
+ content: "";
+ position: absolute;
+ z-index: 10;
+ display: block;
+ border-radius: 9999px;
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity)));
+ outline-style: solid;
+ outline-width: 2px;
+ outline-color: var(--fallback-b1,oklch(var(--b1)/1));
+ width: 15%;
+ height: 15%;
+ top: 7%;
+ right: 7%;
+}
+
+.avatar.offline:before {
+ content: "";
+ position: absolute;
+ z-index: 10;
+ display: block;
+ border-radius: 9999px;
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
+ outline-style: solid;
+ outline-width: 2px;
+ outline-color: var(--fallback-b1,oklch(var(--b1)/1));
+ width: 15%;
+ height: 15%;
+ top: 7%;
+ right: 7%;
+}
+
+.card-compact .card-body {
+ padding: 1rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+}
+
+.card-compact .card-title {
+ margin-bottom: 0.25rem;
+}
+
+.card-normal .card-body {
+ padding: var(--padding-card, 2rem);
+ font-size: 1rem;
+ line-height: 1.5rem;
+}
+
+.card-normal .card-title {
+ margin-bottom: 0.75rem;
+}
+
+.join.join-vertical > :where(*:not(:first-child)) {
+ margin-left: 0px;
+ margin-right: 0px;
+ margin-top: -1px;
+}
+
+.join.join-vertical > :where(*:not(:first-child)):is(.btn) {
+ margin-top: calc(var(--border-btn) * -1);
+}
+
+.join.join-horizontal > :where(*:not(:first-child)) {
+ margin-top: 0px;
+ margin-bottom: 0px;
+ margin-inline-start: -1px;
+}
+
+.join.join-horizontal > :where(*:not(:first-child)):is(.btn) {
+ margin-inline-start: calc(var(--border-btn) * -1);
+}
+
+.menu-horizontal > li:not(.menu-title) > details > ul {
+ margin-inline-start: 0px;
+ margin-top: 1rem;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ padding-inline-end: 0.5rem;
+}
+
+.menu-horizontal > li > details > ul:before {
+ content: none;
+}
+
+:where(.menu-horizontal > li:not(.menu-title) > details > ul) {
+ border-radius: var(--rounded-box, 1rem);
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
+ --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.menu-vertical > li:not(.menu-title) > details > ul {
+ margin-inline-start: 1rem;
+ margin-top: 0px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ padding-inline-end: 0px;
+}
+
+.menu-xs .menu-title {
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+ padding-top: 0.25rem;
+ padding-bottom: 0.25rem;
+}
+
+.menu-sm :where(li:not(.menu-title) > *:not(ul, details, .menu-title)), .menu-sm :where(li:not(.menu-title) > details > summary:not(.menu-title)) {
+ border-radius: var(--rounded-btn, 0.5rem);
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+ padding-top: 0.25rem;
+ padding-bottom: 0.25rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+}
+
+.menu-sm .menu-title {
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+
+.menu-md :where(li:not(.menu-title) > *:not(ul, details, .menu-title)), .menu-md :where(li:not(.menu-title) > details > summary:not(.menu-title)) {
+ border-radius: var(--rounded-btn, 0.5rem);
+ padding-left: 1rem;
+ padding-right: 1rem;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+}
+
+.menu-md .menu-title {
+ padding-left: 1rem;
+ padding-right: 1rem;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+
+.menu-lg .menu-title {
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ padding-top: 0.75rem;
+ padding-bottom: 0.75rem;
+}
+
+.modal-top :where(.modal-box) {
+ width: 100%;
+ max-width: none;
+ --tw-translate-y: -2.5rem;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ border-bottom-right-radius: var(--rounded-box, 1rem);
+ border-bottom-left-radius: var(--rounded-box, 1rem);
+ border-top-left-radius: 0px;
+ border-top-right-radius: 0px;
+}
+
+.modal-middle :where(.modal-box) {
+ width: 91.666667%;
+ max-width: 32rem;
+ --tw-translate-y: 0px;
+ --tw-scale-x: .9;
+ --tw-scale-y: .9;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ border-top-left-radius: var(--rounded-box, 1rem);
+ border-top-right-radius: var(--rounded-box, 1rem);
+ border-bottom-right-radius: var(--rounded-box, 1rem);
+ border-bottom-left-radius: var(--rounded-box, 1rem);
+}
+
+.modal-bottom :where(.modal-box) {
+ width: 100%;
+ max-width: none;
+ --tw-translate-y: 2.5rem;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ border-top-left-radius: var(--rounded-box, 1rem);
+ border-top-right-radius: var(--rounded-box, 1rem);
+ border-bottom-right-radius: 0px;
+ border-bottom-left-radius: 0px;
+}
+
+.stats-horizontal > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-x-reverse: 0;
+ border-right-width: calc(1px * var(--tw-divide-x-reverse));
+ border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
+ --tw-divide-y-reverse: 0;
+ border-top-width: calc(0px * calc(1 - var(--tw-divide-y-reverse)));
+ border-bottom-width: calc(0px * var(--tw-divide-y-reverse));
+}
+
+.stats-horizontal {
+ overflow-x: auto;
+}
+
+.stats-horizontal:where([dir="rtl"], [dir="rtl"] *) {
+ --tw-divide-x-reverse: 1;
+}
+
+.alert.info {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-in,oklch(var(--in)/var(--tw-border-opacity)));
+ background-color: color-mix(in srgb, oklch(var(--in)), oklch(var(--b1)) 85%);
+}
+
+.alert.success {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-su,oklch(var(--su)/var(--tw-border-opacity)));
+ background-color: color-mix(in srgb, oklch(var(--su)), oklch(var(--b1)) 85%);
+}
+
+.alert.warning {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-wa,oklch(var(--wa)/var(--tw-border-opacity)));
+ background-color: color-mix(in srgb, oklch(var(--wa)), oklch(var(--b1)) 85%);
+}
+
+.alert.error {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-er,oklch(var(--er)/var(--tw-border-opacity)));
+ background-color: color-mix(in srgb, oklch(var(--er)), oklch(var(--b1)) 85%);
+}
+
+/*
+ * Example usage:
+ *
+ */
+
+.loading-box {
+ display: grid;
+ place-items: center;
+}
+
+/* Stacks direct children on top of each other */
+
+.loading-box > * {
+ grid-column-start: 1;
+ grid-row-start: 1;
+}
+
+/* Adds overlay */
+
+.loading-box:has(> .htmx-indicator.htmx-request) > *:not(.htmx-indicator) {
+ opacity: 0.3;
+}
+
+/* Disables mouse interactions */
+
+.loading-box:has(> .htmx-indicator.htmx-request) * {
+ pointer-events: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border-width: 0;
+}
+
+.visible {
+ visibility: visible;
+}
+
+.static {
+ position: static;
+}
+
+.z-\[1\] {
+ z-index: 1;
+}
+
+.m-4 {
+ margin: 1rem;
+}
+
+.my-0 {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.my-1 {
+ margin-top: 0.25rem;
+ margin-bottom: 0.25rem;
+}
+
+.-ml-2 {
+ margin-left: -0.5rem;
+}
+
+.ml-2 {
+ margin-left: 0.5rem;
+}
+
+.mt-0\.5 {
+ margin-top: 0.125rem;
+}
+
+.mt-3 {
+ margin-top: 0.75rem;
+}
+
+.block {
+ display: block;
+}
+
+.flex {
+ display: flex;
+}
+
+.table {
+ display: table;
+}
+
+.hidden {
+ display: none;
+}
+
+.size-10 {
+ width: 2.5rem;
+ height: 2.5rem;
+}
+
+.size-5 {
+ width: 1.25rem;
+ height: 1.25rem;
+}
+
+.h-14 {
+ height: 3.5rem;
+}
+
+.h-6 {
+ height: 1.5rem;
+}
+
+.h-auto {
+ height: auto;
+}
+
+.max-h-16 {
+ max-height: 4rem;
+}
+
+.max-h-64 {
+ max-height: 16rem;
+}
+
+.max-h-80 {
+ max-height: 20rem;
+}
+
+.max-h-svh {
+ max-height: 100svh;
+}
+
+.min-h-8 {
+ min-height: 2rem;
+}
+
+.min-h-fit {
+ min-height: -moz-fit-content;
+ min-height: fit-content;
+}
+
+.w-1\/2 {
+ width: 50%;
+}
+
+.w-1\/3 {
+ width: 33.333333%;
+}
+
+.w-2\/3 {
+ width: 66.666667%;
+}
+
+.w-24 {
+ width: 6rem;
+}
+
+.w-52 {
+ width: 13rem;
+}
+
+.w-6 {
+ width: 1.5rem;
+}
+
+.w-fit {
+ width: -moz-fit-content;
+ width: fit-content;
+}
+
+.w-full {
+ width: 100%;
+}
+
+.min-w-48 {
+ min-width: 12rem;
+}
+
+.min-w-fit {
+ min-width: -moz-fit-content;
+ min-width: fit-content;
+}
+
+.max-w-xs {
+ max-width: 20rem;
+}
+
+.flex-1 {
+ flex: 1 1 0%;
+}
+
+.flex-none {
+ flex: none;
+}
+
+.grow {
+ flex-grow: 1;
+}
+
+.border-separate {
+ border-collapse: separate;
+}
+
+.border-spacing-1 {
+ --tw-border-spacing-x: 0.25rem;
+ --tw-border-spacing-y: 0.25rem;
+ border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
+}
+
+.cursor-pointer {
+ cursor: pointer;
+}
+
+.appearance-none {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+.flex-row {
+ flex-direction: row;
+}
+
+.flex-col {
+ flex-direction: column;
+}
+
+.flex-wrap {
+ flex-wrap: wrap;
+}
+
+.flex-nowrap {
+ flex-wrap: nowrap;
+}
+
+.items-end {
+ align-items: flex-end;
+}
+
+.items-center {
+ align-items: center;
+}
+
+.justify-start {
+ justify-content: flex-start;
+}
+
+.justify-between {
+ justify-content: space-between;
+}
+
+.gap-0\.5 {
+ gap: 0.125rem;
+}
+
+.gap-2 {
+ gap: 0.5rem;
+}
+
+.gap-4 {
+ gap: 1rem;
+}
+
+.gap-6 {
+ gap: 1.5rem;
+}
+
+.space-y-2 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-y-reverse: 0;
+ margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
+ margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
+}
+
+.divide-y > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-y-reverse: 0;
+ border-top-width: calc(2px * calc(1 - var(--tw-divide-y-reverse)));
+ border-bottom-width: calc(2px * var(--tw-divide-y-reverse));
+}
+
+.divide-solid > :not([hidden]) ~ :not([hidden]) {
+ border-style: solid;
+}
+
+.divide-none > :not([hidden]) ~ :not([hidden]) {
+ border-style: none;
+}
+
+.overflow-x-auto {
+ overflow-x: auto;
+}
+
+.overflow-y-auto {
+ overflow-y: auto;
+}
+
+.overflow-y-scroll {
+ overflow-y: scroll;
+}
+
+.text-nowrap {
+ text-wrap: nowrap;
+}
+
+.rounded-box {
+ border-radius: var(--rounded-box, 1rem);
+}
+
+.rounded-full {
+ border-radius: 9999px;
+}
+
+.border {
+ border-width: 2px;
+}
+
+.border-2 {
+ border-width: 2px;
+}
+
+.border-3 {
+ border-width: 3px;
+}
+
+.border-b {
+ border-bottom-width: 2px;
+}
+
+.border-t {
+ border-top-width: 2px;
+}
+
+.border-none {
+ border-style: none;
+}
+
+.border-accent {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity)));
+}
+
+.border-primary {
+ --tw-border-opacity: 1;
+ border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));
+}
+
+.border-opacity-100 {
+ --tw-border-opacity: 1;
+}
+
+.bg-base-100 {
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
+}
+
+.bg-base-200 {
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
+}
+
+.bg-neutral {
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
+}
+
+.bg-transparent {
+ background-color: transparent;
+}
+
+.fill-accent {
+ fill: var(--fallback-a,oklch(var(--a)/1));
+}
+
+.object-scale-down {
+ -o-object-fit: scale-down;
+ object-fit: scale-down;
+}
+
+.p-1 {
+ padding: 0.25rem;
+}
+
+.p-2 {
+ padding: 0.5rem;
+}
+
+.p-6 {
+ padding: 1.5rem;
+}
+
+.px-1 {
+ padding-left: 0.25rem;
+ padding-right: 0.25rem;
+}
+
+.px-2 {
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+}
+
+.py-1\.5 {
+ padding-top: 0.375rem;
+ padding-bottom: 0.375rem;
+}
+
+.py-2 {
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+
+.text-start {
+ text-align: start;
+}
+
+.text-2xl {
+ font-size: 1.5rem;
+ line-height: 2rem;
+}
+
+.text-base {
+ font-size: 1rem;
+ line-height: 1.5rem;
+}
+
+.text-xl {
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+}
+
+.text-xs {
+ font-size: 0.75rem;
+ line-height: 1rem;
+}
+
+.font-bold {
+ font-weight: 700;
+}
+
+.font-medium {
+ font-weight: 500;
+}
+
+.capitalize {
+ text-transform: capitalize;
+}
+
+.italic {
+ font-style: italic;
+}
+
+.leading-9 {
+ line-height: 2.25rem;
+}
+
+.leading-none {
+ line-height: 1;
+}
+
+.leading-tight {
+ line-height: 1.25;
+}
+
+.text-base-content {
+ --tw-text-opacity: 1;
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
+}
+
+.text-base-content\/50 {
+ color: var(--fallback-bc,oklch(var(--bc)/0.5));
+}
+
+.text-info {
+ --tw-text-opacity: 1;
+ color: var(--fallback-in,oklch(var(--in)/var(--tw-text-opacity)));
+}
+
+.text-neutral-content {
+ --tw-text-opacity: 1;
+ color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
+}
+
+.text-neutral-content\/80 {
+ color: var(--fallback-nc,oklch(var(--nc)/0.8));
+}
+
+.text-primary {
+ --tw-text-opacity: 1;
+ color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)));
+}
+
+.text-warning {
+ --tw-text-opacity: 1;
+ color: var(--fallback-wa,oklch(var(--wa)/var(--tw-text-opacity)));
+}
+
+.antialiased {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.shadow {
+ --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.shadow-xl {
+ --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.ring {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+}
+
+.ring-inset {
+ --tw-ring-inset: inset;
+}
+
+.ring-base-200 {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-ring-opacity)));
+}
+
+.ring-gray-500\/50 {
+ --tw-ring-color: rgb(107 114 128 / 0.5);
+}
+
+.ring-offset-2 {
+ --tw-ring-offset-width: 2px;
+}
+
+.ring-offset-primary {
+ --tw-ring-offset-color: var(--fallback-p,oklch(var(--p)/1));
+}
+
+.filter {
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
+}
+
+.\[--tab-border-color\:theme\(colors\.primary\)\] {
+ --tab-border-color: var(--fallback-p,oklch(var(--p)/1));
+}
+
+.\[--tab-border\:theme\(borderWidth\.DEFAULT\)\] {
+ --tab-border: 2px;
+}
+
+/* use color-mix to give the illusion of transparency while not actually being transparent */
+
+.hover\:bg-gray-100\/10:hover {
+ background-color: rgb(243 244 246 / 0.1);
+}
+
+.hover\:ring-1:hover {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+}
+
+.focus\:outline-none:focus {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+}
+
+.focus\:ring-2:focus {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+}
+
+@media (min-width: 640px) {
+ .sm\:w-96 {
+ width: 24rem;
+ }
+}
diff --git a/merge/src/argus_htmx/tailwindtheme/README.md b/merge/src/argus_htmx/tailwindtheme/README.md
new file mode 100644
index 000000000..4a550f415
--- /dev/null
+++ b/merge/src/argus_htmx/tailwindtheme/README.md
@@ -0,0 +1,6 @@
+# Files for tailwindcss
+
+`styles.css` and `tailwind.config.js` are automatically generated by the `tailwind_config` django
+management command. The source for these files are in `src/argus_htmx/templates/tailwind/`
+
+The `snippets` contains additional css snippets that are included in the final `styles.css`
diff --git a/merge/src/argus_htmx/tailwindtheme/snippets/10-tailwind.css b/merge/src/argus_htmx/tailwindtheme/snippets/10-tailwind.css
new file mode 100644
index 000000000..b5c61c956
--- /dev/null
+++ b/merge/src/argus_htmx/tailwindtheme/snippets/10-tailwind.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/merge/src/argus_htmx/tailwindtheme/snippets/15-alert-message.css b/merge/src/argus_htmx/tailwindtheme/snippets/15-alert-message.css
new file mode 100644
index 000000000..d737fdace
--- /dev/null
+++ b/merge/src/argus_htmx/tailwindtheme/snippets/15-alert-message.css
@@ -0,0 +1,19 @@
+/* use color-mix to give the illusion of transparency while not actually being transparent */
+@layer components {
+ .alert.info {
+ @apply border-info;
+ background-color: color-mix(in srgb, oklch(var(--in)), oklch(var(--b1)) 85%);
+ }
+ .alert.success {
+ @apply border-success;
+ background-color: color-mix(in srgb, oklch(var(--su)), oklch(var(--b1)) 85%);
+ }
+ .alert.warning {
+ @apply border-warning;
+ background-color: color-mix(in srgb, oklch(var(--wa)), oklch(var(--b1)) 85%);
+ }
+ .alert.error {
+ @apply border-error;
+ background-color: color-mix(in srgb, oklch(var(--er)), oklch(var(--b1)) 85%);
+ }
+}
diff --git a/merge/src/argus_htmx/tailwindtheme/snippets/20-loading-box.css b/merge/src/argus_htmx/tailwindtheme/snippets/20-loading-box.css
new file mode 100644
index 000000000..0dd0d83ef
--- /dev/null
+++ b/merge/src/argus_htmx/tailwindtheme/snippets/20-loading-box.css
@@ -0,0 +1,29 @@
+@layer components {
+ /*
+ * Example usage:
+ *
+ */
+ .loading-box {
+ display: grid;
+ place-items: center;
+ }
+ /* Stacks direct children on top of each other */
+ .loading-box > * {
+ grid-column-start: 1;
+ grid-row-start: 1;
+ }
+ /* Adds overlay */
+ .loading-box:has(> .htmx-indicator.htmx-request) > *:not(.htmx-indicator) {
+ opacity: 0.3;
+ }
+ /* Disables mouse interactions */
+ .loading-box:has(> .htmx-indicator.htmx-request) * {
+ pointer-events: none;
+ user-select: none;
+ }
+}
diff --git a/merge/src/argus_htmx/tailwindtheme/styles.css b/merge/src/argus_htmx/tailwindtheme/styles.css
new file mode 100644
index 000000000..5597f7256
--- /dev/null
+++ b/merge/src/argus_htmx/tailwindtheme/styles.css
@@ -0,0 +1,3 @@
+@import 'snippets/10-tailwind.css';
+@import 'snippets/15-alert-message.css';
+@import 'snippets/20-loading-box.css';
diff --git a/merge/src/argus_htmx/tailwindtheme/tailwind.config.js b/merge/src/argus_htmx/tailwindtheme/tailwind.config.js
new file mode 100644
index 000000000..b97cf09f8
--- /dev/null
+++ b/merge/src/argus_htmx/tailwindtheme/tailwind.config.js
@@ -0,0 +1,61 @@
+const projectPaths = [
+ '../templates/**/*.html',
+ './**/templates/**/*.html',
+ 'src/argus_htmx/templates/**/*.html'
+];
+
+const contentPaths = [...projectPaths];
+console.log(`tailwindcss will scan ${contentPaths}`);
+
+module.exports = {
+ content: contentPaths,
+ theme: {
+ borderWidth: {
+ DEFAULT: '2px',
+ '0': '0',
+ '1': '1px',
+ '2': '2px',
+ '3': '3px',
+ '4': '4px',
+ '6': '6px',
+ '8': '8px',
+ },
+ ...{},
+ },
+ safelist: [
+ "htmx-request"
+ ],
+ daisyui: {
+ themes: [
+ "dark",
+ "light",
+ {
+ "argus": {
+ "primary": "#006d91",
+ "primary-content": "#d1e1e9",
+ "secondary": "#f3b61f",
+ "secondary-content": "#140c00",
+ "accent": "#c84700",
+ "accent-content": "#f8dbd1",
+ "neutral": "#006d91",
+ "neutral-content": "#d1e1e9",
+ "base-100": "#edfaff",
+ "base-200": "#ced9de",
+ "base-300": "#b0babd",
+ "base-content": "#141516",
+ "info": "#0073e5",
+ "info-content": "#000512",
+ "success": "#008700",
+ "success-content": "#d3e7d1",
+ "warning": "#ee4900",
+ "warning-content": "#140200",
+ "error": "#e5545a",
+ "error-content": "#120203"
+ }
+ }
+ ],
+ },
+ plugins: [
+ require("daisyui"),
+ ]
+}
diff --git a/merge/src/argus_htmx/templates/htmx/_base_form_modal.html b/merge/src/argus_htmx/templates/htmx/_base_form_modal.html
new file mode 100644
index 000000000..5098ac90e
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/_base_form_modal.html
@@ -0,0 +1,35 @@
+{{ button_title }}
+
+
+
+
diff --git a/merge/src/argus_htmx/templates/htmx/base.html b/merge/src/argus_htmx/templates/htmx/base.html
new file mode 100644
index 000000000..3b366edb3
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/base.html
@@ -0,0 +1,66 @@
+{% extends "htmx_base.html" %}
+{% load static %}
+{% block header %}
+
+
+
+ {% block logo %}
+
+
+
+ {% endblock logo %}
+
+
+
+
+
+ {% block userlink %}
+ {% if request.user.is_authenticated %}
+
+
+
+ {{ request.user|make_list|first }}
+
+
+
+
+ {% include "htmx/themes/_theme_dropdown.html" %}
+ {% include "htmx/dateformat/_dateformat_dropdown.html" %}
+
+ Preferences…
+
+
+
+
+
+
+ {% else %}
+
Log in
+ {% endif %}
+ {% endblock userlink %}
+
+
+
+{% endblock header %}
diff --git a/merge/src/argus_htmx/templates/htmx/dateformat/_dateformat_dropdown.html b/merge/src/argus_htmx/templates/htmx/dateformat/_dateformat_dropdown.html
new file mode 100644
index 000000000..a684199db
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/dateformat/_dateformat_dropdown.html
@@ -0,0 +1,14 @@
+
+
+ Date format
+ {{ preferences.argus_htmx.datetime_format_name }}
+
+
+
diff --git a/merge/src/argus_htmx/templates/htmx/dateformat/_dateformat_list.html b/merge/src/argus_htmx/templates/htmx/dateformat/_dateformat_list.html
new file mode 100644
index 000000000..7f850fddf
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/dateformat/_dateformat_list.html
@@ -0,0 +1,16 @@
+{% for item in datetime_formats %}
+
+
+
+{% empty %}
+
+ No date formats configured
+
+{% endfor %}
diff --git a/merge/src/argus_htmx/templates/htmx/forms/checkbox_select_multiple.html b/merge/src/argus_htmx/templates/htmx/forms/checkbox_select_multiple.html
new file mode 100644
index 000000000..36a57eb10
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/forms/checkbox_select_multiple.html
@@ -0,0 +1,12 @@
+{% if widget.wrap_label %}
+
+ {% endif %}
+
+ {% if widget.wrap_label %}
+ {{ widget.label }}
+
+{% endif %}
diff --git a/merge/src/argus_htmx/templates/htmx/forms/dropdown_select_multiple.html b/merge/src/argus_htmx/templates/htmx/forms/dropdown_select_multiple.html
new file mode 100644
index 000000000..747276902
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/forms/dropdown_select_multiple.html
@@ -0,0 +1,28 @@
+
+
+
+ {% block field_template %}
+ {% include "django/forms/widgets/multiple_input.html" %}
+ {% endblock field_template %}
+
+
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_base.html b/merge/src/argus_htmx/templates/htmx/incidents/_base.html
new file mode 100644
index 000000000..3247dee34
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_base.html
@@ -0,0 +1,2 @@
+{% extends "htmx/base.html" %}
+{% load django_htmx %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_base_incident_update_modal.html b/merge/src/argus_htmx/templates/htmx/incidents/_base_incident_update_modal.html
new file mode 100644
index 000000000..fa8833ebb
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_base_incident_update_modal.html
@@ -0,0 +1,9 @@
+{% extends "htmx/_base_form_modal.html" %}
+{% block form_control %}
+ hx-post="{% url endpoint|default:'htmx:incidents-update' action=action %}"
+ {% if action_type == "bulk-update" %}
+ hx-include="[name='incident_ids']"
+ {% else %}
+ hx-vals='{"incident_ids": [{{ incident.pk }}] }'
+ {% endif %}
+{% endblock form_control %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_ack.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ack.html
new file mode 100644
index 000000000..5c700e798
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ack.html
@@ -0,0 +1,5 @@
+{% if incident.acked %}
+ Acked
+{% else %}
+ Unacked
+{% endif %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_acknowledge_modal.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_acknowledge_modal.html
new file mode 100644
index 000000000..0bd5578c3
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_acknowledge_modal.html
@@ -0,0 +1,19 @@
+{% extends "htmx/incidents/_base_incident_update_modal.html" %}
+{% block dialogform %}
+
+ Message
+ *
+
+
+
+ Expiration
+
+
+{% endblock dialogform %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_actions.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_actions.html
new file mode 100644
index 000000000..5a607ff5b
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_actions.html
@@ -0,0 +1 @@
+X
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_checkbox.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_checkbox.html
new file mode 100644
index 000000000..7e425d503
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_checkbox.html
@@ -0,0 +1,9 @@
+Select all visible
+
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_close_modal.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_close_modal.html
new file mode 100644
index 000000000..f9964d168
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_close_modal.html
@@ -0,0 +1,10 @@
+{% extends "htmx/incidents/_base_incident_update_modal.html" %}
+{% block dialogform %}
+
+ Reason for closing
+
+
+{% endblock dialogform %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_combined_status.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_combined_status.html
new file mode 100644
index 000000000..dd416b15c
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_combined_status.html
@@ -0,0 +1,2 @@
+{% include "htmx/incidents/_incident_status.html" %}
+{% include "htmx/incidents/_incident_ack.html" %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_description.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_description.html
new file mode 100644
index 000000000..1cdd1d3cb
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_description.html
@@ -0,0 +1 @@
+{{ incident.description }}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_filterbox.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_filterbox.html
new file mode 100644
index 000000000..b3146e5a9
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_filterbox.html
@@ -0,0 +1,50 @@
+{% load widget_tweaks %}
+
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_level.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_level.html
new file mode 100644
index 000000000..4933e86dd
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_level.html
@@ -0,0 +1,2 @@
+{% load argus_htmx %}
+{{ incident.level }} - {{ incident.level|pp_level }}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_pk.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_pk.html
new file mode 100644
index 000000000..7cb82c735
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_pk.html
@@ -0,0 +1 @@
+{{ incident.pk }}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_reopen_modal.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_reopen_modal.html
new file mode 100644
index 000000000..96c71bbbf
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_reopen_modal.html
@@ -0,0 +1,10 @@
+{% extends "htmx/incidents/_base_incident_update_modal.html" %}
+{% block dialogform %}
+
+ Reason for reopening
+
+
+{% endblock dialogform %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_source.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_source.html
new file mode 100644
index 000000000..72e1a8492
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_source.html
@@ -0,0 +1 @@
+{{ incident.source.name }}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_source_select.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_source_select.html
new file mode 100644
index 000000000..84b718488
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_source_select.html
@@ -0,0 +1,8 @@
+{% extends "htmx/forms/dropdown_select_multiple.html" %}
+{% block field_control %}
+ {{ block.super }}
+ hx-get="{% url 'htmx:incidents-filter' %}"
+{% endblock field_control %}
+{% block show_selected %}
+ {{ option.label }}
+{% endblock show_selected %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_start_time.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_start_time.html
new file mode 100644
index 000000000..339965f7a
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_start_time.html
@@ -0,0 +1 @@
+{{ incident.start_time|date:preferences.argus_htmx.datetime_format }}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_start_time_header.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_start_time_header.html
new file mode 100644
index 000000000..5d4784821
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_start_time_header.html
@@ -0,0 +1 @@
+Timestamp
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_status.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_status.html
new file mode 100644
index 000000000..a8b6c7bc0
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_status.html
@@ -0,0 +1,5 @@
+{% if incident.open %}
+ Open
+{% else %}
+ Closed
+{% endif %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_table.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_table.html
new file mode 100644
index 000000000..f1e8c3442
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_table.html
@@ -0,0 +1,102 @@
+
+
+
+ {% block columns %}
+ {% for col in columns %}
+
+ {% if col.header_template %}
+ {% include col.header_template with label=col.label %}
+ {% else %}
+ {{ col.label }}
+ {% endif %}
+
+ {% empty %}
+ No columns configured
+ {% endfor %}
+ {% endblock columns %}
+
+
+
+ {% block incident_rows %}
+ {% include "htmx/incidents/_incident_table_rows.html" with incident_list=page.object_list %}
+ {% endblock incident_rows %}
+
+
+
+
+
+ {% block refresh_info %}
+ {% include "htmx/incidents/_incidents_refresh_info.html" %}
+ {% endblock refresh_info %}
+
+
+ {% if page.number != 1 %}
+
+
+ « First
+
+ {% endif %}
+ {% if page.has_previous %}
+
+ {{ page.previous_page_number }}
+
+ {% endif %}
+ {% if page.paginator.num_pages > 1 %}
+
+ {{ page.number }}
+
+ {% else %}
+
+ {{ page.number }}
+
+ {% endif %}
+ {% if page.has_next %}
+
+ {{ page.next_page_number }}
+
+ {% endif %}
+ {% if page.number != page.paginator.num_pages %}
+
+ » Last
+
+ {% endif %}
+
+
+
+
+
+
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_table_row.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_table_row.html
new file mode 100644
index 000000000..366395466
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_table_row.html
@@ -0,0 +1,13 @@
+
+ {% block columns %}
+ {% for col in columns %}
+
+ {% if col.cell_template %}
+ {% include col.cell_template with column=col %}
+ {% else %}
+ UNDEFINED
+ {% endif %}
+
+ {% endfor %}
+ {% endblock columns %}
+
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_table_rows.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_table_rows.html
new file mode 100644
index 000000000..792b0f298
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_table_rows.html
@@ -0,0 +1,15 @@
+{% for incident in incident_list %}
+ {% block incident %}
+ {% include "htmx/incidents/_incident_table_row.html" %}
+ {% endblock incident %}
+{% empty %}
+
+
+ {% if count %}
+ No incidents found that fit all the filter criteria
+ {% else %}
+ No incidents on record
+ {% endif %}
+
+
+{% endfor %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_tag.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_tag.html
new file mode 100644
index 000000000..da916fc51
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_tag.html
@@ -0,0 +1,2 @@
+{% load argus_htmx %}
+{{ incident|tagvalues:column.context.tag|first }}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket.html
new file mode 100644
index 000000000..a50190274
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket.html
@@ -0,0 +1,11 @@
+{% if incident.ticket_url %}
+
+
+
+
+
+
+{% endif %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_autocreate_modal.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_autocreate_modal.html
new file mode 100644
index 000000000..126094a81
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_autocreate_modal.html
@@ -0,0 +1 @@
+{% extends "htmx/incidents/_base_incident_update_modal.html" %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_edit_modal.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_edit_modal.html
new file mode 100644
index 000000000..e6787a56c
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_edit_modal.html
@@ -0,0 +1,10 @@
+{% extends "htmx/incidents/_base_incident_update_modal.html" %}
+{% block dialogform %}
+
+ Change ticket
+
+
+{% endblock dialogform %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_manual_create_modal.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_manual_create_modal.html
new file mode 100644
index 000000000..d2155c45c
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_manual_create_modal.html
@@ -0,0 +1,10 @@
+{% extends "htmx/incidents/_base_incident_update_modal.html" %}
+{% block dialogform %}
+
+ Ticket url
+
+
+{% endblock dialogform %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incidents_menubar.html b/merge/src/argus_htmx/templates/htmx/incidents/_incidents_menubar.html
new file mode 100644
index 000000000..1a0132f07
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incidents_menubar.html
@@ -0,0 +1,23 @@
+
+ {% block menu_tabs %}
+
+
+ {% include "htmx/incidents/_incident_filterbox.html" %}
+
+
+
+ {% include "htmx/incidents/_incidents_update_menu.html" %}
+
+ {% endblock menu_tabs %}
+
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incidents_refresh_info.html b/merge/src/argus_htmx/templates/htmx/incidents/_incidents_refresh_info.html
new file mode 100644
index 000000000..d6f7a8cc3
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incidents_refresh_info.html
@@ -0,0 +1,46 @@
+
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incidents_update_menu.html b/merge/src/argus_htmx/templates/htmx/incidents/_incidents_update_menu.html
new file mode 100644
index 000000000..2dbaed15f
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_incidents_update_menu.html
@@ -0,0 +1,10 @@
+
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_selected_incidents_header.html b/merge/src/argus_htmx/templates/htmx/incidents/_selected_incidents_header.html
new file mode 100644
index 000000000..c6e07094e
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/_selected_incidents_header.html
@@ -0,0 +1,8 @@
+{{ label }}
+Select all visible
+
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/incident_detail.html b/merge/src/argus_htmx/templates/htmx/incidents/incident_detail.html
new file mode 100644
index 000000000..64d4c627e
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/incident_detail.html
@@ -0,0 +1,123 @@
+{% extends "htmx/base.html" %}
+{% block main %}
+
+ {% block incident_detail %}
+
{{ incident.pk }}: {{ incident.description }}
+
+
+
+ Level
+ {{ incident.level }}
+
+
+ Status
+
+ {% if incident.open %}
+ Open
+ {% else %}
+ Closed
+ {% endif %}
+ {% if incident.acked %}
+ Acknowledged
+ {% else %}
+ Unacknowledged
+ {% endif %}
+ {% if incident.ticket_url %}
+ Ticket {{ incident.ticket_url }}
+ {% else %}
+ No ticket
+ {% endif %}
+
+
+
+
+ Primary details (#{{ incident.pk }})
+
+
Description
+
{{ incident.description }}
+
Start time
+
{{ incident.start_time|date:preferences.argus_htmx.datetime_format }}
+
Duration
+
{{ incident.end_time|date:preferences.argus_htmx.datetime_format }}
+
Source
+
{{ incident.source.name }}
+
Incident id in {{ incident.source.name }}
+
{{ incident.source_incident_id }}
+
More details at
+
+ {% if incident.details_url %}
+ {{ incident.details_url }}
+ {% else %}
+ —
+ {% endif %}
+
+
Ticket
+
+ {% if incident.ticket_url %}
+ {{ incident.ticket_url }}
+ {% else %}
+ —
+ {% endif %}
+
+
+ {% if incident.stateful %}
+ {% if incident.open %}
+ {% include "htmx/incidents/_incident_close_modal.html" with action="close" dialog_id="close-incident-dialog" button_title="Close" header="Manually close incident" explanation="Write a message describing why the incident was manually closed" cancel_text="Cancel" submit_text="Close now" %}
+ {% else %}
+ {% include "htmx/incidents/_incident_reopen_modal.html" with action="reopen" dialog_id="reopen-incident-dialog" button_title="Reopen" header="Manually reopen incident" explanation="Write a message describing why the incident was manually reopend" cancel_text="Cancel" submit_text="Reopen now" %}
+ {% endif %}
+ {% endif %}
+ {% if incident.ticket_url %}
+ {% include "htmx/incidents/_incident_ticket_edit_modal.html" with action="update-ticket" dialog_id="edit-ticket-dialog" button_title="Edit ticket url" header="Edit ticket" explanation="" cancel_text="Cancel" submit_text="Update" %}
+ {% else %}
+
+ {% include "htmx/incidents/_incident_ticket_edit_modal.html" with action="add-ticket" dialog_id="manual-create-ticket-dialog" button_title="Add ticket url" header="Add url to existing ticket" explanation="Are you sure you want to store this url to an existing ticket on this incident?" cancel_text="Cancel" submit_text="Add ticket" %}
+ {% endif %}
+
+
+
+
+
+ Acknowledgements
+
+ {% for ack in incident.acks %}
+
+
{{ ack.event.description }}
+
+ {{ ack.event.actor }}
+ {{ ack.event.timestamp|date:preferences.argus_htmx.datetime_format }}
+
+ {% if ack.expiration %}
Expires: {{ ack.expiration|date:preferences.argus_htmx.datetime_format }}
{% endif %}
+
+ {% endfor %}
+
+ {% include "htmx/incidents/_incident_acknowledge_modal.html" with action="ack" dialog_id="create-acknowledgment-dialog" button_title="Create acknowledgment" header="Submit acknowledgment" explanation="Write a message describing why this incident was acknowledged" cancel_text="Cancel" submit_text="Submit" %}
+
+
+
+
+ Related events
+
+ {% for event in incident.events.all %}
+ {% if not event.ack %}
+
+
{{ event.get_type_display }}
+
{{ event.description }}
+
+ {{ event.actor }}
+ {{ event.timestamp|date:preferences.argus_htmx.datetime_format }}
+
+
+ {% endif %}
+ {% endfor %}
+
+
+
+ {% endblock incident_detail %}
+
+{% endblock main %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/incident_list.html b/merge/src/argus_htmx/templates/htmx/incidents/incident_list.html
new file mode 100644
index 000000000..2e2e708df
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/incident_list.html
@@ -0,0 +1,19 @@
+{% extends base %}
+{% block main %}
+
+ {% if incidents_extra_widget %}
+
+
{% include "htmx/incidents/_incidents_menubar.html" %}
+
{% include incidents_extra_widget %}
+
+ {% else %}
+ {% include "htmx/incidents/_incidents_menubar.html" %}
+ {% endif %}
+
+
+ {% block table %}
+ {% include "htmx/incidents/_incident_table.html" %}
+ {% endblock table %}
+
+
+{% endblock main %}
diff --git a/merge/src/argus_htmx/templates/htmx/incidents/responses/_incidents_table_refresh.html b/merge/src/argus_htmx/templates/htmx/incidents/responses/_incidents_table_refresh.html
new file mode 100644
index 000000000..7dded7ba9
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/incidents/responses/_incidents_table_refresh.html
@@ -0,0 +1,2 @@
+{% block table %}
+{% endblock table %}
diff --git a/merge/src/argus_htmx/templates/htmx/page_size/_page_size_dropdown.html b/merge/src/argus_htmx/templates/htmx/page_size/_page_size_dropdown.html
new file mode 100644
index 000000000..2bc1d20ab
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/page_size/_page_size_dropdown.html
@@ -0,0 +1,14 @@
+
+
+ Page size
+ {{ preferences.argus_htmx.page_size }}
+
+
+
diff --git a/merge/src/argus_htmx/templates/htmx/page_size/_page_size_list.html b/merge/src/argus_htmx/templates/htmx/page_size/_page_size_list.html
new file mode 100644
index 000000000..7b7cee174
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/page_size/_page_size_list.html
@@ -0,0 +1,16 @@
+{% for item in page_sizes %}
+
+
+
+{% empty %}
+
+ No page sizes configured
+
+{% endfor %}
diff --git a/merge/src/argus_htmx/templates/htmx/themes/_current_theme.html b/merge/src/argus_htmx/templates/htmx/themes/_current_theme.html
new file mode 100644
index 000000000..a1cbeb260
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/themes/_current_theme.html
@@ -0,0 +1 @@
+{{ preferences.argus_htmx.theme }}
diff --git a/merge/src/argus_htmx/templates/htmx/themes/_theme_dropdown.html b/merge/src/argus_htmx/templates/htmx/themes/_theme_dropdown.html
new file mode 100644
index 000000000..6f5c4c8a3
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/themes/_theme_dropdown.html
@@ -0,0 +1,14 @@
+
+
+ Theme
+ {% include "./_current_theme.html" %}
+
+
+
diff --git a/merge/src/argus_htmx/templates/htmx/themes/_theme_list.html b/merge/src/argus_htmx/templates/htmx/themes/_theme_list.html
new file mode 100644
index 000000000..f81e79f72
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/themes/_theme_list.html
@@ -0,0 +1,18 @@
+{% for item in theme_list %}
+
+
+
+{% empty %}
+
+ empty
+
+{% endfor %}
diff --git a/merge/src/argus_htmx/templates/htmx/user/preferences.html b/merge/src/argus_htmx/templates/htmx/user/preferences.html
new file mode 100644
index 000000000..c81ebec63
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx/user/preferences.html
@@ -0,0 +1,18 @@
+{% extends "htmx/base.html" %}
+{% block main %}
+
+
{{ page_title }}
+
+ User preferences
+
+
+ {% include "htmx/themes/_theme_dropdown.html" %}
+ {% include "htmx/dateformat/_dateformat_dropdown.html" %}
+ {% include "htmx/page_size/_page_size_dropdown.html" %}
+
+
+
+{% endblock main %}
diff --git a/merge/src/argus_htmx/templates/htmx_base.html b/merge/src/argus_htmx/templates/htmx_base.html
new file mode 100644
index 000000000..54cbd6acb
--- /dev/null
+++ b/merge/src/argus_htmx/templates/htmx_base.html
@@ -0,0 +1,41 @@
+
+{% load static %}
+
+
+
+
+
+
+
+ {% block title %}
+ Argus Server: {{ page_title }}
+ {% endblock title %}
+
+
+
+
+ {% block head %}
+ {% endblock head %}
+
+
+ {% block header %}
+ Argus Server: {{ page_title }}
+ {% endblock header %}
+
+ {% block main %}
+ {% endblock main %}
+
+ {% block footer %}
+ {% endblock footer %}
+ {% block tail %}
+ {% endblock tail %}
+ {% block messages %}
+ {% include "messages/_notification_messages.html" %}
+ {% endblock messages %}
+
+
diff --git a/merge/src/argus_htmx/templates/messages/_notification_message.html b/merge/src/argus_htmx/templates/messages/_notification_message.html
new file mode 100644
index 000000000..ae50a8bbc
--- /dev/null
+++ b/merge/src/argus_htmx/templates/messages/_notification_message.html
@@ -0,0 +1,17 @@
+
diff --git a/merge/src/argus_htmx/templates/messages/_notification_messages.html b/merge/src/argus_htmx/templates/messages/_notification_messages.html
new file mode 100644
index 000000000..02987c00e
--- /dev/null
+++ b/merge/src/argus_htmx/templates/messages/_notification_messages.html
@@ -0,0 +1,7 @@
+
+
+ {% for message in messages %}
+ {% include "./_notification_message.html" %}
+ {% endfor %}
+
+
diff --git a/merge/src/argus_htmx/templates/messages/_notification_messages_htmx_append.html b/merge/src/argus_htmx/templates/messages/_notification_messages_htmx_append.html
new file mode 100644
index 000000000..2c1b6805b
--- /dev/null
+++ b/merge/src/argus_htmx/templates/messages/_notification_messages_htmx_append.html
@@ -0,0 +1,5 @@
+
+ {% for message in messages %}
+ {% include "./_notification_message.html" %}
+ {% endfor %}
+
diff --git a/merge/src/argus_htmx/templates/registration/login.html b/merge/src/argus_htmx/templates/registration/login.html
new file mode 100644
index 000000000..4c89664b8
--- /dev/null
+++ b/merge/src/argus_htmx/templates/registration/login.html
@@ -0,0 +1,42 @@
+{% extends "htmx/base.html" %}
+{% load widget_tweaks %}
+{% block main %}
+
+
+
Log In
+
+ {% if "local" in backends %}
+
+ {% endif %}
+ {% if backends.external %}
+ Login with
+ {% for backend in backends.external %}
+ {{ backend.display_name }}
+ {% endfor %}
+ {% endif %}
+
+
+
+ {% for message in messages %}
+
{{ message }}
+ {% endfor %}
+
+
+{% endblock main %}
+{% comment %} disable default toast messages {% endcomment %}
+{% block messages %}
+{% endblock messages %}
diff --git a/merge/src/argus_htmx/templates/tailwind/styles.css b/merge/src/argus_htmx/templates/tailwind/styles.css
new file mode 100644
index 000000000..d26ee7786
--- /dev/null
+++ b/merge/src/argus_htmx/templates/tailwind/styles.css
@@ -0,0 +1,2 @@
+{% for cssfile in cssfiles %}@import '{{ cssfile }}';
+{% endfor %}
diff --git a/merge/src/argus_htmx/templates/tailwind/tailwind.config.js b/merge/src/argus_htmx/templates/tailwind/tailwind.config.js
new file mode 100644
index 000000000..68f4c9cb4
--- /dev/null
+++ b/merge/src/argus_htmx/templates/tailwind/tailwind.config.js
@@ -0,0 +1,34 @@
+const projectPaths = [
+ '../templates/**/*.html',
+ './**/templates/**/*.html',
+ 'src/argus_htmx/templates/**/*.html'
+];
+
+const contentPaths = [...projectPaths];
+console.log(`tailwindcss will scan ${contentPaths}`);
+
+module.exports = {
+ content: contentPaths,
+ theme: {
+ borderWidth: {
+ DEFAULT: '2px',
+ '0': '0',
+ '1': '1px',
+ '2': '2px',
+ '3': '3px',
+ '4': '4px',
+ '6': '6px',
+ '8': '8px',
+ },
+ ...{{ themeoverride }},
+ },
+ safelist: [
+ "htmx-request"
+ ],
+ daisyui: {
+ themes: {{ daisyuithemes }},
+ },
+ plugins: [
+ require("daisyui"),
+ ]
+}
diff --git a/merge/src/argus_htmx/templatetags/__init__.py b/merge/src/argus_htmx/templatetags/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/merge/src/argus_htmx/templatetags/argus_htmx.py b/merge/src/argus_htmx/templatetags/argus_htmx.py
new file mode 100644
index 000000000..96a7f0618
--- /dev/null
+++ b/merge/src/argus_htmx/templatetags/argus_htmx.py
@@ -0,0 +1,34 @@
+from django import template
+
+
+register = template.Library()
+
+
+@register.filter
+def tagvalues(incident, key) -> list:
+ """Return values of tags with key KEY for incident INCIDENT
+
+ There can be multiple tags with the same key
+ """
+ tags = incident.deprecated_tags
+ return [str(tag.value) for tag in tags if tag.key == key]
+
+
+@register.filter
+def is_acked_by(incident, group: str) -> bool:
+ return incident.is_acked_by(group)
+
+
+@register.filter
+def pp_level(level: int) -> str:
+ level = str(level)
+ mapping = {
+ "1": "Critical",
+ "2": "High",
+ "3": "Moderate",
+ "4": "Low",
+ "5": "Information",
+ }
+ if level not in mapping:
+ return mapping["5"]
+ return mapping[level]
diff --git a/merge/src/argus_htmx/themes/__init__.py b/merge/src/argus_htmx/themes/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/merge/src/argus_htmx/themes/constants.py b/merge/src/argus_htmx/themes/constants.py
new file mode 100644
index 000000000..46f1a72e8
--- /dev/null
+++ b/merge/src/argus_htmx/themes/constants.py
@@ -0,0 +1,13 @@
+from argus_htmx.themes.utils import get_theme_default, get_theme_names
+
+
+__all__ = [
+ "THEME_CHOICES",
+ "THEME_DEFAULT",
+ "THEME_NAMES",
+]
+
+
+THEME_NAMES = sorted(get_theme_names())
+THEME_CHOICES = tuple((theme, theme) for theme in THEME_NAMES)
+THEME_DEFAULT = get_theme_default()
diff --git a/merge/src/argus_htmx/themes/urls.py b/merge/src/argus_htmx/themes/urls.py
new file mode 100644
index 000000000..267667b8b
--- /dev/null
+++ b/merge/src/argus_htmx/themes/urls.py
@@ -0,0 +1,10 @@
+from django.urls import path
+
+from . import views
+
+
+app_name = "htmx"
+urlpatterns = [
+ path("names/", views.theme_names, name="theme-names"),
+ path("change/", views.change_theme, name="change-theme"),
+]
diff --git a/merge/src/argus_htmx/themes/utils.py b/merge/src/argus_htmx/themes/utils.py
new file mode 100644
index 000000000..2cecbdf2d
--- /dev/null
+++ b/merge/src/argus_htmx/themes/utils.py
@@ -0,0 +1,68 @@
+import logging
+from pathlib import Path
+from re import findall
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.contrib.staticfiles.finders import find
+
+from argus_htmx import settings as fallbacks
+
+
+__all__ = [
+ "get_raw_themes_setting",
+ "get_theme_names",
+ "get_theme_default",
+]
+
+
+LOG = logging.getLogger(__name__)
+
+
+def get_raw_themes_setting():
+ return getattr(settings, "DAISYUI_THEMES", fallbacks.DAISYUI_THEMES)
+
+
+def get_themes_from_setting():
+ themes_setting = get_raw_themes_setting()
+ theme_names = []
+ for theme in themes_setting:
+ if isinstance(theme, str):
+ theme_names.append(theme)
+ elif isinstance(theme, dict):
+ theme_names.extend(theme.keys())
+ return theme_names
+
+
+def get_stylesheet_path():
+ return getattr(settings, "STYLESHEET_PATH", fallbacks.STYLESHEET_PATH)
+
+
+def get_themes_from_css():
+ THEME_NAME_RE = "(?P[-_\w]+)"
+ DATA_THEME_RE = f"\[data-theme={THEME_NAME_RE}\]"
+
+ absolute_stylesheet_path = Path(find(get_stylesheet_path()))
+ styles_css = absolute_stylesheet_path.read_text()
+
+ return findall(DATA_THEME_RE, styles_css)
+
+
+def get_theme_names(quiet=True):
+ ERROR_MSG = "Themes in settings are out of sync with themes installed"
+
+ themes_from_setting = set(get_themes_from_setting())
+ themes_from_css = set(get_themes_from_css())
+ installed_themes = themes_from_setting & themes_from_css
+
+ all_themes = themes_from_setting | themes_from_css
+ if all_themes != installed_themes:
+ LOG.warning(ERROR_MSG)
+ if not quiet:
+ raise ImproperlyConfigured(ERROR_MSG)
+
+ return installed_themes
+
+
+def get_theme_default():
+ return getattr(settings, "THEME_DEFAULT", fallbacks.THEME_DEFAULT)
diff --git a/merge/src/argus_htmx/themes/views.py b/merge/src/argus_htmx/themes/views.py
new file mode 100644
index 000000000..1355b1c74
--- /dev/null
+++ b/merge/src/argus_htmx/themes/views.py
@@ -0,0 +1,29 @@
+import logging
+
+from django.shortcuts import render
+
+from django.views.decorators.http import require_GET, require_POST
+from django.http import HttpResponse
+from django_htmx.http import HttpResponseClientRefresh
+
+from argus.auth.utils import save_preference
+
+from argus_htmx.constants import THEME_NAMES
+from argus_htmx.incidents.views import HtmxHttpRequest
+
+LOG = logging.getLogger(__name__)
+THEMES_MODULE = "argus_htmx"
+
+
+@require_GET
+def theme_names(request: HtmxHttpRequest) -> HttpResponse:
+ themes = THEME_NAMES
+ return render(request, "htmx/themes/_theme_list.html", {"theme_list": themes})
+
+
+@require_POST
+def change_theme(request: HtmxHttpRequest) -> HttpResponse:
+ success = save_preference(request, request.POST, "argus_htmx", "theme")
+ if success:
+ return render(request, "htmx/themes/_current_theme.html")
+ return HttpResponseClientRefresh()
diff --git a/merge/src/argus_htmx/timeslots/__init__.py b/merge/src/argus_htmx/timeslots/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/merge/src/argus_htmx/timeslots/urls.py b/merge/src/argus_htmx/timeslots/urls.py
new file mode 100644
index 000000000..efbfffc31
--- /dev/null
+++ b/merge/src/argus_htmx/timeslots/urls.py
@@ -0,0 +1,21 @@
+from django.http import HttpResponse
+from django.template import Template, RequestContext
+from django.urls import path
+
+
+def placeholder(request):
+ template = Template(
+ """{% extends "htmx/base.html" %}
+ {% block main %}
+ TIMESLOT PLACEHOLDER
+ {% endblock main %}
+ """
+ )
+ context = RequestContext(request)
+ return HttpResponse(template.render(context))
+
+
+app_name = "htmx"
+urlpatterns = [
+ path("", placeholder, name="timeslot-placeholder"),
+]
diff --git a/merge/src/argus_htmx/urls.py b/merge/src/argus_htmx/urls.py
new file mode 100644
index 000000000..3b0e3715a
--- /dev/null
+++ b/merge/src/argus_htmx/urls.py
@@ -0,0 +1,25 @@
+from django.contrib.auth import views as django_auth_views
+from django.urls import path, include
+
+from .auth import views as auth_views
+from .incidents.urls import urlpatterns as incident_urls
+from .timeslots.urls import urlpatterns as timeslot_urls
+from .notificationprofiles.urls import urlpatterns as notificationprofile_urls
+from .destinations.urls import urlpatterns as destination_urls
+from .themes.urls import urlpatterns as theme_urls
+from .dateformat.urls import urlpatterns as dateformat_urls
+from .user.urls import urlpatterns as user_urls
+
+app_name = "htmx"
+urlpatterns = [
+ path("accounts/login/", auth_views.LoginView.as_view(), name="login"),
+ path("accounts/logout/", django_auth_views.LogoutView.as_view(), name="logout"),
+ # path("accounts/", include("django.contrib.auth.urls")),
+ path("incidents/", include(incident_urls)),
+ path("timeslots/", include(timeslot_urls)),
+ path("notificationprofiles/", include(notificationprofile_urls)),
+ path("destinations/", include(destination_urls)),
+ path("themes/", include(theme_urls)),
+ path("dateformat/", include(dateformat_urls)),
+ path("user/", include(user_urls)),
+]
diff --git a/merge/src/argus_htmx/user/__init__.py b/merge/src/argus_htmx/user/__init__.py
new file mode 100644
index 000000000..2f037b505
--- /dev/null
+++ b/merge/src/argus_htmx/user/__init__.py
@@ -0,0 +1 @@
+"""User preferences pages"""
diff --git a/merge/src/argus_htmx/user/preferences/__init__.py b/merge/src/argus_htmx/user/preferences/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/merge/src/argus_htmx/user/preferences/models.py b/merge/src/argus_htmx/user/preferences/models.py
new file mode 100644
index 000000000..b87f9b3fb
--- /dev/null
+++ b/merge/src/argus_htmx/user/preferences/models.py
@@ -0,0 +1,44 @@
+from django import forms
+
+from argus.auth.models import preferences
+
+from argus_htmx.constants import (
+ DATETIME_FORMATS,
+ DATETIME_DEFAULT,
+ DATETIME_CHOICES,
+ PAGE_SIZE_CHOICES,
+ DEFAULT_PAGE_SIZE,
+ THEME_CHOICES,
+ THEME_DEFAULT,
+)
+
+
+class DateTimeFormatForm(forms.Form):
+ datetime_format_name = forms.ChoiceField(required=False, choices=DATETIME_CHOICES)
+
+
+class PageSizeForm(forms.Form):
+ page_size = forms.TypedChoiceField(required=False, choices=PAGE_SIZE_CHOICES, coerce=int)
+
+
+class ThemeForm(forms.Form):
+ theme = forms.ChoiceField(choices=THEME_CHOICES)
+
+
+@preferences(namespace="argus_htmx")
+class ArgusHtmxPreferences:
+ FORMS = {
+ "datetime_format_name": DateTimeFormatForm,
+ "page_size": PageSizeForm,
+ "theme": ThemeForm,
+ }
+ _FIELD_DEFAULTS = {
+ "datetime_format_name": DATETIME_DEFAULT,
+ "page_size": DEFAULT_PAGE_SIZE,
+ "theme": THEME_DEFAULT,
+ }
+
+ def update_context(self, context):
+ datetime_format_name = context.get("datetime_format_name", DATETIME_DEFAULT)
+ datetime_format = DATETIME_FORMATS[datetime_format_name]
+ return {"datetime_format": datetime_format}
diff --git a/merge/src/argus_htmx/user/urls.py b/merge/src/argus_htmx/user/urls.py
new file mode 100644
index 000000000..15b8667b8
--- /dev/null
+++ b/merge/src/argus_htmx/user/urls.py
@@ -0,0 +1,11 @@
+from django.urls import path
+
+from . import views
+
+
+app_name = "htmx"
+urlpatterns = [
+ path("", views.user_preferences, name="user-preferences"),
+ path("page_size/names/", views.page_size_names, name="page-sizes"),
+ path("page_size/change/", views.change_page_size, name="change-page-size"),
+]
diff --git a/merge/src/argus_htmx/user/views.py b/merge/src/argus_htmx/user/views.py
new file mode 100644
index 000000000..135c2683b
--- /dev/null
+++ b/merge/src/argus_htmx/user/views.py
@@ -0,0 +1,29 @@
+from django.http import HttpResponse
+from django.shortcuts import render
+from django.views.decorators.http import require_GET, require_POST
+from django_htmx.http import HttpResponseClientRefresh
+
+from argus.auth.utils import save_preference
+
+from argus_htmx.constants import ALLOWED_PAGE_SIZES
+from argus_htmx.incidents.views import HtmxHttpRequest
+
+
+@require_GET
+def page_size_names(request: HtmxHttpRequest) -> HttpResponse:
+ page_sizes = sorted(ALLOWED_PAGE_SIZES)
+ return render(request, "htmx/page_size/_page_size_list.html", {"page_sizes": page_sizes})
+
+
+@require_POST
+def change_page_size(request: HtmxHttpRequest) -> HttpResponse:
+ save_preference(request, request.POST, "argus_htmx", "page_size")
+ return HttpResponseClientRefresh()
+
+
+def user_preferences(request) -> HttpResponse:
+ """Renders the main preferences page for a user"""
+ context = {
+ "page_title": "User preferences",
+ }
+ return render(request, "htmx/user/preferences.html", context=context)
diff --git a/merge/src/argus_htmx/utils.py b/merge/src/argus_htmx/utils.py
new file mode 100644
index 000000000..3d94f24ba
--- /dev/null
+++ b/merge/src/argus_htmx/utils.py
@@ -0,0 +1,87 @@
+# Should be in argus proper and merged with api bulk actions
+#
+# THIS IS LESS DRY-ABLE THAN YOU (or SonarCloud) THINK(S)
+#
+# Uses composition, see bulk_change_incidents()
+
+from typing import List, Dict, Any
+
+from django.utils import timezone
+
+from argus.incident.models import Incident
+from argus.util.signals import bulk_changed
+
+
+def send_changed_incidents(incidents):
+ bulk_changed.send(sender=Incident, instances=incidents)
+
+
+def get_qs_for_incident_ids(incident_ids: List[int], qs=None):
+ # setup
+ if qs is None:
+ qs = Incident.objects.all()
+ qs = qs.filter(pk__in=incident_ids)
+ found_ids = set(qs.values_list("id", flat=True))
+
+ # wash ids
+ missing_ids = set(incident_ids) - found_ids
+ return qs, missing_ids
+
+
+def bulk_ack_queryset(actor, qs, data: Dict[str, Any]):
+ timestamp = data["timestamp"]
+ description = data.get("description", "")
+ expiration = data.get("expiration", None)
+ acks = qs.create_acks(actor, timestamp, description, expiration)
+ incidents = []
+ for ack in acks:
+ incidents.append(ack.event.incident)
+ return incidents
+
+
+def bulk_close_queryset(actor, qs, data: Dict[str, Any]):
+ timestamp = data["timestamp"]
+ description = data.get("description", "")
+ events = qs.close(actor, timestamp, description)
+ incidents = []
+ for event in events:
+ incidents.append(event.incident)
+ return incidents
+
+
+def bulk_reopen_queryset(actor, qs, data: Dict[str, Any]):
+ timestamp = data["timestamp"]
+ description = data.get("description", "")
+ events = qs.reopen(actor, timestamp, description)
+ incidents = []
+ for event in events:
+ incidents.append(event.incident)
+ return incidents
+
+
+def bulk_change_ticket_url_queryset(actor, qs, data: Dict[str, Any]):
+ timestamp = data["timestamp"]
+ ticket_url = data.get("ticket_url", "")
+ return qs.update_ticket_url(actor, ticket_url, timestamp=timestamp)
+
+
+def bulk_change_incidents(actor, incident_ids: List[int], data: Dict[str, Any], func, qs=None):
+ """
+ Update incidents in bulk
+
+ Applies ``func`` to the incidents in ``incident_ids`` with the
+ pre-validated bulk-type dependent key-value pairs in ``data``. Blames it on
+ the user ``actor``. Adds ``timestamp`` to ``data`` if it is not already present.
+
+ Returns a queryset of the changed incidents and a list of incident ids that
+ could not be changed due to for instance
+
+ - The incidents have been deleted in the meantime
+ - We're working on a subset of incidents and the ids are not in that subset
+ """
+ qs, missing_ids = get_qs_for_incident_ids(incident_ids, qs)
+ if not data.get("timestamp"):
+ data["timestamp"] = timezone.now()
+ incidents = func(actor, qs, data)
+ send_changed_incidents(incidents)
+ return incidents, missing_ids
diff --git a/merge/tests/__init__.py b/merge/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/merge/tests/settings.py b/merge/tests/settings.py
new file mode 100644
index 000000000..5d68b97be
--- /dev/null
+++ b/merge/tests/settings.py
@@ -0,0 +1,14 @@
+SECRET_KEY = "fake-key"
+DATABASES = {
+ "default": {
+ "ENGINE": "django.db.backends.sqlite3",
+ "NAME": ":memory:",
+ }
+}
+INSTALLED_APPS = [
+ "django.contrib.auth",
+ "django.contrib.contenttypes",
+ # "argus.auth",
+ "tests",
+]
+ROOT_URLCONF = "tests.urls"
diff --git a/merge/tests/test_middleware.py b/merge/tests/test_middleware.py
new file mode 100644
index 000000000..3947d2ec4
--- /dev/null
+++ b/merge/tests/test_middleware.py
@@ -0,0 +1,50 @@
+from unittest.mock import Mock
+
+from django import test
+from django.http import HttpResponseRedirect
+from django.test.client import RequestFactory
+
+from argus_htmx.middleware import LoginRequiredMiddleware
+
+
+class TestLoginRequiredMiddleware(test.TestCase):
+ def setUp(self):
+ request = RequestFactory().get("/foo")
+ request.login_url = "/login"
+ request.user = Mock()
+ request.get_full_path = Mock(return_value="/bar")
+ self.request = request
+
+ def test_process_view_login_required_false(self):
+ def view_func():
+ return None
+
+ view_func.login_required = False
+ result = LoginRequiredMiddleware(lambda x: x).process_view(self.request, view_func, None, {})
+ self.assertIsNone(result)
+
+ @test.override_settings(PUBLIC_URLS=("/foo",))
+ def test_process_view_public_urls(self):
+ def view_func():
+ return None
+
+ result = LoginRequiredMiddleware(lambda x: x).process_view(self.request, view_func, None, {})
+ self.assertIsNone(result)
+
+ def test_process_view_authenticated(self):
+ def view_func():
+ return None
+
+ self.request.user.is_authenticated = True
+ result = LoginRequiredMiddleware(lambda x: x).process_view(self.request, view_func, None, {})
+ self.assertIsNone(result)
+ delattr(self.request.user, "is_authenticated")
+
+ def test_process_view_redirect(self):
+ def view_func():
+ return None
+
+ self.request.user.is_authenticated = False
+ result = LoginRequiredMiddleware(lambda x: x).process_view(self.request, view_func, None, {})
+ self.assertIsNotNone(result)
+ self.assertIsInstance(result, HttpResponseRedirect)
diff --git a/merge/tests/urls.py b/merge/tests/urls.py
new file mode 100644
index 000000000..060c75da8
--- /dev/null
+++ b/merge/tests/urls.py
@@ -0,0 +1,6 @@
+from django.urls import path, include
+
+app_name = "htmx"
+urlpatterns = [
+ path("accounts/", include("django.contrib.auth.urls")),
+]
diff --git a/merge/tox.ini b/merge/tox.ini
new file mode 100644
index 000000000..4e885d693
--- /dev/null
+++ b/merge/tox.ini
@@ -0,0 +1,24 @@
+[tox]
+requires =
+ tox>=4
+
+env_list = py{310,311,312}-django{42,50,51}
+
+[testenv:tailwind]
+setenv =
+ TAILWINDDIR=src/argus_htmx/tailwindtheme
+ STATICDIR=src/argus_htmx
+allowlist_externals =
+ tailwindcss
+commands =
+ tailwindcss -c {env:TAILWINDDIR}/tailwind.config.js -i {env:TAILWINDDIR}/styles.css --output {env:STATICDIR}/static/styles.css
+
+[testenv]
+setenv =
+ PYTHONPATH = {toxinidir}/src
+commands =
+ python runtests.py
+deps =
+ django42: django>=4.2,<5
+ django50: django>=5.0,<5.1
+ django51: django>=5.1,<5.2