From 6948e4ba21c0980819e7035b1725fe9843eab438 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Mon, 15 Apr 2024 17:42:49 -0400 Subject: [PATCH 001/103] Add the beginnings of an API, plus an API endpoint to display a specific slide --- OpenShow/OpenShow/api.py | 5 +++++ OpenShow/OpenShow/urls.py | 2 ++ OpenShow/slides/api.py | 12 ++++++++++++ requirements.txt | 3 ++- 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 OpenShow/OpenShow/api.py create mode 100644 OpenShow/slides/api.py 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/urls.py b/OpenShow/OpenShow/urls.py index cd13592..2b1755d 100644 --- a/OpenShow/OpenShow/urls.py +++ b/OpenShow/OpenShow/urls.py @@ -17,10 +17,12 @@ from django.urls import path, include from django.conf import settings from django.conf.urls.static import static +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')), diff --git a/OpenShow/slides/api.py b/OpenShow/slides/api.py new file mode 100644 index 0000000..ca5cb71 --- /dev/null +++ b/OpenShow/slides/api.py @@ -0,0 +1,12 @@ +from ninja import Router +from .models import Slide, Show +from django.shortcuts import get_object_or_404 + +router = Router() + + +@router.post("show_slide") +def show_slide(request, slide_pk, show_pk): + slide = get_object_or_404(Slide, pk=slide_pk) + show = get_object_or_404(Show, pk=show_pk) + slide.send_to_display(show.displays.all(), show=show) diff --git a/requirements.txt b/requirements.txt index 791eea8..0168c83 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,5 @@ django-feather pypjlink gunicorn uvicorn -django-environ \ No newline at end of file +django-environ +django-ninja \ No newline at end of file From e207abce08b6022620501e26719d8355e2e3abc3 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Mon, 15 Apr 2024 22:19:02 -0400 Subject: [PATCH 002/103] api: Use POST body rather than URL params for show_slide --- OpenShow/slides/api.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/OpenShow/slides/api.py b/OpenShow/slides/api.py index ca5cb71..a23a2fe 100644 --- a/OpenShow/slides/api.py +++ b/OpenShow/slides/api.py @@ -1,12 +1,18 @@ -from ninja import Router +from ninja import Router, Schema from .models import Slide, Show from django.shortcuts import get_object_or_404 router = Router() +class SlideAndShowSchema(Schema): + slide_pk: int + show_pk: int + + @router.post("show_slide") -def show_slide(request, slide_pk, show_pk): - slide = get_object_or_404(Slide, pk=slide_pk) - show = get_object_or_404(Show, pk=show_pk) +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"} From 2c4bc2c11d417fc23f7928d334522aac277c0987 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Wed, 17 Apr 2024 05:45:33 -0400 Subject: [PATCH 003/103] Only allow each slide to be advanced by next/previous once per auto_advance_duration This keeps slides from being advanced erratically when multiple displays are displaying the same auto-advancing slide deck. --- ...previous_slide_display_slide_changed_at.py | 30 +++++++++++++++++++ OpenShow/slides/models.py | 9 ++++++ OpenShow/slides/views.py | 26 ++++++++++++---- 3 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 OpenShow/slides/migrations/0020_display_previous_slide_display_slide_changed_at.py 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/models.py b/OpenShow/slides/models.py index 20af99e..348178a 100644 --- a/OpenShow/slides/models.py +++ b/OpenShow/slides/models.py @@ -20,6 +20,7 @@ class Display(models.Model): # A set of characteristics used to modify slide ap 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,6 +33,13 @@ 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, @@ -339,6 +347,7 @@ def send_to_display(self, displays:Iterable, show) -> 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: diff --git a/OpenShow/slides/views.py b/OpenShow/slides/views.py index 2b62ed3..a41e0ad 100644 --- a/OpenShow/slides/views.py +++ b/OpenShow/slides/views.py @@ -1,4 +1,7 @@ +from datetime import timedelta + from django.shortcuts import render, get_object_or_404, HttpResponseRedirect, reverse +from django.utils import timezone from django.views.generic import DetailView, FormView, ListView, UpdateView from django.utils.decorators import method_decorator from django.views.decorators.clickjacking import xframe_options_sameorigin @@ -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') @@ -74,6 +78,14 @@ def form_valid(self, form): show = Show.objects.get(pk=form.cleaned_data['show_pk']) if form.cleaned_data['direction']: display = show.displays.all().first() + if display.previous_slide and display.current_slide.auto_advance: + if timezone.now() - \ + display.slide_changed_at < \ + timedelta(seconds=display.previous_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 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 +97,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 +130,7 @@ 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})) + return HttpResponseRedirect(reverse('show', kwargs={'pk': show.pk})) class AdvanceModeView(UpdateView): From 5dc0ecd02b51ca68b7482bbf31a9d84aa2bdb8ea Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Thu, 25 Apr 2024 00:11:35 -0400 Subject: [PATCH 004/103] Rewrite the index Completely replace the slides index, removing the separate editor index and implementing missing.css --- OpenShow/core/templates/core/base.html | 2 + .../editor/templates/editor/deck_editor.html | 2 +- .../editor/templates/editor/edit_theme.html | 2 +- .../editor/templates/editor/show_editor.html | 2 +- .../snippets/hx-simple_create_form.html | 12 +++ .../templates/editor/transition_editor.html | 2 +- OpenShow/slides/editor/views.py | 20 ++-- OpenShow/slides/templates/slides/index.html | 96 +++++++++++++++---- OpenShow/slides/templates/slides/show.html | 1 + OpenShow/slides/urls.py | 2 +- OpenShow/slides/views.py | 18 ++-- 11 files changed, 124 insertions(+), 35 deletions(-) create mode 100644 OpenShow/slides/editor/templates/editor/snippets/hx-simple_create_form.html diff --git a/OpenShow/core/templates/core/base.html b/OpenShow/core/templates/core/base.html index 5395e46..ce30358 100644 --- a/OpenShow/core/templates/core/base.html +++ b/OpenShow/core/templates/core/base.html @@ -8,6 +8,8 @@ + {% block extra_js %} + {% endblock %}
diff --git a/OpenShow/slides/editor/templates/editor/deck_editor.html b/OpenShow/slides/editor/templates/editor/deck_editor.html index 59a6cd7..d2e7a3f 100644 --- a/OpenShow/slides/editor/templates/editor/deck_editor.html +++ b/OpenShow/slides/editor/templates/editor/deck_editor.html @@ -15,7 +15,7 @@ {% block content %}
- +

{{ deck.name }}

    diff --git a/OpenShow/slides/editor/templates/editor/edit_theme.html b/OpenShow/slides/editor/templates/editor/edit_theme.html index 6eb67f5..c460f70 100644 --- a/OpenShow/slides/editor/templates/editor/edit_theme.html +++ b/OpenShow/slides/editor/templates/editor/edit_theme.html @@ -13,7 +13,7 @@ {% block content %}
    - +

    {{ theme.name }}

      diff --git a/OpenShow/slides/editor/templates/editor/show_editor.html b/OpenShow/slides/editor/templates/editor/show_editor.html index 165df1e..60aecb8 100644 --- a/OpenShow/slides/editor/templates/editor/show_editor.html +++ b/OpenShow/slides/editor/templates/editor/show_editor.html @@ -15,7 +15,7 @@ {% block content %}
      - +

      {{ show.name }}

      {# #} {# #} 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..47cd74a --- /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..a210327 100644 --- a/OpenShow/slides/editor/templates/editor/transition_editor.html +++ b/OpenShow/slides/editor/templates/editor/transition_editor.html @@ -10,7 +10,7 @@ {% block content %}
      - +

      {{ transition.name }}

        diff --git a/OpenShow/slides/editor/views.py b/OpenShow/slides/editor/views.py index 0a753af..08c43e7 100644 --- a/OpenShow/slides/editor/views.py +++ b/OpenShow/slides/editor/views.py @@ -39,9 +39,10 @@ class ShowEditorView(DetailView): class ShowCreateView(CreateView): model = Show fields = ['name'] - template_name = 'editor/simple_create_form.html' + template_name = 'editor/snippets/hx-simple_create_form.html' extra_context = { 'action': 'new-show', + 'object_type': 'Show', } @@ -98,9 +99,12 @@ class SlideEditView(UpdateView): class DeckCreateView(CreateView): model = Deck - template_name = 'editor/simple_create_form.html' + template_name = 'editor/snippets/hx-simple_create_form.html' fields = ['name'] - extra_context = {'action': 'new-deck'} + extra_context = { + 'action': 'new-deck', + 'object_type': 'Deck', + } # class DeckUpdateView(UpdateView): @@ -128,9 +132,12 @@ class DeckEditorView(UpdateView): class ThemeCreateView(CreateView): model = Theme - template_name = 'editor/simple_create_form.html' + template_name = 'editor/snippets/hx-simple_create_form.html' fields = ['name'] - extra_context = {'action': 'new-theme'} + extra_context = { + 'action': 'new-theme', + 'object_type': 'Theme', + } class ThemeUpdateView(UpdateView): @@ -203,9 +210,10 @@ class TransitionEditorView(DetailView): class TransitionCreateView(CreateView): model = Transition fields = ["name"] - template_name = "editor/simple_create_form.html" + template_name = 'editor/snippets/hx-simple_create_form.html' extra_context = { 'action': 'new-transition', + 'object_type': 'Transition', } diff --git a/OpenShow/slides/templates/slides/index.html b/OpenShow/slides/templates/slides/index.html index b62559a..009ac06 100644 --- a/OpenShow/slides/templates/slides/index.html +++ b/OpenShow/slides/templates/slides/index.html @@ -1,26 +1,42 @@ -{% extends 'slides/base.html' %} +{% extends 'core/base.html' %} {% load static %} {% load icon %} -{% block additional_css %} - - +{% block extra_js %} + {{ block.super }} + +{% 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,12 +45,56 @@

          Shows

          {% endfor %}
        -
        + + + +
- - {% block content %} - {% endblock content %} - + {% block main %} + + {% block content %} + {% endblock content %} + + {% endblock %} \ 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..66edd20 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: 15rem; + 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; @@ -94,19 +69,19 @@ navbar { display: none; } -#settings-toggle { - margin-bottom: initial; -} +/*#settings-toggle {*/ +/* margin-bottom: initial;*/ +/*}*/ #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; + /*background: #606060;*/ + /*border: 3px solid lightgray;*/ + /*border-radius: 1rem;*/ + /*padding: 1rem;*/ + /*font-size: 1.2rem;*/ + /*gap: 0.5rem;*/ } #show-controls .slide-thumbnail { @@ -134,17 +109,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 +122,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 +140,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 +163,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 +217,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..8d9f148 100644 --- a/OpenShow/slides/static/slides/slide-thumbnail.css +++ b/OpenShow/slides/static/slides/slide-thumbnail.css @@ -1,6 +1,7 @@ .slide-thumbnail .slide { + min-width: 1920px; width: 1920px; - height: 1080px; + min-height: 1080px; transform: scale(0.1); margin-right: calc(-1920px / 10); margin-bottom: calc(-1080px / 10); @@ -16,12 +17,12 @@ max-height: calc(1080px / 10); 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/templates/slides/show.html b/OpenShow/slides/templates/slides/show.html index 5626301..0f53338 100644 --- a/OpenShow/slides/templates/slides/show.html +++ b/OpenShow/slides/templates/slides/show.html @@ -1,128 +1,128 @@ -{% extends 'editor/base.html' %} +{% extends 'core/base.html' %} {% load static %} {% load icon %} -{% block additional_css %} - - +{% block extra_css %} +{# #} + +{% endblock %} + +{% block header %} +

{{ show.name }}

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

{{ show.name }}

- {% icon 'edit-2' %} -
-
-
    - {% 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/views.py b/OpenShow/slides/views.py index c5fd305..aecbf86 100644 --- a/OpenShow/slides/views.py +++ b/OpenShow/slides/views.py @@ -62,6 +62,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 From d9b6c72574d55841570e52c0f1aa0583bd91a7d5 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Fri, 26 Apr 2024 23:18:07 -0400 Subject: [PATCH 008/103] Link slide.css in iframe slide thumbnails --- OpenShow/slides/templates/slides/iframe-slide-thumbnail.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html b/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html index 527d6bc..23808ab 100644 --- a/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html +++ b/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html @@ -1,4 +1,6 @@ +{% load static %} + From 5a356b12961764ccebd4d4db1e8c0108113ad962 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Fri, 26 Apr 2024 23:37:27 -0400 Subject: [PATCH 009/103] Slightly enlarge shows sidebar --- OpenShow/core/static/core/extras.css | 2 +- OpenShow/slides/static/slides/presenter-view.css | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/OpenShow/core/static/core/extras.css b/OpenShow/core/static/core/extras.css index 51cca09..2b9995a 100644 --- a/OpenShow/core/static/core/extras.css +++ b/OpenShow/core/static/core/extras.css @@ -19,7 +19,7 @@ body > header { } .sidebar-layout.double { - grid-template-columns: 15rem 2fr 1fr; + grid-template-columns: 18rem 2fr 1fr; height: calc(100dvh - 7rem); } diff --git a/OpenShow/slides/static/slides/presenter-view.css b/OpenShow/slides/static/slides/presenter-view.css index 66edd20..db062ec 100644 --- a/OpenShow/slides/static/slides/presenter-view.css +++ b/OpenShow/slides/static/slides/presenter-view.css @@ -15,7 +15,7 @@ body:not(#specificity-hack){ } #shows-sidebar { - max-width: 15rem; + max-width: 20rem; max-height: calc(100dvh - 7.5rem); margin: 0.5rem; margin-top: 0; @@ -69,19 +69,14 @@ body:not(#specificity-hack){ display: none; } -/*#settings-toggle {*/ -/* margin-bottom: initial;*/ -/*}*/ +#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 { From 74714a076ddf74dc5d53a12b7f6b5059c6187875 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Fri, 26 Apr 2024 23:47:52 -0400 Subject: [PATCH 010/103] Limit size of show controls sidebar --- OpenShow/core/static/core/extras.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/OpenShow/core/static/core/extras.css b/OpenShow/core/static/core/extras.css index 2b9995a..067916d 100644 --- a/OpenShow/core/static/core/extras.css +++ b/OpenShow/core/static/core/extras.css @@ -33,4 +33,10 @@ h2 { 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 From cd5179c8a2970d72266a0ff40b2e4317db2544d9 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Sat, 27 Apr 2024 19:01:18 -0400 Subject: [PATCH 011/103] Implement a rudimentary direct deck display system --- .../editor/templates/editor/deck_editor.html | 2 +- OpenShow/slides/forms.py | 4 +- .../slides/deck-slide-thumbnail.html | 20 +++ OpenShow/slides/templates/slides/deck.html | 128 ++++++++++++++---- OpenShow/slides/templates/slides/index.html | 2 +- OpenShow/slides/views.py | 16 ++- 6 files changed, 141 insertions(+), 31 deletions(-) create mode 100644 OpenShow/slides/templates/slides/deck-slide-thumbnail.html diff --git a/OpenShow/slides/editor/templates/editor/deck_editor.html b/OpenShow/slides/editor/templates/editor/deck_editor.html index d2e7a3f..c128c85 100644 --- a/OpenShow/slides/editor/templates/editor/deck_editor.html +++ b/OpenShow/slides/editor/templates/editor/deck_editor.html @@ -15,7 +15,7 @@ {% block content %}
- +

{{ deck.name }}

    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/templates/slides/deck-slide-thumbnail.html b/OpenShow/slides/templates/slides/deck-slide-thumbnail.html new file mode 100644 index 0000000..4f8ae13 --- /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..ae10bd7 100644 --- a/OpenShow/slides/templates/slides/deck.html +++ b/OpenShow/slides/templates/slides/deck.html @@ -1,36 +1,114 @@ -{% 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 header_right_button %} + {% icon 'edit-2' %} +{% endblock %} + +{% block main %} +
    +
    +
+
+{#
#} +{#
#} +{# {% csrf_token %}#} +{# #} +{# #} +{# #} +{#
#} +{#
#} +{# {% csrf_token %}#} +{# #} +{# #} +{# #} +{#
#} +{#
#} + +
+{#
#} +{# {% csrf_token %}#} +{#
#} +{# #} +{# #} +{#
#} +{#
#} +{# #} +{# #} +{#
#} +{#
#} +{#
#} +{#
#} +{# {% if show.displays.all.first %}#} +{# {% for display in show.displays.all %}#} +{# #} +{# {% endfor %}#} +{# {% endif %}#} +{#
#} +{#
#} +
+
Displays:
+
    + {% 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/index.html b/OpenShow/slides/templates/slides/index.html index 009ac06..dd83168 100644 --- a/OpenShow/slides/templates/slides/index.html +++ b/OpenShow/slides/templates/slides/index.html @@ -56,7 +56,7 @@

Decks

diff --git a/OpenShow/slides/views.py b/OpenShow/slides/views.py index aecbf86..d73da9a 100644 --- a/OpenShow/slides/views.py +++ b/OpenShow/slides/views.py @@ -74,15 +74,25 @@ 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, show=displays[0].current_show) + 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() From be6b964e06244815980023d28ba725d65793c3f2 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Sat, 27 Apr 2024 19:29:03 -0400 Subject: [PATCH 012/103] Add "clear display" buttons to deck presenter view --- OpenShow/slides/templates/slides/deck.html | 9 ++++++++- OpenShow/slides/templates/slides/display.html | 5 +++++ OpenShow/slides/urls.py | 1 + OpenShow/slides/views.py | 7 +++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/OpenShow/slides/templates/slides/deck.html b/OpenShow/slides/templates/slides/deck.html index ae10bd7..87f3793 100644 --- a/OpenShow/slides/templates/slides/deck.html +++ b/OpenShow/slides/templates/slides/deck.html @@ -87,7 +87,7 @@

{{ deck.name }}

{#
#} {#
#}
-
Displays:
+
Displays:
    {% for display in display_list %}
  • @@ -97,6 +97,13 @@

    {{ deck.name }}

  • {% endfor %}
+ Clear: +
    + {% for display in display_list %} + + {% endfor %} +
+
{#
#} diff --git a/OpenShow/slides/templates/slides/display.html b/OpenShow/slides/templates/slides/display.html index 71aeb2e..46fdcd6 100644 --- a/OpenShow/slides/templates/slides/display.html +++ b/OpenShow/slides/templates/slides/display.html @@ -33,6 +33,11 @@ send "grab-theme" to #slide-style send "grab-slide" to #slide-grabber end + on display-{{ display.pk }}-clear + getTransition() + make a + put it at end of body + end end on animationend remove <.slide:not(:last-child)/> then send unlatch diff --git a/OpenShow/slides/urls.py b/OpenShow/slides/urls.py index 99664b2..1aecb88 100644 --- a/OpenShow/slides/urls.py +++ b/OpenShow/slides/urls.py @@ -14,6 +14,7 @@ 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 d73da9a..44c8208 100644 --- a/OpenShow/slides/views.py +++ b/OpenShow/slides/views.py @@ -159,3 +159,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') + From a12764be7ea30430ded8877ae3183326c64a80ba Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Sat, 27 Apr 2024 19:50:18 -0400 Subject: [PATCH 013/103] Add page titles --- OpenShow/slides/editor/templates/editor/base.html | 2 +- OpenShow/slides/editor/templates/editor/deck_editor.html | 2 ++ OpenShow/slides/editor/templates/editor/edit_theme.html | 2 ++ OpenShow/slides/editor/templates/editor/show_editor.html | 1 + OpenShow/slides/editor/templates/editor/transition_editor.html | 2 ++ OpenShow/slides/templates/slides/deck.html | 2 ++ OpenShow/slides/templates/slides/index.html | 2 ++ OpenShow/slides/templates/slides/show.html | 2 ++ 8 files changed, 14 insertions(+), 1 deletion(-) diff --git a/OpenShow/slides/editor/templates/editor/base.html b/OpenShow/slides/editor/templates/editor/base.html index 4e73c1c..7e724e4 100644 --- a/OpenShow/slides/editor/templates/editor/base.html +++ b/OpenShow/slides/editor/templates/editor/base.html @@ -7,7 +7,7 @@ - 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 c128c85..d362906 100644 --- a/OpenShow/slides/editor/templates/editor/deck_editor.html +++ b/OpenShow/slides/editor/templates/editor/deck_editor.html @@ -12,6 +12,8 @@ {% endblock %} +{% block title %}Editing {{ deck.name }} - OpenShow{% endblock %} + {% block content %}
diff --git a/OpenShow/slides/editor/templates/editor/edit_theme.html b/OpenShow/slides/editor/templates/editor/edit_theme.html index c460f70..760beeb 100644 --- a/OpenShow/slides/editor/templates/editor/edit_theme.html +++ b/OpenShow/slides/editor/templates/editor/edit_theme.html @@ -10,6 +10,8 @@ {% endblock %} +{% block title %}Editing {{ theme.name }} - OpenShow{% endblock %} + {% block content %}
diff --git a/OpenShow/slides/editor/templates/editor/show_editor.html b/OpenShow/slides/editor/templates/editor/show_editor.html index 60aecb8..b1d04b2 100644 --- a/OpenShow/slides/editor/templates/editor/show_editor.html +++ b/OpenShow/slides/editor/templates/editor/show_editor.html @@ -7,6 +7,7 @@ + {% block title %}Editing {{ show.name }} - OpenShow{% endblock %} diff --git a/OpenShow/slides/editor/templates/editor/transition_editor.html b/OpenShow/slides/editor/templates/editor/transition_editor.html index a210327..165dbb9 100644 --- a/OpenShow/slides/editor/templates/editor/transition_editor.html +++ b/OpenShow/slides/editor/templates/editor/transition_editor.html @@ -7,6 +7,8 @@ {% endblock %} +{% block title %}Editing {{ transition.name }} - OpenShow{% endblock %} + {% block content %}
diff --git a/OpenShow/slides/templates/slides/deck.html b/OpenShow/slides/templates/slides/deck.html index 87f3793..7ab95c4 100644 --- a/OpenShow/slides/templates/slides/deck.html +++ b/OpenShow/slides/templates/slides/deck.html @@ -14,6 +14,8 @@

{{ deck.name }}

{% endblock %} +{% block title %}{{ deck.name }} - OpenShow{% endblock %} + {% block header_right_button %} {% icon 'edit-2' %} {% endblock %} diff --git a/OpenShow/slides/templates/slides/index.html b/OpenShow/slides/templates/slides/index.html index dd83168..5ab0e65 100644 --- a/OpenShow/slides/templates/slides/index.html +++ b/OpenShow/slides/templates/slides/index.html @@ -7,6 +7,8 @@ {% endblock %} +{% block title %}OpenShow Slides{% endblock %} + {% block header %}

OpenShow Slides

{% endblock %} diff --git a/OpenShow/slides/templates/slides/show.html b/OpenShow/slides/templates/slides/show.html index 0f53338..dd11a51 100644 --- a/OpenShow/slides/templates/slides/show.html +++ b/OpenShow/slides/templates/slides/show.html @@ -10,6 +10,8 @@ {% endblock %} +{% block title %}{{ show.name }} - OpenShow{% endblock %} + {% block header %}

{{ show.name }}

{% endblock %} From 51b797434421bbacb8cb8e50663b4358caaca32d Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Sat, 1 Jun 2024 21:09:54 -0400 Subject: [PATCH 014/103] Correct PJLink editing templates Fixes #5 --- .../pjlink_integration/templates/pjlink_integration/edit.html | 2 +- .../pjlink_integration/templates/pjlink_integration/list.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 %} From 8b12005915e415ed70c92568c83a28517fcdded4 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Wed, 26 Jun 2024 00:17:46 -0400 Subject: [PATCH 015/103] Use clever _hyperscript trickery to avoid reloading show-container This makes displaying slides WAY faster and avoids ugly iframe flickering when presenting. --- .../slides/templates/slides/deck-slide-thumbnail.html | 2 +- OpenShow/slides/templates/slides/deck.html | 6 ++++++ OpenShow/slides/templates/slides/show.html | 11 ++++++++--- OpenShow/slides/templates/slides/slide-thumbnail.html | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/OpenShow/slides/templates/slides/deck-slide-thumbnail.html b/OpenShow/slides/templates/slides/deck-slide-thumbnail.html index 4f8ae13..9dc9571 100644 --- a/OpenShow/slides/templates/slides/deck-slide-thumbnail.html +++ b/OpenShow/slides/templates/slides/deck-slide-thumbnail.html @@ -13,7 +13,7 @@ set my @value to displays send 'send-slide' to next
"> -
+
diff --git a/OpenShow/slides/templates/slides/deck.html b/OpenShow/slides/templates/slides/deck.html index 7ab95c4..c228a88 100644 --- a/OpenShow/slides/templates/slides/deck.html +++ b/OpenShow/slides/templates/slides/deck.html @@ -32,6 +32,12 @@

{{ deck.name }}

+{# #} +{#The above isn't functional for Decks because we don't store the current deck in displays.#}
  • diff --git a/OpenShow/slides/templates/slides/show.html b/OpenShow/slides/templates/slides/show.html index dd11a51..52d6587 100644 --- a/OpenShow/slides/templates/slides/show.html +++ b/OpenShow/slides/templates/slides/show.html @@ -32,6 +32,11 @@

    {{ show.name }}

+
    {% for segment in show.segments.all %} @@ -45,7 +50,7 @@

    {{ segment.included_deck.name }}

    {% endif %} > {% for slide in segment.included_deck.slides.all %} -
  • +
  • {% if slide.cue %}

    {{ slide.cue }} @@ -74,13 +79,13 @@

    {{ segment.included_deck.name }}

    {% csrf_token %} - +
    {% csrf_token %} - +
diff --git a/OpenShow/slides/templates/slides/slide-thumbnail.html b/OpenShow/slides/templates/slides/slide-thumbnail.html index 39475b5..31fbcb9 100644 --- a/OpenShow/slides/templates/slides/slide-thumbnail.html +++ b/OpenShow/slides/templates/slides/slide-thumbnail.html @@ -5,7 +5,7 @@ {# #} -
+
From 548f165f990324f3eb3b1e6ef3f6f7caf20c453e Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Wed, 26 Jun 2024 14:06:07 -0400 Subject: [PATCH 016/103] Add video icon overlay This is a small icon which will be shown in the lower right corner of a slide thumbnail when the slide will play a video. --- OpenShow/slides/models.py | 9 +++++++++ OpenShow/slides/static/slides/slide.css | 13 +++++++++++++ .../templates/slides/iframe-slide-thumbnail.html | 6 ++++++ 3 files changed, 28 insertions(+) diff --git a/OpenShow/slides/models.py b/OpenShow/slides/models.py index 348178a..dacef42 100644 --- a/OpenShow/slides/models.py +++ b/OpenShow/slides/models.py @@ -429,6 +429,15 @@ 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 + + class Transition(models.Model): name = models.CharField(max_length=30) 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/iframe-slide-thumbnail.html b/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html index 23808ab..1f4cda6 100644 --- a/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html +++ b/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html @@ -1,4 +1,5 @@ {% load static %} +{% load icon %} {% endblock %} +{% block title %}Editing {{ show.name }} - OpenShow{% endblock %} + {% block content %}
From 68869e5d725efd9d349c0b31ee8ec7904c4a61ea Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Thu, 22 Aug 2024 15:49:42 -0400 Subject: [PATCH 029/103] Code formatting --- OpenShow/slides/editor/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/OpenShow/slides/editor/views.py b/OpenShow/slides/editor/views.py index 1dc3c95..a6ec670 100644 --- a/OpenShow/slides/editor/views.py +++ b/OpenShow/slides/editor/views.py @@ -19,7 +19,6 @@ class NotSupportedException(Exception): pass - class IndexView(ListView): queryset = Show.objects.all() template_name = 'editor/index.html' From 3a3e8453c7f7559fdc628f4188e2103c9eef5d3e Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Thu, 22 Aug 2024 16:00:27 -0400 Subject: [PATCH 030/103] Fix slide reordering in Decks This was broken by ae2ad1cc. --- OpenShow/slides/editor/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenShow/slides/editor/views.py b/OpenShow/slides/editor/views.py index a6ec670..710cd5d 100644 --- a/OpenShow/slides/editor/views.py +++ b/OpenShow/slides/editor/views.py @@ -306,7 +306,7 @@ def form_valid(self, form): return super().form_valid(form) def get_success_url(self): - return self.moved_slide.get_absolute_url() + return self.moved_slide.deck_or_segment().get_absolute_url() def push_deck_cues(request, pk): From f9acf778f7def06b0865766d070d252588b3cf9a Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Thu, 22 Aug 2024 16:37:12 -0400 Subject: [PATCH 031/103] Update to django 5.1 This seems to work, but newer versions of channels/eventstream will need some serious refactoring. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0168c83..ae9b2fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -django==4.2.6 +django==5.1 honcho==1.1.0 django-eventstream==4.5.1 channels==3.0.5 From 39c7201cb357c749f2b485815427a45627b0e629 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Tue, 27 Aug 2024 17:27:54 -0400 Subject: [PATCH 032/103] Make element text editable directly in the slide preview --- .../slides/editor/static/editor/editor.css | 14 +++++++- .../editor/templates/editor/edit_slide.html | 2 +- .../templates/editor/element_text_edit.html | 35 +++++++++++++++++++ .../slides/editor/templates/editor/slide.html | 10 ++++++ OpenShow/slides/editor/urls.py | 1 + OpenShow/slides/editor/views.py | 11 ++++++ 6 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 OpenShow/slides/editor/templates/editor/element_text_edit.html create mode 100644 OpenShow/slides/editor/templates/editor/slide.html diff --git a/OpenShow/slides/editor/static/editor/editor.css b/OpenShow/slides/editor/static/editor/editor.css index 09035ea..8ce1d8b 100644 --- a/OpenShow/slides/editor/static/editor/editor.css +++ b/OpenShow/slides/editor/static/editor/editor.css @@ -253,4 +253,16 @@ ul > a { height: 100vh; background-color: rgba(0, 0, 0, 0.4); backdrop-filter: blur(0.5rem); -} \ No newline at end of file +} + +.editor-editable { + outline: 4px solid red; +} + +.element-edit-buttons { + scale: 3; + width: fit-content; + position: absolute; + right: 50px; +} + diff --git a/OpenShow/slides/editor/templates/editor/edit_slide.html b/OpenShow/slides/editor/templates/editor/edit_slide.html index d62d506..2617a7c 100644 --- a/OpenShow/slides/editor/templates/editor/edit_slide.html +++ b/OpenShow/slides/editor/templates/editor/edit_slide.html @@ -42,6 +42,6 @@
- {% 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/element_text_edit.html b/OpenShow/slides/editor/templates/editor/element_text_edit.html new file mode 100644 index 0000000..08ae676 --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/element_text_edit.html @@ -0,0 +1,35 @@ +{% load icon %} +
+ {% csrf_token %} +
{{ object.body|safe }}
+ +
+ +
+
\ 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..948ee4a --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/slide.html @@ -0,0 +1,10 @@ +
+ {% for element in slide.get_elements %} +
+ {{ element.body|safe }} + {% if element.image %} + + {% endif %} +
+ {% endfor %} +
\ No newline at end of file diff --git a/OpenShow/slides/editor/urls.py b/OpenShow/slides/editor/urls.py index 139d085..a0d3d1c 100644 --- a/OpenShow/slides/editor/urls.py +++ b/OpenShow/slides/editor/urls.py @@ -14,6 +14,7 @@ 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', EditSlideElementTextView.as_view(), name='edit-element-text'), 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'), diff --git a/OpenShow/slides/editor/views.py b/OpenShow/slides/editor/views.py index 710cd5d..e18d3aa 100644 --- a/OpenShow/slides/editor/views.py +++ b/OpenShow/slides/editor/views.py @@ -1,3 +1,5 @@ +from xml.dom.minidom import Element + 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 @@ -336,3 +338,12 @@ def pull_aoml_text(request, pk): deck.slide_text_markup = deck.pull_aoml() deck.save() return HttpResponseRedirect(deck.get_absolute_url()) + + +class EditSlideElementTextView(UpdateView): + model = SlideElement + fields = ['body',] + template_name = 'editor/element_text_edit.html' + + def get_success_url(self): + return self.object.get_absolute_url() \ No newline at end of file From b405c552ded628c8d0c14c98b233ff2002372773 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Wed, 28 Aug 2024 14:36:35 -0400 Subject: [PATCH 033/103] Add a hint to empty elements in the editor --- OpenShow/slides/editor/static/editor/editor.css | 13 +++++++++++++ OpenShow/slides/editor/templates/editor/slide.html | 8 ++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/OpenShow/slides/editor/static/editor/editor.css b/OpenShow/slides/editor/static/editor/editor.css index 8ce1d8b..c22e4a5 100644 --- a/OpenShow/slides/editor/static/editor/editor.css +++ b/OpenShow/slides/editor/static/editor/editor.css @@ -244,6 +244,12 @@ ul > a { z-index: 1070; } +.confirm-delete-modal { + background: #daa5a5; + color: #000000; + border: 3px solid #a10000; +} + .modal-container { position: fixed; top: 0; @@ -266,3 +272,10 @@ ul > a { right: 50px; } +.editor-element { + min-height: 2rem; +} + +.editor-element .hint { + opacity: 0.75; +} diff --git a/OpenShow/slides/editor/templates/editor/slide.html b/OpenShow/slides/editor/templates/editor/slide.html index 948ee4a..d02589a 100644 --- a/OpenShow/slides/editor/templates/editor/slide.html +++ b/OpenShow/slides/editor/templates/editor/slide.html @@ -1,7 +1,11 @@
{% for element in slide.get_elements %} -
- {{ element.body|safe }} +
+ {% if element.body %} + {{ element.body|safe }} + {% else %} + Click to edit + {% endif %} {% if element.image %} {% endif %} From a44fad0c04e5408d4d290f59f6d8cd9101bb9239 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Wed, 28 Aug 2024 14:37:57 -0400 Subject: [PATCH 034/103] Modularize Modal CSS --- OpenShow/slides/editor/static/editor/editor.css | 8 ++++---- .../editor/templates/editor/generic_confirm_delete.html | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/OpenShow/slides/editor/static/editor/editor.css b/OpenShow/slides/editor/static/editor/editor.css index c22e4a5..c158042 100644 --- a/OpenShow/slides/editor/static/editor/editor.css +++ b/OpenShow/slides/editor/static/editor/editor.css @@ -226,7 +226,7 @@ ul > a { transition: background-color 0.1s, opacity 0.1s; } -.confirm-delete-modal { +.modal { /*position: absolute;*/ /*top: calc(50%);*/ /*left: 50%;*/ @@ -234,9 +234,9 @@ ul > a { margin-top: calc(50dvh - 4rem); width: 15rem; height: 8rem; - background: #daa5a5; - color: #000000; - border: 3px solid #a10000; + background: #606060; + color: white; + border: 3px solid darkgray; padding: 1rem; text-align: center; diff --git a/OpenShow/slides/editor/templates/editor/generic_confirm_delete.html b/OpenShow/slides/editor/templates/editor/generic_confirm_delete.html index 0180eb6..af3a20e 100644 --- a/OpenShow/slides/editor/templates/editor/generic_confirm_delete.html +++ b/OpenShow/slides/editor/templates/editor/generic_confirm_delete.html @@ -1,5 +1,5 @@ -{# #}
+ \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/slide.html b/OpenShow/slides/editor/templates/editor/slide.html index d02589a..777fbea 100644 --- a/OpenShow/slides/editor/templates/editor/slide.html +++ b/OpenShow/slides/editor/templates/editor/slide.html @@ -1,6 +1,7 @@ +{% load icon %}
{% for element in slide.get_elements %} -
+
{% if element.body %} {{ element.body|safe }} {% else %} @@ -9,6 +10,9 @@ {% if element.image %} {% endif %} +
{% endfor %}
\ No newline at end of file diff --git a/OpenShow/slides/editor/urls.py b/OpenShow/slides/editor/urls.py index 40cc0cd..99f28ff 100644 --- a/OpenShow/slides/editor/urls.py +++ b/OpenShow/slides/editor/urls.py @@ -4,7 +4,8 @@ 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, SlideElementUpdateView, SlideElementDeleteView, SlideElementDeleteView, SlideElementUpdateTextView +from slides.editor.views.slide_element import SlideElementCreateView, SlideElementUpdateView, SlideElementDeleteView, \ + SlideElementDeleteView, SlideElementUpdateTextView, SlideElementUpdateCSSClassView from slides.editor.views.deck import DeckCreateView, DeckEditorView, DeckDeleteView, push_deck_cues, push_deck_slide_text, pull_aoml_text from slides.editor.views.theme import ThemeUpdateView, ThemeCreateView, ThemeDeleteView from slides.editor.views.utils import generate_lorem @@ -25,6 +26,7 @@ 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/reorder', ChangeSlideOrderView.as_view(), name='reorder-slide'), path('deck/new', DeckCreateView.as_view(), name='new-deck'), path('deck/', DeckEditorView.as_view(), name='edit-deck'), diff --git a/OpenShow/slides/editor/views/slide_element.py b/OpenShow/slides/editor/views/slide_element.py index 44a7b66..70ae934 100644 --- a/OpenShow/slides/editor/views/slide_element.py +++ b/OpenShow/slides/editor/views/slide_element.py @@ -36,3 +36,12 @@ class SlideElementUpdateTextView(UpdateView): 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() From 7e939d1ec59bf89a0877fcf4d2eaf410b82fec88 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Sun, 1 Sep 2024 13:29:27 -0400 Subject: [PATCH 044/103] Add inline delete button for slide elements --- OpenShow/slides/editor/static/editor/editor.css | 9 +++++++++ .../editor/templates/editor/delete_element.html | 11 +++++------ OpenShow/slides/editor/templates/editor/slide.html | 1 + 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/OpenShow/slides/editor/static/editor/editor.css b/OpenShow/slides/editor/static/editor/editor.css index 71aaf56..7d06dc3 100644 --- a/OpenShow/slides/editor/static/editor/editor.css +++ b/OpenShow/slides/editor/static/editor/editor.css @@ -321,6 +321,10 @@ ul > a { height: min-content; } +.element-action-button-container > * { + display: inline; +} + .editor-element:hover .element-action-button-container { display: block; } @@ -342,3 +346,8 @@ ul > a { height: 3rem; width: 3rem; } + +button.danger { + background: #9C1A1C; + color: white; +} 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/slide.html b/OpenShow/slides/editor/templates/editor/slide.html index 777fbea..0adcd2d 100644 --- a/OpenShow/slides/editor/templates/editor/slide.html +++ b/OpenShow/slides/editor/templates/editor/slide.html @@ -11,6 +11,7 @@ {% endif %}
From 40f3fd8c8fc52e299a68cf741dbf37528189107c Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Sun, 1 Sep 2024 13:52:28 -0400 Subject: [PATCH 045/103] Don't disappear the button container if it has a form open --- OpenShow/slides/editor/static/editor/editor.css | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenShow/slides/editor/static/editor/editor.css b/OpenShow/slides/editor/static/editor/editor.css index 7d06dc3..3eddee9 100644 --- a/OpenShow/slides/editor/static/editor/editor.css +++ b/OpenShow/slides/editor/static/editor/editor.css @@ -325,6 +325,7 @@ ul > a { display: inline; } +.element-action-button-container:has(form), .editor-element:hover .element-action-button-container { display: block; } From dfa9692f35b1baf064f3db3cc446c21e7b3a7cdb Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Sun, 1 Sep 2024 14:10:11 -0400 Subject: [PATCH 046/103] Add image selection to inline element editor --- .../slides/editor/static/editor/editor.css | 20 +++++++++++++++++++ .../templates/editor/element_image_edit.html | 14 +++++++++++++ .../slides/editor/templates/editor/slide.html | 3 ++- OpenShow/slides/editor/urls.py | 3 ++- OpenShow/slides/editor/views/slide_element.py | 9 +++++++++ 5 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 OpenShow/slides/editor/templates/editor/element_image_edit.html diff --git a/OpenShow/slides/editor/static/editor/editor.css b/OpenShow/slides/editor/static/editor/editor.css index 3eddee9..c11f16b 100644 --- a/OpenShow/slides/editor/static/editor/editor.css +++ b/OpenShow/slides/editor/static/editor/editor.css @@ -319,6 +319,7 @@ ul > a { left: 10px; display: none; height: min-content; + text-shadow: none; } .element-action-button-container > * { @@ -348,6 +349,25 @@ ul > a { 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; 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/slide.html b/OpenShow/slides/editor/templates/editor/slide.html index 0adcd2d..3bf5afb 100644 --- a/OpenShow/slides/editor/templates/editor/slide.html +++ b/OpenShow/slides/editor/templates/editor/slide.html @@ -4,7 +4,7 @@
{% if element.body %} {{ element.body|safe }} - {% else %} + {% elif not element.image and not element.video %} Click to edit {% endif %} {% if element.image %} @@ -13,6 +13,7 @@
{% endfor %} diff --git a/OpenShow/slides/editor/urls.py b/OpenShow/slides/editor/urls.py index 99f28ff..1ae29d9 100644 --- a/OpenShow/slides/editor/urls.py +++ b/OpenShow/slides/editor/urls.py @@ -5,7 +5,7 @@ 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, SlideElementUpdateView, SlideElementDeleteView, \ - SlideElementDeleteView, SlideElementUpdateTextView, SlideElementUpdateCSSClassView + SlideElementDeleteView, SlideElementUpdateTextView, SlideElementUpdateCSSClassView, SlideElementUpdateImageView from slides.editor.views.deck import DeckCreateView, DeckEditorView, DeckDeleteView, push_deck_cues, push_deck_slide_text, pull_aoml_text from slides.editor.views.theme import ThemeUpdateView, ThemeCreateView, ThemeDeleteView from slides.editor.views.utils import generate_lorem @@ -27,6 +27,7 @@ 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/reorder', ChangeSlideOrderView.as_view(), name='reorder-slide'), path('deck/new', DeckCreateView.as_view(), name='new-deck'), path('deck/', DeckEditorView.as_view(), name='edit-deck'), diff --git a/OpenShow/slides/editor/views/slide_element.py b/OpenShow/slides/editor/views/slide_element.py index 70ae934..43cb33b 100644 --- a/OpenShow/slides/editor/views/slide_element.py +++ b/OpenShow/slides/editor/views/slide_element.py @@ -45,3 +45,12 @@ class SlideElementUpdateCSSClassView(UpdateView): 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() From a44f5e63fa2f941366c7c1c259a401a84e78c063 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Sun, 1 Sep 2024 14:21:33 -0400 Subject: [PATCH 047/103] Add video selection to inline element editor --- OpenShow/slides/editor/static/editor/editor.css | 4 ++++ .../templates/editor/element_video_edit.html | 14 ++++++++++++++ OpenShow/slides/editor/templates/editor/slide.html | 3 ++- OpenShow/slides/editor/urls.py | 4 +++- OpenShow/slides/editor/views/slide_element.py | 9 +++++++++ 5 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 OpenShow/slides/editor/templates/editor/element_video_edit.html diff --git a/OpenShow/slides/editor/static/editor/editor.css b/OpenShow/slides/editor/static/editor/editor.css index c11f16b..a4e4901 100644 --- a/OpenShow/slides/editor/static/editor/editor.css +++ b/OpenShow/slides/editor/static/editor/editor.css @@ -372,3 +372,7 @@ button.danger { background: #9C1A1C; color: white; } + +button.green { + background-color: #bedfb8; +} 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/slide.html b/OpenShow/slides/editor/templates/editor/slide.html index 3bf5afb..26adfea 100644 --- a/OpenShow/slides/editor/templates/editor/slide.html +++ b/OpenShow/slides/editor/templates/editor/slide.html @@ -13,7 +13,8 @@
{% endfor %} diff --git a/OpenShow/slides/editor/urls.py b/OpenShow/slides/editor/urls.py index 1ae29d9..864efda 100644 --- a/OpenShow/slides/editor/urls.py +++ b/OpenShow/slides/editor/urls.py @@ -5,7 +5,8 @@ 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, SlideElementUpdateView, SlideElementDeleteView, \ - SlideElementDeleteView, SlideElementUpdateTextView, SlideElementUpdateCSSClassView, SlideElementUpdateImageView + SlideElementDeleteView, SlideElementUpdateTextView, SlideElementUpdateCSSClassView, SlideElementUpdateImageView, \ + SlideElementUpdateVideoView from slides.editor.views.deck import DeckCreateView, DeckEditorView, DeckDeleteView, push_deck_cues, push_deck_slide_text, pull_aoml_text from slides.editor.views.theme import ThemeUpdateView, ThemeCreateView, ThemeDeleteView from slides.editor.views.utils import generate_lorem @@ -28,6 +29,7 @@ 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/reorder', ChangeSlideOrderView.as_view(), name='reorder-slide'), path('deck/new', DeckCreateView.as_view(), name='new-deck'), path('deck/', DeckEditorView.as_view(), name='edit-deck'), diff --git a/OpenShow/slides/editor/views/slide_element.py b/OpenShow/slides/editor/views/slide_element.py index 43cb33b..3b1cd95 100644 --- a/OpenShow/slides/editor/views/slide_element.py +++ b/OpenShow/slides/editor/views/slide_element.py @@ -54,3 +54,12 @@ class SlideElementUpdateImageView(UpdateView): 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 get_success_url(self): + return self.object.get_absolute_url() From 906bae264639e4c345626a672b393a72c3e4c166 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Sun, 1 Sep 2024 14:29:34 -0400 Subject: [PATCH 048/103] Make element editing hint useful for video elements --- OpenShow/slides/editor/static/editor/editor.css | 3 +++ OpenShow/slides/editor/templates/editor/slide.html | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/OpenShow/slides/editor/static/editor/editor.css b/OpenShow/slides/editor/static/editor/editor.css index a4e4901..996c757 100644 --- a/OpenShow/slides/editor/static/editor/editor.css +++ b/OpenShow/slides/editor/static/editor/editor.css @@ -284,6 +284,9 @@ ul > a { .editor-element .hint { opacity: 0.75; + font-size: 3rem; + text-align: center; + width: 100%; } .grow-wrap { diff --git a/OpenShow/slides/editor/templates/editor/slide.html b/OpenShow/slides/editor/templates/editor/slide.html index 26adfea..44323ba 100644 --- a/OpenShow/slides/editor/templates/editor/slide.html +++ b/OpenShow/slides/editor/templates/editor/slide.html @@ -4,7 +4,9 @@
{% if element.body %} {{ element.body|safe }} - {% elif not element.image and not element.video %} + {% elif element.video %} + Video: {{ element.video }} + {% elif not element.image %} Click to edit {% endif %} {% if element.image %} From 0d94a69f5d92b95e762ece9304138996bf052152 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Sun, 1 Sep 2024 14:30:25 -0400 Subject: [PATCH 049/103] Make element editing hint follow the actual UI design --- OpenShow/slides/editor/templates/editor/slide.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenShow/slides/editor/templates/editor/slide.html b/OpenShow/slides/editor/templates/editor/slide.html index 44323ba..0be5e98 100644 --- a/OpenShow/slides/editor/templates/editor/slide.html +++ b/OpenShow/slides/editor/templates/editor/slide.html @@ -7,7 +7,7 @@ {% elif element.video %} Video: {{ element.video }} {% elif not element.image %} - Click to edit + Double-click to edit {% endif %} {% if element.image %} From 249672d2c76f879f8f6ad84d7363ce00da82beff Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Tue, 3 Sep 2024 12:24:13 -0400 Subject: [PATCH 050/103] Implement slide reordering logic - needs CSS polish --- OpenShow/slides/editor/forms.py | 5 ++ .../slides/editor/templates/editor/slide.html | 46 ++++++++++++++++++- OpenShow/slides/editor/urls.py | 3 +- OpenShow/slides/editor/views/slide_element.py | 36 ++++++++++++++- .../0023_alter_slideelement_order.py | 18 ++++++++ OpenShow/slides/models.py | 2 +- 6 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 OpenShow/slides/migrations/0023_alter_slideelement_order.py diff --git a/OpenShow/slides/editor/forms.py b/OpenShow/slides/editor/forms.py index fb0c11b..6a870af 100644 --- a/OpenShow/slides/editor/forms.py +++ b/OpenShow/slides/editor/forms.py @@ -40,6 +40,11 @@ class ChangeSlideOrderForm(Form): 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 diff --git a/OpenShow/slides/editor/templates/editor/slide.html b/OpenShow/slides/editor/templates/editor/slide.html index 0be5e98..0272922 100644 --- a/OpenShow/slides/editor/templates/editor/slide.html +++ b/OpenShow/slides/editor/templates/editor/slide.html @@ -1,7 +1,36 @@ {% load icon %}
{% for element in slide.get_elements %} -
+
+ {% csrf_token %} + + +
+
{% if element.body %} {{ element.body|safe }} {% elif element.video %} @@ -20,4 +49,19 @@
{% 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/urls.py b/OpenShow/slides/editor/urls.py index 864efda..bd6af78 100644 --- a/OpenShow/slides/editor/urls.py +++ b/OpenShow/slides/editor/urls.py @@ -6,7 +6,7 @@ from slides.editor.views.slide import SlideCreateView, SlideEditView, SlideDeleteView, ChangeSlideOrderView from slides.editor.views.slide_element import SlideElementCreateView, SlideElementUpdateView, SlideElementDeleteView, \ SlideElementDeleteView, SlideElementUpdateTextView, SlideElementUpdateCSSClassView, SlideElementUpdateImageView, \ - SlideElementUpdateVideoView + SlideElementUpdateVideoView, ChangeSlideElementOrderView from slides.editor.views.deck import DeckCreateView, DeckEditorView, DeckDeleteView, push_deck_cues, push_deck_slide_text, pull_aoml_text from slides.editor.views.theme import ThemeUpdateView, ThemeCreateView, ThemeDeleteView from slides.editor.views.utils import generate_lorem @@ -30,6 +30,7 @@ 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/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'), diff --git a/OpenShow/slides/editor/views/slide_element.py b/OpenShow/slides/editor/views/slide_element.py index 3b1cd95..e47b630 100644 --- a/OpenShow/slides/editor/views/slide_element.py +++ b/OpenShow/slides/editor/views/slide_element.py @@ -1,7 +1,8 @@ -from django.views.generic import CreateView, DeleteView, UpdateView +from django.views.generic import CreateView, DeleteView, UpdateView, FormView from django.urls import reverse -from slides.models import SlideElement +from slides.models import SlideElement, Slide from slides.editor.forms import DeleteSlideElementForm, EditSlideElementTextForm +from slides.editor.forms import ChangeSlideElementOrderForm class SlideElementCreateView(CreateView): @@ -63,3 +64,34 @@ class SlideElementUpdateVideoView(UpdateView): 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/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/models.py b/OpenShow/slides/models.py index 1ec3bbb..c936137 100644 --- a/OpenShow/slides/models.py +++ b/OpenShow/slides/models.py @@ -323,7 +323,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, From 7cad9f5ab08e58ce15a817990104a02b469fe332 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Tue, 3 Sep 2024 13:24:29 -0400 Subject: [PATCH 051/103] Make dragbox disappear vertically --- OpenShow/slides/editor/static/editor/editor.css | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenShow/slides/editor/static/editor/editor.css b/OpenShow/slides/editor/static/editor/editor.css index 996c757..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; } From 529f241b8e49545cc0fe02a1c611a5809eda4f40 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Tue, 3 Sep 2024 13:32:24 -0400 Subject: [PATCH 052/103] Use new iframe slide thumbnail in Deck editor --- OpenShow/slides/editor/templates/editor/deck_editor.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenShow/slides/editor/templates/editor/deck_editor.html b/OpenShow/slides/editor/templates/editor/deck_editor.html index 38f9297..4a1e131 100644 --- a/OpenShow/slides/editor/templates/editor/deck_editor.html +++ b/OpenShow/slides/editor/templates/editor/deck_editor.html @@ -49,7 +49,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 %}
Date: Tue, 3 Sep 2024 13:35:37 -0400 Subject: [PATCH 053/103] Don't show the video filename hint if there is an image --- OpenShow/slides/editor/templates/editor/slide.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenShow/slides/editor/templates/editor/slide.html b/OpenShow/slides/editor/templates/editor/slide.html index 0272922..dd5c529 100644 --- a/OpenShow/slides/editor/templates/editor/slide.html +++ b/OpenShow/slides/editor/templates/editor/slide.html @@ -33,7 +33,7 @@ > {% if element.body %} {{ element.body|safe }} - {% elif element.video %} + {% elif element.video and not element.image %} Video: {{ element.video }} {% elif not element.image %} Double-click to edit From 4994ec028bec2dc30cccf73aefa7ebfe22734fb2 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Tue, 3 Sep 2024 13:51:36 -0400 Subject: [PATCH 054/103] Don't throw an error when a slide element's body hasn't been initialized --- OpenShow/slides/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenShow/slides/models.py b/OpenShow/slides/models.py index c936137..8213d40 100644 --- a/OpenShow/slides/models.py +++ b/OpenShow/slides/models.py @@ -366,7 +366,8 @@ def save(self, *args, **kwargs): super(SlideElement, self).save(*args, **kwargs) def get_editable_text(self): - self.body = self.body.replace('
', '\n') + if self.body: + self.body = self.body.replace('
', '\n') return self.body From 89a9147338ab69c70680f321b76b342fb6ea6133 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Tue, 3 Sep 2024 13:51:54 -0400 Subject: [PATCH 055/103] Add logic for creating new elements (not final styling) --- .../slides/editor/templates/editor/edit_slide.html | 1 + .../editor/templates/editor/element_create.html | 12 ++++++++++++ OpenShow/slides/editor/views/slide_element.py | 5 ++--- 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 OpenShow/slides/editor/templates/editor/element_create.html diff --git a/OpenShow/slides/editor/templates/editor/edit_slide.html b/OpenShow/slides/editor/templates/editor/edit_slide.html index 2617a7c..87a86ba 100644 --- a/OpenShow/slides/editor/templates/editor/edit_slide.html +++ b/OpenShow/slides/editor/templates/editor/edit_slide.html @@ -9,6 +9,7 @@ {# #} {# {% endfor %}#} {# #} + {% csrf_token %} 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/views/slide_element.py b/OpenShow/slides/editor/views/slide_element.py index e47b630..e61212d 100644 --- a/OpenShow/slides/editor/views/slide_element.py +++ b/OpenShow/slides/editor/views/slide_element.py @@ -7,9 +7,8 @@ class SlideElementCreateView(CreateView): model = SlideElement - fields = ['css_class', 'body', 'order', 'image', 'video', 'slide'] - template_name = 'editor/edit_element.html' - extra_context = {'action': 'new'} + fields = ['css_class', 'slide'] + template_name = 'editor/element_create.html' class SlideElementUpdateView(UpdateView): From a0ad1921d2414404b96dd66e7e23332f665427cc Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Tue, 3 Sep 2024 13:56:03 -0400 Subject: [PATCH 056/103] Remove old slide element editor --- .../editor/templates/editor/edit_element.html | 30 ------------------- .../editor/templates/editor/edit_slide.html | 13 -------- OpenShow/slides/editor/urls.py | 3 +- OpenShow/slides/editor/views/slide_element.py | 7 ----- 4 files changed, 1 insertion(+), 52 deletions(-) delete mode 100644 OpenShow/slides/editor/templates/editor/edit_element.html 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 87a86ba..9c9fa2c 100644 --- a/OpenShow/slides/editor/templates/editor/edit_slide.html +++ b/OpenShow/slides/editor/templates/editor/edit_slide.html @@ -28,19 +28,6 @@ {# #} -
      - {% for element in slide.get_elements %} -
    • - {{ element.body }} -{# #} -
    • - {% endfor %} - -
    {% include 'editor/slide.html' with slide=slide %} diff --git a/OpenShow/slides/editor/urls.py b/OpenShow/slides/editor/urls.py index bd6af78..37b2801 100644 --- a/OpenShow/slides/editor/urls.py +++ b/OpenShow/slides/editor/urls.py @@ -4,7 +4,7 @@ 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, SlideElementUpdateView, SlideElementDeleteView, \ +from slides.editor.views.slide_element import SlideElementCreateView, SlideElementDeleteView, \ SlideElementDeleteView, SlideElementUpdateTextView, SlideElementUpdateCSSClassView, SlideElementUpdateImageView, \ SlideElementUpdateVideoView, ChangeSlideElementOrderView from slides.editor.views.deck import DeckCreateView, DeckEditorView, DeckDeleteView, push_deck_cues, push_deck_slide_text, pull_aoml_text @@ -24,7 +24,6 @@ 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'), diff --git a/OpenShow/slides/editor/views/slide_element.py b/OpenShow/slides/editor/views/slide_element.py index e61212d..d425af6 100644 --- a/OpenShow/slides/editor/views/slide_element.py +++ b/OpenShow/slides/editor/views/slide_element.py @@ -11,13 +11,6 @@ class SlideElementCreateView(CreateView): template_name = 'editor/element_create.html' -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' From eb55e2efcc02d1f6276f57a3569c3fa8000b2830 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Tue, 3 Sep 2024 16:42:30 -0400 Subject: [PATCH 057/103] Don't add a blank slide every time we pull AOML from a deck --- OpenShow/slides/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenShow/slides/models.py b/OpenShow/slides/models.py index 8213d40..3dc0f4c 100644 --- a/OpenShow/slides/models.py +++ b/OpenShow/slides/models.py @@ -186,7 +186,8 @@ def pull_aoml(self): aoml_str = '' for slide in self.slides.all(): aoml_str += slide.pull_aoml() - aoml_str += '~~\r' + if slide != self.slides.last(): + aoml_str += '~~\r' return aoml_str class Meta: From 0f6716bb1d69a43f55fcefe7c193437ecb7602ee Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Tue, 3 Sep 2024 17:25:27 -0400 Subject: [PATCH 058/103] Fix funky import formatting --- OpenShow/slides/editor/urls.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/OpenShow/slides/editor/urls.py b/OpenShow/slides/editor/urls.py index 37b2801..eb629e0 100644 --- a/OpenShow/slides/editor/urls.py +++ b/OpenShow/slides/editor/urls.py @@ -1,16 +1,19 @@ from django.urls import path from slides.editor.views.index import IndexView -from slides.editor.views.show import ShowEditorView, ShowCreateView, ShowDeleteView, SetThemeView, check_theme_compatibility +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, \ - SlideElementDeleteView, SlideElementUpdateTextView, SlideElementUpdateCSSClassView, SlideElementUpdateImageView, \ + SlideElementUpdateTextView, SlideElementUpdateCSSClassView, SlideElementUpdateImageView, \ SlideElementUpdateVideoView, ChangeSlideElementOrderView -from slides.editor.views.deck import DeckCreateView, DeckEditorView, DeckDeleteView, push_deck_cues, push_deck_slide_text, pull_aoml_text +from slides.editor.views.deck import DeckCreateView, DeckEditorView, DeckDeleteView, push_deck_cues, \ + push_deck_slide_text, pull_aoml_text 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.transition import TransitionEditorView, TransitionCreateView, TransitionKeyframeCreateView, \ + TransitionKeyframeUpdateView, TransitionEditorIndexView urlpatterns = [ From af2e2af0dffc8ded694f1c883e7fe6535cc1656c Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Tue, 3 Sep 2024 17:42:35 -0400 Subject: [PATCH 059/103] Add hint icon if a slide will auto advance Fixes #19 --- OpenShow/slides/templates/slides/iframe-slide-thumbnail.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html b/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html index 1f4cda6..8061b65 100644 --- a/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html +++ b/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html @@ -13,5 +13,8 @@ {% if slide.has_video %} {% icon "film" class='slide-info-icon' %} {% endif %} + {% if slide.auto_advance %} + {% icon 'clock' class='slide-info-icon' %} + {% endif %}
    \ No newline at end of file From ca279b84c8964bd78f9179658ac15cfdc3388f06 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Sat, 7 Sep 2024 12:54:58 -0400 Subject: [PATCH 060/103] Vendor HTMX, _hyperscript, and missing.css How did I miss doing this before? --- OpenShow/core/static/css/missing.css | 1 + OpenShow/core/static/js/htmx.js | 1 + OpenShow/core/static/js/hyperscript.js | 1 + OpenShow/core/static/js/sse.js | 369 ++++++++++++++++++ OpenShow/core/templates/core/base.html | 6 +- .../slides/editor/templates/editor/base.html | 6 +- 6 files changed, 378 insertions(+), 6 deletions(-) create mode 100644 OpenShow/core/static/css/missing.css create mode 100644 OpenShow/core/static/js/htmx.js create mode 100644 OpenShow/core/static/js/hyperscript.js create mode 100644 OpenShow/core/static/js/sse.js diff --git a/OpenShow/core/static/css/missing.css b/OpenShow/core/static/css/missing.css new file mode 100644 index 0000000..c07eae1 --- /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/core/base.html b/OpenShow/core/templates/core/base.html index cf7b144..399bb2a 100644 --- a/OpenShow/core/templates/core/base.html +++ b/OpenShow/core/templates/core/base.html @@ -6,10 +6,10 @@ {% block title %}OpenShow{% endblock %} {# + - - + + {% block extra_js %} {% endblock %} diff --git a/OpenShow/slides/editor/templates/editor/base.html b/OpenShow/slides/editor/templates/editor/base.html index 7e724e4..a51e28a 100644 --- a/OpenShow/slides/editor/templates/editor/base.html +++ b/OpenShow/slides/editor/templates/editor/base.html @@ -2,9 +2,9 @@ - - - + + + {% block title %}OpenShow Editor{% endblock %} From eec3b4ecccd49b2e5120c318cc1af788a08fd2fe Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Mon, 9 Sep 2024 17:37:23 -0400 Subject: [PATCH 061/103] Allow resizing slide thumbnails with CSS variable --- .../slides/static/slides/slide-thumbnail.css | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/OpenShow/slides/static/slides/slide-thumbnail.css b/OpenShow/slides/static/slides/slide-thumbnail.css index 8d9f148..27cee2a 100644 --- a/OpenShow/slides/static/slides/slide-thumbnail.css +++ b/OpenShow/slides/static/slides/slide-thumbnail.css @@ -1,11 +1,15 @@ +body { + --display-size-divisor: 10 +} + .slide-thumbnail .slide { min-width: 1920px; width: 1920px; min-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); + 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; @@ -13,8 +17,8 @@ } .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: 1px solid var(--faded-fg); From 4906740330c260dc63dc7940feab78caa02080e7 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Mon, 9 Sep 2024 17:48:21 -0400 Subject: [PATCH 062/103] Implement basic Display editor Closes #11 --- .../templates/editor/display/create.html | 5 +++ .../templates/editor/display/delete.html | 0 .../templates/editor/display/detail.html | 38 +++++++++++++++++++ .../templates/editor/display/update.html | 5 +++ OpenShow/slides/editor/urls.py | 5 +++ OpenShow/slides/editor/views/display.py | 36 ++++++++++++++++++ OpenShow/slides/models.py | 3 ++ OpenShow/slides/templates/slides/index.html | 12 +++++- 8 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 OpenShow/slides/editor/templates/editor/display/create.html create mode 100644 OpenShow/slides/editor/templates/editor/display/delete.html create mode 100644 OpenShow/slides/editor/templates/editor/display/detail.html create mode 100644 OpenShow/slides/editor/templates/editor/display/update.html create mode 100644 OpenShow/slides/editor/views/display.py 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/slides/editor/templates/editor/display/delete.html b/OpenShow/slides/editor/templates/editor/display/delete.html new file mode 100644 index 0000000..e69de29 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/urls.py b/OpenShow/slides/editor/urls.py index eb629e0..95cd69a 100644 --- a/OpenShow/slides/editor/urls.py +++ b/OpenShow/slides/editor/urls.py @@ -14,6 +14,7 @@ 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 urlpatterns = [ @@ -51,4 +52,8 @@ 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'), ] \ No newline at end of file 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/models.py b/OpenShow/slides/models.py index 3dc0f4c..f9ed610 100644 --- a/OpenShow/slides/models.py +++ b/OpenShow/slides/models.py @@ -137,6 +137,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) diff --git a/OpenShow/slides/templates/slides/index.html b/OpenShow/slides/templates/slides/index.html index 5ab0e65..47aff42 100644 --- a/OpenShow/slides/templates/slides/index.html +++ b/OpenShow/slides/templates/slides/index.html @@ -82,8 +82,18 @@

    Themes

    {% endfor %}
    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/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/requirements.txt b/requirements.txt index b8d5fc7..1d861ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,4 +12,6 @@ gunicorn uvicorn django-environ django-ninja -django-srcdoc==1.0.1 \ No newline at end of file +django-srcdoc==1.0.1 +neapolitan==24.6 +django-extensions \ No newline at end of file From 73ac7b4af4fd6e7dba69acb00d74d07c280cdc11 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Wed, 9 Oct 2024 13:43:12 -0400 Subject: [PATCH 068/103] Basic Django Q2 setup --- OpenShow/OpenShow/settings/base.py | 15 +++++++++++++++ OpenShow/Procfile | 0 OpenShow/Procfile.development | 2 ++ requirements.txt | 4 +++- 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 OpenShow/Procfile create mode 100644 OpenShow/Procfile.development diff --git a/OpenShow/OpenShow/settings/base.py b/OpenShow/OpenShow/settings/base.py index cbbd508..f4da48b 100644 --- a/OpenShow/OpenShow/settings/base.py +++ b/OpenShow/OpenShow/settings/base.py @@ -38,6 +38,7 @@ 'django_srcdoc', 'neapolitan', 'django_extensions', + 'django_q', ] MIDDLEWARE = [ @@ -110,3 +111,17 @@ # 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': 90, + 'retry': 120, + 'queue_limit': 50, + 'bulk': 10, + 'orm': 'default' +} diff --git a/OpenShow/Procfile b/OpenShow/Procfile new file mode 100644 index 0000000..e69de29 diff --git a/OpenShow/Procfile.development b/OpenShow/Procfile.development new file mode 100644 index 0000000..af8b13d --- /dev/null +++ b/OpenShow/Procfile.development @@ -0,0 +1,2 @@ +web: python manage.py runserver 8030 +worker: python manage.py qcluster \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 1d861ea..3b5d0b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,6 @@ django-environ django-ninja django-srcdoc==1.0.1 neapolitan==24.6 -django-extensions \ No newline at end of file +django-extensions +django-q2==1.7.2 +honcho==2.0.0 \ No newline at end of file From ef8f11a2abdd93026bc7732cc2f9ef7df2402c49 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Tue, 15 Oct 2024 17:35:49 -0400 Subject: [PATCH 069/103] Initial working automated transcoding --- .ipython/profile_default/ipython_config.py | 2 + .ipython/profile_default/startup/README | 11 ++++ OpenShow/OpenShow/settings/base.py | 8 ++- OpenShow/Procfile.development | 4 +- OpenShow/slides/editor/tasks.py | 60 +++++++++++++++++++ OpenShow/slides/editor/views/slide_element.py | 5 ++ OpenShow/slides/models.py | 20 ++++++- requirements.txt | 4 +- 8 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 .ipython/profile_default/ipython_config.py create mode 100644 .ipython/profile_default/startup/README create mode 100644 OpenShow/slides/editor/tasks.py 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/OpenShow/OpenShow/settings/base.py b/OpenShow/OpenShow/settings/base.py index f4da48b..fce18fe 100644 --- a/OpenShow/OpenShow/settings/base.py +++ b/OpenShow/OpenShow/settings/base.py @@ -119,9 +119,13 @@ Q_CLUSTER = { 'name': 'DjangORM', 'workers': 4, - 'timeout': 90, - 'retry': 120, + '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/Procfile.development b/OpenShow/Procfile.development index af8b13d..0ac4ef0 100644 --- a/OpenShow/Procfile.development +++ b/OpenShow/Procfile.development @@ -1,2 +1,2 @@ -web: python manage.py runserver 8030 -worker: python manage.py qcluster \ No newline at end of file +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/slides/editor/tasks.py b/OpenShow/slides/editor/tasks.py new file mode 100644 index 0000000..193f688 --- /dev/null +++ b/OpenShow/slides/editor/tasks.py @@ -0,0 +1,60 @@ +import pathlib + +from django.conf import settings +from django.utils.text import slugify + +from slides.models import MediaObject +from ffmpeg import FFmpeg, Progress + +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/views/slide_element.py b/OpenShow/slides/editor/views/slide_element.py index a72a1c6..cc0d0ff 100644 --- a/OpenShow/slides/editor/views/slide_element.py +++ b/OpenShow/slides/editor/views/slide_element.py @@ -54,7 +54,12 @@ class SlideElementUpdateVideoView(UpdateView): 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() diff --git a/OpenShow/slides/models.py b/OpenShow/slides/models.py index f063a60..78d4d4a 100644 --- a/OpenShow/slides/models.py +++ b/OpenShow/slides/models.py @@ -3,7 +3,8 @@ from collections.abc import Iterable from django.shortcuts import reverse from django.utils import timezone -from datetime import timedelta +from datetime import timedelta, datetime +from django_q.models import Schedule import tinycss2 @@ -699,6 +700,23 @@ class MediaObject(models.Model): ) 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(), + ) + 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(), + ) + def get_slide_element_template(self): template_name = None match self.media_type: diff --git a/requirements.txt b/requirements.txt index 3b5d0b7..cf44bac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ django==5.1 -honcho==1.1.0 django-eventstream==5.3.1 daphne==4.1.2 django-colorfield==0.8.0 @@ -16,4 +15,5 @@ django-srcdoc==1.0.1 neapolitan==24.6 django-extensions django-q2==1.7.2 -honcho==2.0.0 \ No newline at end of file +honcho==2.0.0 +python-ffmpeg==2.0.12 \ No newline at end of file From ce885b81064b466e13d82be511cac6041d2131f6 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Tue, 15 Oct 2024 17:59:20 -0400 Subject: [PATCH 070/103] Update .gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1f521f4..91be747 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ OpenShow/media/* -*/static-root/ \ No newline at end of file +*/static-root/ +.ipython/profile_default/history.sqlite +*db.sqlite3 \ No newline at end of file From 69bff7ecb11278d8ce1e010fa39b1ef560095a07 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Wed, 16 Oct 2024 17:32:55 -0400 Subject: [PATCH 071/103] Initial Media Object UI --- OpenShow/core/static/core/extras.css | 4 ++ .../templates/editor/mediaobject_detail.html | 29 +++++++++ .../templates/editor/mediaobject_form.html | 60 +++++++++++++++++++ OpenShow/slides/editor/views/media.py | 34 ++++++++++- OpenShow/slides/models.py | 2 + OpenShow/slides/templates/slides/index.html | 19 ++++++ OpenShow/slides/views.py | 3 +- 7 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 OpenShow/slides/editor/templates/editor/mediaobject_detail.html create mode 100644 OpenShow/slides/editor/templates/editor/mediaobject_form.html diff --git a/OpenShow/core/static/core/extras.css b/OpenShow/core/static/core/extras.css index 067916d..afb12ed 100644 --- a/OpenShow/core/static/core/extras.css +++ b/OpenShow/core/static/core/extras.css @@ -27,6 +27,10 @@ h2 { margin: 0; } +.hidden { + display: none; +} + @media screen and (max-width: 50rem) { .sidebar-layout.double { display: flex; 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..9e2090f --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/mediaobject_form.html @@ -0,0 +1,60 @@ +{% extends "core/base.html" %} +{% load icon %} + +{% if object %} + {% block header %} +

    {{ object.title }}

    + {% endblock %} + {% block header_right_button %} + {% icon 'trash-2' %} + {% endblock %} + {% block header_left_button %} + {% icon 'arrow-left' %} + {% endblock %} +{% endif %} + +{% 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/views/media.py b/OpenShow/slides/editor/views/media.py index b7f5935..87a38af 100644 --- a/OpenShow/slides/editor/views/media.py +++ b/OpenShow/slides/editor/views/media.py @@ -1,4 +1,4 @@ -from neapolitan.views import CRUDView +from neapolitan.views import CRUDView, Role from slides.models import MediaObject @@ -12,3 +12,35 @@ class MediaObjectCRUDView(CRUDView): 'embed_url', #'autoplay', # Uncomment this once it will be useful for something ] + + 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/models.py b/OpenShow/slides/models.py index 78d4d4a..2a38c29 100644 --- a/OpenShow/slides/models.py +++ b/OpenShow/slides/models.py @@ -709,6 +709,7 @@ def save(self, *args, **kwargs): 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', @@ -716,6 +717,7 @@ def save(self, *args, **kwargs): schedule_type=Schedule.ONCE, next_run=datetime.utcnow(), ) + print('scheduled audio') def get_slide_element_template(self): template_name = None diff --git a/OpenShow/slides/templates/slides/index.html b/OpenShow/slides/templates/slides/index.html index 47aff42..cde91ab 100644 --- a/OpenShow/slides/templates/slides/index.html +++ b/OpenShow/slides/templates/slides/index.html @@ -30,6 +30,9 @@

    OpenShow Slides

    +

    Shows

    @@ -111,4 +114,20 @@

    Transitions

    {% endfor %}
    + {% endblock %} \ No newline at end of file diff --git a/OpenShow/slides/views.py b/OpenShow/slides/views.py index 06404f4..8f0c72a 100644 --- a/OpenShow/slides/views.py +++ b/OpenShow/slides/views.py @@ -6,7 +6,7 @@ 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, Transition, Theme +from .models import Slide, Display, Show, Deck, Transition, Theme, MediaObject from .forms import SlideDisplayForm, ShowDisplaySelectorForm @@ -39,6 +39,7 @@ def get_context_data(self, **kwargs): 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 From a53de2d7eadb5430ad1210d23a63d58746bcd85b Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Thu, 17 Oct 2024 10:42:37 -0400 Subject: [PATCH 072/103] Fix form template mistakes --- .../templates/editor/mediaobject_form.html | 108 +++++++++--------- 1 file changed, 57 insertions(+), 51 deletions(-) diff --git a/OpenShow/slides/editor/templates/editor/mediaobject_form.html b/OpenShow/slides/editor/templates/editor/mediaobject_form.html index 9e2090f..6ba6e49 100644 --- a/OpenShow/slides/editor/templates/editor/mediaobject_form.html +++ b/OpenShow/slides/editor/templates/editor/mediaobject_form.html @@ -1,60 +1,66 @@ {% extends "core/base.html" %} {% load icon %} -{% if object %} - {% block header %} +{% block header %} + {% if object %}

    {{ object.title }}

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

    New Media Object

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

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

    +{% block header_left_button %} + {% if object %} + {% icon 'arrow-left' %} + {% endif %} +{% endblock %} -
    - {% 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 }} -

    - -
    -
    + on change from #id_media_type + configure_form() + end + on load + configure_form() + end + + {% 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 %} From 9b9aba05707e766e99c471ce3808f733d91a4f9f Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Thu, 17 Oct 2024 12:27:44 -0400 Subject: [PATCH 073/103] Avoid filename collisions --- ...ject_media_type_alter_mediaobject_title.py | 31 +++++++++++++++++++ OpenShow/slides/models.py | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 OpenShow/slides/migrations/0028_alter_mediaobject_media_type_alter_mediaobject_title.py 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/models.py b/OpenShow/slides/models.py index 2a38c29..575e5b1 100644 --- a/OpenShow/slides/models.py +++ b/OpenShow/slides/models.py @@ -665,7 +665,7 @@ class ThemeVariantRule(models.Model): class MediaObject(models.Model): - title = models.CharField(max_length=100) + title = models.CharField(max_length=100, unique=True) media_type = models.CharField( max_length=100, choices=[ From 6c06222debb4fe74bc9eb1071c6eb92a56d81f8e Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Thu, 17 Oct 2024 12:27:56 -0400 Subject: [PATCH 074/103] Fix transcode output path --- OpenShow/slides/editor/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenShow/slides/editor/tasks.py b/OpenShow/slides/editor/tasks.py index 193f688..385944c 100644 --- a/OpenShow/slides/editor/tasks.py +++ b/OpenShow/slides/editor/tasks.py @@ -9,7 +9,7 @@ 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 + 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() From 71ba71e573805eed115c1714b58c8a6109dd9490 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Thu, 17 Oct 2024 12:35:03 -0400 Subject: [PATCH 075/103] Add a production Procfile --- OpenShow/Procfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OpenShow/Procfile b/OpenShow/Procfile index e69de29..07e3cbd 100644 --- a/OpenShow/Procfile +++ 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 From fe44b972450232b692824ee7bafde91262283f7f Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Thu, 17 Oct 2024 12:35:55 -0400 Subject: [PATCH 076/103] Install FFMpeg in the docker image --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 4a84542..acb0f83 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' From ebb1630b26e360d86218d6f1c65756f9a45ec173 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Thu, 17 Oct 2024 12:36:30 -0400 Subject: [PATCH 077/103] Use honcho to start uvicorn and django_q together --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index acb0f83..be6a480 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,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 From 9d599b22f0c5539f15b1dd967f77d224f4dce3fb Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Thu, 17 Oct 2024 12:50:38 -0400 Subject: [PATCH 078/103] Show icon for media objects --- OpenShow/slides/models.py | 7 +++++++ .../slides/templates/slides/iframe-slide-thumbnail.html | 3 +++ 2 files changed, 10 insertions(+) diff --git a/OpenShow/slides/models.py b/OpenShow/slides/models.py index 575e5b1..5c68b0e 100644 --- a/OpenShow/slides/models.py +++ b/OpenShow/slides/models.py @@ -546,6 +546,13 @@ def has_video(self): 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'): diff --git a/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html b/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html index 8061b65..70c28f1 100644 --- a/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html +++ b/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html @@ -13,6 +13,9 @@ {% 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 %} From 0f3882b484dadf914d9e41c9072abce7dc889259 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Thu, 17 Oct 2024 13:07:16 -0400 Subject: [PATCH 079/103] Add display custom CSS to thumbnails This only has an effect if the `display` variable exists in template context where the thumbnail is placed. --- OpenShow/slides/templates/slides/iframe-slide-thumbnail.html | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html b/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html index 70c28f1..f3564cc 100644 --- a/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html +++ b/OpenShow/slides/templates/slides/iframe-slide-thumbnail.html @@ -4,6 +4,7 @@ Slide {{ slide.pk }} From 8e07d46214af7fa9291d8f4f23c06626cb4e9336 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Fri, 18 Oct 2024 00:19:08 -0400 Subject: [PATCH 080/103] Trigger auto advance at end of media playback --- OpenShow/slides/templates/slides/display.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenShow/slides/templates/slides/display.html b/OpenShow/slides/templates/slides/display.html index 1617e46..6351726 100644 --- a/OpenShow/slides/templates/slides/display.html +++ b/OpenShow/slides/templates/slides/display.html @@ -50,7 +50,7 @@
    {% if display.current_slide.auto_advance %} -
    + {% csrf_token %} From ce512a980b09df6fc058cbaabf0819f239499a89 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Sat, 19 Oct 2024 15:46:39 -0400 Subject: [PATCH 081/103] Don't exclude media object templates from the docker image --- .dockerignore | 3 +-- OpenShow/OpenShow/settings/dev.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) 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/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/' From 7e9e9821d0eab9e076841fb5f1d82e641079e26b Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Sat, 19 Oct 2024 15:49:00 -0400 Subject: [PATCH 082/103] Define a sane __str__ for media objects --- OpenShow/slides/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OpenShow/slides/models.py b/OpenShow/slides/models.py index 5c68b0e..7b48aa5 100644 --- a/OpenShow/slides/models.py +++ b/OpenShow/slides/models.py @@ -736,4 +736,6 @@ def get_slide_element_template(self): case 'VIMEO_LIVE_EMBED': template_name = 'slides/media/vimeo_live_embed.html' return template_name - \ No newline at end of file + + def __str__(self): + return self.title \ No newline at end of file From 4242a3105c6a47007eb540b42d60482638695480 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Thu, 24 Oct 2024 17:45:31 -0400 Subject: [PATCH 083/103] Update .gitignore for new media root --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 91be747..cb704c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ OpenShow/media/* +*/media-root/ */static-root/ .ipython/profile_default/history.sqlite *db.sqlite3 \ No newline at end of file From 33e16ce8f78e5deb6d7398afb1a58662eceef53c Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Thu, 24 Oct 2024 17:45:45 -0400 Subject: [PATCH 084/103] Support multipart forms in simple-create-form --- .../editor/templates/editor/snippets/hx-simple_create_form.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 47cd74a..288a506 100644 --- a/OpenShow/slides/editor/templates/editor/snippets/hx-simple_create_form.html +++ b/OpenShow/slides/editor/templates/editor/snippets/hx-simple_create_form.html @@ -1,7 +1,7 @@ {% load icon %}
    New {{ object_type }} - + {% csrf_token %} {% block extra_fields %} {% endblock %} From a27245e439d93a21961429717ce3ea264bb0ce06 Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Thu, 24 Oct 2024 17:46:52 -0400 Subject: [PATCH 085/103] Add "import images" as a method for creating a new deck --- OpenShow/slides/editor/forms.py | 37 ++++++++++++++++++- .../editor/templates/editor/import_deck.html | 9 +++++ OpenShow/slides/editor/urls.py | 3 +- OpenShow/slides/editor/views/deck.py | 33 ++++++++++++++++- OpenShow/slides/templates/slides/index.html | 3 ++ 5 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 OpenShow/slides/editor/templates/editor/import_deck.html diff --git a/OpenShow/slides/editor/forms.py b/OpenShow/slides/editor/forms.py index 6a870af..628b1d9 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, SlideElement +from ..models import Show, Theme, SlideElement, Deck # class SimpleShowForm(ModelForm): @@ -53,3 +53,36 @@ class Meta: 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', + 'theme', + ] + files = MultipleFileField() + image_css_class = CharField() \ No newline at end of file diff --git a/OpenShow/slides/editor/templates/editor/import_deck.html b/OpenShow/slides/editor/templates/editor/import_deck.html new file mode 100644 index 0000000..ce88a19 --- /dev/null +++ b/OpenShow/slides/editor/templates/editor/import_deck.html @@ -0,0 +1,9 @@ +{% extends 'core/base.html' %} + +{% block content %} + + {% csrf_token %} + {{ form }} + + +{% endblock %} \ No newline at end of file diff --git a/OpenShow/slides/editor/urls.py b/OpenShow/slides/editor/urls.py index 1563075..0f25354 100644 --- a/OpenShow/slides/editor/urls.py +++ b/OpenShow/slides/editor/urls.py @@ -9,7 +9,7 @@ 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 + push_deck_slide_text, pull_aoml_text, DeckFromImagesView 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, \ @@ -43,6 +43,7 @@ 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', 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'), diff --git a/OpenShow/slides/editor/views/deck.py b/OpenShow/slides/editor/views/deck.py index e6a817b..16f4ba0 100644 --- a/OpenShow/slides/editor/views/deck.py +++ b/OpenShow/slides/editor/views/deck.py @@ -1,9 +1,10 @@ -from django.views.generic import CreateView, UpdateView, DeleteView +from django.views.generic import CreateView, UpdateView, DeleteView, FormView from django.urls import reverse_lazy -from slides.models import Deck, Slide +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 class DeckCreateView(CreateView): @@ -75,3 +76,31 @@ def pull_aoml_text(request, 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})) diff --git a/OpenShow/slides/templates/slides/index.html b/OpenShow/slides/templates/slides/index.html index cde91ab..3a181e8 100644 --- a/OpenShow/slides/templates/slides/index.html +++ b/OpenShow/slides/templates/slides/index.html @@ -58,6 +58,9 @@

    Decks

    +
      {% for deck in deck_list %}
    • From db27fa37e43f7da6af564cacb1dbed1376d88e5f Mon Sep 17 00:00:00 2001 From: Hans K Date: Tue, 29 Oct 2024 23:18:26 -0400 Subject: [PATCH 086/103] Initial documentation updates for 0.2 --- README.md | 36 ++++++++++++++++++++++++++++++++++-- docker-compose.yml.example | 4 ++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ee365ee..431ea34 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,40 @@ 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! Keep reading to get familiar with more powerful features. ## Development Setup diff --git a/docker-compose.yml.example b/docker-compose.yml.example index 7fb0beb..a4ef965 100644 --- a/docker-compose.yml.example +++ b/docker-compose.yml.example @@ -6,10 +6,10 @@ 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" From 00da8262485c3d99c6cee4481d9db671137782d3 Mon Sep 17 00:00:00 2001 From: Hans K Date: Tue, 29 Oct 2024 23:18:46 -0400 Subject: [PATCH 087/103] Update docker-compose example --- docker-compose.yml.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml.example b/docker-compose.yml.example index a4ef965..0d5e6e9 100644 --- a/docker-compose.yml.example +++ b/docker-compose.yml.example @@ -14,7 +14,7 @@ services: - "/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" From 466d153bd6159789466b5a98a9a82f440b57b8fa Mon Sep 17 00:00:00 2001 From: Hans Kelson Date: Sun, 10 Nov 2024 01:13:36 -0500 Subject: [PATCH 088/103] _hyperscript: Patch version bump --- OpenShow/slides/templates/slides/display.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenShow/slides/templates/slides/display.html b/OpenShow/slides/templates/slides/display.html index 6351726..c4e5474 100644 --- a/OpenShow/slides/templates/slides/display.html +++ b/OpenShow/slides/templates/slides/display.html @@ -4,8 +4,8 @@ OpenShow Display {{ display.pk }} - - + +