diff --git a/.dockerignore b/.dockerignore index b2828c7..bd26c6c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,3 @@ **.sqlite3 **/static-root -**/media-root -**/media \ No newline at end of file +**/media-root \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1f521f4..cb704c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ OpenShow/media/* -*/static-root/ \ No newline at end of file +*/media-root/ +*/static-root/ +.ipython/profile_default/history.sqlite +*db.sqlite3 \ No newline at end of file diff --git a/.ipython/profile_default/ipython_config.py b/.ipython/profile_default/ipython_config.py new file mode 100644 index 0000000..afe4a6b --- /dev/null +++ b/.ipython/profile_default/ipython_config.py @@ -0,0 +1,2 @@ +c.InteractiveShellApp.extensions = ["autoreload"] +c.InteractiveShellApp.exec_lines = ["%autoreload 2"] \ No newline at end of file diff --git a/.ipython/profile_default/startup/README b/.ipython/profile_default/startup/README new file mode 100644 index 0000000..61d4700 --- /dev/null +++ b/.ipython/profile_default/startup/README @@ -0,0 +1,11 @@ +This is the IPython startup directory + +.py and .ipy files in this directory will be run *prior* to any code or files specified +via the exec_lines or exec_files configurables whenever you load this profile. + +Files will be run in lexicographical order, so you can control the execution order of files +with a prefix, e.g.:: + + 00-first.py + 50-middle.py + 99-last.ipy diff --git a/Dockerfile b/Dockerfile index 4a84542..be6a480 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,8 @@ RUN pip install -r requirements.txt COPY OpenShow . +RUN apk add ffmpeg + ARG OPENSHOW_DEBUG='False' ARG OPENSHOW_STATIC_ROOT='/static-root' ARG OPENSHOW_MEDIA_ROOT='/media-root' @@ -28,4 +30,4 @@ EXPOSE 8000/ ENTRYPOINT echo Migrating... \ && python manage.py migrate --no-input \ && python manage.py collectstatic --no-input \ - && python -m uvicorn --host 0.0.0.0 --port 8000 OpenShow.asgi:application + && honcho start diff --git a/OpenShow/OpenShow/api.py b/OpenShow/OpenShow/api.py new file mode 100644 index 0000000..f2e00a9 --- /dev/null +++ b/OpenShow/OpenShow/api.py @@ -0,0 +1,5 @@ +from ninja import NinjaAPI + +api = NinjaAPI() + +api.add_router("/slides/", "slides.api.router") \ No newline at end of file diff --git a/OpenShow/OpenShow/asgi.py b/OpenShow/OpenShow/asgi.py index 03e5b6c..4e9e275 100644 --- a/OpenShow/OpenShow/asgi.py +++ b/OpenShow/OpenShow/asgi.py @@ -14,18 +14,7 @@ import os import django from django.core.asgi import get_asgi_application -from django.urls import path, re_path -from channels.routing import ProtocolTypeRouter, URLRouter -from channels.auth import AuthMiddlewareStack -import django_eventstream os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings") -application = ProtocolTypeRouter({ - 'http': URLRouter([ - path('events', AuthMiddlewareStack( - URLRouter(django_eventstream.routing.urlpatterns) - ), { 'channels': ['test'] }), - re_path(r'', get_asgi_application()), - ]), -}) \ No newline at end of file +application = get_asgi_application() \ No newline at end of file diff --git a/OpenShow/OpenShow/settings/base.py b/OpenShow/OpenShow/settings/base.py index 32ef4f1..2ef589c 100644 --- a/OpenShow/OpenShow/settings/base.py +++ b/OpenShow/OpenShow/settings/base.py @@ -20,6 +20,7 @@ # Application definition INSTALLED_APPS = [ + 'daphne', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -28,13 +29,15 @@ 'django.contrib.staticfiles', 'slides', 'slides.editor', - 'channels', 'django_eventstream', 'uubloomington_api_connector', 'django_feather', 'pjlink_integration', 'core', - 'deck_from_images', + 'django_srcdoc', + 'neapolitan', + 'django_extensions', + 'django_q', ] MIDDLEWARE = [ @@ -107,3 +110,21 @@ # Remove restriction on number of files uploaded for "deck from images" functionality DATA_UPLOAD_MAX_NUMBER_FILES = None + +# The example from https://django-q2.readthedocs.io/en/master/configure.html#orm-configuration +# We are currently only using django-q2 for managing media transcodes; the ORM broker should be fine. +# If this is ever used for more intensive parts of the system (scheduled slides, auto advance, etc.), it might be +# a good idea to look at a Redis (Valkey) setup instead. For now, simplicity is king. +Q_CLUSTER = { + 'name': 'DjangORM', + 'workers': 4, + 'timeout': None, + 'retry': 2147483647, + 'queue_limit': 50, + 'bulk': 10, + 'orm': 'default' +} + +SHELL_PLUS_IMPORTS = [ + 'from slides.editor.tasks import transcode_video' +] \ No newline at end of file diff --git a/OpenShow/OpenShow/settings/dev.py b/OpenShow/OpenShow/settings/dev.py index 9ff4ae6..c3ff79b 100644 --- a/OpenShow/OpenShow/settings/dev.py +++ b/OpenShow/OpenShow/settings/dev.py @@ -20,5 +20,5 @@ STATIC_URL = 'static/' -MEDIA_ROOT = f'{BASE_DIR}/media/' +MEDIA_ROOT = f'{BASE_DIR}/media-root/' MEDIA_URL = '/media/' diff --git a/OpenShow/OpenShow/urls.py b/OpenShow/OpenShow/urls.py index cd13592..da5ecdb 100644 --- a/OpenShow/OpenShow/urls.py +++ b/OpenShow/OpenShow/urls.py @@ -17,13 +17,16 @@ from django.urls import path, include from django.conf import settings from django.conf.urls.static import static +import django_eventstream +from .api import api urlpatterns = [ path('', include('core.urls')), path('admin/', admin.site.urls), + path('api/', api.urls), path('slides/', include('slides.urls')), path('uubloomington/', include('uubloomington_api_connector.urls')), path('pjlink/', include('pjlink_integration.urls')), - path('deck_from_images/', include('deck_from_images.urls')), + path("events/", include(django_eventstream.urls), {"channels": ["test"]}), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/OpenShow/Procfile b/OpenShow/Procfile new file mode 100644 index 0000000..07e3cbd --- /dev/null +++ b/OpenShow/Procfile @@ -0,0 +1,2 @@ +web: python -m uvicorn --host 0.0.0.0 --port 8000 OpenShow.asgi:application +worker: python manage.py qcluster \ No newline at end of file diff --git a/OpenShow/Procfile.development b/OpenShow/Procfile.development new file mode 100644 index 0000000..0ac4ef0 --- /dev/null +++ b/OpenShow/Procfile.development @@ -0,0 +1,2 @@ +web: PYTHONUNBUFFERED=1 python manage.py runserver 8030 +worker: PYTHONUNBUFFERED=1 python manage.py qcluster \ No newline at end of file diff --git a/OpenShow/deck_from_images/__init__.py b/OpenShow/__init__.py similarity index 100% rename from OpenShow/deck_from_images/__init__.py rename to OpenShow/__init__.py diff --git a/OpenShow/core/static/core/extras.css b/OpenShow/core/static/core/extras.css new file mode 100644 index 0000000..afb12ed --- /dev/null +++ b/OpenShow/core/static/core/extras.css @@ -0,0 +1,46 @@ +body > header { + display: grid; + grid-template-columns: 5rem 1fr 5rem; + text-align: center; + height: 6rem; + padding: 0; + margin-bottom: 1rem; +} + +.header-right-button svg, +.header-back-button svg { + height: 100%; + width: 2rem; + margin: auto; +} + +.header-back-button a { + margin: 0; +} + +.sidebar-layout.double { + grid-template-columns: 18rem 2fr 1fr; + height: calc(100dvh - 7rem); +} + +h2 { + margin: 0; +} + +.hidden { + display: none; +} + +@media screen and (max-width: 50rem) { + .sidebar-layout.double { + display: flex; + flex-direction: column; + max-height: calc(100dvh - 7rem); + } +} + +@media screen and (min-width: 1420px) { + .sidebar-layout.double { + grid-template-columns: 18rem 2fr 25rem; + } +} \ No newline at end of file diff --git a/OpenShow/core/static/css/missing.css b/OpenShow/core/static/css/missing.css new file mode 100644 index 0000000..1e06b82 --- /dev/null +++ b/OpenShow/core/static/css/missing.css @@ -0,0 +1 @@ + *,:before,:after{box-sizing:border-box;background-repeat:no-repeat}:before,:after{-webkit-text-decoration:inherit;text-decoration:inherit;vertical-align:inherit}:root{cursor:default;overflow-wrap:break-word;-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:none;-moz-text-size-adjust:none;-ms-text-size-adjust:none;text-size-adjust:none}abbr[title]{text-decoration:underline dotted}strong,b{font-weight:bolder}small{font-size:80%}audio,canvas,iframe,img,svg,video{vertical-align:middle}svg:not([fill]){fill:currentColor}table{border-collapse:collapse;text-indent:0;border-color:currentColor}button,input,select{margin:0}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}fieldset{border:1px solid #a0a0a0}progress{vertical-align:baseline}textarea{margin:0}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}::-webkit-input-placeholder{color:inherit;opacity:.54}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}[hidden]{display:none!important}:focus-visible{outline:.2em solid var(--accent);z-index:32}iframe:focus-visible,html:focus-visible,body:focus-visible{outline:none}:target{outline:.2em solid var(--fg);z-index:2}details>summary:first-of-type{display:list-item}[aria-busy=true]{cursor:progress}[aria-disabled=true],[disabled]{cursor:not-allowed}datalist{display:none!important}:root{--gray-0:#f8fafb;--gray-1:#f2f4f6;--gray-2:#ebedef;--gray-3:#e0e4e5;--gray-4:#d1d6d8;--gray-5:#b1b6b9;--gray-6:#979b9d;--gray-7:#7e8282;--gray-8:#666968;--gray-9:#50514f;--gray-10:#3a3a37;--gray-11:#252521;--gray-12:#121210;--red-0:#fff5f5;--red-1:#ffe3e3;--red-2:#ffc9c9;--red-3:#ffa8a8;--red-4:#ff8787;--red-5:#ff6b6b;--red-6:#fa5252;--red-7:#f03e3e;--red-8:#e03131;--red-9:#c92a2a;--red-10:#b02525;--red-11:#962020;--red-12:#7d1a1a;--pink-0:#fff0f6;--pink-1:#ffdeeb;--pink-2:#fcc2d7;--pink-3:#faa2c1;--pink-4:#f783ac;--pink-5:#f06595;--pink-6:#e64980;--pink-7:#d6336c;--pink-8:#c2255c;--pink-9:#a61e4d;--pink-10:#8c1941;--pink-11:#731536;--pink-12:#59102a;--purple-0:#f8f0fc;--purple-1:#f3d9fa;--purple-2:#eebefa;--purple-3:#e599f7;--purple-4:#da77f2;--purple-5:#cc5de8;--purple-6:#be4bdb;--purple-7:#ae3ec9;--purple-8:#9c36b5;--purple-9:#862e9c;--purple-10:#702682;--purple-11:#5a1e69;--purple-12:#44174f;--violet-0:#f3f0ff;--violet-1:#e5dbff;--violet-2:#d0bfff;--violet-3:#b197fc;--violet-4:#9775fa;--violet-5:#845ef7;--violet-6:#7950f2;--violet-7:#7048e8;--violet-8:#6741d9;--violet-9:#5f3dc4;--violet-10:#5235ab;--violet-11:#462d91;--violet-12:#3a2578;--indigo-0:#edf2ff;--indigo-1:#dbe4ff;--indigo-2:#bac8ff;--indigo-3:#91a7ff;--indigo-4:#748ffc;--indigo-5:#5c7cfa;--indigo-6:#4c6ef5;--indigo-7:#4263eb;--indigo-8:#3b5bdb;--indigo-9:#364fc7;--indigo-10:#2f44ad;--indigo-11:#283a94;--indigo-12:#21307a;--blue-0:#e7f5ff;--blue-1:#d0ebff;--blue-2:#a5d8ff;--blue-3:#74c0fc;--blue-4:#4dabf7;--blue-5:#339af0;--blue-6:#228be6;--blue-7:#1c7ed6;--blue-8:#1971c2;--blue-9:#1864ab;--blue-10:#145591;--blue-11:#114678;--blue-12:#0d375e;--cyan-0:#e3fafc;--cyan-1:#c5f6fa;--cyan-2:#99e9f2;--cyan-3:#66d9e8;--cyan-4:#3bc9db;--cyan-5:#22b8cf;--cyan-6:#15aabf;--cyan-7:#1098ad;--cyan-8:#0c8599;--cyan-9:#0b7285;--cyan-10:#095c6b;--cyan-11:#074652;--cyan-12:#053038;--teal-0:#e6fcf5;--teal-1:#c3fae8;--teal-2:#96f2d7;--teal-3:#63e6be;--teal-4:#38d9a9;--teal-5:#20c997;--teal-6:#12b886;--teal-7:#0ca678;--teal-8:#099268;--teal-9:#087f5b;--teal-10:#066649;--teal-11:#054d37;--teal-12:#033325;--green-0:#ebfbee;--green-1:#d3f9d8;--green-2:#b2f2bb;--green-3:#8ce99a;--green-4:#69db7c;--green-5:#51cf66;--green-6:#40c057;--green-7:#37b24d;--green-8:#2f9e44;--green-9:#2b8a3e;--green-10:#237032;--green-11:#1b5727;--green-12:#133d1b;--lime-0:#f4fce3;--lime-1:#e9fac8;--lime-2:#d8f5a2;--lime-3:#c0eb75;--lime-4:#a9e34b;--lime-5:#94d82d;--lime-6:#82c91e;--lime-7:#74b816;--lime-8:#66a80f;--lime-9:#5c940d;--lime-10:#4c7a0b;--lime-11:#3c6109;--lime-12:#2c4706;--yellow-0:#fff9db;--yellow-1:#fff3bf;--yellow-2:#ffec99;--yellow-3:#ffe066;--yellow-4:#ffd43b;--yellow-5:#fcc419;--yellow-6:#fab005;--yellow-7:#f59f00;--yellow-8:#f08c00;--yellow-9:#e67700;--yellow-10:#b35c00;--yellow-11:#804200;--yellow-12:#663500;--orange-0:#fff4e6;--orange-1:#ffe8cc;--orange-2:#ffd8a8;--orange-3:#ffc078;--orange-4:#ffa94d;--orange-5:#ff922b;--orange-6:#fd7e14;--orange-7:#f76707;--orange-8:#e8590c;--orange-9:#d9480f;--orange-10:#bf400d;--orange-11:#99330b;--orange-12:#802b09;--choco-0:#fff8dc;--choco-1:#fce1bc;--choco-2:#f7ca9e;--choco-3:#f1b280;--choco-4:#e99b62;--choco-5:#df8545;--choco-6:#d46e25;--choco-7:#bd5f1b;--choco-8:#a45117;--choco-9:#8a4513;--choco-10:#703a13;--choco-11:#572f12;--choco-12:#3d210d;--brown-0:#faf4eb;--brown-1:#ede0d1;--brown-2:#e0cab7;--brown-3:#d3b79e;--brown-4:#c5a285;--brown-5:#b78f6d;--brown-6:#a87c56;--brown-7:#956b47;--brown-8:#825b3a;--brown-9:#6f4b2d;--brown-10:#5e3a21;--brown-11:#4e2b15;--brown-12:#422412;--sand-0:#f8fafb;--sand-1:#e6e4dc;--sand-2:#d5cfbd;--sand-3:#c2b9a0;--sand-4:#aea58c;--sand-5:#9a9178;--sand-6:#867c65;--sand-7:#736a53;--sand-8:#5f5746;--sand-9:#4b4639;--sand-10:#38352d;--sand-11:#252521;--sand-12:#121210;--camo-0:#f9fbe7;--camo-1:#e8ed9c;--camo-2:#d2df4e;--camo-3:#c2ce34;--camo-4:#b5bb2e;--camo-5:#a7a827;--camo-6:#999621;--camo-7:#8c851c;--camo-8:#7e7416;--camo-9:#6d6414;--camo-10:#5d5411;--camo-11:#4d460e;--camo-12:#36300a;--jungle-0:#ecfeb0;--jungle-1:#def39a;--jungle-2:#d0e884;--jungle-3:#c2dd6e;--jungle-4:#b5d15b;--jungle-5:#a8c648;--jungle-6:#9bbb36;--jungle-7:#8fb024;--jungle-8:#84a513;--jungle-9:#7a9908;--jungle-10:#658006;--jungle-11:#516605;--jungle-12:#3d4d04}html{font-family:var(--main-font);line-height:var(--rhythm);background:var(--bg);color:var(--fg);scroll-padding-block-start:calc(4*var(--gap))}body{margin:0}header,footer,section+section{margin-block:calc(2*var(--gap))}nav a{color:var(--accent);text-decoration:none}aside{font-size:.8em;line-height:calc(var(--rhythm)*2/3);--gap:calc(var(--rhythm)*var(--density)*2/3);border-block:1px solid var(--graphical-fg);padding-block:var(--gap);margin-block:calc(var(--gap)*3/2)}aside.bg{padding-inline:var(--gap)}aside h1,aside h2,aside h3,aside h4,aside h5,aside h6{text-transform:none;letter-spacing:none;font-size:1em}aside.big{color:var(--accent);background:0 0;border:none;border-radius:0;padding:0;font-style:italic}aside.big:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){border-left:1px solid var(--muted-fg);padding-left:var(--rhythm)}aside.big:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),aside.big:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){border-right:1px solid var(--muted-fg);padding-right:var(--rhythm)}aside.big:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),aside.big:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){border-right:1px solid var(--muted-fg);padding-right:var(--rhythm)}h1,h2,h3,h4,h5,h6,.\,.\,.\,.\,.\,.\{font-family:var(--secondary-font);margin-top:calc(2*var(--gap));margin-bottom:var(--gap);font-size:1em;position:relative}h1,.\{text-transform:none;font-size:2em;line-height:calc(2*var(--rhythm));letter-spacing:0}h2,.\{text-transform:none;font-size:1.6em;line-height:calc(1.5*var(--rhythm));letter-spacing:0}h3,.\{font-size:1.17em;line-height:calc(1*var(--rhythm))}h4,.\,h5,.\,h6,.\{text-transform:none;font-size:1em;line-height:calc(1*var(--rhythm));letter-spacing:0;margin-top:var(--gap)}h1+h2,h2+h3,h3+h4,h4+h5,h5+h6,h1:first-child,h2:first-child,h3:first-child,h4:first-child,h5:first-child,h6:first-child{margin-top:var(--gap)}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{outline:none}:is(h1:target,h2:target,h3:target,h4:target,h5:target,h6:target):before{content:"";background:var(--accent);width:4px;height:100%;display:block;position:absolute;left:-.5em}header{font-family:var(--secondary-font);border-bottom:1px solid var(--graphical-fg)}footer{font-family:var(--secondary-font);font-size:.8em;line-height:calc(var(--rhythm)*2/3);border-top:1px solid var(--graphical-fg)}body>header,body>footer,main+footer{padding:var(--rhythm)calc((100% - var(--eff-line-length))/2)}address{--density:0}p{margin-block:var(--gap)}hr{color:inherit;margin-left:0;margin-right:0;margin-block:var(--gap);border-top:1px solid var(--accent);border-bottom:none;flex:0 1 0;height:auto}hr:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){border-left:1px solid var(--accent);border-right:none}hr:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),hr:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){border-right:1px solid var(--accent);border-left:none}hr:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),hr:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){border-right:1px solid var(--accent);border-left:none}pre{font-family:var(--mono-font);font-size:.9em;line-height:var(--rhythm);tab-size:2;margin:var(--gap)0;scrollbar-width:thin;scrollbar-color:var(--accent)transparent;overflow-x:auto}blockquote{margin-inline:0 var(--gap);padding-inline:var(--gap)0;margin-block:var(--gap);font-size:1.1em;line-height:var(--rhythm);color:var(--muted-fg);font-style:italic}blockquote:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){border-left:1px solid var(--graphical-fg)}blockquote:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),blockquote:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){border-right:1px solid var(--graphical-fg)}blockquote:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),blockquote:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){border-right:1px solid var(--graphical-fg)}blockquote em,blockquote cite,blockquote dfn,blockquote var,blockquote i,blockquote address{font-style:normal}blockquote footer,blockquote footer:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){text-align:right}blockquote footer:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),blockquote footer:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){text-align:left}blockquote footer:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),blockquote footer:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){text-align:left}ul,ol{margin-block:var(--gap)}ul:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)),ol:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){padding-left:var(--rhythm)}ul:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),ul:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),ol:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),ol:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-right:var(--rhythm)}ul:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),ul:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),ol:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),ol:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-right:var(--rhythm)}:is(ul,ol) :is(ul,ol):not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){padding-left:var(--gap)}:is(ul,ol) :is(ul,ol):-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),:is(ul,ol) :is(ul,ol):is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-right:var(--gap)}:is(ul,ol)[role=list]{list-style:none}:is(ul,ol)[role=list]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){padding-left:0}:-webkit-any(ul,ol)[role=list]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),:-webkit-any(ul,ol)[role=list]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-right:0}:is(ul,ol)[role=list]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),:is(ul,ol)[role=list]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-right:0}:is(ul,ol)[role=listbox]{list-style:none}:is(ul,ol)[role=listbox]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){padding-left:0}:-webkit-any(ul,ol)[role=listbox]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),:-webkit-any(ul,ol)[role=listbox]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-right:0}:is(ul,ol)[role=listbox]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),:is(ul,ol)[role=listbox]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-right:0}ol{list-style:decimal}dl{margin-block:var(--gap)}dt{font-weight:700;font-family:var(--secondary-font)}dd:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){margin-left:var(--rhythm)}dd:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),dd:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){margin-right:var(--rhythm)}dd:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),dd:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){margin-right:var(--rhythm)}li::marker{font-family:var(--secondary-font)}figure{max-width:100%;margin-left:0;margin-right:0}figcaption{margin-block:var(--gap);font-family:var(--secondary-font);color:var(--muted-fg)}main{max-width:var(--eff-line-length);width:100%;margin-left:auto;margin-right:auto}main:first-child{padding-top:var(--gap)}a,.\{color:var(--link-fg,var(--accent));border-radius:var(--border-radius);outline-offset:1px;background:0 0;border:none;font-size:1em;text-decoration:underline 1px dotted}.list-of-links :is(a,.\){text-decoration:none}:is(a,.\):hover,:is(a,.\):focus{cursor:pointer;outline:none;text-decoration:underline 2px}small[role=note]{float:inline-end;clear:inline-end;--sidenote-width:20ch;max-width:var(--sidenote-width); font-family:var(--secondary-font);background:var(--bg);margin-bottom:var(--rhythm);border:1px solid #0000;transition:transform .1s ease-in-out;display:block}small[role=note]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){margin-right:calc(1em - var(--sidenote-width));padding-left:1.5ch;padding-right:1ch}small[role=note]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),small[role=note]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){margin-left:calc(1em - var(--sidenote-width));padding-left:1ch;padding-right:1.5ch}small[role=note]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),small[role=note]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){margin-left:calc(1em - var(--sidenote-width));padding-left:1ch;padding-right:1.5ch}small[role=note]:hover,small[role=note]:focus-within{border:1px solid var(--graphical-fg);border-radius:var(--border-radius);transform:translateX(calc(0px - var(--sidenote-width) + min(var(--gutter-width),var(--sidenote-width))))}small,.\{font-size:.8em;line-height:calc(var(--rhythm)*2/3)}s{color:var(--bad-fg)}q{font-style:italic}q em,q cite,q dfn,q var,q i,q address{font-style:normal}time{font-variant-numeric:tabular-nums}code,samp,kbd{font-family:var(--mono-font);font-style:normal}samp{color:var(--ok-fg)}kbd kbd{background:var(--interactive-bg);border:1px outset var(--graphical-fg);border-radius:var(--border-radius);border-bottom-width:3px;padding:0 .3em;font-size:.8em;line-height:1.1em;display:inline-block}sub{vertical-align:bottom;line-height:1}sup{vertical-align:top;line-height:1}mark{background:var(--warn-bg);color:var(--warn-fg)}ins{background:var(--ok-bg);color:var(--ok-fg)}del{background:var(--bad-bg);color:var(--bad-fg)}img,video,audio,iframe,object,embed{width:max-content;max-width:100%;height:auto}table{font-variant-numeric:tabular-nums;font:inherit}caption{font-family:var(--secondary-font);font-style:italic}caption:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){text-align:left}caption:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),caption:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){text-align:right}caption:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),caption:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){text-align:right}tbody{border-block:1px solid var(--faded-fg)}td,th{vertical-align:top}:is(td,th):not(:last-child):not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){padding-right:var(--rhythm)}:-webkit-any(td,th):not(:last-child):-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),:-webkit-any(td,th):not(:last-child):-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-left:var(--rhythm)}:is(td,th):not(:last-child):-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),:is(td,th):not(:last-child):is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-left:var(--rhythm)}th{font-family:var(--secondary-font)}th:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){text-align:left}th:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),th:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){text-align:right}th:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),th:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){text-align:right}input{display:block}label input:not([specificity-hack]){padding-top:0;padding-bottom:0;display:inline}button,.\,input[type=submit]{padding:0 calc(var(--rhythm)/4);vertical-align:middle;box-sizing:border-box;font-size:.8rem;line-height:1.125em;font-family:var(--secondary-font);min-height:var(--rhythm);background:var(--interactive-bg);color:var(--fg);border:1px solid var(--muted-fg);box-shadow:0 2px 4px -2px var(--fg);border-radius:var(--border-radius);justify-content:center;align-items:center;text-decoration:none;display:inline-flex}input[type=submit]:hover,input[type=submit]:focus-visible{filter:brightness(1.1);box-shadow:0 3px 6px -2px var(--fg);text-decoration:none}input[type=submit]:active{box-shadow:none}:is(strong>input[type=submit]){background:var(--accent);color:var(--bg);border:none;font-weight:700}:is(strong>input[type=submit])[disabled]{color:var(--muted-accent)}input[type=reset]{padding:0 calc(var(--rhythm)/4);vertical-align:middle;box-sizing:border-box;font-size:.8rem;line-height:1.125em;font-family:var(--secondary-font);min-height:var(--rhythm);background:var(--interactive-bg);color:var(--fg);border:1px solid var(--muted-fg);box-shadow:0 2px 4px -2px var(--fg);border-radius:var(--border-radius);justify-content:center;align-items:center;text-decoration:none;display:inline-flex}input[type=reset]:hover,input[type=reset]:focus-visible{filter:brightness(1.1);box-shadow:0 3px 6px -2px var(--fg);text-decoration:none}input[type=reset]:active{box-shadow:none}:is(strong>input[type=reset]){background:var(--accent);color:var(--bg);border:none;font-weight:700}:is(strong>input[type=reset])[disabled]{color:var(--muted-accent)}input[type=button]{padding:0 calc(var(--rhythm)/4);vertical-align:middle;box-sizing:border-box;font-size:.8rem;line-height:1.125em;font-family:var(--secondary-font);min-height:var(--rhythm);background:var(--interactive-bg);color:var(--fg);border:1px solid var(--muted-fg);box-shadow:0 2px 4px -2px var(--fg);border-radius:var(--border-radius);justify-content:center;align-items:center;text-decoration:none;display:inline-flex}input[type=button]:hover,input[type=button]:focus-visible{filter:brightness(1.1);box-shadow:0 3px 6px -2px var(--fg);text-decoration:none}input[type=button]:active{box-shadow:none}:is(strong>input[type=button]){background:var(--accent);color:var(--bg);border:none;font-weight:700}:is(strong>input[type=button])[disabled]{color:var(--muted-accent)}input::-webkit-file-upload-button{padding:0 calc(var(--rhythm)/4);vertical-align:middle;box-sizing:border-box;font-size:.8rem;line-height:1.125em;font-family:var(--secondary-font);min-height:var(--rhythm);background:var(--interactive-bg);color:var(--fg);border:1px solid var(--muted-fg);box-shadow:0 2px 4px -2px var(--fg);border-radius:var(--border-radius);justify-content:center;align-items:center;text-decoration:none;display:inline-flex}input::-webkit-file-upload-button:hover{filter:brightness(1.1);box-shadow:0 3px 6px -2px var(--fg);text-decoration:none}input::-webkit-file-upload-button:focus-visible{filter:brightness(1.1);box-shadow:0 3px 6px -2px var(--fg);text-decoration:none}input::-webkit-file-upload-button:active{box-shadow:none}:-webkit-any(strong>:-webkit-any()){background:var(--accent);color:var(--bg);border:none;font-weight:700}:-webkit-any(strong>:-webkit-any())[disabled]{color:var(--muted-accent)}input::-ms-browse{padding:0 calc(var(--rhythm)/4);vertical-align:middle;box-sizing:border-box;font-size:.8rem;line-height:1.125em;font-family:var(--secondary-font);min-height:var(--rhythm);background:var(--interactive-bg);color:var(--fg);border:1px solid var(--muted-fg);box-shadow:0 2px 4px -2px var(--fg);border-radius:var(--border-radius);justify-content:center;align-items:center;text-decoration:none;display:inline-flex}input::-ms-browse:hover{filter:brightness(1.1);box-shadow:0 3px 6px -2px var(--fg);text-decoration:none}input::-ms-browse:focus-visible{filter:brightness(1.1);box-shadow:0 3px 6px -2px var(--fg);text-decoration:none}input::-ms-browse:active{box-shadow:none}input::file-selector-button{padding:0 calc(var(--rhythm)/4);vertical-align:middle;box-sizing:border-box;font-size:.8rem;line-height:1.125em;font-family:var(--secondary-font);min-height:var(--rhythm);background:var(--interactive-bg);color:var(--fg);border:1px solid var(--muted-fg);box-shadow:0 2px 4px -2px var(--fg);border-radius:var(--border-radius);justify-content:center;align-items:center;text-decoration:none;display:inline-flex}input::file-selector-button:hover{filter:brightness(1.1);box-shadow:0 3px 6px -2px var(--fg);text-decoration:none}input::file-selector-button:focus-visible{filter:brightness(1.1);box-shadow:0 3px 6px -2px var(--fg);text-decoration:none}input::file-selector-button:active{box-shadow:none}:is(strong>:is()){background:var(--accent);color:var(--bg);border:none;font-weight:700}:is(strong>:is())[disabled]{color:var(--muted-accent)}:is(button,.\):hover,:is(button,.\):focus-visible{filter:brightness(1.1);box-shadow:0 3px 6px -2px var(--fg);text-decoration:none}:is(button,.\):active{box-shadow:none}:is(strong>:is(button,.\)){background:var(--accent);color:var(--bg);border:none;font-weight:700}:is(strong>:is(button,.\))[disabled]{color:var(--muted-accent)}input[type=submit]:active:-webkit-any([aria-pressed],[aria-expanded]),input[type=submit]:active:-webkit-any([aria-pressed],[aria-expanded]){color:var(--accent);box-shadow:0 1px 5px -1px var(--fg)inset}input[type=submit]:active:-webkit-any([aria-pressed],[aria-expanded]),input[type=submit]:active:is([aria-pressed],[aria-expanded]){color:var(--accent);box-shadow:0 1px 5px -1px var(--fg)inset}input[type=submit]:where([aria-pressed=true],[aria-expanded=true]){box-shadow:0 2px 4px -1px var(--fg)inset;background:var(--pressed-interactive-bg);color:var(--accent)}input[type=submit]:where([aria-pressed=true],[aria-expanded=true]):hover,input[type=submit]:where([aria-pressed=true],[aria-expanded=true]):focus-visible{box-shadow:0 1px 3px -1px var(--fg)inset}input[type=submit].big{min-height:calc(1.5*var(--rhythm));padding-inline:calc(.5*var(--rhythm));font-size:1rem;line-height:var(--rhythm)}input[type=submit]:disabled{color:var(--muted-fg);box-shadow:none}input[type=reset]:active:-webkit-any([aria-pressed],[aria-expanded]),input[type=reset]:active:-webkit-any([aria-pressed],[aria-expanded]){color:var(--accent);box-shadow:0 1px 5px -1px var(--fg)inset}input[type=reset]:active:-webkit-any([aria-pressed],[aria-expanded]),input[type=reset]:active:is([aria-pressed],[aria-expanded]){color:var(--accent);box-shadow:0 1px 5px -1px var(--fg)inset}input[type=reset]:where([aria-pressed=true],[aria-expanded=true]){box-shadow:0 2px 4px -1px var(--fg)inset;background:var(--pressed-interactive-bg);color:var(--accent)}input[type=reset]:where([aria-pressed=true],[aria-expanded=true]):hover,input[type=reset]:where([aria-pressed=true],[aria-expanded=true]):focus-visible{box-shadow:0 1px 3px -1px var(--fg)inset}input[type=reset].big{min-height:calc(1.5*var(--rhythm));padding-inline:calc(.5*var(--rhythm));font-size:1rem;line-height:var(--rhythm)}input[type=reset]:disabled{color:var(--muted-fg);box-shadow:none}input[type=button]:active:-webkit-any([aria-pressed],[aria-expanded]),input[type=button]:active:-webkit-any([aria-pressed],[aria-expanded]){color:var(--accent);box-shadow:0 1px 5px -1px var(--fg)inset}input[type=button]:active:-webkit-any([aria-pressed],[aria-expanded]),input[type=button]:active:is([aria-pressed],[aria-expanded]){color:var(--accent);box-shadow:0 1px 5px -1px var(--fg)inset}input[type=button]:where([aria-pressed=true],[aria-expanded=true]){box-shadow:0 2px 4px -1px var(--fg)inset;background:var(--pressed-interactive-bg);color:var(--accent)}input[type=button]:where([aria-pressed=true],[aria-expanded=true]):hover,input[type=button]:where([aria-pressed=true],[aria-expanded=true]):focus-visible{box-shadow:0 1px 3px -1px var(--fg)inset}input[type=button].big{min-height:calc(1.5*var(--rhythm));padding-inline:calc(.5*var(--rhythm));font-size:1rem;line-height:var(--rhythm)}input[type=button]:disabled{color:var(--muted-fg);box-shadow:none}:-webkit-any(button,.\):active:-webkit-any([aria-pressed],[aria-expanded]),:-webkit-any(button,.\):active:-webkit-any([aria-pressed],[aria-expanded]){color:var(--accent);box-shadow:0 1px 5px -1px var(--fg)inset}:is(button,.\):active:-webkit-any([aria-pressed],[aria-expanded]),:is(button,.\):active:is([aria-pressed],[aria-expanded]){color:var(--accent);box-shadow:0 1px 5px -1px var(--fg)inset}:is(button,.\):where([aria-pressed=true],[aria-expanded=true]){box-shadow:0 2px 4px -1px var(--fg)inset;background:var(--pressed-interactive-bg);color:var(--accent)}:is(button,.\):where([aria-pressed=true],[aria-expanded=true]):hover,:is(button,.\):where([aria-pressed=true],[aria-expanded=true]):focus-visible{box-shadow:0 1px 3px -1px var(--fg)inset}:is(button,.\).big{min-height:calc(1.5*var(--rhythm));padding-inline:calc(.5*var(--rhythm));font-size:1rem;line-height:var(--rhythm)}:is(button,.\):disabled{color:var(--muted-fg);box-shadow:none}input:not([type]),select,textarea,input[type=text]{padding:calc(var(--rhythm)/4);vertical-align:top;font-size:1rem;line-height:inherit;font-family:var(--main-font);background:var(--bg);color:var(--fg);border:1px solid var(--graphical-fg);border-radius:var(--border-radius);vertical-align:top}input[type=text]:focus-visible{border:1px solid var(--accent)}input[type=text]::-moz-placeholder{color:var(--muted-fg);opacity:1}input[type=text]::placeholder{color:var(--muted-fg);opacity:1}input[type=text]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-moz-placeholder{text-align:right}input[type=text]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::placeholder{text-align:right}input[type=text]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=text]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=text]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-moz-placeholder{text-align:left}input[type=text]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=search]{padding:calc(var(--rhythm)/4);vertical-align:top;font-size:1rem;line-height:inherit;font-family:var(--main-font);background:var(--bg);color:var(--fg);border:1px solid var(--graphical-fg);border-radius:var(--border-radius);vertical-align:top}input[type=search]:focus-visible{border:1px solid var(--accent)}input[type=search]::-moz-placeholder{color:var(--muted-fg);opacity:1}input[type=search]::placeholder{color:var(--muted-fg);opacity:1}input[type=search]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-moz-placeholder{text-align:right}input[type=search]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::placeholder{text-align:right}input[type=search]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=search]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=search]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-moz-placeholder{text-align:left}input[type=search]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=tel]{padding:calc(var(--rhythm)/4);vertical-align:top;font-size:1rem;line-height:inherit;font-family:var(--main-font);background:var(--bg);color:var(--fg);border:1px solid var(--graphical-fg);border-radius:var(--border-radius);vertical-align:top}input[type=tel]:focus-visible{border:1px solid var(--accent)}input[type=tel]::-moz-placeholder{color:var(--muted-fg);opacity:1}input[type=tel]::placeholder{color:var(--muted-fg);opacity:1}input[type=tel]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-moz-placeholder{text-align:right}input[type=tel]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::placeholder{text-align:right}input[type=tel]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=tel]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=tel]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-moz-placeholder{text-align:left}input[type=tel]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=url]{padding:calc(var(--rhythm)/4);vertical-align:top;font-size:1rem;line-height:inherit;font-family:var(--main-font);background:var(--bg);color:var(--fg);border:1px solid var(--graphical-fg);border-radius:var(--border-radius);vertical-align:top}input[type=url]:focus-visible{border:1px solid var(--accent)}input[type=url]::-moz-placeholder{color:var(--muted-fg);opacity:1}input[type=url]::placeholder{color:var(--muted-fg);opacity:1}input[type=url]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-moz-placeholder{text-align:right}input[type=url]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::placeholder{text-align:right}input[type=url]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=url]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=url]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-moz-placeholder{text-align:left}input[type=url]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=email]{padding:calc(var(--rhythm)/4);vertical-align:top;font-size:1rem;line-height:inherit;font-family:var(--main-font);background:var(--bg);color:var(--fg);border:1px solid var(--graphical-fg);border-radius:var(--border-radius);vertical-align:top}input[type=email]:focus-visible{border:1px solid var(--accent)}input[type=email]::-moz-placeholder{color:var(--muted-fg);opacity:1}input[type=email]::placeholder{color:var(--muted-fg);opacity:1}input[type=email]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-moz-placeholder{text-align:right}input[type=email]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::placeholder{text-align:right}input[type=email]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=email]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=email]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-moz-placeholder{text-align:left}input[type=email]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=password]{padding:calc(var(--rhythm)/4);vertical-align:top;font-size:1rem;line-height:inherit;font-family:var(--main-font);background:var(--bg);color:var(--fg);border:1px solid var(--graphical-fg);border-radius:var(--border-radius);vertical-align:top}input[type=password]:focus-visible{border:1px solid var(--accent)}input[type=password]::-moz-placeholder{color:var(--muted-fg);opacity:1}input[type=password]::placeholder{color:var(--muted-fg);opacity:1}input[type=password]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-moz-placeholder{text-align:right}input[type=password]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::placeholder{text-align:right}input[type=password]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=password]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=password]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-moz-placeholder{text-align:left}input[type=password]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=date]{padding:calc(var(--rhythm)/4);vertical-align:top;font-size:1rem;line-height:inherit;font-family:var(--main-font);background:var(--bg);color:var(--fg);border:1px solid var(--graphical-fg);border-radius:var(--border-radius);vertical-align:top}input[type=date]:focus-visible{border:1px solid var(--accent)}input[type=date]::-moz-placeholder{color:var(--muted-fg);opacity:1}input[type=date]::placeholder{color:var(--muted-fg);opacity:1}input[type=date]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-moz-placeholder{text-align:right}input[type=date]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::placeholder{text-align:right}input[type=date]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=date]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=date]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-moz-placeholder{text-align:left}input[type=date]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=month]{padding:calc(var(--rhythm)/4);vertical-align:top;font-size:1rem;line-height:inherit;font-family:var(--main-font);background:var(--bg);color:var(--fg);border:1px solid var(--graphical-fg);border-radius:var(--border-radius);vertical-align:top}input[type=month]:focus-visible{border:1px solid var(--accent)}input[type=month]::-moz-placeholder{color:var(--muted-fg);opacity:1}input[type=month]::placeholder{color:var(--muted-fg);opacity:1}input[type=month]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-moz-placeholder{text-align:right}input[type=month]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::placeholder{text-align:right}input[type=month]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=month]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=month]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-moz-placeholder{text-align:left}input[type=month]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=week]{padding:calc(var(--rhythm)/4);vertical-align:top;font-size:1rem;line-height:inherit;font-family:var(--main-font);background:var(--bg);color:var(--fg);border:1px solid var(--graphical-fg);border-radius:var(--border-radius);vertical-align:top}input[type=week]:focus-visible{border:1px solid var(--accent)}input[type=week]::-moz-placeholder{color:var(--muted-fg);opacity:1}input[type=week]::placeholder{color:var(--muted-fg);opacity:1}input[type=week]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-moz-placeholder{text-align:right}input[type=week]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::placeholder{text-align:right}input[type=week]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=week]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=week]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-moz-placeholder{text-align:left}input[type=week]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=time]{padding:calc(var(--rhythm)/4);vertical-align:top;font-size:1rem;line-height:inherit;font-family:var(--main-font);background:var(--bg);color:var(--fg);border:1px solid var(--graphical-fg);border-radius:var(--border-radius);vertical-align:top}input[type=time]:focus-visible{border:1px solid var(--accent)}input[type=time]::-moz-placeholder{color:var(--muted-fg);opacity:1}input[type=time]::placeholder{color:var(--muted-fg);opacity:1}input[type=time]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-moz-placeholder{text-align:right}input[type=time]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::placeholder{text-align:right}input[type=time]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=time]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=time]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-moz-placeholder{text-align:left}input[type=time]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=datetime]{padding:calc(var(--rhythm)/4);vertical-align:top;font-size:1rem;line-height:inherit;font-family:var(--main-font);background:var(--bg);color:var(--fg);border:1px solid var(--graphical-fg);border-radius:var(--border-radius);vertical-align:top}input[type=datetime]:focus-visible{border:1px solid var(--accent)}input[type=datetime]::-moz-placeholder{color:var(--muted-fg);opacity:1}input[type=datetime]::placeholder{color:var(--muted-fg);opacity:1}input[type=datetime]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-moz-placeholder{text-align:right}input[type=datetime]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::placeholder{text-align:right}input[type=datetime]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=datetime]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=datetime]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-moz-placeholder{text-align:left}input[type=datetime]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=datetime-local]{padding:calc(var(--rhythm)/4);vertical-align:top;font-size:1rem;line-height:inherit;font-family:var(--main-font);background:var(--bg);color:var(--fg);border:1px solid var(--graphical-fg);border-radius:var(--border-radius);vertical-align:top}input[type=datetime-local]:focus-visible{border:1px solid var(--accent)}input[type=datetime-local]::-moz-placeholder{color:var(--muted-fg);opacity:1}input[type=datetime-local]::placeholder{color:var(--muted-fg);opacity:1}input[type=datetime-local]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-moz-placeholder{text-align:right}input[type=datetime-local]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::placeholder{text-align:right}input[type=datetime-local]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=datetime-local]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=datetime-local]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-moz-placeholder{text-align:left}input[type=datetime-local]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=number]{padding:calc(var(--rhythm)/4);vertical-align:top;font-size:1rem;line-height:inherit;font-family:var(--main-font);background:var(--bg);color:var(--fg);border:1px solid var(--graphical-fg);border-radius:var(--border-radius);vertical-align:top}input[type=number]:focus-visible{border:1px solid var(--accent)}input[type=number]::-moz-placeholder{color:var(--muted-fg);opacity:1}input[type=number]::placeholder{color:var(--muted-fg);opacity:1}input[type=number]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-moz-placeholder{text-align:right}input[type=number]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::placeholder{text-align:right}input[type=number]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=number]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=number]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-moz-placeholder{text-align:left}input[type=number]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}:is(input:not([type]),select,textarea):focus-visible{border:1px solid var(--accent)}:is(input:not([type]),select,textarea)::-moz-placeholder{color:var(--muted-fg);opacity:1}:is(input:not([type]),select,textarea)::placeholder{color:var(--muted-fg);opacity:1}:is(input:not([type]),select,textarea):not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-moz-placeholder{text-align:right}:is(input:not([type]),select,textarea):not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::placeholder{text-align:right}:-webkit-any(input:not([type]),select,textarea):-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}:is(input:not([type]),select,textarea):-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}:is(input:not([type]),select,textarea):is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-moz-placeholder{text-align:left}:is(input:not([type]),select,textarea):is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::placeholder{text-align:left}input[type=range]{padding:calc(var(--gap)/4);width:100%}input[type=color]{height:calc(1.5*var(--rhythm));background:0 0;border:none;margin:0;padding:0}input[type=file]{padding:calc(var(--gap)/4)0;font:inherit;line-height:calc(var(--rhythm)/2)}input[type=file]::-webkit-file-upload-button{margin-top:.1em;margin-bottom:0}input[type=file]::file-selector-button{margin-top:.1em;margin-bottom:0}input[type=file]::file-selector-button{margin-top:.1em;margin-bottom:0}input[type=file]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-webkit-file-upload-button{margin-right:1ch}input[type=file]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::-ms-browse{margin-right:1ch}input[type=file]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi))::file-selector-button{margin-right:1ch}input[type=file]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-webkit-file-upload-button{margin-left:1ch}input[type=file]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::-ms-browse{margin-left:1ch}input[type=file]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi))::file-selector-button{margin-left:1ch}select[multiple]{vertical-align:top}optgroup:before{color:var(--muted-fg);font-style:normal}label[for]{padding-block:calc(var(--gap)/4);display:block}fieldset{padding:var(--gap);margin:var(--gap)0;border-radius:var(--border-radius);border:1px solid var(--graphical-fg);width:100%;position:relative}fieldset>legend+*{margin-top:0}details:not(specificity-hack){padding-top:0}details:not(specificity-hack):not([open]){padding-bottom:0}summary{margin:calc(0px - var(--gap));margin-top:calc(0px - var(--gap));padding-inline:var(--gap);font-family:var(--secondary-font);cursor:pointer;margin-bottom:0;font-weight:700}summary:focus-visible,summary:active{filter:brightness(.8);outline:none}dialog{inline-inset:0;background-color:var(--bg);color:var(--fg);border-color:var(--fg);width:fit-content;height:fit-content;margin:auto!important}dialog[open]::-webkit-backdrop{opacity:.4;background:#000;animation:2s bg;display:block}dialog[open]::backdrop{opacity:.4;background:#000;animation:2s bg;display:block}@keyframes bg{0%{background:0 0}}dialog:not([open]){display:none}.box,[role=menu],.sidebar-layout>header,[role=tabpanel],figure,details,:where(dialog){margin:var(--gap)0;padding:var(--gap);border-radius:var(--border-radius);background:var(--box-bg);border:1px solid var(--graphical-fg);overflow:clip}.titlebar{margin-inline:calc(0px - var(--gap));padding-inline:var(--gap);font:inherit;font-family:var(--secondary-font);translate:0 calc(-1px - var(--gap));background:var(--graphical-fg);color:var(--bg);text-shadow:0 .1em .2em var(--fg);border-bottom:1px solid;border-bottom-color:inherit;margin-bottom:calc(0px - var(--gap));font-weight:700}.sub-title,sub-title{color:var(--muted-fg);font-weight:400;display:block}.tool-bar,[role=toolbar]{gap:calc(var(--gap)/2);flex-flow:wrap;display:flex}:is(.tool-bar,[role=toolbar])>*{margin:0}.sidebar-layout header li{margin-block:calc(.5*var(--gap))}.sidebar-layout header a{font-weight:700}@media (width>=75ch){.sidebar-layout{grid-template-columns:25ch auto;display:grid;inset:0}.sidebar-layout>header{border-top:none;border-bottom:none;margin:0}.sidebar-layout>header:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){border-left:none}.sidebar-layout>header:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.sidebar-layout>header:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){border-right:none}.sidebar-layout>header:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.sidebar-layout>header:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){border-right:none}.sidebar-layout>:nth-child(2){--full-width:calc(100vw - 25ch);margin-top:var(--gap);overflow:auto}}.breadcrumbs[aria-label]{font-family:var(--secondary-font)}.breadcrumbs[aria-label] ul,.breadcrumbs[aria-label] ol{list-style:none}.breadcrumbs[aria-label] ul:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)),.breadcrumbs[aria-label] ol:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){padding-left:0}.breadcrumbs[aria-label] ul:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.breadcrumbs[aria-label] ul:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.breadcrumbs[aria-label] ol:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.breadcrumbs[aria-label] ol:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-right:0}.breadcrumbs[aria-label] ul:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.breadcrumbs[aria-label] ul:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.breadcrumbs[aria-label] ol:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.breadcrumbs[aria-label] ol:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-right:0}.breadcrumbs[aria-label] li{display:inline}.breadcrumbs[aria-label] li+li:before{content:" / "/"";content:" / ";display:inline}.breadcrumbs[aria-label] [aria-current=page]{font-weight:700}.chip,chip{font-family:var(--secondary-font);border:1px solid var(--accent);background:var(--box-bg);border-radius:calc(var(--rhythm)/2);padding-inline:calc(var(--rhythm)/2)}.navbar{padding:var(--rhythm);font-family:var(--secondary-font);background:var(--box-bg);border-bottom:1px solid var(--accent);scrollbar-width:thin;z-index:5;align-items:center;gap:var(--gap);flex-flow:row;display:flex;position:-webkit-sticky;position:sticky;top:0;left:0;right:0;overflow-x:auto}.navbar.expanded{flex-flow:column;align-items:start;max-height:90vh;overflow-y:auto}.navbar.expanded ul[role=list]{flex-flow:column}.navbar *{flex-shrink:0;margin-top:0;margin-bottom:0}.navbar:not(.expanded)>:first-child:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)),.navbar:not(.expanded) nav>:first-child:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){margin-left:auto}.navbar:not(.expanded)>:first-child:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.navbar:not(.expanded)>:first-child:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.navbar:not(.expanded) nav>:first-child:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.navbar:not(.expanded) nav>:first-child:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.navbar:not(.expanded)>:last-child:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)),.navbar:not(.expanded) nav>:last-child:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){margin-right:auto}.navbar:not(.expanded)>:last-child:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.navbar:not(.expanded)>:last-child:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.navbar:not(.expanded) nav>:last-child:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.navbar:not(.expanded) nav>:last-child:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){margin-left:auto}.navbar hr{align-self:stretch}.navbar nav ul[role=list]{gap:var(--rhythm);flex-flow:row;display:flex}.navbar nav ul[role=list] *{flex-shrink:0}.navbar nav ul[role=list]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){padding-left:0}.navbar nav ul[role=list]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.navbar nav ul[role=list]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-right:0}.navbar nav ul[role=list]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.navbar nav ul[role=list]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-right:0}.navbar a{padding-left:.2em;padding-right:.2em;font-weight:700;text-decoration:none}.navbar a:hover,.navbar a:focus{text-decoration:underline}.navbar [aria-current=page]{position:relative}.navbar [aria-current=page]:after{content:"";bottom:calc(-1*var(--gap));background:currentColor;width:100%;height:6px;display:block;position:absolute}.navbar.expanded [aria-current=page]:after{left:calc(-1*var(--gap));width:6px;height:100%;position:absolute;top:0}.permalink-anchor{display:none}:hover>.permalink-anchor{display:initial}button.iconbutton{color:currentColor;box-shadow:none;line-height:var(--rhythm);text-align:center;background:0 0;border:none;border-radius:50%;width:24px;height:24px;padding:0;font-size:24px;transition:font-weight .2s ease-in-out;display:inline-block}button.iconbutton:hover,button.iconbutton:focus-visible{box-shadow:none;outline:1px solid var(--accent);outline-offset:6px}button.iconbutton:active{box-shadow:none;outline-offset:3px;background:0 0}button.iconbutton[aria-pressed=true]{box-shadow:none;transform:none}[role=tablist]{scrollbar-width:thin;gap:.5ch;display:flex}[role=tab][role=tab]{all:initial;font-family:var(--secondary-font);padding:0 calc(var(--rhythm)/4);min-height:var(--rhythm);color:var(--fg);border:solid var(--graphical-fg);background:var(--interactive-bg);border-width:1px;margin:0;position:relative;bottom:-1px}[role=tab][role=tab]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)),[role=tab][role=tab]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[role=tab][role=tab]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){border-top-left-radius:.4em;border-top-right-radius:.4em}[role=tab][role=tab]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)),[role=tab][role=tab]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[role=tab][role=tab]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){border-top-left-radius:.4em;border-top-right-radius:.4em}[role=tab][role=tab]:active,[role=tab][role=tab][aria-selected=true]{background:var(--box-bg);border-bottom:1px solid #0000}[role=tab][role=tab]:hover{background-color:var(--box-bg);box-shadow:none}[role=tab][role=tab]:focus-visible{box-shadow:none;color:var(--accent);text-decoration:underline}[role=tabpanel]{z-index:1;margin-top:0}[role=tabpanel]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)),[role=tabpanel]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[role=tabpanel]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){border-top-left-radius:0;border-top-right-radius:0}[role=tabpanel]:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)),[role=tabpanel]:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[role=tabpanel]:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){border-top-left-radius:0;border-top-right-radius:0}[role=menu]{z-index:10;padding:calc(var(--gap)/2)0;flex-flow:column;margin:1px 0 0;display:flex;position:absolute}[role=menuitem]{padding:0 calc(var(--gap)/2);color:var(--fg);border-radius:0;text-decoration:none;display:block}[role=menuitem]:focus,[role=menuitem]:active{background:var(--accent);color:var(--bg)}[role=listbox]{list-style:none}[role=listbox] [role=option]{margin-inline:calc(-1*var(--gap));padding-inline:var(--gap);border-radius:0}[role=listbox] [role=option][aria-selected=true]{background:var(--interactive-bg)}[role=listbox] [role=option].active{--temporary-bg:var(--accent);--temporary-fg:var(--bg);--temporary-accent:parent-var(--muted-accent);--temporary-muted-accent:parent-var(--box-bg);background:var(--temporary-bg);color:var(--temporary-fg)}[role=listbox] [role=option].active>*{--bg:var(--temporary-bg);--fg:var(--temporary-fg);--accent:var(--temporary-accent);--muted-accent:var(--temporary-muted-accent)}[aria-orientation=vertical]{text-align:center;flex-direction:column;width:fit-content}.plain{--box-bg:var(--plain-bg);--accent:var(--plain-fg);--graphical-fg:var(--plain-graphical-fg)}.info{--box-bg:var(--info-bg);--accent:var(--info-fg);--graphical-fg:var(--info-graphical-fg)}.ok{--box-bg:var(--ok-bg);--accent:var(--ok-fg);--graphical-fg:var(--ok-graphical-fg)}.warn{--box-bg:var(--warn-bg);--accent:var(--warn-fg);--graphical-fg:var(--warn-graphical-fg)}.bad{--box-bg:var(--bad-bg);--accent:var(--bad-fg);--graphical-fg:var(--bad-graphical-fg)}.color{color:var(--accent)}.bg{background:var(--box-bg)}.border{border-style:solid;border-color:var(--graphical-fg)}:root{--fg:var(--gray-12);--muted-fg:var(--gray-10);--faded-fg:var(--gray-6);--graphical-fg:var(--plain-graphical-fg);--plain-fg:var(--blue-10);--info-fg:var(--blue-11);--ok-fg:var(--green-11);--bad-fg:var(--red-11);--warn-fg:var(--yellow-11);--plain-graphical-fg:var(--gray-6);--info-graphical-fg:var(--blue-6);--ok-graphical-fg:var(--green-6);--bad-graphical-fg:var(--red-6);--warn-graphical-fg:var(--yellow-6);--bg:var(--gray-0);--box-bg:var(--plain-bg);--interactive-bg:var(--gray-4);--plain-bg:var(--gray-1);--info-bg:var(--blue-1);--ok-bg:var(--green-1);--bad-bg:var(--red-1);--warn-bg:var(--yellow-1);--accent:var(--blue-10);--muted-accent:var(--blue-7);--rhythm:1.4rem;--line-length:40rem;--border-radius:.2rem;--main-font:"Source Sans 3","Source Sans Pro",-apple-system,system-ui,sans-serif;--secondary-font:var(--main-font);--mono-font:"M Plus Code Latin",monospace,monospace;--density:1;--full-width:100vw;--eff-line-length:min(calc(var(--full-width) - (2*var(--rhythm))),var(--line-length));--gutter-width:calc(( var(--full-width) - var(--eff-line-length))/2)}@media (prefers-color-scheme:dark){:root:not(.-no-dark-theme){--fg:var(--gray-0);--muted-fg:var(--gray-2);--faded-fg:var(--gray-7);--plain-bg:var(--gray-11);--info-bg:var(--blue-12);--ok-bg:var(--green-12);--bad-bg:var(--red-12);--warn-bg:var(--yellow-12);--plain-faded-fg:var(--blue-6);--info-faded-fg:var(--blue-6);--ok-faded-fg:var(--green-6);--bad-faded-fg:var(--red-6);--warn-faded-fg:var(--yellow-6);--bg:var(--gray-12);--box-bg:var(--gray-10);--interactive-bg:var(--gray-8);--plain-fg:(--blue-2);--info-fg:var(--blue-2);--ok-fg:var(--green-2);--bad-fg:var(--red-2);--warn-fg:var(--yellow-2);--accent:var(--blue-2);--muted-accent:var(--blue-5)}}*{--gap:calc(var(--rhythm)*var(--density));accent-color:var(--accent)}.textcolumns{--col-width:30ch;column-width:var(--col-width);column-gap:var(--gap);margin-block:var(--gap)}.textcolumns :first-child{margin-top:0!important}.text-align\:center{text-align:center}.center{place-items:center;display:grid}.container{max-width:var(--eff-line-length);margin-left:auto;margin-right:auto}.fullbleed{width:var(--full-width);transform:translateX(calc(-.5*var(--full-width)));border-left:none;border-right:none;border-radius:0;position:relative;left:50%}.fullscreen{border-left:none;border-right:none;border-radius:0;width:100vw;height:100vh;position:relative;left:50%;transform:translate(-50vw)}.width\:100\%{width:100%;max-width:100%}.height\:100\%{height:100%;max-height:100%}:is(body,.box,[role=menu],.sidebar-layout>header,[role=tabpanel],figure,details,dialog,aside,fieldset,dd,td,th)>:first-child:first-child:first-child:first-child,:is(body,.box,[role=menu],.sidebar-layout>header,[role=tabpanel],figure,details,dialog,aside,fieldset,dd,td,th)>:first-child>:first-child:first-child:first-child,:is(body,.box,[role=menu],.sidebar-layout>header,[role=tabpanel],figure,details,dialog,aside,fieldset,dd,td,th)>:first-child>:first-child>:first-child:first-child,:is(body,.box,[role=menu],.sidebar-layout>header,[role=tabpanel],figure,details,dialog,aside,fieldset,dd,td,th)>:first-child>:first-child>:first-child>:first-child{margin-top:0}:is(body,.box,[role=menu],.sidebar-layout>header,[role=tabpanel],figure,details,dialog,aside,fieldset,dd,td,th)>:last-child:last-child:last-child:last-child,:is(body,.box,[role=menu],.sidebar-layout>header,[role=tabpanel],figure,details,dialog,aside,fieldset,dd,td,th)>:last-child>:last-child:last-child:last-child,:is(body,.box,[role=menu],.sidebar-layout>header,[role=tabpanel],figure,details,dialog,aside,fieldset,dd,td,th)>:last-child>:last-child>:last-child:last-child,:is(body,.box,[role=menu],.sidebar-layout>header,[role=tabpanel],figure,details,dialog,aside,fieldset,dd,td,th)>:last-child>:last-child>:last-child>:last-child{margin-bottom:0}.padding{padding-inline:var(--gap)}.padding-block{padding-block:var(--gap)}.padding-block-start{padding-top:var(--gap)}.padding-block-end{padding-bottom:var(--gap)}.padding-inline{padding-inline:var(--gap)}.padding-inline-start:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){padding-left:var(--gap)}.padding-inline-start:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.padding-inline-start:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-right:var(--gap)}.padding-inline-start:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.padding-inline-start:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-right:var(--gap)}.padding-inline-end:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){padding-left:var(--gap)}.padding-inline-end:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.padding-inline-end:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-right:var(--gap)}.padding-inline-end:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.padding-inline-end:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){padding-right:var(--gap)}.margin{margin:var(--gap)}.margin-block{margin-block:var(--gap)}.margin-block-start{margin-top:var(--gap)}.margin-block-end{margin-bottom:var(--gap)}.margin-inline{margin-inline:var(--gap)}.margin-inline-start:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){margin-left:var(--gap)}.margin-inline-start:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.margin-inline-start:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.margin-inline-end:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){margin-right:var(--gap)}.margin-inline-start:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.margin-inline-start:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.margin-inline-end:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){margin-right:var(--gap)}.margin-inline-end:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.margin-inline-end:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){margin-left:var(--gap)}.margin-inline-end:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),.margin-inline-end:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){margin-left:var(--gap)}.flow-gap>:not(:last-child){margin-bottom:var(--gap)}.inline{display:inline}.block{display:block}.contents{display:contents}.table{width:100%;margin:0;display:table}.row,.rows>*{display:table-row}:is(.row,.rows>*):not(:last-child):not([specificity-hack])>*{margin-bottom:var(--gap)}:is(.row,.rows>*)>:not([specificity-hack]){vertical-align:top;display:table-cell}:is(.row,.rows>*)>*+:not([specificity-hack]){display:inline-block}:is(.row,.rows>*)>*+:not([specificity-hack]):not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){margin-left:var(--gap)}:is(.row,.rows>*)>*+:not([specificity-hack]):-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),:is(.row,.rows>*)>*+:not([specificity-hack]):is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){margin-right:var(--gap)}.big{font-size:1.4em;line-height:calc(1.5*var(--rhythm))}.nested-list ul,.nested-list ol{margin-top:0;margin-bottom:0}.fixed{position:fixed}.sticky{position:-webkit-sticky;position:sticky}.top{top:0}.right{right:0}.bottom{bottom:0}.left{left:0}.float\:left{float:left}.float\:right{float:right}.overflow\:auto{overflow:auto}.overflow\:scroll{overflow:scroll}.airy{--density:3}.spacious{--density:2}.dense{--density:1}.crowded{--density:.5}.packed{--density:0}.autodensity{--density:1}@media (width>=768px){.autodensity{--density:2}}@media (width>=1024px){.autodensity{--density:3}}.vh,v-h{clip:rect(0 0 0 0);-webkit-clip-path:inset(50%);clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;overflow:hidden}.all\:initial{all:initial}.bold{font-weight:700}.italic{font-style:italic}.italic em,.italic cite,.italic dfn,.italic var,.italic i,.italic address{font-style:normal}.allcaps{text-transform:uppercase;letter-spacing:.1rem}.primary-font{font-family:var(--primary-font)}.secondary-font{font-family:var(--secondary-font)}.display-font{font-family:var(--display-font)}.mono-font,.monospace{font-family:var(--mono-font)}.massivetext{font-size:calc(.13*var(--eff-line-length));letter-spacing:0;line-height:1em}.aestheticbreak{height:calc(.5*var(--gap));margin:0;padding:0;display:block}.f-row{gap:var(--gap);flex-direction:row;display:flex}.f-row>*{margin:0}.f-col{gap:var(--gap);flex-direction:column;display:flex}.f-col>*{margin:0}.f-switch{gap:var(--gap);--f-switch-threshold:55ch;flex-wrap:wrap;display:flex}.f-switch>*{flex-grow:1;flex-basis:calc((var(--f-switch-threshold) - 100%)*999);margin:0}.justify-content\:start{justify-content:start}.justify-content\:end{justify-content:end}.justify-content\:baseline{justify-content:baseline}.justify-content\:center{justify-content:center}.justify-content\:stretch{justify-content:stretch}.justify-content\:space-between{justify-content:space-between}.justify-content\:space-around{justify-content:space-around}.justify-content\:space-evenly{justify-content:space-evenly}.align-items\:start{align-items:start}.align-items\:end{align-items:end}.align-items\:baseline{align-items:baseline}.align-items\:center{align-items:center}.align-items\:stretch{align-items:stretch}.align-self\:start{align-self:start}.align-self\:end{align-self:end}.align-self\:baseline{align-self:baseline}.align-self\:center{align-self:center}.align-self\:stretch{align-self:stretch}.flex-grow\:0{flex-grow:0}.flex-grow\:1{flex-grow:1}.flex-grow\:2{flex-grow:2}.flex-grow\:3{flex-grow:3}.flex-grow\:4{flex-grow:4}.flex-grow\:5{flex-grow:5}.flex-grow\:6{flex-grow:6}.flex-grow\:7{flex-grow:7}.flex-grow\:8{flex-grow:8}.flex-grow\:9{flex-grow:9}.flex-grow\:10{flex-grow:10}.flex-grow\:11{flex-grow:11}.flex-grow\:12{flex-grow:12}.flex-wrap\:wrap{flex-wrap:wrap}.flex-wrap\:nowrap{flex-wrap:nowrap}.grid{grid-auto-columns:var(--grid-col-width,1fr);grid-auto-rows:var(--grid-row-width,auto);gap:var(--gap);display:grid}.grid>*{margin:0}.grid-even-rows{--grid-row-width:1fr}.grid-variable-cols{--grid-column-width:auto}[data-cols^="1 "]{grid-column-start:1}[data-cols$=\ 1]{grid-column-end:2}[data-cols="1"]{grid-column:1}[data-cols^="2 "]{grid-column-start:2}[data-cols$=\ 2]{grid-column-end:3}[data-cols="2"]{grid-column:2}[data-cols^="3 "]{grid-column-start:3}[data-cols$=\ 3]{grid-column-end:4}[data-cols="3"]{grid-column:3}[data-cols^="4 "]{grid-column-start:4}[data-cols$=\ 4]{grid-column-end:5}[data-cols="4"]{grid-column:4}[data-cols^="5 "]{grid-column-start:5}[data-cols$=\ 5]{grid-column-end:6}[data-cols="5"]{grid-column:5}[data-cols^="6 "]{grid-column-start:6}[data-cols$=\ 6]{grid-column-end:7}[data-cols="6"]{grid-column:6}[data-cols^="7 "]{grid-column-start:7}[data-cols$=\ 7]{grid-column-end:8}[data-cols="7"]{grid-column:7}[data-cols^="8 "]{grid-column-start:8}[data-cols$=\ 8]{grid-column-end:9}[data-cols="8"]{grid-column:8}[data-cols^="9 "]{grid-column-start:9}[data-cols$=\ 9]{grid-column-end:10}[data-cols="9"]{grid-column:9}[data-cols^="10 "]{grid-column-start:10}[data-cols$=\ 10]{grid-column-end:11}[data-cols="10"]{grid-column:10}[data-cols^="11 "]{grid-column-start:11}[data-cols$=\ 11]{grid-column-end:12}[data-cols="11"]{grid-column:11}[data-cols^="12 "]{grid-column-start:12}[data-cols$=\ 12]{grid-column-end:13}[data-cols="12"]{grid-column:12}[data-rows^="1 "]{grid-row-start:1}[data-rows$=\ 1]{grid-row-end:2}[data-rows="1"]{grid-row:1}[data-rows^="2 "]{grid-row-start:2}[data-rows$=\ 2]{grid-row-end:3}[data-rows="2"]{grid-row:2}[data-rows^="3 "]{grid-row-start:3}[data-rows$=\ 3]{grid-row-end:4}[data-rows="3"]{grid-row:3}[data-rows^="4 "]{grid-row-start:4}[data-rows$=\ 4]{grid-row-end:5}[data-rows="4"]{grid-row:4}[data-rows^="5 "]{grid-row-start:5}[data-rows$=\ 5]{grid-row-end:6}[data-rows="5"]{grid-row:5}[data-rows^="6 "]{grid-row-start:6}[data-rows$=\ 6]{grid-row-end:7}[data-rows="6"]{grid-row:6}[data-rows^="7 "]{grid-row-start:7}[data-rows$=\ 7]{grid-row-end:8}[data-rows="7"]{grid-row:7}[data-rows^="8 "]{grid-row-start:8}[data-rows$=\ 8]{grid-row-end:9}[data-rows="8"]{grid-row:8}[data-rows^="9 "]{grid-row-start:9}[data-rows$=\ 9]{grid-row-end:10}[data-rows="9"]{grid-row:9}[data-rows^="10 "]{grid-row-start:10}[data-rows$=\ 10]{grid-row-end:11}[data-rows="10"]{grid-row:10}[data-rows^="11 "]{grid-row-start:11}[data-rows$=\ 11]{grid-row-end:12}[data-rows="11"]{grid-row:11}[data-rows^="12 "]{grid-row-start:12}[data-rows$=\ 12]{grid-row-end:13}[data-rows="12"]{grid-row:12}@media (width<=768px){[data-cols\@s^="1 "]{grid-column-start:1}[data-cols\@s$=\ 1]{grid-column-end:2}[data-cols\@s="1"]{grid-column:1}[data-cols\@s^="2 "]{grid-column-start:2}[data-cols\@s$=\ 2]{grid-column-end:3}[data-cols\@s="2"]{grid-column:2}[data-cols\@s^="3 "]{grid-column-start:3}[data-cols\@s$=\ 3]{grid-column-end:4}[data-cols\@s="3"]{grid-column:3}[data-cols\@s^="4 "]{grid-column-start:4}[data-cols\@s$=\ 4]{grid-column-end:5}[data-cols\@s="4"]{grid-column:4}[data-cols\@s^="5 "]{grid-column-start:5}[data-cols\@s$=\ 5]{grid-column-end:6}[data-cols\@s="5"]{grid-column:5}[data-cols\@s^="6 "]{grid-column-start:6}[data-cols\@s$=\ 6]{grid-column-end:7}[data-cols\@s="6"]{grid-column:6}[data-cols\@s^="7 "]{grid-column-start:7}[data-cols\@s$=\ 7]{grid-column-end:8}[data-cols\@s="7"]{grid-column:7}[data-cols\@s^="8 "]{grid-column-start:8}[data-cols\@s$=\ 8]{grid-column-end:9}[data-cols\@s="8"]{grid-column:8}[data-cols\@s^="9 "]{grid-column-start:9}[data-cols\@s$=\ 9]{grid-column-end:10}[data-cols\@s="9"]{grid-column:9}[data-cols\@s^="10 "]{grid-column-start:10}[data-cols\@s$=\ 10]{grid-column-end:11}[data-cols\@s="10"]{grid-column:10}[data-cols\@s^="11 "]{grid-column-start:11}[data-cols\@s$=\ 11]{grid-column-end:12}[data-cols\@s="11"]{grid-column:11}[data-cols\@s^="12 "]{grid-column-start:12}[data-cols\@s$=\ 12]{grid-column-end:13}[data-cols\@s="12"]{grid-column:12}[data-rows\@s^="1 "]{grid-row-start:1}[data-rows\@s$=\ 1]{grid-row-end:2}[data-rows\@s="1"]{grid-row:1}[data-rows\@s^="2 "]{grid-row-start:2}[data-rows\@s$=\ 2]{grid-row-end:3}[data-rows\@s="2"]{grid-row:2}[data-rows\@s^="3 "]{grid-row-start:3}[data-rows\@s$=\ 3]{grid-row-end:4}[data-rows\@s="3"]{grid-row:3}[data-rows\@s^="4 "]{grid-row-start:4}[data-rows\@s$=\ 4]{grid-row-end:5}[data-rows\@s="4"]{grid-row:4}[data-rows\@s^="5 "]{grid-row-start:5}[data-rows\@s$=\ 5]{grid-row-end:6}[data-rows\@s="5"]{grid-row:5}[data-rows\@s^="6 "]{grid-row-start:6}[data-rows\@s$=\ 6]{grid-row-end:7}[data-rows\@s="6"]{grid-row:6}[data-rows\@s^="7 "]{grid-row-start:7}[data-rows\@s$=\ 7]{grid-row-end:8}[data-rows\@s="7"]{grid-row:7}[data-rows\@s^="8 "]{grid-row-start:8}[data-rows\@s$=\ 8]{grid-row-end:9}[data-rows\@s="8"]{grid-row:8}[data-rows\@s^="9 "]{grid-row-start:9}[data-rows\@s$=\ 9]{grid-row-end:10}[data-rows\@s="9"]{grid-row:9}[data-rows\@s^="10 "]{grid-row-start:10}[data-rows\@s$=\ 10]{grid-row-end:11}[data-rows\@s="10"]{grid-row:10}[data-rows\@s^="11 "]{grid-row-start:11}[data-rows\@s$=\ 11]{grid-row-end:12}[data-rows\@s="11"]{grid-row:11}[data-rows\@s^="12 "]{grid-row-start:12}[data-rows\@s$=\ 12]{grid-row-end:13}[data-rows\@s="12"]{grid-row:12}}@media (width>=1024px){[data-cols\@l^="1 "]{grid-column-start:1}[data-cols\@l$=\ 1]{grid-column-end:2}[data-cols\@l="1"]{grid-column:1}[data-cols\@l^="2 "]{grid-column-start:2}[data-cols\@l$=\ 2]{grid-column-end:3}[data-cols\@l="2"]{grid-column:2}[data-cols\@l^="3 "]{grid-column-start:3}[data-cols\@l$=\ 3]{grid-column-end:4}[data-cols\@l="3"]{grid-column:3}[data-cols\@l^="4 "]{grid-column-start:4}[data-cols\@l$=\ 4]{grid-column-end:5}[data-cols\@l="4"]{grid-column:4}[data-cols\@l^="5 "]{grid-column-start:5}[data-cols\@l$=\ 5]{grid-column-end:6}[data-cols\@l="5"]{grid-column:5}[data-cols\@l^="6 "]{grid-column-start:6}[data-cols\@l$=\ 6]{grid-column-end:7}[data-cols\@l="6"]{grid-column:6}[data-cols\@l^="7 "]{grid-column-start:7}[data-cols\@l$=\ 7]{grid-column-end:8}[data-cols\@l="7"]{grid-column:7}[data-cols\@l^="8 "]{grid-column-start:8}[data-cols\@l$=\ 8]{grid-column-end:9}[data-cols\@l="8"]{grid-column:8}[data-cols\@l^="9 "]{grid-column-start:9}[data-cols\@l$=\ 9]{grid-column-end:10}[data-cols\@l="9"]{grid-column:9}[data-cols\@l^="10 "]{grid-column-start:10}[data-cols\@l$=\ 10]{grid-column-end:11}[data-cols\@l="10"]{grid-column:10}[data-cols\@l^="11 "]{grid-column-start:11}[data-cols\@l$=\ 11]{grid-column-end:12}[data-cols\@l="11"]{grid-column:11}[data-cols\@l^="12 "]{grid-column-start:12}[data-cols\@l$=\ 12]{grid-column-end:13}[data-cols\@l="12"]{grid-column:12}[data-rows\@l^="1 "]{grid-row-start:1}[data-rows\@l$=\ 1]{grid-row-end:2}[data-rows\@l="1"]{grid-row:1}[data-rows\@l^="2 "]{grid-row-start:2}[data-rows\@l$=\ 2]{grid-row-end:3}[data-rows\@l="2"]{grid-row:2}[data-rows\@l^="3 "]{grid-row-start:3}[data-rows\@l$=\ 3]{grid-row-end:4}[data-rows\@l="3"]{grid-row:3}[data-rows\@l^="4 "]{grid-row-start:4}[data-rows\@l$=\ 4]{grid-row-end:5}[data-rows\@l="4"]{grid-row:4}[data-rows\@l^="5 "]{grid-row-start:5}[data-rows\@l$=\ 5]{grid-row-end:6}[data-rows\@l="5"]{grid-row:5}[data-rows\@l^="6 "]{grid-row-start:6}[data-rows\@l$=\ 6]{grid-row-end:7}[data-rows\@l="6"]{grid-row:6}[data-rows\@l^="7 "]{grid-row-start:7}[data-rows\@l$=\ 7]{grid-row-end:8}[data-rows\@l="7"]{grid-row:7}[data-rows\@l^="8 "]{grid-row-start:8}[data-rows\@l$=\ 8]{grid-row-end:9}[data-rows\@l="8"]{grid-row:8}[data-rows\@l^="9 "]{grid-row-start:9}[data-rows\@l$=\ 9]{grid-row-end:10}[data-rows\@l="9"]{grid-row:9}[data-rows\@l^="10 "]{grid-row-start:10}[data-rows\@l$=\ 10]{grid-row-end:11}[data-rows\@l="10"]{grid-row:10}[data-rows\@l^="11 "]{grid-row-start:11}[data-rows\@l$=\ 11]{grid-row-end:12}[data-rows\@l="11"]{grid-row:11}[data-rows\@l^="12 "]{grid-row-start:12}[data-rows\@l$=\ 12]{grid-row-end:13}[data-rows\@l="12"]{grid-row:12}} \ No newline at end of file diff --git a/OpenShow/core/static/js/htmx.js b/OpenShow/core/static/js/htmx.js new file mode 100644 index 0000000..de5f0f1 --- /dev/null +++ b/OpenShow/core/static/js/htmx.js @@ -0,0 +1 @@ +(function(e,t){if(typeof define==="function"&&define.amd){define([],t)}else if(typeof module==="object"&&module.exports){module.exports=t()}else{e.htmx=e.htmx||t()}})(typeof self!=="undefined"?self:this,function(){return function(){"use strict";var Q={onLoad:F,process:zt,on:de,off:ge,trigger:ce,ajax:Nr,find:C,findAll:f,closest:v,values:function(e,t){var r=dr(e,t||"post");return r.values},remove:_,addClass:z,removeClass:n,toggleClass:$,takeClass:W,defineExtension:Ur,removeExtension:Br,logAll:V,logNone:j,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:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",useTemplateFragments:false,scrollBehavior:"smooth",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get"],selfRequestsOnly:false,ignoreTitle:false,scrollIntoViewOnBoost:true,triggerSpecsCache:null},parseInterval:d,_:t,createEventSource:function(e){return new EventSource(e,{withCredentials:true})},createWebSocket:function(e){var t=new WebSocket(e,[]);t.binaryType=Q.config.wsBinaryType;return t},version:"1.9.12"};var r={addTriggerHandler:Lt,bodyContains:se,canAccessLocalStorage:U,findThisElement:xe,filterValues:yr,hasAttribute:o,getAttributeValue:te,getClosestAttributeValue:ne,getClosestMatch:c,getExpressionVars:Hr,getHeaders:xr,getInputValues:dr,getInternalData:ae,getSwapSpecification:wr,getTriggerSpecs:it,getTarget:ye,makeFragment:l,mergeObjects:le,makeSettleInfo:T,oobSwap:Ee,querySelectorExt:ue,selectAndSwap:je,settleImmediately:nr,shouldCancel:ut,triggerEvent:ce,triggerErrorEvent:fe,withExtensions:R};var w=["get","post","put","delete","patch"];var i=w.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");var S=e("head"),q=e("title"),H=e("svg",true);function e(e,t){return new RegExp("<"+e+"(\\s[^>]*>|>)([\\s\\S]*?)<\\/"+e+">",!!t?"gim":"im")}function d(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.getAttribute&&e.getAttribute(t)}function o(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){return e.parentElement}function re(){return document}function c(e,t){while(e&&!t(e)){e=u(e)}return e?e:null}function L(e,t,r){var n=te(t,r);var i=te(t,"hx-disinherit");if(e!==t&&i&&(i==="*"||i.split(" ").indexOf(r)>=0)){return"unset"}else{return n}}function ne(t,r){var n=null;c(t,function(e){return n=L(t,e,r)});if(n!=="unset"){return n}}function h(e,t){var r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector;return r&&r.call(e,t)}function A(e){var t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;var r=t.exec(e);if(r){return r[1].toLowerCase()}else{return""}}function s(e,t){var r=new DOMParser;var n=r.parseFromString(e,"text/html");var i=n.body;while(t>0){t--;i=i.firstChild}if(i==null){i=re().createDocumentFragment()}return i}function N(e){return/",0);var a=i.querySelector("template").content;if(Q.config.allowScriptTags){oe(a.querySelectorAll("script"),function(e){if(Q.config.inlineScriptNonce){e.nonce=Q.config.inlineScriptNonce}e.htmxExecuted=navigator.userAgent.indexOf("Firefox")===-1})}else{oe(a.querySelectorAll("script"),function(e){_(e)})}return a}switch(r){case"thead":case"tbody":case"tfoot":case"colgroup":case"caption":return s(""+n+"
",1);case"col":return s(""+n+"
",2);case"tr":return s(""+n+"
",2);case"td":case"th":return s(""+n+"
",3);case"script":case"style":return s("
"+n+"
",1);default:return s(n,0)}}function ie(e){if(e){e()}}function I(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function k(e){return I(e,"Function")}function P(e){return I(e,"Object")}function ae(e){var t="htmx-internal-data";var r=e[t];if(!r){r=e[t]={}}return r}function M(e){var t=[];if(e){for(var r=0;r=0}function se(e){if(e.getRootNode&&e.getRootNode()instanceof window.ShadowRoot){return re().body.contains(e.getRootNode().host)}else{return re().body.contains(e)}}function D(e){return e.trim().split(/\s+/)}function le(e,t){for(var r in t){if(t.hasOwnProperty(r)){e[r]=t[r]}}return e}function E(e){try{return JSON.parse(e)}catch(e){b(e);return null}}function U(){var e="htmx:localStorageTest";try{localStorage.setItem(e,e);localStorage.removeItem(e);return true}catch(e){return false}}function B(t){try{var e=new URL(t);if(e){t=e.pathname+e.search}if(!/^\/$/.test(t)){t=t.replace(/\/+$/,"")}return t}catch(e){return t}}function t(e){return Tr(re().body,function(){return eval(e)})}function F(t){var e=Q.on("htmx:load",function(e){t(e.detail.elt)});return e}function V(){Q.logger=function(e,t,r){if(console){console.log(t,e,r)}}}function j(){Q.logger=null}function C(e,t){if(t){return e.querySelector(t)}else{return C(re(),e)}}function f(e,t){if(t){return e.querySelectorAll(t)}else{return f(re(),e)}}function _(e,t){e=p(e);if(t){setTimeout(function(){_(e);e=null},t)}else{e.parentElement.removeChild(e)}}function z(e,t,r){e=p(e);if(r){setTimeout(function(){z(e,t);e=null},r)}else{e.classList&&e.classList.add(t)}}function n(e,t,r){e=p(e);if(r){setTimeout(function(){n(e,t);e=null},r)}else{if(e.classList){e.classList.remove(t);if(e.classList.length===0){e.removeAttribute("class")}}}}function $(e,t){e=p(e);e.classList.toggle(t)}function W(e,t){e=p(e);oe(e.parentElement.children,function(e){n(e,t)});z(e,t)}function v(e,t){e=p(e);if(e.closest){return e.closest(t)}else{do{if(e==null||h(e,t)){return e}}while(e=e&&u(e));return null}}function g(e,t){return e.substring(0,t.length)===t}function G(e,t){return e.substring(e.length-t.length)===t}function J(e){var t=e.trim();if(g(t,"<")&&G(t,"/>")){return t.substring(1,t.length-2)}else{return t}}function Z(e,t){if(t.indexOf("closest ")===0){return[v(e,J(t.substr(8)))]}else if(t.indexOf("find ")===0){return[C(e,J(t.substr(5)))]}else if(t==="next"){return[e.nextElementSibling]}else if(t.indexOf("next ")===0){return[K(e,J(t.substr(5)))]}else if(t==="previous"){return[e.previousElementSibling]}else if(t.indexOf("previous ")===0){return[Y(e,J(t.substr(9)))]}else if(t==="document"){return[document]}else if(t==="window"){return[window]}else if(t==="body"){return[document.body]}else{return re().querySelectorAll(J(t))}}var K=function(e,t){var r=re().querySelectorAll(t);for(var n=0;n=0;n--){var i=r[n];if(i.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_FOLLOWING){return i}}};function ue(e,t){if(t){return Z(e,t)[0]}else{return Z(re().body,e)[0]}}function p(e){if(I(e,"String")){return C(e)}else{return e}}function ve(e,t,r){if(k(t)){return{target:re().body,event:e,listener:t}}else{return{target:p(e),event:t,listener:r}}}function de(t,r,n){jr(function(){var e=ve(t,r,n);e.target.addEventListener(e.event,e.listener)});var e=k(r);return e?r:n}function ge(t,r,n){jr(function(){var e=ve(t,r,n);e.target.removeEventListener(e.event,e.listener)});return k(r)?r:n}var pe=re().createElement("output");function me(e,t){var r=ne(e,t);if(r){if(r==="this"){return[xe(e,t)]}else{var n=Z(e,r);if(n.length===0){b('The selector "'+r+'" on '+t+" returned no matches!");return[pe]}else{return n}}}}function xe(e,t){return c(e,function(e){return te(e,t)!=null})}function ye(e){var t=ne(e,"hx-target");if(t){if(t==="this"){return xe(e,"hx-target")}else{return ue(e,t)}}else{var r=ae(e);if(r.boosted){return re().body}else{return e}}}function be(e){var t=Q.config.attributesToSettle;for(var r=0;r0){o=e.substr(0,e.indexOf(":"));t=e.substr(e.indexOf(":")+1,e.length)}else{o=e}var r=re().querySelectorAll(t);if(r){oe(r,function(e){var t;var r=i.cloneNode(true);t=re().createDocumentFragment();t.appendChild(r);if(!Se(o,e)){t=r}var n={shouldSwap:true,target:e,fragment:t};if(!ce(e,"htmx:oobBeforeSwap",n))return;e=n.target;if(n["shouldSwap"]){Fe(o,e,e,t,a)}oe(a.elts,function(e){ce(e,"htmx:oobAfterSwap",n)})});i.parentNode.removeChild(i)}else{i.parentNode.removeChild(i);fe(re().body,"htmx:oobErrorNoTarget",{content:i})}return e}function Ce(e,t,r){var n=ne(e,"hx-select-oob");if(n){var i=n.split(",");for(var a=0;a0){var r=t.replace("'","\\'");var n=e.tagName.replace(":","\\:");var i=o.querySelector(n+"[id='"+r+"']");if(i&&i!==o){var a=e.cloneNode();we(e,i);s.tasks.push(function(){we(e,a)})}}})}function Oe(e){return function(){n(e,Q.config.addedClass);zt(e);Nt(e);qe(e);ce(e,"htmx:load")}}function qe(e){var t="[autofocus]";var r=h(e,t)?e:e.querySelector(t);if(r!=null){r.focus()}}function a(e,t,r,n){Te(e,r,n);while(r.childNodes.length>0){var i=r.firstChild;z(i,Q.config.addedClass);e.insertBefore(i,t);if(i.nodeType!==Node.TEXT_NODE&&i.nodeType!==Node.COMMENT_NODE){n.tasks.push(Oe(i))}}}function He(e,t){var r=0;while(r-1){var t=e.replace(H,"");var r=t.match(q);if(r){return r[2]}}}function je(e,t,r,n,i,a){i.title=Ve(n);var o=l(n);if(o){Ce(r,o,i);o=Be(r,o,a);Re(o);return Fe(e,r,t,o,i)}}function _e(e,t,r){var n=e.getResponseHeader(t);if(n.indexOf("{")===0){var i=E(n);for(var a in i){if(i.hasOwnProperty(a)){var o=i[a];if(!P(o)){o={value:o}}ce(r,a,o)}}}else{var s=n.split(",");for(var l=0;l0){var o=t[0];if(o==="]"){n--;if(n===0){if(a===null){i=i+"true"}t.shift();i+=")})";try{var s=Tr(e,function(){return Function(i)()},function(){return true});s.source=i;return s}catch(e){fe(re().body,"htmx:syntax:error",{error:e,source:i});return null}}}else if(o==="["){n++}if(Qe(o,a,r)){i+="(("+r+"."+o+") ? ("+r+"."+o+") : (window."+o+"))"}else{i=i+o}a=t.shift()}}}function y(e,t){var r="";while(e.length>0&&!t.test(e[0])){r+=e.shift()}return r}function tt(e){var t;if(e.length>0&&Ze.test(e[0])){e.shift();t=y(e,Ke).trim();e.shift()}else{t=y(e,x)}return t}var rt="input, textarea, select";function nt(e,t,r){var n=[];var i=Ye(t);do{y(i,Je);var a=i.length;var o=y(i,/[,\[\s]/);if(o!==""){if(o==="every"){var s={trigger:"every"};y(i,Je);s.pollInterval=d(y(i,/[,\[\s]/));y(i,Je);var l=et(e,i,"event");if(l){s.eventFilter=l}n.push(s)}else if(o.indexOf("sse:")===0){n.push({trigger:"sse",sseEvent:o.substr(4)})}else{var u={trigger:o};var l=et(e,i,"event");if(l){u.eventFilter=l}while(i.length>0&&i[0]!==","){y(i,Je);var f=i.shift();if(f==="changed"){u.changed=true}else if(f==="once"){u.once=true}else if(f==="consume"){u.consume=true}else if(f==="delay"&&i[0]===":"){i.shift();u.delay=d(y(i,x))}else if(f==="from"&&i[0]===":"){i.shift();if(Ze.test(i[0])){var c=tt(i)}else{var c=y(i,x);if(c==="closest"||c==="find"||c==="next"||c==="previous"){i.shift();var h=tt(i);if(h.length>0){c+=" "+h}}}u.from=c}else if(f==="target"&&i[0]===":"){i.shift();u.target=tt(i)}else if(f==="throttle"&&i[0]===":"){i.shift();u.throttle=d(y(i,x))}else if(f==="queue"&&i[0]===":"){i.shift();u.queue=y(i,x)}else if(f==="root"&&i[0]===":"){i.shift();u[f]=tt(i)}else if(f==="threshold"&&i[0]===":"){i.shift();u[f]=y(i,x)}else{fe(e,"htmx:syntax:error",{token:i.shift()})}}n.push(u)}}if(i.length===a){fe(e,"htmx:syntax:error",{token:i.shift()})}y(i,Je)}while(i[0]===","&&i.shift());if(r){r[t]=n}return n}function it(e){var t=te(e,"hx-trigger");var r=[];if(t){var n=Q.config.triggerSpecsCache;r=n&&n[t]||nt(e,t,n)}if(r.length>0){return r}else if(h(e,"form")){return[{trigger:"submit"}]}else if(h(e,'input[type="button"], input[type="submit"]')){return[{trigger:"click"}]}else if(h(e,rt)){return[{trigger:"change"}]}else{return[{trigger:"click"}]}}function at(e){ae(e).cancelled=true}function ot(e,t,r){var n=ae(e);n.timeout=setTimeout(function(){if(se(e)&&n.cancelled!==true){if(!ct(r,e,Wt("hx:poll:trigger",{triggerSpec:r,target:e}))){t(e)}ot(e,t,r)}},r.pollInterval)}function st(e){return location.hostname===e.hostname&&ee(e,"href")&&ee(e,"href").indexOf("#")!==0}function lt(t,r,e){if(t.tagName==="A"&&st(t)&&(t.target===""||t.target==="_self")||t.tagName==="FORM"){r.boosted=true;var n,i;if(t.tagName==="A"){n="get";i=ee(t,"href")}else{var a=ee(t,"method");n=a?a.toLowerCase():"get";if(n==="get"){}i=ee(t,"action")}e.forEach(function(e){ht(t,function(e,t){if(v(e,Q.config.disableSelector)){m(e);return}he(n,i,e,t)},r,e,true)})}}function ut(e,t){if(e.type==="submit"||e.type==="click"){if(t.tagName==="FORM"){return true}if(h(t,'input[type="submit"], button')&&v(t,"form")!==null){return true}if(t.tagName==="A"&&t.href&&(t.getAttribute("href")==="#"||t.getAttribute("href").indexOf("#")!==0)){return true}}return false}function ft(e,t){return ae(e).boosted&&e.tagName==="A"&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function ct(e,t,r){var n=e.eventFilter;if(n){try{return n.call(t,r)!==true}catch(e){fe(re().body,"htmx:eventFilter:error",{error:e,source:n.source});return true}}return false}function ht(a,o,e,s,l){var u=ae(a);var t;if(s.from){t=Z(a,s.from)}else{t=[a]}if(s.changed){t.forEach(function(e){var t=ae(e);t.lastValue=e.value})}oe(t,function(n){var i=function(e){if(!se(a)){n.removeEventListener(s.trigger,i);return}if(ft(a,e)){return}if(l||ut(e,a)){e.preventDefault()}if(ct(s,a,e)){return}var t=ae(e);t.triggerSpec=s;if(t.handledFor==null){t.handledFor=[]}if(t.handledFor.indexOf(a)<0){t.handledFor.push(a);if(s.consume){e.stopPropagation()}if(s.target&&e.target){if(!h(e.target,s.target)){return}}if(s.once){if(u.triggeredOnce){return}else{u.triggeredOnce=true}}if(s.changed){var r=ae(n);if(r.lastValue===n.value){return}r.lastValue=n.value}if(u.delayed){clearTimeout(u.delayed)}if(u.throttle){return}if(s.throttle>0){if(!u.throttle){o(a,e);u.throttle=setTimeout(function(){u.throttle=null},s.throttle)}}else if(s.delay>0){u.delayed=setTimeout(function(){o(a,e)},s.delay)}else{ce(a,"htmx:trigger");o(a,e)}}};if(e.listenerInfos==null){e.listenerInfos=[]}e.listenerInfos.push({trigger:s.trigger,listener:i,on:n});n.addEventListener(s.trigger,i)})}var vt=false;var dt=null;function gt(){if(!dt){dt=function(){vt=true};window.addEventListener("scroll",dt);setInterval(function(){if(vt){vt=false;oe(re().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"),function(e){pt(e)})}},200)}}function pt(t){if(!o(t,"data-hx-revealed")&&X(t)){t.setAttribute("data-hx-revealed","true");var e=ae(t);if(e.initHash){ce(t,"revealed")}else{t.addEventListener("htmx:afterProcessNode",function(e){ce(t,"revealed")},{once:true})}}}function mt(e,t,r){var n=D(r);for(var i=0;i=0){var t=wt(n);setTimeout(function(){xt(s,r,n+1)},t)}};t.onopen=function(e){n=0};ae(s).webSocket=t;t.addEventListener("message",function(e){if(yt(s)){return}var t=e.data;R(s,function(e){t=e.transformResponse(t,null,s)});var r=T(s);var n=l(t);var i=M(n.children);for(var a=0;a0){ce(u,"htmx:validation:halted",i);return}t.send(JSON.stringify(l));if(ut(e,u)){e.preventDefault()}})}else{fe(u,"htmx:noWebSocketSourceError")}}function wt(e){var t=Q.config.wsReconnectDelay;if(typeof t==="function"){return t(e)}if(t==="full-jitter"){var r=Math.min(e,6);var n=1e3*Math.pow(2,r);return n*Math.random()}b('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"')}function St(e,t,r){var n=D(r);for(var i=0;i0){setTimeout(i,n)}else{i()}}function Ht(t,i,e){var a=false;oe(w,function(r){if(o(t,"hx-"+r)){var n=te(t,"hx-"+r);a=true;i.path=n;i.verb=r;e.forEach(function(e){Lt(t,e,i,function(e,t){if(v(e,Q.config.disableSelector)){m(e);return}he(r,n,e,t)})})}});return a}function Lt(n,e,t,r){if(e.sseEvent){Rt(n,r,e.sseEvent)}else if(e.trigger==="revealed"){gt();ht(n,r,t,e);pt(n)}else if(e.trigger==="intersect"){var i={};if(e.root){i.root=ue(n,e.root)}if(e.threshold){i.threshold=parseFloat(e.threshold)}var a=new IntersectionObserver(function(e){for(var t=0;t0){t.polling=true;ot(n,r,e)}else{ht(n,r,t,e)}}function At(e){if(!e.htmxExecuted&&Q.config.allowScriptTags&&(e.type==="text/javascript"||e.type==="module"||e.type==="")){var t=re().createElement("script");oe(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}var r=e.parentElement;try{r.insertBefore(t,e)}catch(e){b(e)}finally{if(e.parentElement){e.parentElement.removeChild(e)}}}}function Nt(e){if(h(e,"script")){At(e)}oe(f(e,"script"),function(e){At(e)})}function It(e){var t=e.attributes;if(!t){return false}for(var r=0;r0){var o=n.shift();var s=o.match(/^\s*([a-zA-Z:\-\.]+:)(.*)/);if(a===0&&s){o.split(":");i=s[1].slice(0,-1);r[i]=s[2]}else{r[i]+=o}a+=Bt(o)}for(var l in r){Ft(e,l,r[l])}}}function jt(e){Ae(e);for(var t=0;tQ.config.historyCacheSize){i.shift()}while(i.length>0){try{localStorage.setItem("htmx-history-cache",JSON.stringify(i));break}catch(e){fe(re().body,"htmx:historyCacheError",{cause:e,cache:i});i.shift()}}}function Yt(e){if(!U()){return null}e=B(e);var t=E(localStorage.getItem("htmx-history-cache"))||[];for(var r=0;r=200&&this.status<400){ce(re().body,"htmx:historyCacheMissLoad",o);var e=l(this.response);e=e.querySelector("[hx-history-elt],[data-hx-history-elt]")||e;var t=Zt();var r=T(t);var n=Ve(this.response);if(n){var i=C("title");if(i){i.innerHTML=n}else{window.document.title=n}}Ue(t,e,r);nr(r.tasks);Jt=a;ce(re().body,"htmx:historyRestore",{path:a,cacheMiss:true,serverResponse:this.response})}else{fe(re().body,"htmx:historyCacheMissLoadError",o)}};e.send()}function ar(e){er();e=e||location.pathname+location.search;var t=Yt(e);if(t){var r=l(t.content);var n=Zt();var i=T(n);Ue(n,r,i);nr(i.tasks);document.title=t.title;setTimeout(function(){window.scrollTo(0,t.scroll)},0);Jt=e;ce(re().body,"htmx:historyRestore",{path:e,item:t})}else{if(Q.config.refreshOnHistoryMiss){window.location.reload(true)}else{ir(e)}}}function or(e){var t=me(e,"hx-indicator");if(t==null){t=[e]}oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)+1;e.classList["add"].call(e.classList,Q.config.requestClass)});return t}function sr(e){var t=me(e,"hx-disabled-elt");if(t==null){t=[]}oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)+1;e.setAttribute("disabled","")});return t}function lr(e,t){oe(e,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.classList["remove"].call(e.classList,Q.config.requestClass)}});oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.removeAttribute("disabled")}})}function ur(e,t){for(var r=0;r=0}function wr(e,t){var r=t?t:ne(e,"hx-swap");var n={swapStyle:ae(e).boosted?"innerHTML":Q.config.defaultSwapStyle,swapDelay:Q.config.defaultSwapDelay,settleDelay:Q.config.defaultSettleDelay};if(Q.config.scrollIntoViewOnBoost&&ae(e).boosted&&!br(e)){n["show"]="top"}if(r){var i=D(r);if(i.length>0){for(var a=0;a0?l.join(":"):null;n["scroll"]=u;n["scrollTarget"]=f}else if(o.indexOf("show:")===0){var c=o.substr(5);var l=c.split(":");var h=l.pop();var f=l.length>0?l.join(":"):null;n["show"]=h;n["showTarget"]=f}else if(o.indexOf("focus-scroll:")===0){var v=o.substr("focus-scroll:".length);n["focusScroll"]=v=="true"}else if(a==0){n["swapStyle"]=o}else{b("Unknown modifier in hx-swap: "+o)}}}}return n}function Sr(e){return ne(e,"hx-encoding")==="multipart/form-data"||h(e,"form")&&ee(e,"enctype")==="multipart/form-data"}function Er(t,r,n){var i=null;R(r,function(e){if(i==null){i=e.encodeParameters(t,n,r)}});if(i!=null){return i}else{if(Sr(r)){return mr(n)}else{return pr(n)}}}function T(e){return{tasks:[],elts:[e]}}function Cr(e,t){var r=e[0];var n=e[e.length-1];if(t.scroll){var i=null;if(t.scrollTarget){i=ue(r,t.scrollTarget)}if(t.scroll==="top"&&(r||i)){i=i||r;i.scrollTop=0}if(t.scroll==="bottom"&&(n||i)){i=i||n;i.scrollTop=i.scrollHeight}}if(t.show){var i=null;if(t.showTarget){var a=t.showTarget;if(t.showTarget==="window"){a="body"}i=ue(r,a)}if(t.show==="top"&&(r||i)){i=i||r;i.scrollIntoView({block:"start",behavior:Q.config.scrollBehavior})}if(t.show==="bottom"&&(n||i)){i=i||n;i.scrollIntoView({block:"end",behavior:Q.config.scrollBehavior})}}}function Rr(e,t,r,n){if(n==null){n={}}if(e==null){return n}var i=te(e,t);if(i){var a=i.trim();var o=r;if(a==="unset"){return null}if(a.indexOf("javascript:")===0){a=a.substr(11);o=true}else if(a.indexOf("js:")===0){a=a.substr(3);o=true}if(a.indexOf("{")!==0){a="{"+a+"}"}var s;if(o){s=Tr(e,function(){return Function("return ("+a+")")()},{})}else{s=E(a)}for(var l in s){if(s.hasOwnProperty(l)){if(n[l]==null){n[l]=s[l]}}}}return Rr(u(e),t,r,n)}function Tr(e,t,r){if(Q.config.allowEval){return t()}else{fe(e,"htmx:evalDisallowedError");return r}}function Or(e,t){return Rr(e,"hx-vars",true,t)}function qr(e,t){return Rr(e,"hx-vals",false,t)}function Hr(e){return le(Or(e),qr(e))}function Lr(t,r,n){if(n!==null){try{t.setRequestHeader(r,n)}catch(e){t.setRequestHeader(r,encodeURIComponent(n));t.setRequestHeader(r+"-URI-AutoEncoded","true")}}}function Ar(t){if(t.responseURL&&typeof URL!=="undefined"){try{var e=new URL(t.responseURL);return e.pathname+e.search}catch(e){fe(re().body,"htmx:badResponseUrl",{url:t.responseURL})}}}function O(e,t){return t.test(e.getAllResponseHeaders())}function Nr(e,t,r){e=e.toLowerCase();if(r){if(r instanceof Element||I(r,"String")){return he(e,t,null,null,{targetOverride:p(r),returnPromise:true})}else{return he(e,t,p(r.source),r.event,{handler:r.handler,headers:r.headers,values:r.values,targetOverride:p(r.target),swapOverride:r.swap,select:r.select,returnPromise:true})}}else{return he(e,t,null,null,{returnPromise:true})}}function Ir(e){var t=[];while(e){t.push(e);e=e.parentElement}return t}function kr(e,t,r){var n;var i;if(typeof URL==="function"){i=new URL(t,document.location.href);var a=document.location.origin;n=a===i.origin}else{i=t;n=g(t,document.location.origin)}if(Q.config.selfRequestsOnly){if(!n){return false}}return ce(e,"htmx:validateUrl",le({url:i,sameHost:n},r))}function he(t,r,n,i,a,e){var o=null;var s=null;a=a!=null?a:{};if(a.returnPromise&&typeof Promise!=="undefined"){var l=new Promise(function(e,t){o=e;s=t})}if(n==null){n=re().body}var M=a.handler||Mr;var X=a.select||null;if(!se(n)){ie(o);return l}var u=a.targetOverride||ye(n);if(u==null||u==pe){fe(n,"htmx:targetError",{target:te(n,"hx-target")});ie(s);return l}var f=ae(n);var c=f.lastButtonClicked;if(c){var h=ee(c,"formaction");if(h!=null){r=h}var v=ee(c,"formmethod");if(v!=null){if(v.toLowerCase()!=="dialog"){t=v}}}var d=ne(n,"hx-confirm");if(e===undefined){var D=function(e){return he(t,r,n,i,a,!!e)};var U={target:u,elt:n,path:r,verb:t,triggeringEvent:i,etc:a,issueRequest:D,question:d};if(ce(n,"htmx:confirm",U)===false){ie(o);return l}}var g=n;var p=ne(n,"hx-sync");var m=null;var x=false;if(p){var B=p.split(":");var F=B[0].trim();if(F==="this"){g=xe(n,"hx-sync")}else{g=ue(n,F)}p=(B[1]||"drop").trim();f=ae(g);if(p==="drop"&&f.xhr&&f.abortable!==true){ie(o);return l}else if(p==="abort"){if(f.xhr){ie(o);return l}else{x=true}}else if(p==="replace"){ce(g,"htmx:abort")}else if(p.indexOf("queue")===0){var V=p.split(" ");m=(V[1]||"last").trim()}}if(f.xhr){if(f.abortable){ce(g,"htmx:abort")}else{if(m==null){if(i){var y=ae(i);if(y&&y.triggerSpec&&y.triggerSpec.queue){m=y.triggerSpec.queue}}if(m==null){m="last"}}if(f.queuedRequests==null){f.queuedRequests=[]}if(m==="first"&&f.queuedRequests.length===0){f.queuedRequests.push(function(){he(t,r,n,i,a)})}else if(m==="all"){f.queuedRequests.push(function(){he(t,r,n,i,a)})}else if(m==="last"){f.queuedRequests=[];f.queuedRequests.push(function(){he(t,r,n,i,a)})}ie(o);return l}}var b=new XMLHttpRequest;f.xhr=b;f.abortable=x;var w=function(){f.xhr=null;f.abortable=false;if(f.queuedRequests!=null&&f.queuedRequests.length>0){var e=f.queuedRequests.shift();e()}};var j=ne(n,"hx-prompt");if(j){var S=prompt(j);if(S===null||!ce(n,"htmx:prompt",{prompt:S,target:u})){ie(o);w();return l}}if(d&&!e){if(!confirm(d)){ie(o);w();return l}}var E=xr(n,u,S);if(t!=="get"&&!Sr(n)){E["Content-Type"]="application/x-www-form-urlencoded"}if(a.headers){E=le(E,a.headers)}var _=dr(n,t);var C=_.errors;var R=_.values;if(a.values){R=le(R,a.values)}var z=Hr(n);var $=le(R,z);var T=yr($,n);if(Q.config.getCacheBusterParam&&t==="get"){T["org.htmx.cache-buster"]=ee(u,"id")||"true"}if(r==null||r===""){r=re().location.href}var O=Rr(n,"hx-request");var W=ae(n).boosted;var q=Q.config.methodsThatUseUrlParams.indexOf(t)>=0;var H={boosted:W,useUrlParams:q,parameters:T,unfilteredParameters:$,headers:E,target:u,verb:t,errors:C,withCredentials:a.credentials||O.credentials||Q.config.withCredentials,timeout:a.timeout||O.timeout||Q.config.timeout,path:r,triggeringEvent:i};if(!ce(n,"htmx:configRequest",H)){ie(o);w();return l}r=H.path;t=H.verb;E=H.headers;T=H.parameters;C=H.errors;q=H.useUrlParams;if(C&&C.length>0){ce(n,"htmx:validation:halted",H);ie(o);w();return l}var G=r.split("#");var J=G[0];var L=G[1];var A=r;if(q){A=J;var Z=Object.keys(T).length!==0;if(Z){if(A.indexOf("?")<0){A+="?"}else{A+="&"}A+=pr(T);if(L){A+="#"+L}}}if(!kr(n,A,H)){fe(n,"htmx:invalidPath",H);ie(s);return l}b.open(t.toUpperCase(),A,true);b.overrideMimeType("text/html");b.withCredentials=H.withCredentials;b.timeout=H.timeout;if(O.noHeaders){}else{for(var N in E){if(E.hasOwnProperty(N)){var K=E[N];Lr(b,N,K)}}}var I={xhr:b,target:u,requestConfig:H,etc:a,boosted:W,select:X,pathInfo:{requestPath:r,finalRequestPath:A,anchor:L}};b.onload=function(){try{var e=Ir(n);I.pathInfo.responsePath=Ar(b);M(n,I);lr(k,P);ce(n,"htmx:afterRequest",I);ce(n,"htmx:afterOnLoad",I);if(!se(n)){var t=null;while(e.length>0&&t==null){var r=e.shift();if(se(r)){t=r}}if(t){ce(t,"htmx:afterRequest",I);ce(t,"htmx:afterOnLoad",I)}}ie(o);w()}catch(e){fe(n,"htmx:onLoadError",le({error:e},I));throw e}};b.onerror=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:sendError",I);ie(s);w()};b.onabort=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:sendAbort",I);ie(s);w()};b.ontimeout=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:timeout",I);ie(s);w()};if(!ce(n,"htmx:beforeRequest",I)){ie(o);w();return l}var k=or(n);var P=sr(n);oe(["loadstart","loadend","progress","abort"],function(t){oe([b,b.upload],function(e){e.addEventListener(t,function(e){ce(n,"htmx:xhr:"+t,{lengthComputable:e.lengthComputable,loaded:e.loaded,total:e.total})})})});ce(n,"htmx:beforeSend",I);var Y=q?null:Er(b,n,T);b.send(Y);return l}function Pr(e,t){var r=t.xhr;var n=null;var i=null;if(O(r,/HX-Push:/i)){n=r.getResponseHeader("HX-Push");i="push"}else if(O(r,/HX-Push-Url:/i)){n=r.getResponseHeader("HX-Push-Url");i="push"}else if(O(r,/HX-Replace-Url:/i)){n=r.getResponseHeader("HX-Replace-Url");i="replace"}if(n){if(n==="false"){return{}}else{return{type:i,path:n}}}var a=t.pathInfo.finalRequestPath;var o=t.pathInfo.responsePath;var s=ne(e,"hx-push-url");var l=ne(e,"hx-replace-url");var u=ae(e).boosted;var f=null;var c=null;if(s){f="push";c=s}else if(l){f="replace";c=l}else if(u){f="push";c=o||a}if(c){if(c==="false"){return{}}if(c==="true"){c=o||a}if(t.pathInfo.anchor&&c.indexOf("#")===-1){c=c+"#"+t.pathInfo.anchor}return{type:f,path:c}}else{return{}}}function Mr(l,u){var f=u.xhr;var c=u.target;var e=u.etc;var t=u.requestConfig;var h=u.select;if(!ce(l,"htmx:beforeOnLoad",u))return;if(O(f,/HX-Trigger:/i)){_e(f,"HX-Trigger",l)}if(O(f,/HX-Location:/i)){er();var r=f.getResponseHeader("HX-Location");var v;if(r.indexOf("{")===0){v=E(r);r=v["path"];delete v["path"]}Nr("GET",r,v).then(function(){tr(r)});return}var n=O(f,/HX-Refresh:/i)&&"true"===f.getResponseHeader("HX-Refresh");if(O(f,/HX-Redirect:/i)){location.href=f.getResponseHeader("HX-Redirect");n&&location.reload();return}if(n){location.reload();return}if(O(f,/HX-Retarget:/i)){if(f.getResponseHeader("HX-Retarget")==="this"){u.target=l}else{u.target=ue(l,f.getResponseHeader("HX-Retarget"))}}var d=Pr(l,u);var i=f.status>=200&&f.status<400&&f.status!==204;var g=f.response;var a=f.status>=400;var p=Q.config.ignoreTitle;var o=le({shouldSwap:i,serverResponse:g,isError:a,ignoreTitle:p},u);if(!ce(c,"htmx:beforeSwap",o))return;c=o.target;g=o.serverResponse;a=o.isError;p=o.ignoreTitle;u.target=c;u.failed=a;u.successful=!a;if(o.shouldSwap){if(f.status===286){at(l)}R(l,function(e){g=e.transformResponse(g,f,l)});if(d.type){er()}var s=e.swapOverride;if(O(f,/HX-Reswap:/i)){s=f.getResponseHeader("HX-Reswap")}var v=wr(l,s);if(v.hasOwnProperty("ignoreTitle")){p=v.ignoreTitle}c.classList.add(Q.config.swappingClass);var m=null;var x=null;var y=function(){try{var e=document.activeElement;var t={};try{t={elt:e,start:e?e.selectionStart:null,end:e?e.selectionEnd:null}}catch(e){}var r;if(h){r=h}if(O(f,/HX-Reselect:/i)){r=f.getResponseHeader("HX-Reselect")}if(d.type){ce(re().body,"htmx:beforeHistoryUpdate",le({history:d},u));if(d.type==="push"){tr(d.path);ce(re().body,"htmx:pushedIntoHistory",{path:d.path})}else{rr(d.path);ce(re().body,"htmx:replacedInHistory",{path:d.path})}}var n=T(c);je(v.swapStyle,c,l,g,n,r);if(t.elt&&!se(t.elt)&&ee(t.elt,"id")){var i=document.getElementById(ee(t.elt,"id"));var a={preventScroll:v.focusScroll!==undefined?!v.focusScroll:!Q.config.defaultFocusScroll};if(i){if(t.start&&i.setSelectionRange){try{i.setSelectionRange(t.start,t.end)}catch(e){}}i.focus(a)}}c.classList.remove(Q.config.swappingClass);oe(n.elts,function(e){if(e.classList){e.classList.add(Q.config.settlingClass)}ce(e,"htmx:afterSwap",u)});if(O(f,/HX-Trigger-After-Swap:/i)){var o=l;if(!se(l)){o=re().body}_e(f,"HX-Trigger-After-Swap",o)}var s=function(){oe(n.tasks,function(e){e.call()});oe(n.elts,function(e){if(e.classList){e.classList.remove(Q.config.settlingClass)}ce(e,"htmx:afterSettle",u)});if(u.pathInfo.anchor){var e=re().getElementById(u.pathInfo.anchor);if(e){e.scrollIntoView({block:"start",behavior:"auto"})}}if(n.title&&!p){var t=C("title");if(t){t.innerHTML=n.title}else{window.document.title=n.title}}Cr(n.elts,v);if(O(f,/HX-Trigger-After-Settle:/i)){var r=l;if(!se(l)){r=re().body}_e(f,"HX-Trigger-After-Settle",r)}ie(m)};if(v.settleDelay>0){setTimeout(s,v.settleDelay)}else{s()}}catch(e){fe(l,"htmx:swapError",u);ie(x);throw e}};var b=Q.config.globalViewTransitions;if(v.hasOwnProperty("transition")){b=v.transition}if(b&&ce(l,"htmx:beforeTransition",u)&&typeof Promise!=="undefined"&&document.startViewTransition){var w=new Promise(function(e,t){m=e;x=t});var S=y;y=function(){document.startViewTransition(function(){S();return w})}}if(v.swapDelay>0){setTimeout(y,v.swapDelay)}else{y()}}if(a){fe(l,"htmx:responseError",le({error:"Response Status Error Code "+f.status+" from "+u.pathInfo.requestPath},u))}}var Xr={};function Dr(){return{init:function(e){return null},onEvent:function(e,t){return true},transformResponse:function(e,t,r){return e},isInlineSwap:function(e){return false},handleSwap:function(e,t,r,n){return false},encodeParameters:function(e,t,r){return null}}}function Ur(e,t){if(t.init){t.init(r)}Xr[e]=le(Dr(),t)}function Br(e){delete Xr[e]}function Fr(e,r,n){if(e==undefined){return r}if(r==undefined){r=[]}if(n==undefined){n=[]}var t=te(e,"hx-ext");if(t){oe(t.split(","),function(e){e=e.replace(/ /g,"");if(e.slice(0,7)=="ignore:"){n.push(e.slice(7));return}if(n.indexOf(e)<0){var t=Xr[e];if(t&&r.indexOf(t)<0){r.push(t)}}})}return Fr(u(e),r,n)}var Vr=false;re().addEventListener("DOMContentLoaded",function(){Vr=true});function jr(e){if(Vr||re().readyState==="complete"){e()}else{re().addEventListener("DOMContentLoaded",e)}}function _r(){if(Q.config.includeIndicatorStyles!==false){re().head.insertAdjacentHTML("beforeend","")}}function zr(){var e=re().querySelector('meta[name="htmx-config"]');if(e){return E(e.content)}else{return null}}function $r(){var e=zr();if(e){Q.config=le(Q.config,e)}}jr(function(){$r();_r();var e=re().body;zt(e);var t=re().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(e){var t=e.target;var r=ae(t);if(r&&r.xhr){r.xhr.abort()}});const r=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(e){if(e.state&&e.state.htmx){ar();oe(t,function(e){ce(e,"htmx:restored",{document:re(),triggerEvent:ce})})}else{if(r){r(e)}}};setTimeout(function(){ce(e,"htmx:load",{});e=null},0)});return Q}()}); \ No newline at end of file diff --git a/OpenShow/core/static/js/hyperscript.js b/OpenShow/core/static/js/hyperscript.js new file mode 100644 index 0000000..355c78a --- /dev/null +++ b/OpenShow/core/static/js/hyperscript.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 q=true;var S=false}else if(r.matchToken("first")){var N=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||N&&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.forEach(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.12",browserInit:w});return S})); diff --git a/OpenShow/core/static/js/sse.js b/OpenShow/core/static/js/sse.js new file mode 100644 index 0000000..28c4dd3 --- /dev/null +++ b/OpenShow/core/static/js/sse.js @@ -0,0 +1,369 @@ +/* +Server Sent Events Extension +============================ +This extension adds support for Server Sent Events to htmx. See /www/extensions/sse.md for usage instructions. + +*/ + +(function() { + + /** @type {import("../htmx").HtmxInternalApi} */ + var api; + + htmx.defineExtension("sse", { + + /** + * Init saves the provided reference to the internal HTMX API. + * + * @param {import("../htmx").HtmxInternalApi} api + * @returns void + */ + init: function(apiRef) { + // store a reference to the internal API. + api = apiRef; + + // set a function in the public API for creating new EventSource objects + if (htmx.createEventSource == undefined) { + htmx.createEventSource = createEventSource; + } + }, + + /** + * onEvent handles all events passed to this extension. + * + * @param {string} name + * @param {Event} evt + * @returns void + */ + onEvent: function(name, evt) { + + var parent = evt.target || evt.detail.elt; + switch (name) { + + case "htmx:beforeCleanupElement": + var internalData = api.getInternalData(parent) + // Try to remove remove an EventSource when elements are removed + if (internalData.sseEventSource) { + internalData.sseEventSource.close(); + } + + return; + + // Try to create EventSources when elements are processed + case "htmx:afterProcessNode": + ensureEventSourceOnElement(parent); + } + } + }); + + /////////////////////////////////////////////// + // HELPER FUNCTIONS + /////////////////////////////////////////////// + + + /** + * createEventSource is the default method for creating new EventSource objects. + * it is hoisted into htmx.config.createEventSource to be overridden by the user, if needed. + * + * @param {string} url + * @returns EventSource + */ + function createEventSource(url) { + return new EventSource(url, { withCredentials: true }); + } + + function splitOnWhitespace(trigger) { + return trigger.trim().split(/\s+/); + } + + function getLegacySSEURL(elt) { + var legacySSEValue = api.getAttributeValue(elt, "hx-sse"); + if (legacySSEValue) { + var values = splitOnWhitespace(legacySSEValue); + for (var i = 0; i < values.length; i++) { + var value = values[i].split(/:(.+)/); + if (value[0] === "connect") { + return value[1]; + } + } + } + } + + function getLegacySSESwaps(elt) { + var legacySSEValue = api.getAttributeValue(elt, "hx-sse"); + var returnArr = []; + if (legacySSEValue != null) { + var values = splitOnWhitespace(legacySSEValue); + for (var i = 0; i < values.length; i++) { + var value = values[i].split(/:(.+)/); + if (value[0] === "swap") { + returnArr.push(value[1]); + } + } + } + return returnArr; + } + + /** + * registerSSE looks for attributes that can contain sse events, right + * now hx-trigger and sse-swap and adds listeners based on these attributes too + * the closest event source + * + * @param {HTMLElement} elt + */ + function registerSSE(elt) { + // Add message handlers for every `sse-swap` attribute + queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function (child) { + // Find closest existing event source + var sourceElement = api.getClosestMatch(child, hasEventSource); + if (sourceElement == null) { + // api.triggerErrorEvent(elt, "htmx:noSSESourceError") + return null; // no eventsource in parentage, orphaned element + } + + // Set internalData and source + var internalData = api.getInternalData(sourceElement); + var source = internalData.sseEventSource; + + var sseSwapAttr = api.getAttributeValue(child, "sse-swap"); + if (sseSwapAttr) { + var sseEventNames = sseSwapAttr.split(","); + } else { + var sseEventNames = getLegacySSESwaps(child); + } + + for (var i = 0; i < sseEventNames.length; i++) { + var sseEventName = sseEventNames[i].trim(); + var listener = function(event) { + + // If the source is missing then close SSE + if (maybeCloseSSESource(sourceElement)) { + return; + } + + // If the body no longer contains the element, remove the listener + if (!api.bodyContains(child)) { + source.removeEventListener(sseEventName, listener); + return; + } + + // swap the response into the DOM and trigger a notification + if(!api.triggerEvent(elt, "htmx:sseBeforeMessage", event)) { + return; + } + swap(child, event.data); + api.triggerEvent(elt, "htmx:sseMessage", event); + }; + + // Register the new listener + api.getInternalData(child).sseEventListener = listener; + source.addEventListener(sseEventName, listener); + } + }); + + // Add message handlers for every `hx-trigger="sse:*"` attribute + queryAttributeOnThisOrChildren(elt, "hx-trigger").forEach(function(child) { + // Find closest existing event source + var sourceElement = api.getClosestMatch(child, hasEventSource); + if (sourceElement == null) { + // api.triggerErrorEvent(elt, "htmx:noSSESourceError") + return null; // no eventsource in parentage, orphaned element + } + + // Set internalData and source + var internalData = api.getInternalData(sourceElement); + var source = internalData.sseEventSource; + + var sseEventName = api.getAttributeValue(child, "hx-trigger"); + if (sseEventName == null) { + return; + } + + // Only process hx-triggers for events with the "sse:" prefix + if (sseEventName.slice(0, 4) != "sse:") { + return; + } + + // remove the sse: prefix from here on out + sseEventName = sseEventName.substr(4); + + var listener = function() { + if (maybeCloseSSESource(sourceElement)) { + return + } + + if (!api.bodyContains(child)) { + source.removeEventListener(sseEventName, listener); + } + } + }); + } + + /** + * ensureEventSourceOnElement creates a new EventSource connection on the provided element. + * If a usable EventSource already exists, then it is returned. If not, then a new EventSource + * is created and stored in the element's internalData. + * @param {HTMLElement} elt + * @param {number} retryCount + * @returns {EventSource | null} + */ + function ensureEventSourceOnElement(elt, retryCount) { + + if (elt == null) { + return null; + } + + // handle extension source creation attribute + queryAttributeOnThisOrChildren(elt, "sse-connect").forEach(function(child) { + var sseURL = api.getAttributeValue(child, "sse-connect"); + if (sseURL == null) { + return; + } + + ensureEventSource(child, sseURL, retryCount); + }); + + // handle legacy sse, remove for HTMX2 + queryAttributeOnThisOrChildren(elt, "hx-sse").forEach(function(child) { + var sseURL = getLegacySSEURL(child); + if (sseURL == null) { + return; + } + + ensureEventSource(child, sseURL, retryCount); + }); + + registerSSE(elt); + } + + function ensureEventSource(elt, url, retryCount) { + var source = htmx.createEventSource(url); + + source.onerror = function(err) { + + // Log an error event + api.triggerErrorEvent(elt, "htmx:sseError", { error: err, source: source }); + + // If parent no longer exists in the document, then clean up this EventSource + if (maybeCloseSSESource(elt)) { + return; + } + + // Otherwise, try to reconnect the EventSource + if (source.readyState === EventSource.CLOSED) { + retryCount = retryCount || 0; + var timeout = Math.random() * (2 ^ retryCount) * 500; + window.setTimeout(function() { + ensureEventSourceOnElement(elt, Math.min(7, retryCount + 1)); + }, timeout); + } + }; + + source.onopen = function(evt) { + api.triggerEvent(elt, "htmx:sseOpen", { source: source }); + } + + api.getInternalData(elt).sseEventSource = source; + } + + /** + * maybeCloseSSESource confirms that the parent element still exists. + * If not, then any associated SSE source is closed and the function returns true. + * + * @param {HTMLElement} elt + * @returns boolean + */ + function maybeCloseSSESource(elt) { + if (!api.bodyContains(elt)) { + var source = api.getInternalData(elt).sseEventSource; + if (source != undefined) { + source.close(); + // source = null + return true; + } + } + return false; + } + + /** + * queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT. + * + * @param {HTMLElement} elt + * @param {string} attributeName + */ + function queryAttributeOnThisOrChildren(elt, attributeName) { + + var result = []; + + // If the parent element also contains the requested attribute, then add it to the results too. + if (api.hasAttribute(elt, attributeName)) { + result.push(elt); + } + + // Search all child nodes that match the requested attribute + elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "]").forEach(function(node) { + result.push(node); + }); + + return result; + } + + /** + * @param {HTMLElement} elt + * @param {string} content + */ + function swap(elt, content) { + + api.withExtensions(elt, function(extension) { + content = extension.transformResponse(content, null, elt); + }); + + var swapSpec = api.getSwapSpecification(elt); + var target = api.getTarget(elt); + var settleInfo = api.makeSettleInfo(elt); + + api.selectAndSwap(swapSpec.swapStyle, target, elt, content, settleInfo); + + settleInfo.elts.forEach(function(elt) { + if (elt.classList) { + elt.classList.add(htmx.config.settlingClass); + } + api.triggerEvent(elt, 'htmx:beforeSettle'); + }); + + // Handle settle tasks (with delay if requested) + if (swapSpec.settleDelay > 0) { + setTimeout(doSettle(settleInfo), swapSpec.settleDelay); + } else { + doSettle(settleInfo)(); + } + } + + /** + * doSettle mirrors much of the functionality in htmx that + * settles elements after their content has been swapped. + * TODO: this should be published by htmx, and not duplicated here + * @param {import("../htmx").HtmxSettleInfo} settleInfo + * @returns () => void + */ + function doSettle(settleInfo) { + + return function() { + settleInfo.tasks.forEach(function(task) { + task.call(); + }); + + settleInfo.elts.forEach(function(elt) { + if (elt.classList) { + elt.classList.remove(htmx.config.settlingClass); + } + api.triggerEvent(elt, 'htmx:afterSettle'); + }); + } + } + + function hasEventSource(node) { + return api.getInternalData(node).sseEventSource != null; + } + +})(); diff --git a/OpenShow/core/templates/base.html b/OpenShow/core/templates/base.html new file mode 100644 index 0000000..0768606 --- /dev/null +++ b/OpenShow/core/templates/base.html @@ -0,0 +1,45 @@ + {% load static %} +{% load icon %} + + + + + {% block title %}OpenShow{% endblock %} +{# + + + + + {% block extra_js %} + {% endblock %} + {% block extra_css %} + {% endblock %} + + +
+
+ {% if previous_page %} + {% icon 'arrow-left' %} + {% endif %} + {% block header_left_button %} + {% endblock %} +
+
+ {% block header %} +

OpenShow

+ {% endblock %} +
+
+ {% block header_right_button %} + {% endblock %} +
+
+ {% block main %} + + {% block content %} + {% endblock content %} + + {% endblock %} + + \ No newline at end of file diff --git a/OpenShow/core/templates/core/base.html b/OpenShow/core/templates/core/base.html index 5395e46..399bb2a 100644 --- a/OpenShow/core/templates/core/base.html +++ b/OpenShow/core/templates/core/base.html @@ -1,23 +1,45 @@ {% load static %} +{% load icon %} {% block title %}OpenShow{% endblock %} {# - - + + + + + + {% block extra_js %} + {% endblock %} + {% block extra_css %} + {% endblock %}
- {% block header %} -

OpenShow

- {% endblock %} +
+ {% if previous_page %} + {% icon 'arrow-left' %} + {% endif %} + {% block header_left_button %} + {% endblock %} +
+
+ {% block header %} +

OpenShow

+ {% endblock %} +
+
+ {% block header_right_button %} + {% endblock %} +
- - {% block content %} - {% endblock content %} - + {% block main %} + + {% block content %} + {% endblock content %} + + {% endblock %} \ No newline at end of file diff --git a/OpenShow/core/templates/core/index.html b/OpenShow/core/templates/core/index.html index af9be8d..fb41868 100644 --- a/OpenShow/core/templates/core/index.html +++ b/OpenShow/core/templates/core/index.html @@ -23,12 +23,5 @@ This module integrates OpenShow with uubloomington.org, so a Show can automatically be generated from an Order of Service.

UUCB API Integration -
  • - - ALPHA, USE WITH CARE: Create Deck from Images - - This module (which is basely a proof of concept) takes multiple uploaded image files and rather ham-handedly creates slides for them. USE CAREFULLY.

    - Deck from Images -
  • {% endblock %} \ No newline at end of file diff --git a/OpenShow/deck_from_images/apps.py b/OpenShow/deck_from_images/apps.py deleted file mode 100644 index c51df63..0000000 --- a/OpenShow/deck_from_images/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class DeckFromImagesConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'deck_from_images' diff --git a/OpenShow/deck_from_images/forms.py b/OpenShow/deck_from_images/forms.py deleted file mode 100644 index be357b5..0000000 --- a/OpenShow/deck_from_images/forms.py +++ /dev/null @@ -1,31 +0,0 @@ -from django import forms - - -class MultipleFileInput(forms.ClearableFileInput): - allow_multiple_selected = True - - -class MultipleFileField(forms.FileField): - def __init__(self, *args, **kwargs): - kwargs.setdefault("widget", MultipleFileInput()) - super().__init__(*args, **kwargs) - - def clean(self, data, initial=None): - print('cleaning') - print(data) - single_file_clean = super().clean - if isinstance(data, (list, tuple)): - result = [single_file_clean(d, initial) for d in data] - print('many') - else: - result = single_file_clean(data, initial) - print('single') - print('hi!') - return result - - -class DeckFromImagesForm(forms.Form): - name = forms.CharField(max_length=100) - files = MultipleFileField() - transition_pk = forms.IntegerField() # this is horrible and should be replaced with a civilized selection system. - image_css_class = forms.CharField(max_length=1000) diff --git a/OpenShow/deck_from_images/templates/deck_from_images/upload.html b/OpenShow/deck_from_images/templates/deck_from_images/upload.html deleted file mode 100644 index bf8707e..0000000 --- a/OpenShow/deck_from_images/templates/deck_from_images/upload.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends 'core/base.html' %} - -{% block content %} -
    - Create Deck from Many Images -
    - {% csrf_token %} - {{ form }} - -
    -
    -{% endblock %} \ No newline at end of file diff --git a/OpenShow/deck_from_images/tests.py b/OpenShow/deck_from_images/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/OpenShow/deck_from_images/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/OpenShow/deck_from_images/urls.py b/OpenShow/deck_from_images/urls.py deleted file mode 100644 index 7859125..0000000 --- a/OpenShow/deck_from_images/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.urls import path - -from .views import DeckFromImagesFormView - -urlpatterns = [ - path('new', DeckFromImagesFormView.as_view(), name='deck-from-images') -] \ No newline at end of file diff --git a/OpenShow/deck_from_images/views.py b/OpenShow/deck_from_images/views.py deleted file mode 100644 index 831a47b..0000000 --- a/OpenShow/deck_from_images/views.py +++ /dev/null @@ -1,47 +0,0 @@ -from django.views.generic.edit import FormView -from .forms import DeckFromImagesForm - -from slides.models import Deck, Slide, SlideElement, Transition - - -class DeckFromImagesFormView(FormView): - form_class = DeckFromImagesForm - template_name = "deck_from_images/upload.html" - # success_url = "..." # Replace with your URL or reverse(). - - def __init__(self): - self.new_deck = None - super().__init__() - - def post(self, request, *args, **kwargs): - form_class = self.get_form_class() - form = self.get_form(form_class) - if form.is_valid(): - return self.form_valid(form) - else: - return self.form_invalid(form) - - def form_valid(self, form): - files = form.cleaned_data["files"] - new_default_transition = Transition.objects.get(pk=form.cleaned_data["transition_pk"]) - self.new_deck = Deck( - name=form.cleaned_data["name"], - default_transition=new_default_transition, - ) - self.new_deck.save() - for image in files: - print('image!') - new_slide = Slide(deck=self.new_deck) - new_slide.save() - new_slide_element = SlideElement( - css_class=form.cleaned_data["image_css_class"], - order=0, - slide=new_slide, - image=image, - body="", - ) - new_slide_element.save() - return super().form_valid(self) - - def get_success_url(self): - return self.new_deck.get_absolute_url() diff --git a/OpenShow/pjlink_integration/templates/pjlink_integration/edit.html b/OpenShow/pjlink_integration/templates/pjlink_integration/edit.html index 98261ca..1e06984 100644 --- a/OpenShow/pjlink_integration/templates/pjlink_integration/edit.html +++ b/OpenShow/pjlink_integration/templates/pjlink_integration/edit.html @@ -8,5 +8,5 @@ {{ form.address }}

    - + \ No newline at end of file diff --git a/OpenShow/pjlink_integration/templates/pjlink_integration/list.html b/OpenShow/pjlink_integration/templates/pjlink_integration/list.html index 1ee1c88..464ce91 100644 --- a/OpenShow/pjlink_integration/templates/pjlink_integration/list.html +++ b/OpenShow/pjlink_integration/templates/pjlink_integration/list.html @@ -17,7 +17,7 @@

    PJLink Integration

    - +
    {% endfor %} diff --git a/OpenShow/pjlink_integration/views.py b/OpenShow/pjlink_integration/views.py index e5f73a4..026a188 100644 --- a/OpenShow/pjlink_integration/views.py +++ b/OpenShow/pjlink_integration/views.py @@ -9,6 +9,9 @@ class ProjectorListView(ListView): model = Projector template_name = 'pjlink_integration/list.html' + extra_context = { + 'previous_page': 'index', + } class ProjectorCreateView(CreateView): diff --git a/OpenShow/slides/aoml_parser.py b/OpenShow/slides/aoml_parser.py index b8f26d1..b15c69a 100644 --- a/OpenShow/slides/aoml_parser.py +++ b/OpenShow/slides/aoml_parser.py @@ -13,7 +13,9 @@ def parse_element_body(markup:str) -> str: :return: """ body = markup + body = "".join(body.splitlines()) body = body.replace("\\", "
    ") + print(bytes(body, "utf-8")) return body diff --git a/OpenShow/slides/api.py b/OpenShow/slides/api.py new file mode 100644 index 0000000..005a72b --- /dev/null +++ b/OpenShow/slides/api.py @@ -0,0 +1,29 @@ +from ninja import Router, Schema +from .models import Slide, Show, Display +from django.shortcuts import get_object_or_404 + +router = Router() + + +class SlideAndShowSchema(Schema): + slide_pk: int + show_pk: int + + +class SlideAdvanceSchema(Schema): + display_pk: int + direction: str + + +@router.post("show_slide") +def show_slide(request, slide_and_show: SlideAndShowSchema): + slide = get_object_or_404(Slide, pk=slide_and_show.slide_pk) + show = get_object_or_404(Show, pk=slide_and_show.show_pk) + slide.send_to_display(show.displays.all(), show=show) + return {"message": "OK"} + + +@router.post("next_slide") +def next_slide(request, slide_advance: SlideAdvanceSchema): + display = get_object_or_404(Display, pk=slide_advance.display_pk) + display.advance_slide(slide_advance.direction) diff --git a/OpenShow/slides/editor/forms.py b/OpenShow/slides/editor/forms.py index be73edb..8e5cd59 100644 --- a/OpenShow/slides/editor/forms.py +++ b/OpenShow/slides/editor/forms.py @@ -1,6 +1,6 @@ -from django.forms import Form, ModelForm, IntegerField, ChoiceField, Select +from django.forms import Form, ModelForm, IntegerField, ChoiceField, Select, ClearableFileInput, FileField, CharField from django.urls import reverse_lazy -from ..models import Show, Theme +from ..models import Show, Theme, SlideElement, Deck # class SimpleShowForm(ModelForm): @@ -38,3 +38,66 @@ class ChangeSlideOrderForm(Form): deck_or_segment_pk = IntegerField() moved_slide_pk = IntegerField() next_slide_pk = IntegerField(required=False) + + +class ChangeSlideElementOrderForm(Form): + moved_element_pk = IntegerField() + next_element_pk = IntegerField(required=False) + + +class EditSlideElementTextForm(ModelForm): + class Meta: + model = SlideElement + fields = ['body',] + + def save(self, commit=True): + self.instance.body = self.cleaned_data['body'].replace('\n', '
    ') + return super(EditSlideElementTextForm, self).save() + + +class MultipleFileInput(ClearableFileInput): + allow_multiple_selected = True + + +class MultipleFileField(FileField): + def __init__(self, *args, **kwargs): + kwargs.setdefault("widget", MultipleFileInput()) + super().__init__(*args, **kwargs) + + def clean(self, data, initial=None): + single_file_clean = super().clean + if isinstance(data, (list, tuple)): + result = [single_file_clean(d, initial) for d in data] + else: + result = single_file_clean(data, initial) + return result + + +class DeckFromImagesForm(ModelForm): + class Meta: + model = Deck + fields = [ + 'name', + 'default_transition', + 'default_transition_duration', + 'default_auto_advance', + 'default_auto_advance_duration', + 'advance_in_loop', + 'theme', + ] + files = MultipleFileField() + image_css_class = CharField() + + +class ImportImagesForm(Form): + OVERWRITE = 'OVERWRITE' + APPEND = 'APPEND' + IMPORT_MODE_CHOICES = [ + (APPEND, 'Append'), + (OVERWRITE, 'Overwrite (DANGER!! Will permanently erase all slides in this deck!)'), + ] + files = MultipleFileField() + image_css_class = CharField() + mode = ChoiceField( + choices=IMPORT_MODE_CHOICES, + ) \ No newline at end of file diff --git a/OpenShow/slides/editor/static/editor/editor.css b/OpenShow/slides/editor/static/editor/editor.css index a151e9c..9ec6301 100644 --- a/OpenShow/slides/editor/static/editor/editor.css +++ b/OpenShow/slides/editor/static/editor/editor.css @@ -207,6 +207,7 @@ ul > a { height: 1rem; background-color: unset; margin-block: 0; + margin-bottom: -1rem; transition: height, background-color 500ms; } @@ -224,4 +225,158 @@ ul > a { :not(.htmx-swapping) { transition: background-color 0.1s, opacity 0.1s; -} \ No newline at end of file +} + +.modal { + /*position: absolute;*/ + /*top: calc(50%);*/ + /*left: 50%;*/ + margin: auto; + margin-top: calc(50dvh - 4rem); + width: 15rem; + height: 8rem; + background: #606060; + color: white; + border: 3px solid darkgray; + padding: 1rem; + text-align: center; + + border-radius: 0.5rem; + z-index: 1070; +} + +.confirm-delete-modal { + background: #daa5a5; + color: #000000; + border: 3px solid #a10000; +} + +.modal-container { + position: fixed; + top: 0; + left: 0; + z-index: 1; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(0.5rem); +} + +.editor-editable { + outline: 4px solid red; + font-size: unset; + overflow: unset; + height: unset; + text-align: unset; + background: transparent; +} + +.element-edit-buttons { + scale: 3; + width: fit-content; + position: absolute; + right: 50px; +} + +.editor-element { + min-height: 2rem; + position: relative; +} + +.editor-element .hint { + opacity: 0.75; + font-size: 3rem; + text-align: center; + width: 100%; +} + +.grow-wrap { + display: grid; +} + +.grow-wrap::after { + content: attr(data-replicated-value) " "; + white-space: pre-wrap; + visibility: hidden; +} + +.grow-wrap > textarea { + resize: none; + overflow: hidden; +} + +.grow-wrap > textarea, +.grow-wrap::after { + outline: 4px solid red; + border: none; + padding: 0; + font: inherit; + grid-area: 1 / 1 / 2 / 2; +} + +/*Thanks to https://css-tricks.com/the-cleanest-trick-for-autogrowing-textareas/ for grow-wrap*/ + +.element-action-button-container { + width: fit-content; + position: absolute; + top: 0; + left: 10px; + display: none; + height: min-content; + text-shadow: none; +} + +.element-action-button-container > * { + display: inline; +} + +.element-action-button-container:has(form), +.editor-element:hover .element-action-button-container { + display: block; +} + +.element-action-button-container form > * { + display: inline; + width: unset; + vertical-align: middle; + height: 4rem; + font-size: 3rem; +} + +.element-action-button-container button { + height: 4rem; + width: 4rem; +} + +.element-action-button-container button svg { + height: 3rem; + width: 3rem; +} + +.element-action-button-container .element-form-box { + background: #606060; + border: 6px solid darkgray; + border-radius: 2rem; + font-size: 2rem; + display: inline-block; + padding: 1rem; + text-align: left; +} + +.element-action-button-container .element-form-box * { + font-size: 2rem; +} + +.element-action-button-container .element-form-box input[type=checkbox] { + height: 2rem; + width: 2rem; +} + +button.danger { + background: #9C1A1C; + color: white; +} + +button.green { + background-color: #bedfb8; +} diff --git a/OpenShow/slides/editor/tasks.py b/OpenShow/slides/editor/tasks.py new file mode 100644 index 0000000..338abc0 --- /dev/null +++ b/OpenShow/slides/editor/tasks.py @@ -0,0 +1,70 @@ +import pathlib, subprocess, json + +from django.conf import settings +from django.utils.text import slugify + +from slides.models import MediaObject +from ffmpeg import FFmpeg, Progress + +def get_mediafile_seconds(media_path): + result = subprocess.check_output( + f'ffprobe -v quiet -show_streams -select_streams v:0 -of json "{settings.MEDIA_ROOT + media_path}"', + shell=True).decode() + fields = json.loads(result)['streams'][0] + + duration = fields['tags']['DURATION'] + # fps = eval(fields['r_frame_rate']) + return duration + +def transcode_video(media_object_pk: int) -> None: + media_object = MediaObject.objects.get(pk=media_object_pk) + final_file_name = slugify(media_object.title) + '.mp4' + tmp_transcode_out_path = settings.MEDIA_ROOT + '/media_final/video/' + final_file_name + pathlib.Path(tmp_transcode_out_path).parents[0].mkdir(parents=True, exist_ok=True) + ffmpeg = ( + FFmpeg() + .option("y") + .input(media_object.raw_file.path) + .output( + tmp_transcode_out_path, + {"codec:v": "libsvtav1", "codec:a": "mp3"}, + preset=8, + crf=40, + ) + ) + + @ffmpeg.on("progress") + def print_progress(progress: Progress): + print(progress) + # TODO: Implement an (optional) Redis/Valkey backend for Eventstream so we can send progress back to the editor + + ffmpeg.execute() + media_object.final_file = 'media_final/video/' + final_file_name + media_object.save() + + +def transcode_audio(media_object_pk: int) -> None: + media_object = MediaObject.objects.get(pk=media_object_pk) + final_file_name = slugify(media_object.title) + '.mp3' + tmp_transcode_out_path = settings.MEDIA_ROOT + '/media_final/audio/' + final_file_name + pathlib.Path(tmp_transcode_out_path).parents[0].mkdir(parents=True, exist_ok=True) + ffmpeg = ( + FFmpeg() + .option("y") + .input(media_object.raw_file.path) + .output( + tmp_transcode_out_path, + {"codec:a": "mp3"}, + preset=8, + crf=40, + ) + ) + + @ffmpeg.on("progress") + def print_progress(progress: Progress): + print(progress) + # TODO: Implement an (optional) Redis/Valkey backend for Eventstream so we can send progress back to the editor + + ffmpeg.execute() + media_object.final_file = 'media_final/audio/' + final_file_name + media_object.save() \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/base.html b/OpenShow/slides/editor/templates/editor/base.html index 4e73c1c..a51e28a 100644 --- a/OpenShow/slides/editor/templates/editor/base.html +++ b/OpenShow/slides/editor/templates/editor/base.html @@ -2,12 +2,12 @@ - - - + + + - OpenShow Editor + {% block title %}OpenShow Editor{% endblock %} diff --git a/OpenShow/slides/editor/templates/editor/deck_editor.html b/OpenShow/slides/editor/templates/editor/deck_editor.html index 59a6cd7..93ddefb 100644 --- a/OpenShow/slides/editor/templates/editor/deck_editor.html +++ b/OpenShow/slides/editor/templates/editor/deck_editor.html @@ -12,11 +12,101 @@ {% endblock %} +{% block title %}Editing {{ deck.name }} - OpenShow{% endblock %} + {% block content %}
    - +

    {{ deck.name }}

    +
      {% for slide in deck.slides.all %} @@ -47,7 +137,7 @@

      {{ deck.name }}

      call event.dataTransfer.setData('text/plain',target) {# don't know why, drop event never fires unless something is set here #} then set @value of to my @data-slide-pk" > - {% include "slides/slide.html" with slide=slide %} + {% include "slides/slide-thumbnail.html" with slide=slide %} {% endfor %}
      {{ deck.name }} {{ form.default_auto_advance }} {{ form.default_auto_advance_duration }} + + {{ form.advance_in_loop }}
      @@ -111,6 +203,10 @@

      {{ deck.name }}

      Push Slide Text + + Pull Deck AOML + +
    {% endblock %} \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/delete_element.html b/OpenShow/slides/editor/templates/editor/delete_element.html index 2079317..0eddfff 100644 --- a/OpenShow/slides/editor/templates/editor/delete_element.html +++ b/OpenShow/slides/editor/templates/editor/delete_element.html @@ -1,6 +1,5 @@ -
    - {% csrf_token %} - - - -
    \ No newline at end of file +{% load icon %} +
    {% csrf_token %} + + +
    diff --git a/OpenShow/slides/editor/templates/editor/display/create.html b/OpenShow/slides/editor/templates/editor/display/create.html new file mode 100644 index 0000000..62d1031 --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/display/create.html @@ -0,0 +1,5 @@ +
    + {% csrf_token %} + {{ form }} + +
    \ No newline at end of file diff --git a/OpenShow/deck_from_images/migrations/__init__.py b/OpenShow/slides/editor/templates/editor/display/delete.html similarity index 100% rename from OpenShow/deck_from_images/migrations/__init__.py rename to OpenShow/slides/editor/templates/editor/display/delete.html diff --git a/OpenShow/slides/editor/templates/editor/display/detail.html b/OpenShow/slides/editor/templates/editor/display/detail.html new file mode 100644 index 0000000..d5185dd --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/display/detail.html @@ -0,0 +1,38 @@ +{% extends 'core/base.html' %} +{% load icon %} +{% load static %} + +{% block extra_css %} + + +{% endblock %} + +{% block header %} +

    {{ object.name }}

    +{% endblock %} + +{% block title %}Editing {{ object.name }} - OpenShow{% endblock %} + +{% block header_right_button %} + + {% icon 'edit-2' %} + +{% endblock %} + +{% block content %} +

    Current Slide

    +
    + {% if display.current_slide %} + {% include 'slides/slide-thumbnail.html' with slide=object.current_slide %} + {% else %} + None + {% endif %} +
    + Open this display (in a new tab) +{% endblock %} diff --git a/OpenShow/slides/editor/templates/editor/display/update.html b/OpenShow/slides/editor/templates/editor/display/update.html new file mode 100644 index 0000000..4ff7409 --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/display/update.html @@ -0,0 +1,5 @@ +
    + {% csrf_token %} + {{ form }} + +
    \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/edit_element.html b/OpenShow/slides/editor/templates/editor/edit_element.html deleted file mode 100644 index 97955ee..0000000 --- a/OpenShow/slides/editor/templates/editor/edit_element.html +++ /dev/null @@ -1,30 +0,0 @@ -
  • -
    - {% csrf_token %} - -
    - Body - {{ form.body }} -
    -
    - CSS Class - {{ form.css_class }} -
    -
    - Element Order - {{ form.order }} -
    -
    - image: {{ form.image }} -
    -
    - video: {{ form.video }} -
    - -
    - {% if action == 'edit' %} - - {% endif %} -
  • \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/edit_slide.html b/OpenShow/slides/editor/templates/editor/edit_slide.html index 166ffbf..9c9fa2c 100644 --- a/OpenShow/slides/editor/templates/editor/edit_slide.html +++ b/OpenShow/slides/editor/templates/editor/edit_slide.html @@ -9,6 +9,8 @@ {# #} {# {% endfor %}#} {# #} + +
    {% csrf_token %} {{ form.transition }} @@ -26,21 +28,8 @@ {# #}
    -
      - {% for element in slide.get_elements %} -
    • - {{ element.body }} -{# #} -
    • - {% endfor %} - -
    - {% include 'slides/slide.html' with slide=slide %} + {% include 'editor/slide.html' with slide=slide %}
    {% endblock %} \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/edit_theme.html b/OpenShow/slides/editor/templates/editor/edit_theme.html index 6eb67f5..57e36ae 100644 --- a/OpenShow/slides/editor/templates/editor/edit_theme.html +++ b/OpenShow/slides/editor/templates/editor/edit_theme.html @@ -10,10 +10,12 @@ {% endblock %} +{% block title %}Editing {{ theme.name }} - OpenShow{% endblock %} + {% block content %}
    - +

    {{ theme.name }}

      @@ -46,6 +48,7 @@

      {{ theme.name }}

      {{ form.css }} +
        {% for css_class in theme.get_css_classes %}
      • diff --git a/OpenShow/slides/editor/templates/editor/element_create.html b/OpenShow/slides/editor/templates/editor/element_create.html new file mode 100644 index 0000000..074d6eb --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/element_create.html @@ -0,0 +1,12 @@ +{% load icon %} +
        + {% csrf_token %} + {{ form.css_class.as_field_group }} + + +
        \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/element_css_class_edit.html b/OpenShow/slides/editor/templates/editor/element_css_class_edit.html new file mode 100644 index 0000000..da954b7 --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/element_css_class_edit.html @@ -0,0 +1,13 @@ +{% load icon %} +
        {# Halt doubleclick events so selecting text in our fields isn't super frustrating #} + {% csrf_token %} + {{ form.css_class }} + +
        \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/element_image_edit.html b/OpenShow/slides/editor/templates/editor/element_image_edit.html new file mode 100644 index 0000000..7ba7e48 --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/element_image_edit.html @@ -0,0 +1,14 @@ +{% load icon %} +
        {# Halt doubleclick events so selecting text in our fields isn't super frustrating #} + {% csrf_token %} + {{ form.image }} + +
        \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/element_media_object_edit.html b/OpenShow/slides/editor/templates/editor/element_media_object_edit.html new file mode 100644 index 0000000..d0cf42d --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/element_media_object_edit.html @@ -0,0 +1,15 @@ +{% load icon %} +
        {# Halt doubleclick events so selecting text in our fields isn't super frustrating #} + {% csrf_token %} + {{ form.media_object }} + {{ form.errors }} + +
        \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/element_text_edit.html b/OpenShow/slides/editor/templates/editor/element_text_edit.html new file mode 100644 index 0000000..12a4da3 --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/element_text_edit.html @@ -0,0 +1,33 @@ +{% load icon %} +
        + {% csrf_token %} +
        + +
        +
        + +
        +
        \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/element_video_edit.html b/OpenShow/slides/editor/templates/editor/element_video_edit.html new file mode 100644 index 0000000..962a950 --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/element_video_edit.html @@ -0,0 +1,14 @@ +{% load icon %} +
        {# Halt doubleclick events so selecting text in our fields isn't super frustrating #} + {% csrf_token %} + {{ form.video }} + +
        \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/generic_confirm_delete.html b/OpenShow/slides/editor/templates/editor/generic_confirm_delete.html new file mode 100644 index 0000000..af3a20e --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/generic_confirm_delete.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/import_images_to_deck.html b/OpenShow/slides/editor/templates/editor/import_images_to_deck.html new file mode 100644 index 0000000..ccc481c --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/import_images_to_deck.html @@ -0,0 +1,22 @@ +{% extends 'core/base.html' %} +{% load icon %} + +{% block header %} +

        Importing Images to deck "{{ deck.name }}"

        +{% endblock %} + +{% block header_left_button %} + {% icon 'chevron-left' %} +{% endblock %} + +{% block content %} +

        + Submitting images here will import them to the deck "{{ deck.name }}", creating a new slide for each submitted image. "Append" will place your imported images at the end. "Overwrite" will replace all slides in the deck with the images you upload. +

        +
        +
        + {% csrf_token %} + {{ form }} + +
        +{% endblock %} \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/mediaobject_detail.html b/OpenShow/slides/editor/templates/editor/mediaobject_detail.html new file mode 100644 index 0000000..c1447fe --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/mediaobject_detail.html @@ -0,0 +1,29 @@ +{% extends "core/base.html" %} +{% load neapolitan %} +{% load icon %} + +{% block title %}{{ object.title }} - OpenShow Editor{% endblock %} +{% block header %}

        {{ object.title }}

        {% endblock %} + +{% block header_right_button %} + {% icon 'edit-2' %} +{% endblock %} + +{% block content %} + {% if object.media_type == "VIDEO" and object.final_file or object.media_type == "AUDIO" and object.final_file %} + {% if object.media_type == "VIDEO" %} + + {% elif object.media_type == "AUDIO" %} + + {% endif %} + {% elif object.media_type == "VIMEO_LIVE_EMBED" %} + {% include object.get_slide_element_template with media_object=object %} + {% else %} +

        This Media Object is still transcoding. Please reload the page in a few minutes.

        + {% endif %} +

        + Type: {{ object.get_media_type_display }} +

        +{% endblock %} diff --git a/OpenShow/slides/editor/templates/editor/mediaobject_form.html b/OpenShow/slides/editor/templates/editor/mediaobject_form.html new file mode 100644 index 0000000..6ba6e49 --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/mediaobject_form.html @@ -0,0 +1,66 @@ +{% extends "core/base.html" %} +{% load icon %} + +{% block header %} + {% if object %} +

        {{ object.title }}

        + {% else %} +

        New Media Object

        + {% endif %} +{% endblock %} + +{% block header_right_button %} + {% if object %} + {% icon 'trash-2' %} + {% endif %} +{% endblock %} + +{% block header_left_button %} + {% if object %} + {% icon 'arrow-left' %} + {% endif %} +{% endblock %} + +{% block content %} +

        {% if object %}Edit {{object_verbose_name}}{% else %}Create {{object_verbose_name}}{% endif %}

        +
        + {% if not object %} + New Media Object + + {% endif %} +
        + {% csrf_token %} + {{ form.title.as_field_group }} +

        + {{ form.media_type.as_field_group }} +

        +

        + {{ form.embed_url.as_field_group }} +

        +

        + {{ form.raw_file.as_field_group }} +

        + +
        +
        +{% endblock %} diff --git a/OpenShow/slides/editor/templates/editor/show_editor.html b/OpenShow/slides/editor/templates/editor/show_editor.html index 165df1e..d4c56ab 100644 --- a/OpenShow/slides/editor/templates/editor/show_editor.html +++ b/OpenShow/slides/editor/templates/editor/show_editor.html @@ -12,10 +12,12 @@ {% endblock %} +{% block title %}Editing {{ show.name }} - OpenShow{% endblock %} + {% block content %}
        - +

        {{ show.name }}

        {# #} {# #} @@ -72,6 +74,7 @@

        Deck: {{ segment.included_deck.name }}

        +
    {% endblock %} \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/slide.html b/OpenShow/slides/editor/templates/editor/slide.html new file mode 100644 index 0000000..796236d --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/slide.html @@ -0,0 +1,71 @@ +{% load icon %} +
    + {% for element in slide.get_elements %} +
    + {% csrf_token %} + + +
    +
    + {% if element.body %} + {{ element.body|safe }} + {% elif element.video and not element.image %} + Video: {{ element.video }} + {% elif element.media_object %} + Media Object {{ element.media_object }} + {% elif not element.image %} + Double-click to edit + {% endif %} + {% if element.image %} + + {% endif %} + +
    + {% endfor %} +
    + {% csrf_token %} + {# don't sent next_slide as this is the end #} + +
    +
    \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/snippets/hx-simple_create_form.html b/OpenShow/slides/editor/templates/editor/snippets/hx-simple_create_form.html new file mode 100644 index 0000000..288a506 --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/snippets/hx-simple_create_form.html @@ -0,0 +1,12 @@ +{% load icon %} +
    + New {{ object_type }} +
    + {% csrf_token %} + {% block extra_fields %} + {% endblock %} + {{ form }} + {{ form.errors }} + +
    +
    \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/transition_editor.html b/OpenShow/slides/editor/templates/editor/transition_editor.html index 6549749..165dbb9 100644 --- a/OpenShow/slides/editor/templates/editor/transition_editor.html +++ b/OpenShow/slides/editor/templates/editor/transition_editor.html @@ -7,10 +7,12 @@ {% endblock %} +{% block title %}Editing {{ transition.name }} - OpenShow{% endblock %} + {% block content %}
    - +

    {{ transition.name }}

      diff --git a/OpenShow/slides/editor/urls.py b/OpenShow/slides/editor/urls.py index be7cb41..746882c 100644 --- a/OpenShow/slides/editor/urls.py +++ b/OpenShow/slides/editor/urls.py @@ -1,24 +1,53 @@ from django.urls import path -from .views import * + +from slides.editor.views.index import IndexView +from slides.editor.views.show import ShowEditorView, ShowCreateView, ShowDeleteView, SetThemeView, \ + check_theme_compatibility +from slides.editor.views.segment import SegmentCreateView, SegmentUpdateView +from slides.editor.views.slide import SlideCreateView, SlideEditView, SlideDeleteView, ChangeSlideOrderView +from slides.editor.views.slide_element import SlideElementCreateView, SlideElementDeleteView, \ + SlideElementUpdateTextView, SlideElementUpdateCSSClassView, SlideElementUpdateImageView, \ + SlideElementUpdateVideoView, ChangeSlideElementOrderView, SlideElementUpdateMediaObjectView +from slides.editor.views.deck import DeckCreateView, DeckEditorView, DeckDeleteView, push_deck_cues, \ + push_deck_slide_text, pull_aoml_text, DeckFromImagesView, ImportImagesToExistingDeckView +from slides.editor.views.theme import ThemeUpdateView, ThemeCreateView, ThemeDeleteView +from slides.editor.views.utils import generate_lorem +from slides.editor.views.transition import TransitionEditorView, TransitionCreateView, TransitionKeyframeCreateView, \ + TransitionKeyframeUpdateView, TransitionEditorIndexView +from slides.editor.views.display import DisplayCreateView, DisplayDeleteView, DisplayUpdateView, DisplayDetailView +from slides.editor.views.media import MediaObjectCRUDView + urlpatterns = [ path('', IndexView.as_view(), name='editor_index'), path('show/', ShowEditorView.as_view(), name='edit-show'), path('show/new', ShowCreateView.as_view(), name='new-show'), + path('show//delete', ShowDeleteView.as_view(), name='delete-show'), path('segment/', SegmentUpdateView.as_view(), name='edit-segment'), path('segment/new', SegmentCreateView.as_view(), name='new-segment'), path('slide/new', SlideCreateView.as_view(), name='new-slide'), path('slide/', SlideEditView.as_view(), name='edit-slide'), + path('slide//delete', SlideDeleteView.as_view(), name='delete-slide'), path('slide/element/new', SlideElementCreateView.as_view(), name='new-element'), - path('slide/element/', SlideElementUpdateView.as_view(), name='edit-element'), path('slide/element/delete/', SlideElementDeleteView.as_view(), name='delete-element'), + path('slide/element//text-edit', SlideElementUpdateTextView.as_view(), name='edit-element-text'), + path('slide/element//css-class', SlideElementUpdateCSSClassView.as_view(), name='edit-element-css-class'), + path('slide/element//image', SlideElementUpdateImageView.as_view(), name='edit-element-image'), + path('slide/element//video', SlideElementUpdateVideoView.as_view(), name='edit-element-video'), + path('slide/element//media_object', SlideElementUpdateMediaObjectView.as_view(), name='edit-element-media_object'), + path('slide/element/reorder', ChangeSlideElementOrderView.as_view(), name='reorder-element'), path('slide/reorder', ChangeSlideOrderView.as_view(), name='reorder-slide'), path('deck/new', DeckCreateView.as_view(), name='new-deck'), path('deck/', DeckEditorView.as_view(), name='edit-deck'), + path('deck//delete', DeckDeleteView.as_view(), name='delete-deck'), path('deck//push-cues', push_deck_cues, name='push-deck-cues'), path('deck//push-text', push_deck_slide_text, name='push-deck-text'), + path('deck//pull-aoml', pull_aoml_text, name='pull-aoml-text'), + path('deck//import-images', ImportImagesToExistingDeckView.as_view(), name='import-images-to-deck'), + path('deck/import/images', DeckFromImagesView.as_view(), name='import-deck-from-images'), path('theme/new', ThemeCreateView.as_view(), name='new-theme'), path('theme/', ThemeUpdateView.as_view(), name='edit-theme'), + path('theme//delete', ThemeDeleteView.as_view(), name='delete-theme'), path('lorem//', generate_lorem, name='lorem'), path('show//set-theme/', SetThemeView.as_view(), name='set-theme'), path('show/check-theme-compatibility', check_theme_compatibility, name='check-theme-compatibility'), @@ -27,4 +56,9 @@ path('transition/new', TransitionCreateView.as_view(), name='new-transition'), path('transition/keyframe/new', TransitionKeyframeCreateView.as_view(), name='new-keyframe'), path('transition/keyframe/', TransitionKeyframeUpdateView.as_view(), name='edit-keyframe'), + path('display/new', DisplayCreateView.as_view(), name='new-display'), + path('display/', DisplayDetailView.as_view(), name='display-detail'), + path('display//update', DisplayUpdateView.as_view(), name='update-display'), + path('display//delete', DisplayDeleteView.as_view(), name='delete-display'), + *MediaObjectCRUDView.get_urls(), ] \ No newline at end of file diff --git a/OpenShow/slides/editor/views.py b/OpenShow/slides/editor/views.py deleted file mode 100644 index 0a753af..0000000 --- a/OpenShow/slides/editor/views.py +++ /dev/null @@ -1,280 +0,0 @@ -from django.shortcuts import render, reverse, get_object_or_404, HttpResponseRedirect -from django.views.generic import DetailView, CreateView, ListView, UpdateView, FormView, DeleteView -from django.db import transaction - -from ..models import Show, Segment, Slide, SlideElement, Display, Deck, Theme, Transition, TransitionKeyframe - -from .forms import DeleteSlideElementForm, SetThemeForm, ChangeSlideOrderForm - -import slides.aoml_parser as aoml - -from django.urls import reverse_lazy - -import lorem - -# Create your views here. - - -class NotSupportedException(Exception): - pass - - - -class IndexView(ListView): - queryset = Show.objects.all() - template_name = 'editor/index.html' - extra_context = { - 'deck_list': Deck.objects.all, - 'theme_list': Theme.objects.all, - 'display_list': Display.objects.all, - } - - -class ShowEditorView(DetailView): - queryset = Show.objects.all() - template_name = 'editor/show_editor.html' - # extra_context = {'display': Display.objects.all().first()} - - -class ShowCreateView(CreateView): - model = Show - fields = ['name'] - template_name = 'editor/simple_create_form.html' - extra_context = { - 'action': 'new-show', - } - - -class SegmentCreateView(CreateView): - model = Segment - fields = ['show', 'name', 'order'] - template_name = 'editor/new_segment_form.html' - - -class SegmentUpdateView(UpdateView): - model = Segment - fields = ['name', 'order', 'included_deck'] - template_name = 'editor/edit_segment_form.html' - - -class SlideCreateView(CreateView): - model = Slide - fields = ['segment', 'deck'] - - def form_valid(self, form): - self.success_url = self.request.META['HTTP_REFERER'] - return super().form_valid(form) - - -class SlideElementCreateView(CreateView): - model = SlideElement - fields = ['css_class', 'body', 'order', 'image', 'video', 'slide'] - template_name = 'editor/edit_element.html' - extra_context = {'action': 'new'} - - -class SlideElementUpdateView(UpdateView): - model = SlideElement - fields = ['css_class', 'body', 'order', 'image','video', 'slide'] - template_name = 'editor/edit_element.html' - extra_context = {'action': 'edit'} - - -class SlideElementDeleteView(DeleteView): - model = SlideElement - template_name = 'editor/delete_element.html' - form_class = DeleteSlideElementForm - - def form_valid(self, form): - self.success_url = reverse('edit-slide', kwargs={"pk": form.cleaned_data["slide_pk"]}) - return super().form_valid(form) - - -class SlideEditView(UpdateView): - model = Slide - fields = ['transition', 'transition_duration', 'auto_advance', 'auto_advance_duration', 'order'] - template_name = 'editor/edit_slide.html' - - -class DeckCreateView(CreateView): - model = Deck - template_name = 'editor/simple_create_form.html' - fields = ['name'] - extra_context = {'action': 'new-deck'} - - -# class DeckUpdateView(UpdateView): -# model = Deck -# template_name = 'editor/edit_deck.html' -# fields = ['name', 'theme'] - - -class DeckEditorView(UpdateView): - queryset = Deck.objects.all() - model = Deck - fields = [ - 'name', - 'theme', - 'default_transition', - 'default_transition_duration', - 'default_auto_advance', - 'default_auto_advance_duration', - 'script', - 'slide_text_markup', - ] - template_name = 'editor/deck_editor.html' - # extra_context = {'display': Display.objects.all().first()} - - -class ThemeCreateView(CreateView): - model = Theme - template_name = 'editor/simple_create_form.html' - fields = ['name'] - extra_context = {'action': 'new-theme'} - - -class ThemeUpdateView(UpdateView): - model = Theme - template_name = 'editor/edit_theme.html' - fields = ['name', 'css'] - extra_context = { - 'theme_list': Theme.objects.all - } - - -class SetThemeView(UpdateView): - model = Show - # fields = ['theme'] - form_class = SetThemeForm - template_name = 'editor/set_theme.html' - - -# class CheckThemeCompatibilityView(FormView): -# form_class = SetThemeForm -# template_name = 'editor/show_compatibility_snippet.html' -# -# def form_valid(self, form): -# show = Show.objects.get(form.cleaned_data['show_pk']) -# missing = show.check_compatibility(form.cleaned_data['theme']) -# if len(missing) <= 0: -# missing = None -# return render(self.request, 'editor/show_compatibility_snippet.html', { -# 'missing': missing -# }) - - -def generate_lorem(request, css_class:str, words:int): - return render(request, 'editor/lorem.html', - context={ - 'css_class': css_class, - 'lorem': lorem.get_word(count=words) - }) - - -def check_theme_compatibility(request): - form = SetThemeForm(request.POST) - if form.is_valid(): - show = Show.objects.get(pk=form.cleaned_data['show_pk']) - theme = form.cleaned_data['theme'] - missing = show.check_compatibility(theme) - if len(missing) == 0: - missing = None - return render(request, 'editor/show_compatibility_snippet.html', { - 'missing': missing, - }) - - -class TransitionEditorIndexView(ListView): - queryset = Transition.objects.all() - template_name = 'editor/transition_editor.html' - extra_context = { - 'transition_list': Transition.objects.all() - } - - -class TransitionEditorView(DetailView): - model = Transition - template_name = 'editor/transition_editor.html' - extra_context = { - 'transition_list': Transition.objects.all - } - - -class TransitionCreateView(CreateView): - model = Transition - fields = ["name"] - template_name = "editor/simple_create_form.html" - extra_context = { - 'action': 'new-transition', - } - - -class TransitionKeyframeCreateView(CreateView): - model = TransitionKeyframe - fields = ["transition", "marker", "css", "out"] - template_name = "editor/edit_keyframe.html" - extra_context = { - 'action': 'new-keyframe', - } - - -class TransitionKeyframeUpdateView(UpdateView): - model = TransitionKeyframe - fields = ["marker", "css"] - template_name = "editor/edit_keyframe.html" - extra_context = { - 'action': 'edit-keyframe', - } - - -class ChangeSlideOrderView(FormView): - form_class = ChangeSlideOrderForm - - def __init__(self): - self.moved_slide = None - super().__init__() - - def form_valid(self, form): - self.moved_slide = Slide.objects.get(pk=form.cleaned_data['moved_slide_pk']) - if form.cleaned_data['next_slide_pk']: - next_slide = Slide.objects.get(pk=form.cleaned_data['next_slide_pk']) - previous_slide = Slide.objects.filter( - deck=self.moved_slide.deck, - order__lt=next_slide.order, - ).last() - if previous_slide: - order_difference = next_slide.order - previous_slide.order - self.moved_slide.order = next_slide.order - (order_difference / 2) - self.moved_slide.save() - else: - self.moved_slide.order = next_slide.order - 1 - self.moved_slide.save() - else: - self.moved_slide.order = self.moved_slide.deck_or_segment().slides.last().order + 10 - self.moved_slide.save() - return super().form_valid(form) - - def get_success_url(self): - return self.moved_slide.get_absolute_url() - - -def push_deck_cues(request, pk): - deck = get_object_or_404(Deck, pk=pk) - deck.push_cues() - return HttpResponseRedirect(deck.get_absolute_url()) - - -@transaction.atomic() -def push_deck_slide_text(request, pk): - deck = get_object_or_404(Deck, pk=pk) - existing_slides = list(deck.slides.all()) - if len(existing_slides) > 0: - for slide in existing_slides: # TODO: Add some options here... - slide.delete() - for slide_markup in aoml.parse_markup(deck.slide_text_markup): - slide = Slide(deck=deck) - slide.save() - for element in aoml.parse_slide(slide_markup): - element.slide = slide - element.save() - return HttpResponseRedirect(deck.get_absolute_url()) diff --git a/OpenShow/slides/editor/views/__init__.py b/OpenShow/slides/editor/views/__init__.py new file mode 100644 index 0000000..9d01dd7 --- /dev/null +++ b/OpenShow/slides/editor/views/__init__.py @@ -0,0 +1,2 @@ +class NotSupportedException(Exception): + pass diff --git a/OpenShow/slides/editor/views/deck.py b/OpenShow/slides/editor/views/deck.py new file mode 100644 index 0000000..4478293 --- /dev/null +++ b/OpenShow/slides/editor/views/deck.py @@ -0,0 +1,145 @@ +from django.views.generic import CreateView, UpdateView, DeleteView, FormView +from django.urls import reverse_lazy +from slides.models import Deck, Slide, SlideElement +from django.db import transaction +from django.shortcuts import get_object_or_404, HttpResponseRedirect +import slides.aoml_parser as aoml +from slides.editor.forms import DeckFromImagesForm, ImportImagesForm + + +class DeckCreateView(CreateView): + model = Deck + template_name = 'editor/snippets/hx-simple_create_form.html' + fields = ['name'] + extra_context = { + 'action': 'new-deck', + 'object_type': 'Deck', + } + + +# class DeckUpdateView(UpdateView): +# model = Deck +# template_name = 'editor/edit_deck.html' +# fields = ['name', 'theme'] + + +class DeckEditorView(UpdateView): + queryset = Deck.objects.all() + model = Deck + fields = [ + 'name', + 'theme', + 'default_transition', + 'default_transition_duration', + 'default_auto_advance', + 'default_auto_advance_duration', + 'advance_in_loop', + 'script', + 'slide_text_markup', + ] + template_name = 'editor/deck_editor.html' + # extra_context = {'display': Display.objects.all().first()} + + +class DeckDeleteView(DeleteView): + model = Deck + success_url = reverse_lazy('slides-index') + template_name = 'editor/generic_confirm_delete.html' + extra_context = { + 'action': 'delete-deck', + } + + +def push_deck_cues(request, pk): + deck = get_object_or_404(Deck, pk=pk) + deck.push_cues() + return HttpResponseRedirect(deck.get_absolute_url()) + + +@transaction.atomic() +def push_deck_slide_text(request, pk): + deck = get_object_or_404(Deck, pk=pk) + existing_slides = list(deck.slides.all()) + if len(existing_slides) > 0: + for slide in existing_slides: # TODO: Add some options here... + slide.delete() + for slide_markup in aoml.parse_markup(deck.slide_text_markup): + slide = Slide(deck=deck) + slide.save() + for element in aoml.parse_slide(slide_markup): + element.slide = slide + element.save() + return HttpResponseRedirect(deck.get_absolute_url()) + + +def pull_aoml_text(request, pk): + deck = get_object_or_404(Deck, pk=pk) + deck.slide_text_markup = deck.pull_aoml() + deck.save() + return HttpResponseRedirect(deck.get_absolute_url()) + + +class DeckFromImagesView(FormView): + form_class = DeckFromImagesForm + template_name = 'editor/snippets/hx-simple_create_form.html' + extra_context = { + 'previous_page': 'slides-index', + 'action': 'import-deck-from-images', + 'object_type': 'Deck From Many Images', + } + + def form_valid(self, form): + files = form.cleaned_data['files'] + print(files) + print('^^FILES') + form.save() + for image in files: + new_slide = Slide(deck=form.instance) + new_slide.save() + new_slide_element = SlideElement( + css_class=form.cleaned_data["image_css_class"], + order=0, + slide=new_slide, + image=image, + body="", + ) + new_slide_element.save() + return HttpResponseRedirect(reverse_lazy('edit-deck', kwargs={'pk': form.instance.pk})) + + +class ImportImagesToExistingDeckView(FormView): + model = Deck + form_class = ImportImagesForm + template_name = 'editor/import_images_to_deck.html' + + def get_object(self): + db_object = get_object_or_404(self.model, pk=self.kwargs['pk']) + return db_object + + def get_context_data(self, **kwargs): + context = super(self.__class__, self).get_context_data(**kwargs) + context['deck'] = self.get_object() + return context + + def form_valid(self, form): + deck = self.get_object() + # If we're in overwrite mode, delete all the deck's slides first + if form.cleaned_data['mode'] == ImportImagesForm.OVERWRITE: + existing_slides = list(deck.slides.all()) + if len(existing_slides) > 0: + for slide in existing_slides: + slide.delete() + # Actually import the images + files = form.cleaned_data['files'] + for image in files: + new_slide = Slide(deck=deck) + new_slide.save() + new_slide_element = SlideElement( + css_class=form.cleaned_data["image_css_class"], + order=0, + slide=new_slide, + image=image, + body="", + ) + new_slide_element.save() + return HttpResponseRedirect(reverse_lazy('edit-deck', kwargs={'pk': deck.pk})) diff --git a/OpenShow/slides/editor/views/display.py b/OpenShow/slides/editor/views/display.py new file mode 100644 index 0000000..9c5b901 --- /dev/null +++ b/OpenShow/slides/editor/views/display.py @@ -0,0 +1,36 @@ +from django.views.generic import CreateView, UpdateView, DeleteView, DetailView +from slides.models import Display + + +class DisplayDetailView(DetailView): + model = Display + template_name = 'editor/display/detail.html' + extra_context = { + 'previous_page': 'slides-index', + } + + +class DisplayCreateView(CreateView): + model = Display + fields = [ + 'name', + ] + template_name = 'editor/display/create.html' + + +class DisplayUpdateView(UpdateView): + model = Display + fields = [ + 'name', + # 'pixel_width', + # 'pixel_height', + # Changing these will break things right now; non-1080p resolutions are not supported. See #22. + 'custom_css', + # Display.custom_css might go away to be replaced by theme display variants. + ] + template_name = 'editor/display/update.html' + + +class DisplayDeleteView(DeleteView): + model = Display + template_name = 'editor/display/delete.html' \ No newline at end of file diff --git a/OpenShow/slides/editor/views/index.py b/OpenShow/slides/editor/views/index.py new file mode 100644 index 0000000..41c62a1 --- /dev/null +++ b/OpenShow/slides/editor/views/index.py @@ -0,0 +1,14 @@ +from slides.models import Deck, Theme, Display, Show +from django.views.generic import ListView + +class IndexView(ListView): + queryset = Show.objects.all() + template_name = 'editor/index.html' + extra_context = { + 'deck_list': Deck.objects.all, + 'theme_list': Theme.objects.all, + 'display_list': Display.objects.all, + } + + +# TODO: Remove this entire view; it is probably not used anymore diff --git a/OpenShow/slides/editor/views/media.py b/OpenShow/slides/editor/views/media.py new file mode 100644 index 0000000..0bac849 --- /dev/null +++ b/OpenShow/slides/editor/views/media.py @@ -0,0 +1,56 @@ +from django.core.exceptions import ImproperlyConfigured +from django.http import HttpResponseRedirect +from django.urls import reverse +from neapolitan.views import CRUDView, Role + +from slides.models import MediaObject + + +class MediaObjectCRUDView(CRUDView): + model = MediaObject + fields = [ + 'title', + 'media_type', + 'raw_file', + 'embed_url', + #'autoplay', # Uncomment this once it will be useful for something + ] + + def get_success_url(self): + if self.role is Role.DELETE: + success_url = reverse('slides-index') + else: + success_url = super(self.__class__).get_success_url() + return success_url + + def detail(self, request, *args, **kwargs): + """GET handler for the detail view.""" + self.object = self.get_object() + context = self.get_context_data() + context['previous_page'] = 'slides-index' + return self.render_to_response(context) + + def get_template_names(self): + """ + Returns a list of template names to use when rendering the response. + + If `.template_name` is not specified, uses the + "{app_label}/{model_name}{template_name_suffix}.html" model template + pattern, with the fallback to the + "neapolitan/object{template_name_suffix}.html" default templates. + """ + if self.template_name is not None: + return [self.template_name] + + if self.model is not None and self.template_name_suffix is not None: + return [ + f"editor/" + f"{self.model._meta.object_name.lower()}" + f"{self.template_name_suffix}.html", + f"neapolitan/object{self.template_name_suffix}.html", + ] + msg = ( + "'%s' must either define 'template_name' or 'model' and " + "'template_name_suffix', or override 'get_template_names()'" + ) + raise ImproperlyConfigured(msg % self.__class__.__name__) diff --git a/OpenShow/slides/editor/views/segment.py b/OpenShow/slides/editor/views/segment.py new file mode 100644 index 0000000..edbec3b --- /dev/null +++ b/OpenShow/slides/editor/views/segment.py @@ -0,0 +1,14 @@ +from django.views.generic import CreateView, UpdateView +from slides.models import Segment + + +class SegmentCreateView(CreateView): + model = Segment + fields = ['show', 'name', 'order'] + template_name = 'editor/new_segment_form.html' + + +class SegmentUpdateView(UpdateView): + model = Segment + fields = ['name', 'order', 'included_deck'] + template_name = 'editor/edit_segment_form.html' diff --git a/OpenShow/slides/editor/views/show.py b/OpenShow/slides/editor/views/show.py new file mode 100644 index 0000000..2e81f3d --- /dev/null +++ b/OpenShow/slides/editor/views/show.py @@ -0,0 +1,48 @@ +from django.views.generic import DetailView, CreateView, DeleteView, UpdateView +from django.urls import reverse_lazy +from django.shortcuts import render +from slides.models import Show +from slides.editor.forms import SetThemeForm + +class ShowEditorView(DetailView): + queryset = Show.objects.all() + template_name = 'editor/show_editor.html' + # extra_context = {'display': Display.objects.all().first()} + + +class ShowCreateView(CreateView): + model = Show + fields = ['name'] + template_name = 'editor/snippets/hx-simple_create_form.html' + extra_context = { + 'action': 'new-show', + 'object_type': 'Show', + } + + +class ShowDeleteView(DeleteView): + model = Show + success_url = reverse_lazy('slides-index') + template_name = 'editor/generic_confirm_delete.html' + extra_context = { + 'action': 'delete-show', + } + + +class SetThemeView(UpdateView): + model = Show + form_class = SetThemeForm + template_name = 'editor/set_theme.html' + + +def check_theme_compatibility(request): + form = SetThemeForm(request.POST) + if form.is_valid(): + show = Show.objects.get(pk=form.cleaned_data['show_pk']) + theme = form.cleaned_data['theme'] + missing = show.check_compatibility(theme) + if len(missing) == 0: + missing = None + return render(request, 'editor/show_compatibility_snippet.html', { + 'missing': missing, + }) diff --git a/OpenShow/slides/editor/views/slide.py b/OpenShow/slides/editor/views/slide.py new file mode 100644 index 0000000..1053e86 --- /dev/null +++ b/OpenShow/slides/editor/views/slide.py @@ -0,0 +1,69 @@ +from django.views.generic import CreateView, FormView, UpdateView, DeleteView +from django.urls import reverse_lazy +from slides.models import Slide +from slides.editor.forms import ChangeSlideOrderForm + + +class SlideCreateView(CreateView): + model = Slide + fields = ['segment', 'deck'] + + def form_valid(self, form): + self.success_url = self.request.META['HTTP_REFERER'] + return super().form_valid(form) + + +class SlideEditView(UpdateView): + model = Slide + fields = ['transition', 'transition_duration', 'auto_advance', 'auto_advance_duration', 'order'] + template_name = 'editor/edit_slide.html' + + +class SlideDeleteView(DeleteView): + model = Slide + template_name = 'editor/generic_confirm_delete.html' + extra_context = { + 'action': 'delete-slide', + } + + def get_success_url(self): + if self.object.deck: + success_url = reverse_lazy('edit-deck', kwargs={"pk": self.object.deck.pk}) + elif self.object.segment: + success_url = reverse_lazy('edit-show', kwargs={"pk": self.object.segment.show.pk}) + else: + success_url = reverse_lazy('index') + return success_url + + + + +class ChangeSlideOrderView(FormView): + form_class = ChangeSlideOrderForm + + def __init__(self): + self.moved_slide = None + super().__init__() + + def form_valid(self, form): + self.moved_slide = Slide.objects.get(pk=form.cleaned_data['moved_slide_pk']) + if form.cleaned_data['next_slide_pk']: + next_slide = Slide.objects.get(pk=form.cleaned_data['next_slide_pk']) + previous_slide = Slide.objects.filter( + deck=self.moved_slide.deck, + order__lt=next_slide.order, + ).last() + if previous_slide: + order_difference = next_slide.order - previous_slide.order + self.moved_slide.order = next_slide.order - (order_difference / 2) + self.moved_slide.save() + else: + self.moved_slide.order = next_slide.order - 1 + self.moved_slide.save() + else: + self.moved_slide.order = self.moved_slide.deck_or_segment().slides.last().order + 10 + self.moved_slide.save() + return super().form_valid(form) + + def get_success_url(self): + return self.moved_slide.deck_or_segment().get_absolute_url() diff --git a/OpenShow/slides/editor/views/slide_element.py b/OpenShow/slides/editor/views/slide_element.py new file mode 100644 index 0000000..cc0d0ff --- /dev/null +++ b/OpenShow/slides/editor/views/slide_element.py @@ -0,0 +1,103 @@ +from django.views.generic import CreateView, DeleteView, UpdateView, FormView +from django.urls import reverse +from slides.models import SlideElement, Slide +from slides.editor.forms import DeleteSlideElementForm, EditSlideElementTextForm +from slides.editor.forms import ChangeSlideElementOrderForm + + +class SlideElementCreateView(CreateView): + model = SlideElement + fields = ['css_class', 'slide'] + template_name = 'editor/element_create.html' + + +class SlideElementDeleteView(DeleteView): + model = SlideElement + template_name = 'editor/delete_element.html' + form_class = DeleteSlideElementForm + + def form_valid(self, form): + self.success_url = reverse('edit-slide', kwargs={"pk": form.cleaned_data["slide_pk"]}) + return super().form_valid(form) + + +class SlideElementUpdateTextView(UpdateView): + form_class = EditSlideElementTextForm + model = SlideElement + # fields = ['body',] + template_name = 'editor/element_text_edit.html' + + def get_success_url(self): + return self.object.get_absolute_url() + + +class SlideElementUpdateCSSClassView(UpdateView): + model = SlideElement + fields = ['css_class'] + template_name = 'editor/element_css_class_edit.html' + + def get_success_url(self): + return self.object.get_absolute_url() + + +class SlideElementUpdateImageView(UpdateView): + model = SlideElement + fields = ['image'] + template_name = 'editor/element_image_edit.html' + + def get_success_url(self): + return self.object.get_absolute_url() + + +class SlideElementUpdateVideoView(UpdateView): + model = SlideElement + fields = ['video'] + template_name = 'editor/element_video_edit.html' + + def form_invalid(self, form): + print(form.errors) + return(super().form_invalid(form)) + + def get_success_url(self): + print('SUCCESS') + return self.object.get_absolute_url() + + +class SlideElementUpdateMediaObjectView(UpdateView): + model = SlideElement + fields = ['media_object'] + template_name = 'editor/element_media_object_edit.html' + + def get_success_url(self): + return self.object.get_absolute_url() + + +class ChangeSlideElementOrderView(FormView): + form_class = ChangeSlideElementOrderForm + + def __init__(self): + self.moved_slide = None + super().__init__() + + def form_valid(self, form): + self.moved_element = SlideElement.objects.get(pk=form.cleaned_data['moved_element_pk']) + if form.cleaned_data['next_element_pk']: + next_element = SlideElement.objects.get(pk=form.cleaned_data['next_element_pk']) + previous_element = SlideElement.objects.filter( + slide=self.moved_element.slide, + order__lt=next_element.order, + ).last() + if previous_element: + order_difference = next_element.order - previous_element.order + self.moved_element.order = next_element.order - (order_difference / 2) + self.moved_element.save() + else: + self.moved_element.order = next_element.order - 1 + self.moved_element.save() + else: + self.moved_element.order = self.moved_element.slide.elements.last().order + 10 + self.moved_element.save() + return super().form_valid(form) + + def get_success_url(self): + return self.moved_element.slide.get_absolute_url() diff --git a/OpenShow/slides/editor/views/theme.py b/OpenShow/slides/editor/views/theme.py new file mode 100644 index 0000000..c3c5526 --- /dev/null +++ b/OpenShow/slides/editor/views/theme.py @@ -0,0 +1,31 @@ +from django.views.generic import CreateView, UpdateView, DeleteView +from django.urls import reverse_lazy +from slides.models import Theme + + +class ThemeCreateView(CreateView): + model = Theme + template_name = 'editor/snippets/hx-simple_create_form.html' + fields = ['name'] + extra_context = { + 'action': 'new-theme', + 'object_type': 'Theme', + } + + +class ThemeUpdateView(UpdateView): + model = Theme + template_name = 'editor/edit_theme.html' + fields = ['name', 'css'] + extra_context = { + 'theme_list': Theme.objects.all + } + + +class ThemeDeleteView(DeleteView): + model = Theme + success_url = reverse_lazy('slides-index') + template_name = 'editor/generic_confirm_delete.html' + extra_context = { + 'action': 'delete-theme', + } diff --git a/OpenShow/slides/editor/views/transition.py b/OpenShow/slides/editor/views/transition.py new file mode 100644 index 0000000..02462ff --- /dev/null +++ b/OpenShow/slides/editor/views/transition.py @@ -0,0 +1,46 @@ +from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView +from slides.models import Transition, TransitionKeyframe + + +class TransitionEditorIndexView(ListView): + queryset = Transition.objects.all() + template_name = 'editor/transition_editor.html' + extra_context = { + 'transition_list': Transition.objects.all() + } + + +class TransitionEditorView(DetailView): + model = Transition + template_name = 'editor/transition_editor.html' + extra_context = { + 'transition_list': Transition.objects.all + } + + +class TransitionCreateView(CreateView): + model = Transition + fields = ["name"] + template_name = 'editor/snippets/hx-simple_create_form.html' + extra_context = { + 'action': 'new-transition', + 'object_type': 'Transition', + } + + +class TransitionKeyframeCreateView(CreateView): + model = TransitionKeyframe + fields = ["transition", "marker", "css", "out"] + template_name = "editor/edit_keyframe.html" + extra_context = { + 'action': 'new-keyframe', + } + + +class TransitionKeyframeUpdateView(UpdateView): + model = TransitionKeyframe + fields = ["marker", "css"] + template_name = "editor/edit_keyframe.html" + extra_context = { + 'action': 'edit-keyframe', + } diff --git a/OpenShow/slides/editor/views/utils.py b/OpenShow/slides/editor/views/utils.py new file mode 100644 index 0000000..c058e19 --- /dev/null +++ b/OpenShow/slides/editor/views/utils.py @@ -0,0 +1,10 @@ +from django.shortcuts import render +import lorem + + +def generate_lorem(request, css_class:str, words:int): + return render(request, 'editor/lorem.html', + context={ + 'css_class': css_class, + 'lorem': lorem.get_word(count=words) + }) diff --git a/OpenShow/slides/forms.py b/OpenShow/slides/forms.py index f0cfd25..42faeb6 100644 --- a/OpenShow/slides/forms.py +++ b/OpenShow/slides/forms.py @@ -4,15 +4,17 @@ class SlideDisplayForm(Form): - show_pk = IntegerField() + show_pk = IntegerField(required=False) slide_pk = IntegerField(required=False) direction = CharField(required=False) + display_pk_multiple = CharField(required=False) class Meta: fields = [ 'show_pk', 'slide_pk', 'direction', + 'display_pk_multiple', ] diff --git a/OpenShow/slides/migrations/0020_display_previous_slide_display_slide_changed_at.py b/OpenShow/slides/migrations/0020_display_previous_slide_display_slide_changed_at.py new file mode 100644 index 0000000..c0ae3f9 --- /dev/null +++ b/OpenShow/slides/migrations/0020_display_previous_slide_display_slide_changed_at.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.6 on 2024-04-17 09:32 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("slides", "0019_alter_display_current_slide"), + ] + + operations = [ + migrations.AddField( + model_name="display", + name="previous_slide", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="slides.slide", + ), + ), + migrations.AddField( + model_name="display", + name="slide_changed_at", + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/OpenShow/slides/migrations/0021_display_current_deck.py b/OpenShow/slides/migrations/0021_display_current_deck.py new file mode 100644 index 0000000..f248c16 --- /dev/null +++ b/OpenShow/slides/migrations/0021_display_current_deck.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.6 on 2024-07-09 14:16 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("slides", "0020_display_previous_slide_display_slide_changed_at"), + ] + + operations = [ + migrations.AddField( + model_name="display", + name="current_deck", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="current_displays", + to="slides.deck", + ), + ), + ] diff --git a/OpenShow/slides/migrations/0022_themerule_themevariant_themevariantrule.py b/OpenShow/slides/migrations/0022_themerule_themevariant_themevariantrule.py new file mode 100644 index 0000000..8c5c74a --- /dev/null +++ b/OpenShow/slides/migrations/0022_themerule_themevariant_themevariantrule.py @@ -0,0 +1,96 @@ +# Generated by Django 5.1 on 2024-08-28 20:32 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("slides", "0021_display_current_deck"), + ] + + operations = [ + migrations.CreateModel( + name="ThemeRule", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "css_selector", + models.CharField(help_text="CSS Selector", max_length=100), + ), + ("properties", models.TextField()), + ("base_rule", models.BooleanField(default=False)), + ( + "theme", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="rules", + to="slides.theme", + ), + ), + ], + ), + migrations.CreateModel( + name="ThemeVariant", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=100)), + ( + "theme", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="variants", + to="slides.theme", + ), + ), + ], + ), + migrations.CreateModel( + name="ThemeVariantRule", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("properties", models.TextField()), + ( + "rule", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="variants", + to="slides.themerule", + ), + ), + ( + "variant", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="rules", + to="slides.themevariant", + ), + ), + ], + ), + ] diff --git a/OpenShow/slides/migrations/0023_alter_slideelement_order.py b/OpenShow/slides/migrations/0023_alter_slideelement_order.py new file mode 100644 index 0000000..42a7ae8 --- /dev/null +++ b/OpenShow/slides/migrations/0023_alter_slideelement_order.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1 on 2024-09-03 15:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("slides", "0022_themerule_themevariant_themevariantrule"), + ] + + operations = [ + migrations.AlterField( + model_name="slideelement", + name="order", + field=models.FloatField(), + ), + ] diff --git a/OpenShow/slides/migrations/0024_mediaobject.py b/OpenShow/slides/migrations/0024_mediaobject.py new file mode 100644 index 0000000..9ff5834 --- /dev/null +++ b/OpenShow/slides/migrations/0024_mediaobject.py @@ -0,0 +1,49 @@ +# Generated by Django 5.1 on 2024-09-10 21:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("slides", "0023_alter_slideelement_order"), + ] + + operations = [ + migrations.CreateModel( + name="MediaObject", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "media_type", + models.CharField( + choices=[ + ("VIMEO_EMBED", "Vimeo Embed"), + ("VIDEO", "Video"), + ("AUDIO", "Audio"), + ], + default="VIDEO", + max_length=100, + ), + ), + ( + "raw_file", + models.FileField(blank=True, null=True, upload_to="media_intake/"), + ), + ( + "final_file", + models.FileField(blank=True, null=True, upload_to="media_final/"), + ), + ("embed_url", models.URLField(blank=True, null=True)), + ("autoplay", models.BooleanField(default=True)), + ], + ), + ] diff --git a/OpenShow/slides/migrations/0025_mediaobject_file_hash.py b/OpenShow/slides/migrations/0025_mediaobject_file_hash.py new file mode 100644 index 0000000..bfe20ff --- /dev/null +++ b/OpenShow/slides/migrations/0025_mediaobject_file_hash.py @@ -0,0 +1,18 @@ + # Generated by Django 5.1 on 2024-09-10 22:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("slides", "0024_mediaobject"), + ] + + operations = [ + migrations.AddField( + model_name="mediaobject", + name="file_hash", + field=models.CharField(blank=True, max_length=256, null=True), + ), + ] diff --git a/OpenShow/slides/migrations/0026_mediaobject_title.py b/OpenShow/slides/migrations/0026_mediaobject_title.py new file mode 100644 index 0000000..eb0e6cf --- /dev/null +++ b/OpenShow/slides/migrations/0026_mediaobject_title.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1 on 2024-10-08 21:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("slides", "0025_mediaobject_file_hash"), + ] + + operations = [ + migrations.AddField( + model_name="mediaobject", + name="title", + field=models.CharField(default="", max_length=100), + preserve_default=False, + ), + ] diff --git a/OpenShow/slides/migrations/0027_slideelement_media_object.py b/OpenShow/slides/migrations/0027_slideelement_media_object.py new file mode 100644 index 0000000..3ddc431 --- /dev/null +++ b/OpenShow/slides/migrations/0027_slideelement_media_object.py @@ -0,0 +1,25 @@ +# Generated by Django 5.1 on 2024-10-08 23:02 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("slides", "0026_mediaobject_title"), + ] + + operations = [ + migrations.AddField( + model_name="slideelement", + name="media_object", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="elements", + to="slides.mediaobject", + ), + ), + ] diff --git a/OpenShow/slides/migrations/0028_alter_mediaobject_media_type_alter_mediaobject_title.py b/OpenShow/slides/migrations/0028_alter_mediaobject_media_type_alter_mediaobject_title.py new file mode 100644 index 0000000..bd97fcd --- /dev/null +++ b/OpenShow/slides/migrations/0028_alter_mediaobject_media_type_alter_mediaobject_title.py @@ -0,0 +1,31 @@ +# Generated by Django 5.1 on 2024-10-17 16:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("slides", "0027_slideelement_media_object"), + ] + + operations = [ + migrations.AlterField( + model_name="mediaobject", + name="media_type", + field=models.CharField( + choices=[ + ("VIMEO_LIVE_EMBED", "Vimeo Live Embed"), + ("VIDEO", "Video"), + ("AUDIO", "Audio"), + ], + default="VIDEO", + max_length=100, + ), + ), + migrations.AlterField( + model_name="mediaobject", + name="title", + field=models.CharField(max_length=100, unique=True), + ), + ] diff --git a/OpenShow/slides/migrations/0029_deck_advance_in_loop.py b/OpenShow/slides/migrations/0029_deck_advance_in_loop.py new file mode 100644 index 0000000..da4f9dc --- /dev/null +++ b/OpenShow/slides/migrations/0029_deck_advance_in_loop.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1 on 2024-11-15 17:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('slides', '0028_alter_mediaobject_media_type_alter_mediaobject_title'), + ] + + operations = [ + migrations.AddField( + model_name='deck', + name='advance_in_loop', + field=models.BooleanField(default=False), + ), + ] diff --git a/OpenShow/slides/models.py b/OpenShow/slides/models.py index 20af99e..4b6e170 100644 --- a/OpenShow/slides/models.py +++ b/OpenShow/slides/models.py @@ -2,6 +2,9 @@ from django_eventstream import send_event from collections.abc import Iterable from django.shortcuts import reverse +from django.utils import timezone +from datetime import timedelta, datetime +from django_q.models import Schedule import tinycss2 @@ -15,11 +18,13 @@ class InvalidArgumentException(Exception): class NotSupportedException(Exception): pass + class Display(models.Model): # A set of characteristics used to modify slide appearance for different displays name = models.CharField(max_length=100) pixel_width = models.IntegerField(default=1920) pixel_height = models.IntegerField(default=1080) custom_css = models.TextField(null=True, blank=True) + slide_changed_at = models.DateTimeField(auto_now=True) current_show = models.ForeignKey( to='Show', null=True, @@ -32,17 +37,103 @@ class Display(models.Model): # A set of characteristics used to modify slide ap blank=True, on_delete=models.SET_NULL, ) + previous_slide = models.ForeignKey( + to='Slide', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) current_theme = models.ForeignKey( to='Theme', null=True, blank=True, on_delete=models.SET_NULL, ) + current_deck = models.ForeignKey( + to='Deck', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='current_displays' + ) segments = models.ManyToManyField( to='Segment', blank=True, ) # TODO: What the heck is this for? + def advance_slide(self, direction): + if self.current_deck: + self._advance_slide_in_deck(direction) + elif self.current_show: + self._advance_slide_in_show(direction) + + def _advance_slide_in_deck(self, direction): + if self.previous_slide and self.current_slide.auto_advance: + if timezone.now() - \ + self.slide_changed_at < \ + timedelta(seconds=self.current_slide.auto_advance_duration): + # Abort and continue silently if we're getting a "next slide" directive and + # the previous slide's auto_advance_duration has not passed + # Manually selecting a different slide will override this. + return 1 + slide = self.current_slide.next(direction) + if not slide: + if self.current_slide.deck.advance_in_loop: + slide = self.current_slide.deck.slides.first() + else: + slide = self.current_slide + slide.send_to_display([self, ]) + + def _advance_slide_in_show(self, direction): + if self.previous_slide and self.current_slide.auto_advance: + if timezone.now() - \ + self.slide_changed_at < \ + timedelta(seconds=self.current_slide.auto_advance_duration): + # Abort and continue silently if we're getting a "next slide" directive and + # the previous slide's auto_advance_duration has not passed + # Manually selecting a different slide will override this. + return 1 + slide = self.current_slide.next(direction) + next_segment = None + if not slide: + if self.current_show.advance_between_segments: + try: # TODO: review this horrible try/except block + if self.current_slide.deck: + # If we're out of room to iterate through a deck... + current_segment = self.current_show.segments.filter(included_deck=self.current_slide.deck).first() + if current_segment.slides.first() and direction == 'forward': + slide = current_segment.slides.first() + elif direction == 'reverse': + slide = current_segment.next_with_slides(direction).get_last_slide() + elif direction == 'forward': + slide = current_segment.next_with_slides(direction).get_first_slide() + else: # ..if current_slide.segment + current_segment = self.current_slide.segment + if direction == 'reverse' and current_segment.included_deck: + slide = current_segment.included_deck.slides.last() + elif direction == 'reverse': + slide = current_segment.next_with_slides(direction).get_last_slide() + elif direction == 'forward': + slide = current_segment.next_with_slides(direction).get_first_slide() + except AttributeError: + slide = self.current_slide + elif self.current_show.advance_loop: + if direction == 'reverse': + if self.current_slide.deck: + slide = self.current_slide.deck.slides.last() + else: + slide = self.current_slide.segment.slides.last() + elif direction == 'forward': + if self.current_slide.deck: + slide = self.current_slide.deck.slides.first() + else: + slide = self.current_slide.segment.slides.first() + else: + slide = self.current_slide + slide.send_to_display([self, ], self.current_show) + return 0 + def __str__(self): return self.name # TODO: Make it possible to pause auto-advance on displays, configurable in the show view. @@ -50,6 +141,9 @@ def __str__(self): # TODO: The Display should probably know the current segment, as well as the current show. # auto_advance_paused = models.BooleanField(default=False, null=False) + def get_absolute_url(self): + return reverse('display-detail', kwargs={'pk': self.pk}) + class Deck(models.Model): # A Reusable set of slides, which can be included in a Show Segment name = models.CharField(max_length=100) @@ -72,6 +166,7 @@ class Deck(models.Model): # A Reusable set of slides, which can be included in blank=True, ) default_auto_advance = models.BooleanField(default=False, null=False) + advance_in_loop = models.BooleanField(default=False, null=False) default_auto_advance_duration = models.FloatField(default=10) script = models.TextField(null=True, blank=True) slide_text_markup = models.TextField(null=True, blank=True) @@ -95,6 +190,13 @@ def push_cues(self): slide.cue = cue slide.save() + def pull_aoml(self): + aoml_str = '' + for slide in self.slides.all(): + aoml_str += slide.pull_aoml() + if slide != self.slides.last(): + aoml_str += '~~\r' + return aoml_str class Meta: ordering = ('name',) @@ -230,7 +332,7 @@ def get_last_slide(self): class SlideElement(models.Model): # An individual piece of a slide (a block of text, a video, etc.). It's just HTML :) css_class = models.CharField(max_length=100) body = models.TextField(null=True, blank=True) - order = models.IntegerField() + order = models.FloatField() slide = models.ForeignKey( to='Slide', unique=False, @@ -249,6 +351,13 @@ class SlideElement(models.Model): # An individual piece of a slide (a block of null=True, upload_to='element_videos/' ) + media_object = models.ForeignKey( + to='MediaObject', + blank=True, + null=True, + on_delete=models.SET_NULL, + related_name='elements', + ) def get_absolute_url(self): return reverse('edit-slide', kwargs={'pk': self.slide.pk}) @@ -265,6 +374,7 @@ class Meta: ordering = ["-order"] def save(self, *args, **kwargs): + print(f'MEDIA{self.media_object}') if not self.pk: if self.slide.elements.last(): self.order = self.slide.elements.last().order + 10 @@ -272,6 +382,11 @@ def save(self, *args, **kwargs): self.order = 1 super(SlideElement, self).save(*args, **kwargs) + def get_editable_text(self): + if self.body: + self.body = self.body.replace('
      ', '\n') + return self.body + class QRCodeElement(SlideElement): link = models.TextField() @@ -328,20 +443,27 @@ def get_absolute_url(self): # raise RuntimeError('A slide must be part of something... something has gone very wrong.') return reverse('edit-slide', kwargs={'pk': self.pk}) - def send_to_display(self, displays:Iterable, show) -> None: + def send_to_display(self, displays:Iterable, show:None or Show = None) -> None: """ :param displays: An iterable (probably a QuerySet) of Display objects to display the slide on :param show: - The Show object which this slide is being displayed from currently + The Show object which this slide is being displayed from currently. If this is set to None or not supplied, + we assume that the slide is being displayed directly from a deck rather than a show. :return: This method always returns None. """ for display in displays: slide_theme = self.get_theme() + display.previous_slide = display.current_slide # do the slide shuffle display.current_slide = self display.current_show = show - if display.current_theme != slide_theme: + if show is None: + display.current_deck = self.deck + else: + display.current_deck = None + if display.current_theme != slide_theme and slide_theme is not None: + # TODO: Add a UI warning to make it more obvious when a show has no theme set display.current_theme = slide_theme display.save() send_event('test', f'display-{display.pk}-theme', f'sending theme {slide_theme.pk} to display {display.pk}') @@ -401,6 +523,7 @@ def next(self, direction): return None # or, if there are no more (say, we're at the beginning/end...) return self # TODO: Make this mess better - add a toggle for "continue past end of deck" on the show control sidebar + # TODO: Not going to pull a chesterton's fence here while working on something else, but let's revisit whether the above return ever gets executed. I don't think it does. def save(self, *args, **kwargs): if not self.pk: @@ -420,6 +543,28 @@ def save(self, *args, **kwargs): self.order = self.segment.slides.last().order + 10 super(Slide, self).save(*args, **kwargs) + def has_video(self): + result = False + for element in self.elements.all(): + # print(bool(element.video)) + if element.video: + result = True + return result + + def has_mediaobject(self): + result = False + for element in self.elements.all(): + if element.media_object: + result = True + return result + + def pull_aoml(self): + aoml_str = '' + for element in self.elements.all().order_by('order'): + aoml_str += f'>>{element.css_class}||\r{element.body}\r'.replace('
      ', '\\') + # TODO: Handle video/image once the AOML spec supports media + return aoml_str + class Transition(models.Model): name = models.CharField(max_length=30) @@ -492,3 +637,110 @@ def __str__(self): return self.name +class ThemeRule(models.Model): + css_selector = models.CharField(max_length=100, help_text="CSS Selector") + properties = models.TextField() + base_rule = models.BooleanField(default=False) + theme = models.ForeignKey( + to=Theme, + on_delete=models.CASCADE, + related_name='rules', + ) + + +class ThemeVariant(models.Model): + name = models.CharField(max_length=100) + theme = models.ForeignKey( + to=Theme, + on_delete=models.CASCADE, + related_name='variants', + ) + + +class ThemeVariantRule(models.Model): + rule = models.ForeignKey( + to=ThemeRule, + on_delete=models.CASCADE, + related_name='variants', + ) + properties = models.TextField() + variant = models.ForeignKey( + to=ThemeVariant, + on_delete=models.CASCADE, + related_name='rules', + ) + + +VIMEO_LIVE_EMBED = 'VIMEO_LIVE_EMBED' +VIDEO = 'VIDEO' +AUDIO = 'AUDIO' + + +class MediaObject(models.Model): + title = models.CharField(max_length=100, unique=True) + media_type = models.CharField( + max_length=100, + choices=[ + (VIMEO_LIVE_EMBED, 'Vimeo Live Embed'), + (VIDEO, 'Video'), + (AUDIO, 'Audio'), + ], + default=VIDEO, + ) + # We'll use the same field regardless of what file type is uploaded - the FileField does no validation, so there's + # no particular benefit to adding more fields here. + raw_file = models.FileField( + blank=True, + null=True, + upload_to='media_intake/' + ) + # We'll use the same field regardless of what file type is uploaded - the FileField does no validation, so there's + # no particular benefit to adding more fields here. + final_file = models.FileField( + blank=True, + null=True, + upload_to='media_final/' + ) + embed_url = models.URLField( + blank=True, + null=True, + ) + autoplay = models.BooleanField( + default=True, + # Currently, there would be no mechanism to manually start a non-autoplaying media object, so this will just sit + # until more features are implemented, but let's leave the bones of it here for the future. + ) + file_hash = models.CharField(max_length=256, null=True, blank=True) + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + if self.media_type == VIDEO and not self.final_file: + Schedule.objects.create( + func='slides.editor.tasks.transcode_video', + args=self.pk, + schedule_type=Schedule.ONCE, + next_run=datetime.utcnow(), + ) + print('scheduled video') + elif self.media_type == AUDIO and not self.final_file: + Schedule.objects.create( + func='slides.editor.tasks.transcode_audio', + args=self.pk, + schedule_type=Schedule.ONCE, + next_run=datetime.utcnow(), + ) + print('scheduled audio') + + def get_slide_element_template(self): + template_name = None + match self.media_type: + case 'VIDEO': + template_name = 'slides/media/video.html' + case 'AUDIO': + template_name = 'slides/media/audio.html' + case 'VIMEO_LIVE_EMBED': + template_name = 'slides/media/vimeo_live_embed.html' + return template_name + + def __str__(self): + return self.title \ No newline at end of file diff --git a/OpenShow/slides/static/slides/presenter-view.css b/OpenShow/slides/static/slides/presenter-view.css index 225b12f..db062ec 100644 --- a/OpenShow/slides/static/slides/presenter-view.css +++ b/OpenShow/slides/static/slides/presenter-view.css @@ -1,72 +1,49 @@ -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} +/** {*/ +/* box-sizing: border-box;*/ +/* margin: 0;*/ +/* padding: 0;*/ +/*}*/ -body { - display: grid; - grid-template-columns: 1fr 3fr 1fr; - height: 100dvh; +body:not(#specificity-hack){ + max-height: 100dvh; width: 100%; - background: #303030; -} - -navbar { - grid-row: 1; - grid-column: span 3; - height: 3rem; - background: #606060; - border: 3px solid darkgray; - border-radius: 1rem; - display: grid; - grid-template-columns: 3rem 1fr 3rem; - margin: 0.5rem; } +#show-container, #shows-sidebar { - background: #303030; + margin: 0; } -#shows-sidebar ul { - display: flex; - flex-direction: column; - border-right: 3px solid darkgray; - height: calc(100dvh - 4rem); +#shows-sidebar { + max-width: 20rem; + max-height: calc(100dvh - 7.5rem); + margin: 0.5rem; + margin-top: 0; overflow: scroll; - list-style: none; - gap: 0.5rem; - padding: 0.5rem; -} - -#shows-sidebar li { - height: 4rem; - background: #606060; - border: 3px solid darkgray; - border-radius: 1rem; - padding-left: 0.5rem; -} - -#shows-sidebar a { - color: white; - text-decoration: none; } #show-container { overflow: scroll; - height: 100%; + height: calc(100dvh - 7rem); } #show.presentation { - background: #303030; - color: white; + /*background: #303030;*/ + /*color: white;*/ padding: 0.5rem; + padding-top: 0; +} + +#show.presentation h2 { + margin-top: 0; } #show.presentation > ul { + margin-top: 0; display: flex; flex-direction: column; - list-style: none + list-style: none; + padding: 0; } #show.presentation > ul > li > ul { @@ -75,13 +52,11 @@ navbar { gap: 1rem; list-style: none; margin: 1rem; + padding: 0; } -#show.presentation {} #show-controls { - background: #303030; - color: white; padding: 0.5rem; container: show-controls / inline-size; display: flex; @@ -96,17 +71,12 @@ navbar { #settings-toggle { margin-bottom: initial; + margin-inline: auto; } #show-controls .settings.expand { display: flex; flex-direction: column; - background: #606060; - border: 3px solid lightgray; - border-radius: 1rem; - padding: 1rem; - font-size: 1.2rem; - gap: 0.5rem; } #show-controls .slide-thumbnail { @@ -134,17 +104,11 @@ navbar { #forward-reverse .icon-button { height: 4rem; width: 100%; - border: 3px solid darkgray; - background: #505050; -} - -#forward-reverse .icon-button:hover { - background: #404040; - border: 3px solid #808080; } #forward-reverse .icon-button.htmx-request { opacity: 30%; + transition: opacity 0.3s; } #forward-reverse .icon-button:not(.htmx-request) { @@ -153,7 +117,7 @@ navbar { } .selected { - box-shadow: 0px 0px 15px 1px #51ffcf;; + box-shadow: 0px 0px 15px 1px var(--info-graphical-fg); } .mobile-tab-controls { @@ -171,9 +135,8 @@ navbar { display: flex; height: 5rem; width: 100%; - background: #303030; - border-top: 3px solid darkgray; grid-row: 3; + padding: 0.25rem; } .mobile-tab-controls > * { @@ -195,8 +158,6 @@ navbar { #forward-reverse .icon-button:hover, #forward-reverse .icon-button { height: 8rem; - background: #505050; - border: 3px solid darkgray; } #show-controls.mobile-hidden, @@ -251,52 +212,52 @@ navbar { } } -.deck-slide .slide-thumbnail { - border-radius: 0; -} - -.deck-slide { - outline: 3px solid darkgray; - border-radius: 0.5rem; - overflow: clip; -} - -.deck-slide:hover { - outline: 3px solid #808080; -} - -.deck-slide p { - margin: 0.5rem; -} +/*.deck-slide .slide-thumbnail {*/ +/* border-radius: 0;*/ +/*}*/ -/*.display-preview {*/ -/* width: 1920px;*/ -/* height: 1080px;*/ -/* transform: scale(10%);*/ -/* margin-right: calc(-1920px / 10);*/ -/* margin-bottom: calc(-1080px / 10);*/ -/* margin-left: calc(calc(100% - calc(1920px / 10)) / 2);*/ -/* overflow: hidden;*/ +/*.deck-slide {*/ /* outline: 3px solid darkgray;*/ /* border-radius: 0.5rem;*/ +/* overflow: clip;*/ /*}*/ -/* Temporary slide thumbnails- eventually, these need to display an accurate representation of the slide. For now, we'll make it the right size and just throw the text in. */ - -/*.slide-thumbnail .slide {*/ -/* width: calc(1920px / 10);*/ -/* height: calc(1080px / 10);*/ -/* background: black;*/ -/* color: white;*/ -/* font-family: sans-serif;*/ +/*.deck-slide:hover {*/ +/* outline: 3px solid #808080;*/ /*}*/ -/*.slide-thumbnail {*/ -/* max-width: calc(1920px / 10);*/ -/* max-height: calc(1080px / 10);*/ -/* overflow: hidden;*/ +/*.deck-slide p {*/ /* margin: 0.5rem;*/ -/* border: 3px solid darkgray;*/ -/* border-radius: 0.5rem;*/ /*}*/ +/*!*.display-preview {*!*/ +/*!* width: 1920px;*!*/ +/*!* height: 1080px;*!*/ +/*!* transform: scale(10%);*!*/ +/*!* margin-right: calc(-1920px / 10);*!*/ +/*!* margin-bottom: calc(-1080px / 10);*!*/ +/*!* margin-left: calc(calc(100% - calc(1920px / 10)) / 2);*!*/ +/*!* overflow: hidden;*!*/ +/*!* outline: 3px solid darkgray;*!*/ +/*!* border-radius: 0.5rem;*!*/ +/*!*}*!*/ + +/*!* Temporary slide thumbnails- eventually, these need to display an accurate representation of the slide. For now, we'll make it the right size and just throw the text in. *!*/ + +/*!*.slide-thumbnail .slide {*!*/ +/*!* width: calc(1920px / 10);*!*/ +/*!* height: calc(1080px / 10);*!*/ +/*!* background: black;*!*/ +/*!* color: white;*!*/ +/*!* font-family: sans-serif;*!*/ +/*!*}*!*/ + +/*!*.slide-thumbnail {*!*/ +/*!* max-width: calc(1920px / 10);*!*/ +/*!* max-height: calc(1080px / 10);*!*/ +/*!* overflow: hidden;*!*/ +/*!* margin: 0.5rem;*!*/ +/*!* border: 3px solid darkgray;*!*/ +/*!* border-radius: 0.5rem;*!*/ +/*!*}*!*/ + diff --git a/OpenShow/slides/static/slides/slide-thumbnail.css b/OpenShow/slides/static/slides/slide-thumbnail.css index 1c23e51..27cee2a 100644 --- a/OpenShow/slides/static/slides/slide-thumbnail.css +++ b/OpenShow/slides/static/slides/slide-thumbnail.css @@ -1,10 +1,15 @@ +body { + --display-size-divisor: 10 +} + .slide-thumbnail .slide { + min-width: 1920px; width: 1920px; - height: 1080px; - transform: scale(0.1); - margin-right: calc(-1920px / 10); - margin-bottom: calc(-1080px / 10); - margin-left: calc(calc(100% - calc(1920px / 10)) / 2); + min-height: 1080px; + transform: scale(calc(1 / var(--display-size-divisor))); + margin-right: calc(-1920px / var(--display-size-divisor)); + margin-bottom: calc(-1080px / var(--display-size-divisor)); + margin-left: calc(calc(100% - calc(1920px / var(--display-size-divisor))) / 2); transform-origin: left top; background: black; align-self: center; @@ -12,16 +17,16 @@ } .slide-thumbnail { - max-width: calc(1920px / 10); - max-height: calc(1080px / 10); + max-width: calc(1920px / var(--display-size-divisor)); + max-height: calc(1080px / var(--display-size-divisor)); overflow: hidden; /*margin: 0.5rem;*/ - outline: 3px solid darkgray; - border-radius: 0.5rem; + outline: 1px solid var(--faded-fg); + border-radius: var(--border-radius); } .slide-thumbnail:hover { - outline: 3px solid #808080; + box-shadow: 0px 0px 15px 1px var(--ok-graphical-fg); } .slide-thumbnail form { diff --git a/OpenShow/slides/static/slides/slide.css b/OpenShow/slides/static/slides/slide.css index 1e00c87..5926727 100644 --- a/OpenShow/slides/static/slides/slide.css +++ b/OpenShow/slides/static/slides/slide.css @@ -36,4 +36,17 @@ body { #slide-grabber { display: none; +} + +#slide-info-overlays { + position: absolute; + bottom: 45px; + right: 45px; + height: 200px; +} + +.slide-info-icon { + height: 200px; + width: 200px; + color: white; } \ No newline at end of file diff --git a/OpenShow/slides/templates/slides/deck-slide-thumbnail.html b/OpenShow/slides/templates/slides/deck-slide-thumbnail.html new file mode 100644 index 0000000..9dc9571 --- /dev/null +++ b/OpenShow/slides/templates/slides/deck-slide-thumbnail.html @@ -0,0 +1,20 @@ +{% load srcdoc %} +
      +
      + {% csrf_token%} + + +
      + +
      +
      +
      \ No newline at end of file diff --git a/OpenShow/slides/templates/slides/deck.html b/OpenShow/slides/templates/slides/deck.html index 3b7526d..d24bc63 100644 --- a/OpenShow/slides/templates/slides/deck.html +++ b/OpenShow/slides/templates/slides/deck.html @@ -1,36 +1,137 @@ -{% extends 'editor/base.html' %} +{% extends 'core/base.html' %} {% load static %} +{% load icon %} -{% block additional_css %} - +{% block extra_css %} +{# #} - + + +{% endblock %} + +{% block header %} +

      {{ deck.name }}

      {% endblock %} -{% block content %} -
      -
        - {% for deck in deck_list %} - +{% block title %}{{ deck.name }} - OpenShow{% endblock %} + +{% block header_right_button %} + {% icon 'edit-2' %} +{% endblock %} + +{% block main %} +
      +
      +
    +
    +
    + + +
    + +
    +{#
    #} +{# {% csrf_token %}#} +{#
    #} +{# #} +{# #} +{#
    #} +{#
    #} +{# #} +{# #} +{#
    #} +{#
    #} +{#
    #} +{#
    #} +{# {% if show.displays.all.first %}#} +{# {% for display in show.displays.all %}#} +{# #} +{# {% endfor %}#} +{# {% endif %}#} +{#
    #} +{#
    #} +
    +
    Displays: +
      + {% for display in display_list %} +
    • + +
    • + {% endfor %} +
    + Clear: +
      + {% for display in display_list %} + + {% endfor %} +
    +
    +
    +
    +{#
    #} +{# {% if show.displays.all.first %}#} +{# #} +{# {% endif %}#} +{#
    #} +
    +
    + + + +
    {% endblock %} \ No newline at end of file diff --git a/OpenShow/slides/templates/slides/display.html b/OpenShow/slides/templates/slides/display.html index 71aeb2e..d7b37e9 100644 --- a/OpenShow/slides/templates/slides/display.html +++ b/OpenShow/slides/templates/slides/display.html @@ -4,11 +4,11 @@ OpenShow Display {{ display.pk }} - - + + + Slide {{ slide.pk }} + + + {% include 'slides/slide.html' with slide=slide %} +
    + {% if slide.has_video %} + {% icon "film" class='slide-info-icon' %} + {% endif %} + {% if slide.has_mediaobject %} + {% icon "play" class='slide-info-icon' %} + {% endif %} + {% if slide.auto_advance %} + {% icon 'clock' class='slide-info-icon' %} + {% endif %} +
    + \ No newline at end of file diff --git a/OpenShow/slides/templates/slides/index.html b/OpenShow/slides/templates/slides/index.html index b62559a..3a181e8 100644 --- a/OpenShow/slides/templates/slides/index.html +++ b/OpenShow/slides/templates/slides/index.html @@ -1,26 +1,47 @@ -{% extends 'slides/base.html' %} +{% extends 'core/base.html' %} {% load static %} {% load icon %} -{% block additional_css %} - - +{% block extra_js %} + {{ block.super }} + +{% endblock %} + +{% block title %}OpenShow Slides{% endblock %} + +{% block header %} +

    OpenShow Slides

    {% endblock %} {% block content %} -
    - -

    OpenShow Slides

    - -
    -
    +
    + + + + + + +
    +

    Shows

    +

    + Shows are collections of slides which show up in the "presentation" view, and can be pushed to displays. +

    +
      {% for show in show_list %}
    • @@ -29,8 +50,17 @@

      Shows

      {% endfor %}
    -
    + + + + + {% endblock %} \ No newline at end of file diff --git a/OpenShow/slides/templates/slides/media/audio.html b/OpenShow/slides/templates/slides/media/audio.html new file mode 100644 index 0000000..b3230c3 --- /dev/null +++ b/OpenShow/slides/templates/slides/media/audio.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/OpenShow/slides/templates/slides/media/video.html b/OpenShow/slides/templates/slides/media/video.html new file mode 100644 index 0000000..b3230c3 --- /dev/null +++ b/OpenShow/slides/templates/slides/media/video.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/OpenShow/slides/templates/slides/media/vimeo_live_embed.html b/OpenShow/slides/templates/slides/media/vimeo_live_embed.html new file mode 100644 index 0000000..86158a7 --- /dev/null +++ b/OpenShow/slides/templates/slides/media/vimeo_live_embed.html @@ -0,0 +1,10 @@ +
    + +
    + \ No newline at end of file diff --git a/OpenShow/slides/templates/slides/show.html b/OpenShow/slides/templates/slides/show.html index d22beba..52d6587 100644 --- a/OpenShow/slides/templates/slides/show.html +++ b/OpenShow/slides/templates/slides/show.html @@ -1,127 +1,135 @@ -{% extends 'editor/base.html' %} +{% extends 'core/base.html' %} {% load static %} {% load icon %} -{% block additional_css %} - - +{% block extra_css %} +{# #} + +{% endblock %} + +{% block title %}{{ show.name }} - OpenShow{% endblock %} + +{% block header %} +

    {{ show.name }}

    {% endblock %} -{% block content %} - - -

    {{ show.name }}

    -
    -
    -
      - {% for show in shows %} - +{% block header_right_button %} + {% icon 'edit-2' %} +{% endblock %} + +{% block main %} +
    +
    - -
    -
    - {% csrf_token %} -
    - - +
    +
    + + {% csrf_token %} + + + + +
    + {% csrf_token %} + + + +
    +
    + +
    +
    + {% csrf_token %} +
    + + +
    +
    + + +
    +
    +
    +
    + {% if show.displays.all.first %} + {% for display in show.displays.all %} + + {% endfor %} + {% endif %}
    -
    - - +
    +
    +
    Displays:
    +
    + {% csrf_token %} + {{ display_selector_form.displays }} + +
    +
    Hold ctrl to select multiple
    - -
    -
    +
    +
    {% if show.displays.all.first %} - {% for display in show.displays.all %} - - {% endfor %} + {% endif %}
    -
    -
    -
    Displays:
    -
    - {% csrf_token %} - {{ display_selector_form.displays }} - -
    -
    Hold ctrl to select multiple
    -
    -
    - {% if show.displays.all.first %} - - {% endif %} +
    + + +
    -
    - - - -
    {% endblock %} \ No newline at end of file diff --git a/OpenShow/slides/templates/slides/slide-thumbnail.html b/OpenShow/slides/templates/slides/slide-thumbnail.html index c7b43a4..31fbcb9 100644 --- a/OpenShow/slides/templates/slides/slide-thumbnail.html +++ b/OpenShow/slides/templates/slides/slide-thumbnail.html @@ -1,12 +1,12 @@ +{% load srcdoc %}
    {% csrf_token%} {# #} -
    -{# #} - {% include 'slides/slide.html' with slide=slide %} +
    +
    \ No newline at end of file diff --git a/OpenShow/slides/templatetags/media_element.py b/OpenShow/slides/templatetags/media_element.py new file mode 100644 index 0000000..c187556 --- /dev/null +++ b/OpenShow/slides/templatetags/media_element.py @@ -0,0 +1,11 @@ +from django import template +from django.shortcuts import get_object_or_404 + +register = template.Library() + +@register.inclusion_tag('media_element_base.html') +def media_element(media_object, **kwargs): + context = { + 'media_object': media_object, + } + return context diff --git a/OpenShow/slides/urls.py b/OpenShow/slides/urls.py index a6a83e2..1aecb88 100644 --- a/OpenShow/slides/urls.py +++ b/OpenShow/slides/urls.py @@ -8,12 +8,13 @@ urlpatterns = [ # path('send_message', views.send_message), - path('', ListView.as_view(template_name='slides/index.html', model=Show), name='slides-index'), + path('', views.IndexView.as_view(), name='slides-index'), path('get_message/', include(django_eventstream.urls)), path('', views.SlideView.as_view(), name='slide'), path('displays/', views.DisplayView.as_view(), name='display'), path('displays//style', views.DisplayView.as_view(template_name='slides/display_style.css'), name='display-style'), path('displays//transition', views.DisplayView.as_view(template_name='slides/transition.css'), name='display-transition'), + path('displays//clear', views.clear_slide, name='clear-slide'), path('show/', views.ShowView.as_view(), name='show'), path('show//advance_mode', views.AdvanceModeView.as_view(), name='advance-mode'), path('show//advance_loop', views.AdvanceLoopView.as_view(), name='advance-loop'), diff --git a/OpenShow/slides/views.py b/OpenShow/slides/views.py index 2b62ed3..a620af0 100644 --- a/OpenShow/slides/views.py +++ b/OpenShow/slides/views.py @@ -1,9 +1,12 @@ +from datetime import timedelta + from django.shortcuts import render, get_object_or_404, HttpResponseRedirect, reverse -from django.views.generic import DetailView, FormView, ListView, UpdateView +from django.utils import timezone +from django.views.generic import DetailView, FormView, ListView, UpdateView, TemplateView from django.utils.decorators import method_decorator from django.views.decorators.clickjacking import xframe_options_sameorigin from django.template import loader -from .models import Slide, Display, Show, Deck +from .models import Slide, Display, Show, Deck, Transition, Theme, MediaObject from .forms import SlideDisplayForm, ShowDisplaySelectorForm @@ -13,6 +16,7 @@ from django_eventstream import send_event from django.shortcuts import HttpResponse + # This approach doesn't work well due to many extra characters being inserted into the HTML/CSS. # def send_slide_to_display(request, slide, display): # slide_template = loader.get_template('slides/slide.html') @@ -24,12 +28,20 @@ # send_event('test', f'display-{display}', rendered_slide) -class IndexView(ListView): +class IndexView(TemplateView): model = Show template_name = 'slides/index.html' - extra_context = { - 'deck_list': Deck.objects.all() - } + + def get_context_data(self, **kwargs): + context = super(IndexView, self).get_context_data(**kwargs) + context['show_list'] = Show.objects.all() + context['deck_list'] = Deck.objects.all() + context['theme_list'] = Theme.objects.all() + context['display_list'] = Display.objects.all() + context['transition_list'] = Transition.objects.all() + context['mediaobject_list'] = MediaObject.objects.all() + context['previous_page'] = 'index' + return context class SlideView(DetailView): @@ -51,6 +63,7 @@ def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['display_selector_form'] = ShowDisplaySelectorForm(instance=context['show']) context['shows'] = Show.objects.all() + context['previous_page'] = 'slides-index' return context @@ -62,18 +75,36 @@ class ShowDisplaySelectorView(UpdateView): class DeckView(DetailView): model = Deck template_name = "slides/deck.html" - extra_context = { - 'decks': Deck.objects.all - } + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['decks'] = Deck.objects.all() + context['display_list'] = Display.objects.all() + context['previous_page'] = 'slides-index' + return context class ShowSlideView(FormView): form_class = SlideDisplayForm def form_valid(self, form): + if form.cleaned_data['display_pk_multiple']: + display_pks = form.cleaned_data['display_pk_multiple'].split(',') + displays = [Display.objects.get(pk=int(display_pk)) for display_pk in display_pks] + slide = Slide.objects.get(pk=form.cleaned_data['slide_pk']) + slide.send_to_display(displays) + return HttpResponseRedirect(reverse('deck', kwargs={'pk': slide.deck.pk})) show = Show.objects.get(pk=form.cleaned_data['show_pk']) if form.cleaned_data['direction']: display = show.displays.all().first() + if display.current_slide and display.current_slide.auto_advance: + if timezone.now() - \ + display.slide_changed_at < \ + timedelta(seconds=display.current_slide.auto_advance_duration): + # Abort and continue silently if we're getting a "next slide" directive and + # the current slide's auto_advance_duration has not passed + # Manually selecting a different slide will override this. + return HttpResponseRedirect(reverse('show', kwargs={'pk': show.pk})) current_slide = display.current_slide slide = current_slide.next(form.cleaned_data['direction']) next_segment = None @@ -85,17 +116,21 @@ def form_valid(self, form): if current_segment.slides.first() and form.cleaned_data['direction'] == 'forward': slide = current_segment.slides.first() elif form.cleaned_data['direction'] == 'reverse': - slide = current_segment.next_with_slides(form.cleaned_data['direction']).get_last_slide() + slide = current_segment.next_with_slides( + form.cleaned_data['direction']).get_last_slide() elif form.cleaned_data['direction'] == 'forward': - slide = current_segment.next_with_slides(form.cleaned_data['direction']).get_first_slide() + slide = current_segment.next_with_slides( + form.cleaned_data['direction']).get_first_slide() else: # ..if current_slide.segment current_segment = current_slide.segment if form.cleaned_data['direction'] == 'reverse' and current_segment.included_deck: slide = current_segment.included_deck.slides.last() elif form.cleaned_data['direction'] == 'reverse': - slide = current_segment.next_with_slides(form.cleaned_data['direction']).get_last_slide() + slide = current_segment.next_with_slides( + form.cleaned_data['direction']).get_last_slide() elif form.cleaned_data['direction'] == 'forward': - slide = current_segment.next_with_slides(form.cleaned_data['direction']).get_first_slide() + slide = current_segment.next_with_slides( + form.cleaned_data['direction']).get_first_slide() except AttributeError: slide = current_slide elif show.advance_loop: @@ -114,7 +149,8 @@ def form_valid(self, form): else: slide = Slide.objects.get(pk=form.cleaned_data['slide_pk']) slide.send_to_display(show.displays.all(), show=show) - return HttpResponseRedirect(reverse('show', kwargs={'pk':show.pk})) + print(f'SHOWING SLIDE{slide.pk}') + return HttpResponseRedirect(reverse('show', kwargs={'pk': show.pk})) class AdvanceModeView(UpdateView): @@ -125,3 +161,10 @@ class AdvanceModeView(UpdateView): class AdvanceLoopView(UpdateView): # TODO: Combine this and the above view, make this all one proper Django form model = Show fields = ['advance_loop'] + + +def clear_slide(request, pk): + display = Display.objects.get(pk=pk) + send_event('test', f'display-{display.pk}-clear', f'clearing display {display.pk}') + return HttpResponse('OK') + diff --git a/OpenShow/uubloomington_api_connector/views.py b/OpenShow/uubloomington_api_connector/views.py index 2cc2989..52c6bfd 100644 --- a/OpenShow/uubloomington_api_connector/views.py +++ b/OpenShow/uubloomington_api_connector/views.py @@ -14,6 +14,9 @@ class CreateShowFromOOSView(FormView): form_class = CreateShowFromOOSForm template_name = 'uubloomington_api_connector/create_show_from_oos.html' # success_url = reverse_lazy('edit-show', pk=object.pk) + extra_context = { + 'previous_page': 'index', + } def __init__(self): self.new_show = None diff --git a/README.md b/README.md index ee365ee..1d07d7a 100644 --- a/README.md +++ b/README.md @@ -37,17 +37,52 @@ As of release v0.1.0, OpenShow now has an official Docker container. The contain This is just my method, and nothing about OpenShow requires that you use Traefik or NGINX. It's the way I'm familiar with, though, and it works well for me. -A quickstart guide for actually using OpenShow will be written soon. However, for now: -The one thing you won't be able to find by poking around the UI is the URL to actually see a display. That is `/slides/displays/`, so the first display you add will be `localhost:8000/slides/displays/1`. +### Container Setup +> [!NOTE] +> This setup guide assumes that your container host is running linux. Any distro with a functional docker-compose should work. OpenShow has not been tested running anywhere other than a Linux host. If you have a working setup on another OS, please open an issue and share notes! + +1. Save `docker-compose.yml.example` as `docker-compose.yml` somewhere on your system. +2. Edit `docker-compose.yml` to your specification. + 1. Out of the box, the only thing you must change is the value of `OPENSHOW_SECRET_KEY`. Set that to a random 50 character string. + 2. Be advised that the default docker-compose file will create a directory `./openshow` relative to the directory containing your docker-compose setup and place the application state within. This includes media files, static files, and the sqlite database. +3. Start up the containers with `docker-compose up -d`. +4. Open a web browser and visit [localhost:8080](localhost:8080). You should see the OpenShow index, assuming everything worked correctly. OpenShow currently has no authentication at all, so there's no initial account creation necessary. + +### Usage +Now that you have a working instance of OpenShow, the next step is to learn how to use it. In-depth documentation is in progress, but here's a general overview. +Refer to the "Concepts" section above for explanation of terminology such as "Show", "Segment", "Theme", etc. +#### Hello, World! +1. Click "Slides" to enter the slides module. Other modules are not essential to most installations. +2. Before you start creating slides, you'll need a theme. Create one by clicking on the "Themes" tab, followed by "New Theme". Set a name and submit it. + > [!NOTE] + > The current theme editor dates back to the initial prototype of OpenShow, and is due for a complete rewrite. This isn't the final form of this interface (thank goodness!). +3. The unlabeled text box below the preview winodw is where you put CSS rules which make up your theme. If you're not experienced with CSS, or if you just want a somewhat sane place to start, copy the contents of `example-theme.css` from this repository into that text box and click "Submit". +4. Next, you'll need a display. Click the back button on the left side of the header to return to the slides index, select the "Displays" tab, and click "New Display". Enter a name for your display and submit it. +5. Click the link to open that display in a new tab, then move that tab to a new window. Put it somewhere that you can get to it easily. +4. Click the back button to return to the slides index. You'll automatically be on the Show page. Click "New Show", enter a name, and submit it. +5. Welcome to the show editor! Now, you'll want to select the theme you just created. Click on "Set Theme", select your theme in the resulting dropdown, and click "Submit". +6. Next, create a segment using the plus button in the left sidebar. Enter a name and click the checkbox. +7. You'll see a box in the sidebar labeled with the name of your segment. Click on the smaller plus button near the bottom of the segment to add a slide. +8. Click on the slide. This will open your new slide for editing. +9. The bottom left panel of the editor contains options for changing the properties of the entire slide. You can leave all of that alone for now and click "New Element". +10. Enter the CSS class which you would like to apply to your new element. This should be a class (or set of classes) which is defined in the CSS you placed in the theme in step three. Click the checkmark button. +11. You should see placeholder text appear in the preview area saying "Double-click to edit". Follow that direction and add some text to your slide element. Press ctrl+enter or click the checkmark to save the content. +12. Click the back button again, which this time will take you to the presenter view for your show. In the right sidebar, there's a gear button. Click that, select your display in the list, and click the checkmark. That sets this Show object to send slides to your display. +13. In the main body of the presenter view, you'll see a thumbnail for the slide you just created. Click on it! The slide you created should appear in the browser window where you opened your display. + +Congratulations! You've just displayed your first slide using OpenShow! Hopefully that gives you an idea of the UI. Many pieces of the editor have function descriptions built in - otherwise, more documentation will be forthcoming. Feel free to file an issue if you have any question, even if it's probably not a bug. ## Development Setup -This is a standard Django + Channels application, and a development copy can be set up (on Linux) by: +OpenShow uses [honcho](https://honcho.readthedocs.io/en/latest/) both in development and production, as we need a separate worker process for video transcoding. +In production, you should almost certainly use the docker container, but here's how you set up a development environment: 1. Clone this repository 2. Create a python virtual environment: `python3 -m venv venv` 3. Activate the virtual environment: `source venv/bin/activate` 4. Install requirements as usual: `pip install -r requirements.txt` -5. Run the server: `python manage.py runserver` + > [!NOTE] + > If you want your development instance to transcode media objects, you'll need to install `ffmpeg` as well. Everything else will work without it. +5. Run the server: `honcho start -f Procfile.development` -Once you have this running, you can navigate to localhost:8000 in your web browser and start messing with OpenShow. +Once you have this running, you can navigate to localhost:8030 in your web browser and start messing with OpenShow. The development server port can be changed by editing `Procfile.development` to suit your needs. diff --git a/ROADMAP.md b/ROADMAP.md index a0af2da..b5dc6f9 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -11,13 +11,13 @@ These are features which are planned for the future. Anything crossed out here h - ~Mobile presenter remote~ ## v0.2.0 +- Bunch of stuff got done, check the release notes + +## v0.3.0 - Display element on only one display (Possible, but hacky- needs a custom CSS class set on the display. This will be added to the next major overhaul of the element editor.) - Overhauled segment and show editor - New theme format and theme editor -## v0.3.0 -- Deprecate old theme format - ## Future - Video capture input - Audio capture input diff --git a/docker-compose.yml.example b/docker-compose.yml.example index 7fb0beb..0d5e6e9 100644 --- a/docker-compose.yml.example +++ b/docker-compose.yml.example @@ -6,15 +6,15 @@ services: command: - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - - "--entrypoints.web.address=:80" + - "--entrypoints.web.address=:8080" - "--providers.docker.useBindPortIP=true" ports: - - "80:80" + - "8080:8080" volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" openshow-dynamic: - image: sheepman4267/openshow:0.1.0 + image: sheepman4267/openshow:0.2.0 volumes: - "./openshow/static-root:/static-root" - "./openshow/media-root:/media-root" diff --git a/example-theme.css b/example-theme.css new file mode 100644 index 0000000..49a7e10 --- /dev/null +++ b/example-theme.css @@ -0,0 +1,59 @@ +* { + font-family: sans-serif; +} + +body.slide-body { + background-color: black; + overflow: clip; + width: 1920px; +} + +.slide { + width: 1920px; + height: 1080px; + overflow: clip; +} + +.lyrics { + text-align: center; + font-size: 60pt; + margin-top: 1rem; + margin-bottom: auto; + margin-inline: 4rem; + color: white; + text-shadow: 2px 2px 6px #000000; +} + +.subtitle { + color: white; + align-self: center; + font-size: 50pt; + margin-bottom: 1rem; + margin-top: auto; + text-shadow: 2px 2px 6px #000000; +} + +.fit-image-height { + display: flex; +} + +.fit-image-height img { + width: auto; + height: 100vh; + max-height: 100%; + margin-inline: auto; +} + +.annotation { + font-size: 30pt; + color: white; + text-align: right; + margin-right: 2rem; + margin-bottom: 1rem; + margin-top: auto; + text-shadow: 2px 2px 6px #000000; +} + +.audio video { + display: none; +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 791eea8..cf44bac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ -django==4.2.6 -honcho==1.1.0 -django-eventstream==4.5.1 -channels==3.0.5 +django==5.1 +django-eventstream==5.3.1 +daphne==4.1.2 django-colorfield==0.8.0 htmlmin tinycss2 @@ -10,4 +9,11 @@ django-feather pypjlink gunicorn uvicorn -django-environ \ No newline at end of file +django-environ +django-ninja +django-srcdoc==1.0.1 +neapolitan==24.6 +django-extensions +django-q2==1.7.2 +honcho==2.0.0 +python-ffmpeg==2.0.12 \ No newline at end of file