)]}'
{"version": 3, "sources": ["/web/static/lib/luxon/luxon.js", "/web/static/src/env.js", "/web/static/src/core/assets.js", "/web/static/src/core/browser/browser.js", "/web/static/src/core/browser/cookie_service.js", "/web/static/src/core/browser/feature_detection.js", "/web/static/src/core/browser/router_service.js", "/web/static/src/core/browser/title_service.js", "/web/static/src/core/checkbox/checkbox.js", "/web/static/src/core/confirmation_dialog/confirmation_dialog.js", "/web/static/src/core/context.js", "/web/static/src/core/datepicker/datepicker.js", "/web/static/src/core/debug/debug_context.js", "/web/static/src/core/debug/debug_menu_basic.js", "/web/static/src/core/debug/debug_menu_items.js", "/web/static/src/core/debug/debug_providers.js", "/web/static/src/core/debug/debug_utils.js", "/web/static/src/core/debug/profiling/profiling_item.js", "/web/static/src/core/debug/profiling/profiling_service.js", "/web/static/src/core/dialog/dialog.js", "/web/static/src/core/dialog/dialog_container.js", "/web/static/src/core/dialog/dialog_service.js", "/web/static/src/core/domain.js", "/web/static/src/core/dropdown/dropdown.js", "/web/static/src/core/dropdown/dropdown_item.js", "/web/static/src/core/dropdown/dropdown_navigation_hook.js", "/web/static/src/core/effects/effect_container.js", "/web/static/src/core/effects/effect_service.js", "/web/static/src/core/effects/rainbow_man.js", "/web/static/src/core/errors/error_dialogs.js", "/web/static/src/core/errors/error_handlers.js", "/web/static/src/core/errors/error_service.js", "/web/static/src/core/errors/error_utils.js", "/web/static/src/core/file_input/file_input.js", "/web/static/src/core/hotkeys/hotkey_hook.js", "/web/static/src/core/hotkeys/hotkey_service.js", "/web/static/src/core/l10n/dates.js", "/web/static/src/core/l10n/localization.js", "/web/static/src/core/l10n/localization_service.js", "/web/static/src/core/l10n/translation.js", "/web/static/src/core/main_components_container.js", "/web/static/src/core/network/download.js", "/web/static/src/core/network/http_service.js", "/web/static/src/core/network/rpc_service.js", "/web/static/src/core/notifications/notification.js", "/web/static/src/core/notifications/notification_container.js", "/web/static/src/core/notifications/notification_service.js", "/web/static/src/core/orm_service.js", "/web/static/src/core/popover/popover.js", "/web/static/src/core/popover/popover_container.js", "/web/static/src/core/popover/popover_hook.js", "/web/static/src/core/popover/popover_service.js", "/web/static/src/core/position/position_hook.js", "/web/static/src/core/py_js/py.js", "/web/static/src/core/py_js/py_builtin.js", "/web/static/src/core/py_js/py_date.js", "/web/static/src/core/py_js/py_interpreter.js", "/web/static/src/core/py_js/py_parser.js", "/web/static/src/core/py_js/py_tokenizer.js", "/web/static/src/core/py_js/py_utils.js", "/web/static/src/core/registry.js", "/web/static/src/core/scroller_service.js", "/web/static/src/core/tooltip/tooltip.js", "/web/static/src/core/tooltip/tooltip_hook.js", "/web/static/src/core/ui/block_ui.js", "/web/static/src/core/ui/ui_service.js", "/web/static/src/core/user_service.js", "/web/static/src/core/utils/arrays.js", "/web/static/src/core/utils/components.js", "/web/static/src/core/utils/concurrency.js", "/web/static/src/core/utils/functions.js", "/web/static/src/core/utils/hooks.js", "/web/static/src/core/utils/numbers.js", "/web/static/src/core/utils/objects.js", "/web/static/src/core/utils/patch.js", "/web/static/src/core/utils/scrolling.js", "/web/static/src/core/utils/search.js", "/web/static/src/core/utils/strings.js", "/web/static/src/core/utils/timing.js", "/web/static/src/core/utils/ui.js", "/web/static/src/core/utils/urls.js", "/web/static/src/core/utils/xml.js", "/web/static/src/public/error_notifications.js", "/web_editor/static/src/js/frontend/loader_loading.js", "/web/static/src/legacy/utils.js", "/web/static/src/legacy/js/owl_compatibility.js", "/web/static/src/legacy/js/services/session.js", "/web/static/src/legacy/js/public/public_env.js", "/web/static/src/legacy/js/public/public_root.js", "/website/static/src/js/content/website_root_instance.js", "/web/static/src/legacy/js/public/public_widget.js", "/web/static/src/legacy/legacy_promise_error_handler.js", "/web/static/src/legacy/legacy_rpc_error_handler.js", "/web/static/src/legacy/frontend/keyboard_navigation_mixin.js", "/bus/static/src/js/longpolling_bus.js", "/bus/static/src/js/crosstab_bus.js", "/bus/static/src/js/services/bus_service.js", "/web_tour/static/src/js/public/tour_manager.js", "/web_editor/static/src/js/wysiwyg/dialog.js", "/web_editor/static/src/js/frontend/loader.js", "/auth_signup/static/src/js/signup.js", "/portal/static/src/js/portal.js", "/portal/static/src/js/portal_chatter.js", "/portal/static/src/js/portal_composer.js", "/portal/static/src/js/portal_signature.js", "/portal/static/src/js/portal_sidebar.js", "/account/static/src/js/account_portal_sidebar.js", "/payment/static/lib/jquery.payment/jquery.payment.js", "/payment/static/src/js/checkout_form.js", "/payment/static/src/js/manage_form.js", "/payment/static/src/js/payment_form_mixin.js", "/payment/static/src/js/post_processing.js", "/sale/static/src/js/sale_portal_sidebar.js", "/sale/static/src/js/payment_form.js", "/sale_management/static/src/js/sale_management.js", "/google_recaptcha/static/src/js/recaptcha.js", "/website/static/src/js/utils.js", "/website/static/src/js/content/website_root.js", "/website/static/src/js/widgets/dialog.js", "/website/static/src/js/widgets/fullscreen_indication.js", "/website/static/src/js/content/compatibility.js", "/website/static/src/js/content/menu.js", "/website/static/src/js/content/snippets.animation.js", "/website/static/src/js/menu/navbar.js", "/website/static/src/js/show_password.js", "/website/static/src/js/post_link.js", "/website/static/src/js/user_custom_javascript.js", "/website/static/src/snippets/s_map/000.js", "/purchase/static/src/js/purchase_datetimepicker.js", "/purchase/static/src/js/purchase_portal_sidebar.js", "/project/static/src/js/portal_rating.js", "/website_payment/static/src/js/website_payment_donation.js", "/website_payment/static/src/js/website_payment_form.js", "/website_mail/static/src/js/follow.js", "/portal_rating/static/src/js/portal_chatter.js", "/portal_rating/static/src/js/portal_composer.js", "/portal_rating/static/src/js/portal_rating_composer.js", "/sale/static/src/js/variant_mixin.js", "/website_sale/static/src/js/variant_mixin.js", "/website_sale/static/src/js/website_sale.js", "/website_sale/static/src/js/website_sale_utils.js", "/website_sale/static/src/js/website_sale_payment.js", "/website_sale/static/src/js/website_sale_validate.js", "/website_sale/static/src/js/website_sale_recently_viewed.js", "/website_sale/static/src/js/website_sale_tracking.js", "/website/static/lib/multirange/multirange_custom.js", "/website_sale/static/src/js/website_sale_category_link.js", "/auth_totp_portal/static/src/js/totp_frontend.js", "/hotel_online/static/src/js/bootstrap.min.js", "/hotel_online/static/lib/slider/js/lightslider.js", "/hotel_online/static/lib/slider/js/test.js", "/hotel_online/static/lib/slider/js/new_js_file.js", "/hotel_online/static/src/js/website_sale_validate.js", "/hotel_online/static/src/js/website_sale_inherit.js", "/web_unsplash/static/src/js/unsplash_beacon.js", "/website_enterprise/static/src/js/website_enterprise.js", "/website_sale_stock/static/src/js/variant_mixin.js", "/website/static/src/snippets/s_share/000.js", "/website/static/src/snippets/s_facebook_page/000.js", "/website/static/src/snippets/s_image_gallery/000.js", "/website/static/src/snippets/s_countdown/000.js", "/website/static/src/snippets/s_popup/000.js", "/website/static/src/snippets/s_table_of_content/000.js", "/website/static/src/snippets/s_chart/000.js", "/website/static/src/snippets/s_google_map/000.js", "/website/static/src/snippets/s_dynamic_snippet/000.js", "/website/static/src/snippets/s_dynamic_snippet_carousel/000.js", "/website/static/src/snippets/s_website_form/000.js", "/website/static/src/snippets/s_searchbar/000.js", "/website_payment/static/src/snippets/s_donation/000.js", "/website_sale/static/src/snippets/s_dynamic_snippet_products/000.js"], "mappings": "AAAA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACvmQA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtKA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3PA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5EA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3DA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjDA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpNA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtCA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7DA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7BA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/UA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1FA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChBA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnFA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnCA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACdA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7GA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClDA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtCA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpEA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzRA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3UA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3EA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClRA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxBA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrGA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzDA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrLA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChOA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzIA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrGA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvFA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1BA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3XA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChWA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjGA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7DA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtCA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClhBA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3CA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrIA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxEA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzBA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzEA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvOA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/EA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9CA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjEA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxQA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtCA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7DA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/3BA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChXA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClYA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9TA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/HA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChJA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9DA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACXA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxHA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpFA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClJA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrEA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5JA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnBA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3KA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtBA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1QA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtBA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvBA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5IA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnGA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7DA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvSA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvGA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjBA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxDA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5CA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrCA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACjCA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3TA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxjBA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACXA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACXA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;ACxaA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;ACNA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;AC9TA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtCA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/CA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC9QA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC9QA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC5ZA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC5JA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACrBA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpFA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AClFA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACzBA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC5TA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC3VA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC5MA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACrMA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC3EA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACzEA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC5oBA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACtGA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AChPA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACrhBA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACrJA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACrHA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACrCA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC9FA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7DA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpYA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;AC/WA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrBA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxCA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACtCA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC/sBA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC3lDA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;ACtSA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AChDA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC9CA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxBA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpCA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;ACNA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AChHA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnCA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChKA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACjIA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACjYA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACzJA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACrIA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC5pBA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpEA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC//BA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC1IA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC9HA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnDA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpEA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC1HA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACzVA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACdA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AClTA;;;;;;AAAA;AACA;ACDA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxjCA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC9EA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC3CA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AChLA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACjCA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AClCA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnCA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC3GA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC1DA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AChEA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxLA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxaA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC/RA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC3HA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC9IA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACjGA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACjQA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxEA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACztBA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrSA;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/KA;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA", "sourcesContent": ["var luxon = (function (exports) {\n  'use strict';\n\n  function _defineProperties(target, props) {\n    for (var i = 0; i < props.length; i++) {\n      var descriptor = props[i];\n      descriptor.enumerable = descriptor.enumerable || false;\n      descriptor.configurable = true;\n      if (\"value\" in descriptor) descriptor.writable = true;\n      Object.defineProperty(target, descriptor.key, descriptor);\n    }\n  }\n\n  function _createClass(Constructor, protoProps, staticProps) {\n    if (protoProps) _defineProperties(Constructor.prototype, protoProps);\n    if (staticProps) _defineProperties(Constructor, staticProps);\n    return Constructor;\n  }\n\n  function _inheritsLoose(subClass, superClass) {\n    subClass.prototype = Object.create(superClass.prototype);\n    subClass.prototype.constructor = subClass;\n    subClass.__proto__ = superClass;\n  }\n\n  function _getPrototypeOf(o) {\n    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {\n      return o.__proto__ || Object.getPrototypeOf(o);\n    };\n    return _getPrototypeOf(o);\n  }\n\n  function _setPrototypeOf(o, p) {\n    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {\n      o.__proto__ = p;\n      return o;\n    };\n\n    return _setPrototypeOf(o, p);\n  }\n\n  function _isNativeReflectConstruct() {\n    if (typeof Reflect === \"undefined\" || !Reflect.construct) return false;\n    if (Reflect.construct.sham) return false;\n    if (typeof Proxy === \"function\") return true;\n\n    try {\n      Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n\n  function _construct(Parent, args, Class) {\n    if (_isNativeReflectConstruct()) {\n      _construct = Reflect.construct;\n    } else {\n      _construct = function _construct(Parent, args, Class) {\n        var a = [null];\n        a.push.apply(a, args);\n        var Constructor = Function.bind.apply(Parent, a);\n        var instance = new Constructor();\n        if (Class) _setPrototypeOf(instance, Class.prototype);\n        return instance;\n      };\n    }\n\n    return _construct.apply(null, arguments);\n  }\n\n  function _isNativeFunction(fn) {\n    return Function.toString.call(fn).indexOf(\"[native code]\") !== -1;\n  }\n\n  function _wrapNativeSuper(Class) {\n    var _cache = typeof Map === \"function\" ? new Map() : undefined;\n\n    _wrapNativeSuper = function _wrapNativeSuper(Class) {\n      if (Class === null || !_isNativeFunction(Class)) return Class;\n\n      if (typeof Class !== \"function\") {\n        throw new TypeError(\"Super expression must either be null or a function\");\n      }\n\n      if (typeof _cache !== \"undefined\") {\n        if (_cache.has(Class)) return _cache.get(Class);\n\n        _cache.set(Class, Wrapper);\n      }\n\n      function Wrapper() {\n        return _construct(Class, arguments, _getPrototypeOf(this).constructor);\n      }\n\n      Wrapper.prototype = Object.create(Class.prototype, {\n        constructor: {\n          value: Wrapper,\n          enumerable: false,\n          writable: true,\n          configurable: true\n        }\n      });\n      return _setPrototypeOf(Wrapper, Class);\n    };\n\n    return _wrapNativeSuper(Class);\n  }\n\n  function _objectWithoutPropertiesLoose(source, excluded) {\n    if (source == null) return {};\n    var target = {};\n    var sourceKeys = Object.keys(source);\n    var key, i;\n\n    for (i = 0; i < sourceKeys.length; i++) {\n      key = sourceKeys[i];\n      if (excluded.indexOf(key) >= 0) continue;\n      target[key] = source[key];\n    }\n\n    return target;\n  }\n\n  function _unsupportedIterableToArray(o, minLen) {\n    if (!o) return;\n    if (typeof o === \"string\") return _arrayLikeToArray(o, minLen);\n    var n = Object.prototype.toString.call(o).slice(8, -1);\n    if (n === \"Object\" && o.constructor) n = o.constructor.name;\n    if (n === \"Map\" || n === \"Set\") return Array.from(n);\n    if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);\n  }\n\n  function _arrayLikeToArray(arr, len) {\n    if (len == null || len > arr.length) len = arr.length;\n\n    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];\n\n    return arr2;\n  }\n\n  function _createForOfIteratorHelperLoose(o) {\n    var i = 0;\n\n    if (typeof Symbol === \"undefined\" || o[Symbol.iterator] == null) {\n      if (Array.isArray(o) || (o = _unsupportedIterableToArray(o))) return function () {\n        if (i >= o.length) return {\n          done: true\n        };\n        return {\n          done: false,\n          value: o[i++]\n        };\n      };\n      throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n    }\n\n    i = o[Symbol.iterator]();\n    return i.next.bind(i);\n  }\n\n  // these aren't really private, but nor are they really useful to document\n\n  /**\n   * @private\n   */\n  var LuxonError = /*#__PURE__*/function (_Error) {\n    _inheritsLoose(LuxonError, _Error);\n\n    function LuxonError() {\n      return _Error.apply(this, arguments) || this;\n    }\n\n    return LuxonError;\n  }( /*#__PURE__*/_wrapNativeSuper(Error));\n  /**\n   * @private\n   */\n\n\n  var InvalidDateTimeError = /*#__PURE__*/function (_LuxonError) {\n    _inheritsLoose(InvalidDateTimeError, _LuxonError);\n\n    function InvalidDateTimeError(reason) {\n      return _LuxonError.call(this, \"Invalid DateTime: \" + reason.toMessage()) || this;\n    }\n\n    return InvalidDateTimeError;\n  }(LuxonError);\n  /**\n   * @private\n   */\n\n  var InvalidIntervalError = /*#__PURE__*/function (_LuxonError2) {\n    _inheritsLoose(InvalidIntervalError, _LuxonError2);\n\n    function InvalidIntervalError(reason) {\n      return _LuxonError2.call(this, \"Invalid Interval: \" + reason.toMessage()) || this;\n    }\n\n    return InvalidIntervalError;\n  }(LuxonError);\n  /**\n   * @private\n   */\n\n  var InvalidDurationError = /*#__PURE__*/function (_LuxonError3) {\n    _inheritsLoose(InvalidDurationError, _LuxonError3);\n\n    function InvalidDurationError(reason) {\n      return _LuxonError3.call(this, \"Invalid Duration: \" + reason.toMessage()) || this;\n    }\n\n    return InvalidDurationError;\n  }(LuxonError);\n  /**\n   * @private\n   */\n\n  var ConflictingSpecificationError = /*#__PURE__*/function (_LuxonError4) {\n    _inheritsLoose(ConflictingSpecificationError, _LuxonError4);\n\n    function ConflictingSpecificationError() {\n      return _LuxonError4.apply(this, arguments) || this;\n    }\n\n    return ConflictingSpecificationError;\n  }(LuxonError);\n  /**\n   * @private\n   */\n\n  var InvalidUnitError = /*#__PURE__*/function (_LuxonError5) {\n    _inheritsLoose(InvalidUnitError, _LuxonError5);\n\n    function InvalidUnitError(unit) {\n      return _LuxonError5.call(this, \"Invalid unit \" + unit) || this;\n    }\n\n    return InvalidUnitError;\n  }(LuxonError);\n  /**\n   * @private\n   */\n\n  var InvalidArgumentError = /*#__PURE__*/function (_LuxonError6) {\n    _inheritsLoose(InvalidArgumentError, _LuxonError6);\n\n    function InvalidArgumentError() {\n      return _LuxonError6.apply(this, arguments) || this;\n    }\n\n    return InvalidArgumentError;\n  }(LuxonError);\n  /**\n   * @private\n   */\n\n  var ZoneIsAbstractError = /*#__PURE__*/function (_LuxonError7) {\n    _inheritsLoose(ZoneIsAbstractError, _LuxonError7);\n\n    function ZoneIsAbstractError() {\n      return _LuxonError7.call(this, \"Zone is an abstract class\") || this;\n    }\n\n    return ZoneIsAbstractError;\n  }(LuxonError);\n\n  /**\n   * @private\n   */\n  var n = \"numeric\",\n      s = \"short\",\n      l = \"long\";\n  var DATE_SHORT = {\n    year: n,\n    month: n,\n    day: n\n  };\n  var DATE_MED = {\n    year: n,\n    month: s,\n    day: n\n  };\n  var DATE_MED_WITH_WEEKDAY = {\n    year: n,\n    month: s,\n    day: n,\n    weekday: s\n  };\n  var DATE_FULL = {\n    year: n,\n    month: l,\n    day: n\n  };\n  var DATE_HUGE = {\n    year: n,\n    month: l,\n    day: n,\n    weekday: l\n  };\n  var TIME_SIMPLE = {\n    hour: n,\n    minute: n\n  };\n  var TIME_WITH_SECONDS = {\n    hour: n,\n    minute: n,\n    second: n\n  };\n  var TIME_WITH_SHORT_OFFSET = {\n    hour: n,\n    minute: n,\n    second: n,\n    timeZoneName: s\n  };\n  var TIME_WITH_LONG_OFFSET = {\n    hour: n,\n    minute: n,\n    second: n,\n    timeZoneName: l\n  };\n  var TIME_24_SIMPLE = {\n    hour: n,\n    minute: n,\n    hour12: false\n  };\n  /**\n   * {@link toLocaleString}; format like '09:30:23', always 24-hour.\n   */\n\n  var TIME_24_WITH_SECONDS = {\n    hour: n,\n    minute: n,\n    second: n,\n    hour12: false\n  };\n  /**\n   * {@link toLocaleString}; format like '09:30:23 EDT', always 24-hour.\n   */\n\n  var TIME_24_WITH_SHORT_OFFSET = {\n    hour: n,\n    minute: n,\n    second: n,\n    hour12: false,\n    timeZoneName: s\n  };\n  /**\n   * {@link toLocaleString}; format like '09:30:23 Eastern Daylight Time', always 24-hour.\n   */\n\n  var TIME_24_WITH_LONG_OFFSET = {\n    hour: n,\n    minute: n,\n    second: n,\n    hour12: false,\n    timeZoneName: l\n  };\n  /**\n   * {@link toLocaleString}; format like '10/14/1983, 9:30 AM'. Only 12-hour if the locale is.\n   */\n\n  var DATETIME_SHORT = {\n    year: n,\n    month: n,\n    day: n,\n    hour: n,\n    minute: n\n  };\n  /**\n   * {@link toLocaleString}; format like '10/14/1983, 9:30:33 AM'. Only 12-hour if the locale is.\n   */\n\n  var DATETIME_SHORT_WITH_SECONDS = {\n    year: n,\n    month: n,\n    day: n,\n    hour: n,\n    minute: n,\n    second: n\n  };\n  var DATETIME_MED = {\n    year: n,\n    month: s,\n    day: n,\n    hour: n,\n    minute: n\n  };\n  var DATETIME_MED_WITH_SECONDS = {\n    year: n,\n    month: s,\n    day: n,\n    hour: n,\n    minute: n,\n    second: n\n  };\n  var DATETIME_MED_WITH_WEEKDAY = {\n    year: n,\n    month: s,\n    day: n,\n    weekday: s,\n    hour: n,\n    minute: n\n  };\n  var DATETIME_FULL = {\n    year: n,\n    month: l,\n    day: n,\n    hour: n,\n    minute: n,\n    timeZoneName: s\n  };\n  var DATETIME_FULL_WITH_SECONDS = {\n    year: n,\n    month: l,\n    day: n,\n    hour: n,\n    minute: n,\n    second: n,\n    timeZoneName: s\n  };\n  var DATETIME_HUGE = {\n    year: n,\n    month: l,\n    day: n,\n    weekday: l,\n    hour: n,\n    minute: n,\n    timeZoneName: l\n  };\n  var DATETIME_HUGE_WITH_SECONDS = {\n    year: n,\n    month: l,\n    day: n,\n    weekday: l,\n    hour: n,\n    minute: n,\n    second: n,\n    timeZoneName: l\n  };\n\n  /*\n    This is just a junk drawer, containing anything used across multiple classes.\n    Because Luxon is small(ish), this should stay small and we won't worry about splitting\n    it up into, say, parsingUtil.js and basicUtil.js and so on. But they are divided up by feature area.\n  */\n  /**\n   * @private\n   */\n  // TYPES\n\n  function isUndefined(o) {\n    return typeof o === \"undefined\";\n  }\n  function isNumber(o) {\n    return typeof o === \"number\";\n  }\n  function isInteger(o) {\n    return typeof o === \"number\" && o % 1 === 0;\n  }\n  function isString(o) {\n    return typeof o === \"string\";\n  }\n  function isDate(o) {\n    return Object.prototype.toString.call(o) === \"[object Date]\";\n  } // CAPABILITIES\n\n  function hasIntl() {\n    try {\n      return typeof Intl !== \"undefined\" && Intl.DateTimeFormat;\n    } catch (e) {\n      return false;\n    }\n  }\n  function hasFormatToParts() {\n    return !isUndefined(Intl.DateTimeFormat.prototype.formatToParts);\n  }\n  function hasRelative() {\n    try {\n      return typeof Intl !== \"undefined\" && !!Intl.RelativeTimeFormat;\n    } catch (e) {\n      return false;\n    }\n  } // OBJECTS AND ARRAYS\n\n  function maybeArray(thing) {\n    return Array.isArray(thing) ? thing : [thing];\n  }\n  function bestBy(arr, by, compare) {\n    if (arr.length === 0) {\n      return undefined;\n    }\n\n    return arr.reduce(function (best, next) {\n      var pair = [by(next), next];\n\n      if (!best) {\n        return pair;\n      } else if (compare(best[0], pair[0]) === best[0]) {\n        return best;\n      } else {\n        return pair;\n      }\n    }, null)[1];\n  }\n  function pick(obj, keys) {\n    return keys.reduce(function (a, k) {\n      a[k] = obj[k];\n      return a;\n    }, {});\n  }\n  function hasOwnProperty(obj, prop) {\n    return Object.prototype.hasOwnProperty.call(obj, prop);\n  } // NUMBERS AND STRINGS\n\n  function integerBetween(thing, bottom, top) {\n    return isInteger(thing) && thing >= bottom && thing <= top;\n  } // x % n but takes the sign of n instead of x\n\n  function floorMod(x, n) {\n    return x - n * Math.floor(x / n);\n  }\n  function padStart(input, n) {\n    if (n === void 0) {\n      n = 2;\n    }\n\n    if (input.toString().length < n) {\n      return (\"0\".repeat(n) + input).slice(-n);\n    } else {\n      return input.toString();\n    }\n  }\n  function parseInteger(string) {\n    if (isUndefined(string) || string === null || string === \"\") {\n      return undefined;\n    } else {\n      return parseInt(string, 10);\n    }\n  }\n  function parseMillis(fraction) {\n    // Return undefined (instead of 0) in these cases, where fraction is not set\n    if (isUndefined(fraction) || fraction === null || fraction === \"\") {\n      return undefined;\n    } else {\n      var f = parseFloat(\"0.\" + fraction) * 1000;\n      return Math.floor(f);\n    }\n  }\n  function roundTo(number, digits, towardZero) {\n    if (towardZero === void 0) {\n      towardZero = false;\n    }\n\n    var factor = Math.pow(10, digits),\n        rounder = towardZero ? Math.trunc : Math.round;\n    return rounder(number * factor) / factor;\n  } // DATE BASICS\n\n  function isLeapYear(year) {\n    return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);\n  }\n  function daysInYear(year) {\n    return isLeapYear(year) ? 366 : 365;\n  }\n  function daysInMonth(year, month) {\n    var modMonth = floorMod(month - 1, 12) + 1,\n        modYear = year + (month - modMonth) / 12;\n\n    if (modMonth === 2) {\n      return isLeapYear(modYear) ? 29 : 28;\n    } else {\n      return [31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][modMonth - 1];\n    }\n  } // covert a calendar object to a local timestamp (epoch, but with the offset baked in)\n\n  function objToLocalTS(obj) {\n    var d = Date.UTC(obj.year, obj.month - 1, obj.day, obj.hour, obj.minute, obj.second, obj.millisecond); // for legacy reasons, years between 0 and 99 are interpreted as 19XX; revert that\n\n    if (obj.year < 100 && obj.year >= 0) {\n      d = new Date(d);\n      d.setUTCFullYear(d.getUTCFullYear() - 1900);\n    }\n\n    return +d;\n  }\n  function weeksInWeekYear(weekYear) {\n    var p1 = (weekYear + Math.floor(weekYear / 4) - Math.floor(weekYear / 100) + Math.floor(weekYear / 400)) % 7,\n        last = weekYear - 1,\n        p2 = (last + Math.floor(last / 4) - Math.floor(last / 100) + Math.floor(last / 400)) % 7;\n    return p1 === 4 || p2 === 3 ? 53 : 52;\n  }\n  function untruncateYear(year) {\n    if (year > 99) {\n      return year;\n    } else return year > 60 ? 1900 + year : 2000 + year;\n  } // PARSING\n\n  function parseZoneInfo(ts, offsetFormat, locale, timeZone) {\n    if (timeZone === void 0) {\n      timeZone = null;\n    }\n\n    var date = new Date(ts),\n        intlOpts = {\n      hour12: false,\n      year: \"numeric\",\n      month: \"2-digit\",\n      day: \"2-digit\",\n      hour: \"2-digit\",\n      minute: \"2-digit\"\n    };\n\n    if (timeZone) {\n      intlOpts.timeZone = timeZone;\n    }\n\n    var modified = Object.assign({\n      timeZoneName: offsetFormat\n    }, intlOpts),\n        intl = hasIntl();\n\n    if (intl && hasFormatToParts()) {\n      var parsed = new Intl.DateTimeFormat(locale, modified).formatToParts(date).find(function (m) {\n        return m.type.toLowerCase() === \"timezonename\";\n      });\n      return parsed ? parsed.value : null;\n    } else if (intl) {\n      // this probably doesn't work for all locales\n      var without = new Intl.DateTimeFormat(locale, intlOpts).format(date),\n          included = new Intl.DateTimeFormat(locale, modified).format(date),\n          diffed = included.substring(without.length),\n          trimmed = diffed.replace(/^[, \\u200e]+/, \"\");\n      return trimmed;\n    } else {\n      return null;\n    }\n  } // signedOffset('-5', '30') -> -330\n\n  function signedOffset(offHourStr, offMinuteStr) {\n    var offHour = parseInt(offHourStr, 10); // don't || this because we want to preserve -0\n\n    if (Number.isNaN(offHour)) {\n      offHour = 0;\n    }\n\n    var offMin = parseInt(offMinuteStr, 10) || 0,\n        offMinSigned = offHour < 0 || Object.is(offHour, -0) ? -offMin : offMin;\n    return offHour * 60 + offMinSigned;\n  } // COERCION\n\n  function asNumber(value) {\n    var numericValue = Number(value);\n    if (typeof value === \"boolean\" || value === \"\" || Number.isNaN(numericValue)) throw new InvalidArgumentError(\"Invalid unit value \" + value);\n    return numericValue;\n  }\n  function normalizeObject(obj, normalizer, nonUnitKeys) {\n    var normalized = {};\n\n    for (var u in obj) {\n      if (hasOwnProperty(obj, u)) {\n        if (nonUnitKeys.indexOf(u) >= 0) continue;\n        var v = obj[u];\n        if (v === undefined || v === null) continue;\n        normalized[normalizer(u)] = asNumber(v);\n      }\n    }\n\n    return normalized;\n  }\n  function formatOffset(offset, format) {\n    var hours = Math.trunc(Math.abs(offset / 60)),\n        minutes = Math.trunc(Math.abs(offset % 60)),\n        sign = offset >= 0 ? \"+\" : \"-\";\n\n    switch (format) {\n      case \"short\":\n        return \"\" + sign + padStart(hours, 2) + \":\" + padStart(minutes, 2);\n\n      case \"narrow\":\n        return \"\" + sign + hours + (minutes > 0 ? \":\" + minutes : \"\");\n\n      case \"techie\":\n        return \"\" + sign + padStart(hours, 2) + padStart(minutes, 2);\n\n      default:\n        throw new RangeError(\"Value format \" + format + \" is out of range for property format\");\n    }\n  }\n  function timeObject(obj) {\n    return pick(obj, [\"hour\", \"minute\", \"second\", \"millisecond\"]);\n  }\n  var ianaRegex = /[A-Za-z_+-]{1,256}(:?\\/[A-Za-z_+-]{1,256}(\\/[A-Za-z_+-]{1,256})?)?/;\n\n  function stringify(obj) {\n    return JSON.stringify(obj, Object.keys(obj).sort());\n  }\n  /**\n   * @private\n   */\n\n\n  var monthsLong = [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\", \"August\", \"September\", \"October\", \"November\", \"December\"];\n  var monthsShort = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\n  var monthsNarrow = [\"J\", \"F\", \"M\", \"A\", \"M\", \"J\", \"J\", \"A\", \"S\", \"O\", \"N\", \"D\"];\n  function months(length) {\n    switch (length) {\n      case \"narrow\":\n        return monthsNarrow;\n\n      case \"short\":\n        return monthsShort;\n\n      case \"long\":\n        return monthsLong;\n\n      case \"numeric\":\n        return [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\", \"11\", \"12\"];\n\n      case \"2-digit\":\n        return [\"01\", \"02\", \"03\", \"04\", \"05\", \"06\", \"07\", \"08\", \"09\", \"10\", \"11\", \"12\"];\n\n      default:\n        return null;\n    }\n  }\n  var weekdaysLong = [\"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\", \"Sunday\"];\n  var weekdaysShort = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"];\n  var weekdaysNarrow = [\"M\", \"T\", \"W\", \"T\", \"F\", \"S\", \"S\"];\n  function weekdays(length) {\n    switch (length) {\n      case \"narrow\":\n        return weekdaysNarrow;\n\n      case \"short\":\n        return weekdaysShort;\n\n      case \"long\":\n        return weekdaysLong;\n\n      case \"numeric\":\n        return [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\"];\n\n      default:\n        return null;\n    }\n  }\n  var meridiems = [\"AM\", \"PM\"];\n  var erasLong = [\"Before Christ\", \"Anno Domini\"];\n  var erasShort = [\"BC\", \"AD\"];\n  var erasNarrow = [\"B\", \"A\"];\n  function eras(length) {\n    switch (length) {\n      case \"narrow\":\n        return erasNarrow;\n\n      case \"short\":\n        return erasShort;\n\n      case \"long\":\n        return erasLong;\n\n      default:\n        return null;\n    }\n  }\n  function meridiemForDateTime(dt) {\n    return meridiems[dt.hour < 12 ? 0 : 1];\n  }\n  function weekdayForDateTime(dt, length) {\n    return weekdays(length)[dt.weekday - 1];\n  }\n  function monthForDateTime(dt, length) {\n    return months(length)[dt.month - 1];\n  }\n  function eraForDateTime(dt, length) {\n    return eras(length)[dt.year < 0 ? 0 : 1];\n  }\n  function formatRelativeTime(unit, count, numeric, narrow) {\n    if (numeric === void 0) {\n      numeric = \"always\";\n    }\n\n    if (narrow === void 0) {\n      narrow = false;\n    }\n\n    var units = {\n      years: [\"year\", \"yr.\"],\n      quarters: [\"quarter\", \"qtr.\"],\n      months: [\"month\", \"mo.\"],\n      weeks: [\"week\", \"wk.\"],\n      days: [\"day\", \"day\", \"days\"],\n      hours: [\"hour\", \"hr.\"],\n      minutes: [\"minute\", \"min.\"],\n      seconds: [\"second\", \"sec.\"]\n    };\n    var lastable = [\"hours\", \"minutes\", \"seconds\"].indexOf(unit) === -1;\n\n    if (numeric === \"auto\" && lastable) {\n      var isDay = unit === \"days\";\n\n      switch (count) {\n        case 1:\n          return isDay ? \"tomorrow\" : \"next \" + units[unit][0];\n\n        case -1:\n          return isDay ? \"yesterday\" : \"last \" + units[unit][0];\n\n        case 0:\n          return isDay ? \"today\" : \"this \" + units[unit][0];\n\n      }\n    }\n\n    var isInPast = Object.is(count, -0) || count < 0,\n        fmtValue = Math.abs(count),\n        singular = fmtValue === 1,\n        lilUnits = units[unit],\n        fmtUnit = narrow ? singular ? lilUnits[1] : lilUnits[2] || lilUnits[1] : singular ? units[unit][0] : unit;\n    return isInPast ? fmtValue + \" \" + fmtUnit + \" ago\" : \"in \" + fmtValue + \" \" + fmtUnit;\n  }\n  function formatString(knownFormat) {\n    // these all have the offsets removed because we don't have access to them\n    // without all the intl stuff this is backfilling\n    var filtered = pick(knownFormat, [\"weekday\", \"era\", \"year\", \"month\", \"day\", \"hour\", \"minute\", \"second\", \"timeZoneName\", \"hour12\"]),\n        key = stringify(filtered),\n        dateTimeHuge = \"EEEE, LLLL d, yyyy, h:mm a\";\n\n    switch (key) {\n      case stringify(DATE_SHORT):\n        return \"M/d/yyyy\";\n\n      case stringify(DATE_MED):\n        return \"LLL d, yyyy\";\n\n      case stringify(DATE_MED_WITH_WEEKDAY):\n        return \"EEE, LLL d, yyyy\";\n\n      case stringify(DATE_FULL):\n        return \"LLLL d, yyyy\";\n\n      case stringify(DATE_HUGE):\n        return \"EEEE, LLLL d, yyyy\";\n\n      case stringify(TIME_SIMPLE):\n        return \"h:mm a\";\n\n      case stringify(TIME_WITH_SECONDS):\n        return \"h:mm:ss a\";\n\n      case stringify(TIME_WITH_SHORT_OFFSET):\n        return \"h:mm a\";\n\n      case stringify(TIME_WITH_LONG_OFFSET):\n        return \"h:mm a\";\n\n      case stringify(TIME_24_SIMPLE):\n        return \"HH:mm\";\n\n      case stringify(TIME_24_WITH_SECONDS):\n        return \"HH:mm:ss\";\n\n      case stringify(TIME_24_WITH_SHORT_OFFSET):\n        return \"HH:mm\";\n\n      case stringify(TIME_24_WITH_LONG_OFFSET):\n        return \"HH:mm\";\n\n      case stringify(DATETIME_SHORT):\n        return \"M/d/yyyy, h:mm a\";\n\n      case stringify(DATETIME_MED):\n        return \"LLL d, yyyy, h:mm a\";\n\n      case stringify(DATETIME_FULL):\n        return \"LLLL d, yyyy, h:mm a\";\n\n      case stringify(DATETIME_HUGE):\n        return dateTimeHuge;\n\n      case stringify(DATETIME_SHORT_WITH_SECONDS):\n        return \"M/d/yyyy, h:mm:ss a\";\n\n      case stringify(DATETIME_MED_WITH_SECONDS):\n        return \"LLL d, yyyy, h:mm:ss a\";\n\n      case stringify(DATETIME_MED_WITH_WEEKDAY):\n        return \"EEE, d LLL yyyy, h:mm a\";\n\n      case stringify(DATETIME_FULL_WITH_SECONDS):\n        return \"LLLL d, yyyy, h:mm:ss a\";\n\n      case stringify(DATETIME_HUGE_WITH_SECONDS):\n        return \"EEEE, LLLL d, yyyy, h:mm:ss a\";\n\n      default:\n        return dateTimeHuge;\n    }\n  }\n\n  function stringifyTokens(splits, tokenToString) {\n    var s = \"\";\n\n    for (var _iterator = _createForOfIteratorHelperLoose(splits), _step; !(_step = _iterator()).done;) {\n      var token = _step.value;\n\n      if (token.literal) {\n        s += token.val;\n      } else {\n        s += tokenToString(token.val);\n      }\n    }\n\n    return s;\n  }\n\n  var _macroTokenToFormatOpts = {\n    D: DATE_SHORT,\n    DD: DATE_MED,\n    DDD: DATE_FULL,\n    DDDD: DATE_HUGE,\n    t: TIME_SIMPLE,\n    tt: TIME_WITH_SECONDS,\n    ttt: TIME_WITH_SHORT_OFFSET,\n    tttt: TIME_WITH_LONG_OFFSET,\n    T: TIME_24_SIMPLE,\n    TT: TIME_24_WITH_SECONDS,\n    TTT: TIME_24_WITH_SHORT_OFFSET,\n    TTTT: TIME_24_WITH_LONG_OFFSET,\n    f: DATETIME_SHORT,\n    ff: DATETIME_MED,\n    fff: DATETIME_FULL,\n    ffff: DATETIME_HUGE,\n    F: DATETIME_SHORT_WITH_SECONDS,\n    FF: DATETIME_MED_WITH_SECONDS,\n    FFF: DATETIME_FULL_WITH_SECONDS,\n    FFFF: DATETIME_HUGE_WITH_SECONDS\n  };\n  /**\n   * @private\n   */\n\n  var Formatter = /*#__PURE__*/function () {\n    Formatter.create = function create(locale, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      return new Formatter(locale, opts);\n    };\n\n    Formatter.parseFormat = function parseFormat(fmt) {\n      var current = null,\n          currentFull = \"\",\n          bracketed = false;\n      var splits = [];\n\n      for (var i = 0; i < fmt.length; i++) {\n        var c = fmt.charAt(i);\n\n        if (c === \"'\") {\n          if (currentFull.length > 0) {\n            splits.push({\n              literal: bracketed,\n              val: currentFull\n            });\n          }\n\n          current = null;\n          currentFull = \"\";\n          bracketed = !bracketed;\n        } else if (bracketed) {\n          currentFull += c;\n        } else if (c === current) {\n          currentFull += c;\n        } else {\n          if (currentFull.length > 0) {\n            splits.push({\n              literal: false,\n              val: currentFull\n            });\n          }\n\n          currentFull = c;\n          current = c;\n        }\n      }\n\n      if (currentFull.length > 0) {\n        splits.push({\n          literal: bracketed,\n          val: currentFull\n        });\n      }\n\n      return splits;\n    };\n\n    Formatter.macroTokenToFormatOpts = function macroTokenToFormatOpts(token) {\n      return _macroTokenToFormatOpts[token];\n    };\n\n    function Formatter(locale, formatOpts) {\n      this.opts = formatOpts;\n      this.loc = locale;\n      this.systemLoc = null;\n    }\n\n    var _proto = Formatter.prototype;\n\n    _proto.formatWithSystemDefault = function formatWithSystemDefault(dt, opts) {\n      if (this.systemLoc === null) {\n        this.systemLoc = this.loc.redefaultToSystem();\n      }\n\n      var df = this.systemLoc.dtFormatter(dt, Object.assign({}, this.opts, opts));\n      return df.format();\n    };\n\n    _proto.formatDateTime = function formatDateTime(dt, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      var df = this.loc.dtFormatter(dt, Object.assign({}, this.opts, opts));\n      return df.format();\n    };\n\n    _proto.formatDateTimeParts = function formatDateTimeParts(dt, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      var df = this.loc.dtFormatter(dt, Object.assign({}, this.opts, opts));\n      return df.formatToParts();\n    };\n\n    _proto.resolvedOptions = function resolvedOptions(dt, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      var df = this.loc.dtFormatter(dt, Object.assign({}, this.opts, opts));\n      return df.resolvedOptions();\n    };\n\n    _proto.num = function num(n, p) {\n      if (p === void 0) {\n        p = 0;\n      }\n\n      // we get some perf out of doing this here, annoyingly\n      if (this.opts.forceSimple) {\n        return padStart(n, p);\n      }\n\n      var opts = Object.assign({}, this.opts);\n\n      if (p > 0) {\n        opts.padTo = p;\n      }\n\n      return this.loc.numberFormatter(opts).format(n);\n    };\n\n    _proto.formatDateTimeFromString = function formatDateTimeFromString(dt, fmt) {\n      var _this = this;\n\n      var knownEnglish = this.loc.listingMode() === \"en\",\n          useDateTimeFormatter = this.loc.outputCalendar && this.loc.outputCalendar !== \"gregory\" && hasFormatToParts(),\n          string = function string(opts, extract) {\n        return _this.loc.extract(dt, opts, extract);\n      },\n          formatOffset = function formatOffset(opts) {\n        if (dt.isOffsetFixed && dt.offset === 0 && opts.allowZ) {\n          return \"Z\";\n        }\n\n        return dt.isValid ? dt.zone.formatOffset(dt.ts, opts.format) : \"\";\n      },\n          meridiem = function meridiem() {\n        return knownEnglish ? meridiemForDateTime(dt) : string({\n          hour: \"numeric\",\n          hour12: true\n        }, \"dayperiod\");\n      },\n          month = function month(length, standalone) {\n        return knownEnglish ? monthForDateTime(dt, length) : string(standalone ? {\n          month: length\n        } : {\n          month: length,\n          day: \"numeric\"\n        }, \"month\");\n      },\n          weekday = function weekday(length, standalone) {\n        return knownEnglish ? weekdayForDateTime(dt, length) : string(standalone ? {\n          weekday: length\n        } : {\n          weekday: length,\n          month: \"long\",\n          day: \"numeric\"\n        }, \"weekday\");\n      },\n          maybeMacro = function maybeMacro(token) {\n        var formatOpts = Formatter.macroTokenToFormatOpts(token);\n\n        if (formatOpts) {\n          return _this.formatWithSystemDefault(dt, formatOpts);\n        } else {\n          return token;\n        }\n      },\n          era = function era(length) {\n        return knownEnglish ? eraForDateTime(dt, length) : string({\n          era: length\n        }, \"era\");\n      },\n          tokenToString = function tokenToString(token) {\n        // Where possible: http://cldr.unicode.org/translation/date-time#TOC-Stand-Alone-vs.-Format-Styles\n        switch (token) {\n          // ms\n          case \"S\":\n            return _this.num(dt.millisecond);\n\n          case \"u\": // falls through\n\n          case \"SSS\":\n            return _this.num(dt.millisecond, 3);\n          // seconds\n\n          case \"s\":\n            return _this.num(dt.second);\n\n          case \"ss\":\n            return _this.num(dt.second, 2);\n          // minutes\n\n          case \"m\":\n            return _this.num(dt.minute);\n\n          case \"mm\":\n            return _this.num(dt.minute, 2);\n          // hours\n\n          case \"h\":\n            return _this.num(dt.hour % 12 === 0 ? 12 : dt.hour % 12);\n\n          case \"hh\":\n            return _this.num(dt.hour % 12 === 0 ? 12 : dt.hour % 12, 2);\n\n          case \"H\":\n            return _this.num(dt.hour);\n\n          case \"HH\":\n            return _this.num(dt.hour, 2);\n          // offset\n\n          case \"Z\":\n            // like +6\n            return formatOffset({\n              format: \"narrow\",\n              allowZ: _this.opts.allowZ\n            });\n\n          case \"ZZ\":\n            // like +06:00\n            return formatOffset({\n              format: \"short\",\n              allowZ: _this.opts.allowZ\n            });\n\n          case \"ZZZ\":\n            // like +0600\n            return formatOffset({\n              format: \"techie\",\n              allowZ: _this.opts.allowZ\n            });\n\n          case \"ZZZZ\":\n            // like EST\n            return dt.zone.offsetName(dt.ts, {\n              format: \"short\",\n              locale: _this.loc.locale\n            });\n\n          case \"ZZZZZ\":\n            // like Eastern Standard Time\n            return dt.zone.offsetName(dt.ts, {\n              format: \"long\",\n              locale: _this.loc.locale\n            });\n          // zone\n\n          case \"z\":\n            // like America/New_York\n            return dt.zoneName;\n          // meridiems\n\n          case \"a\":\n            return meridiem();\n          // dates\n\n          case \"d\":\n            return useDateTimeFormatter ? string({\n              day: \"numeric\"\n            }, \"day\") : _this.num(dt.day);\n\n          case \"dd\":\n            return useDateTimeFormatter ? string({\n              day: \"2-digit\"\n            }, \"day\") : _this.num(dt.day, 2);\n          // weekdays - standalone\n\n          case \"c\":\n            // like 1\n            return _this.num(dt.weekday);\n\n          case \"ccc\":\n            // like 'Tues'\n            return weekday(\"short\", true);\n\n          case \"cccc\":\n            // like 'Tuesday'\n            return weekday(\"long\", true);\n\n          case \"ccccc\":\n            // like 'T'\n            return weekday(\"narrow\", true);\n          // weekdays - format\n\n          case \"E\":\n            // like 1\n            return _this.num(dt.weekday);\n\n          case \"EEE\":\n            // like 'Tues'\n            return weekday(\"short\", false);\n\n          case \"EEEE\":\n            // like 'Tuesday'\n            return weekday(\"long\", false);\n\n          case \"EEEEE\":\n            // like 'T'\n            return weekday(\"narrow\", false);\n          // months - standalone\n\n          case \"L\":\n            // like 1\n            return useDateTimeFormatter ? string({\n              month: \"numeric\",\n              day: \"numeric\"\n            }, \"month\") : _this.num(dt.month);\n\n          case \"LL\":\n            // like 01, doesn't seem to work\n            return useDateTimeFormatter ? string({\n              month: \"2-digit\",\n              day: \"numeric\"\n            }, \"month\") : _this.num(dt.month, 2);\n\n          case \"LLL\":\n            // like Jan\n            return month(\"short\", true);\n\n          case \"LLLL\":\n            // like January\n            return month(\"long\", true);\n\n          case \"LLLLL\":\n            // like J\n            return month(\"narrow\", true);\n          // months - format\n\n          case \"M\":\n            // like 1\n            return useDateTimeFormatter ? string({\n              month: \"numeric\"\n            }, \"month\") : _this.num(dt.month);\n\n          case \"MM\":\n            // like 01\n            return useDateTimeFormatter ? string({\n              month: \"2-digit\"\n            }, \"month\") : _this.num(dt.month, 2);\n\n          case \"MMM\":\n            // like Jan\n            return month(\"short\", false);\n\n          case \"MMMM\":\n            // like January\n            return month(\"long\", false);\n\n          case \"MMMMM\":\n            // like J\n            return month(\"narrow\", false);\n          // years\n\n          case \"y\":\n            // like 2014\n            return useDateTimeFormatter ? string({\n              year: \"numeric\"\n            }, \"year\") : _this.num(dt.year);\n\n          case \"yy\":\n            // like 14\n            return useDateTimeFormatter ? string({\n              year: \"2-digit\"\n            }, \"year\") : _this.num(dt.year.toString().slice(-2), 2);\n\n          case \"yyyy\":\n            // like 0012\n            return useDateTimeFormatter ? string({\n              year: \"numeric\"\n            }, \"year\") : _this.num(dt.year, 4);\n\n          case \"yyyyyy\":\n            // like 000012\n            return useDateTimeFormatter ? string({\n              year: \"numeric\"\n            }, \"year\") : _this.num(dt.year, 6);\n          // eras\n\n          case \"G\":\n            // like AD\n            return era(\"short\");\n\n          case \"GG\":\n            // like Anno Domini\n            return era(\"long\");\n\n          case \"GGGGG\":\n            return era(\"narrow\");\n\n          case \"kk\":\n            return _this.num(dt.weekYear.toString().slice(-2), 2);\n\n          case \"kkkk\":\n            return _this.num(dt.weekYear, 4);\n\n          case \"W\":\n            return _this.num(dt.weekNumber);\n\n          case \"WW\":\n            return _this.num(dt.weekNumber, 2);\n\n          case \"o\":\n            return _this.num(dt.ordinal);\n\n          case \"ooo\":\n            return _this.num(dt.ordinal, 3);\n\n          case \"q\":\n            // like 1\n            return _this.num(dt.quarter);\n\n          case \"qq\":\n            // like 01\n            return _this.num(dt.quarter, 2);\n\n          case \"X\":\n            return _this.num(Math.floor(dt.ts / 1000));\n\n          case \"x\":\n            return _this.num(dt.ts);\n\n          default:\n            return maybeMacro(token);\n        }\n      };\n\n      return stringifyTokens(Formatter.parseFormat(fmt), tokenToString);\n    };\n\n    _proto.formatDurationFromString = function formatDurationFromString(dur, fmt) {\n      var _this2 = this;\n\n      var tokenToField = function tokenToField(token) {\n        switch (token[0]) {\n          case \"S\":\n            return \"millisecond\";\n\n          case \"s\":\n            return \"second\";\n\n          case \"m\":\n            return \"minute\";\n\n          case \"h\":\n            return \"hour\";\n\n          case \"d\":\n            return \"day\";\n\n          case \"M\":\n            return \"month\";\n\n          case \"y\":\n            return \"year\";\n\n          default:\n            return null;\n        }\n      },\n          tokenToString = function tokenToString(lildur) {\n        return function (token) {\n          var mapped = tokenToField(token);\n\n          if (mapped) {\n            return _this2.num(lildur.get(mapped), token.length);\n          } else {\n            return token;\n          }\n        };\n      },\n          tokens = Formatter.parseFormat(fmt),\n          realTokens = tokens.reduce(function (found, _ref) {\n        var literal = _ref.literal,\n            val = _ref.val;\n        return literal ? found : found.concat(val);\n      }, []),\n          collapsed = dur.shiftTo.apply(dur, realTokens.map(tokenToField).filter(function (t) {\n        return t;\n      }));\n\n      return stringifyTokens(tokens, tokenToString(collapsed));\n    };\n\n    return Formatter;\n  }();\n\n  var Invalid = /*#__PURE__*/function () {\n    function Invalid(reason, explanation) {\n      this.reason = reason;\n      this.explanation = explanation;\n    }\n\n    var _proto = Invalid.prototype;\n\n    _proto.toMessage = function toMessage() {\n      if (this.explanation) {\n        return this.reason + \": \" + this.explanation;\n      } else {\n        return this.reason;\n      }\n    };\n\n    return Invalid;\n  }();\n\n  /**\n   * @interface\n   */\n\n  var Zone = /*#__PURE__*/function () {\n    function Zone() {}\n\n    var _proto = Zone.prototype;\n\n    /**\n     * Returns the offset's common name (such as EST) at the specified timestamp\n     * @abstract\n     * @param {number} ts - Epoch milliseconds for which to get the name\n     * @param {Object} opts - Options to affect the format\n     * @param {string} opts.format - What style of offset to return. Accepts 'long' or 'short'.\n     * @param {string} opts.locale - What locale to return the offset name in.\n     * @return {string}\n     */\n    _proto.offsetName = function offsetName(ts, opts) {\n      throw new ZoneIsAbstractError();\n    }\n    /**\n     * Returns the offset's value as a string\n     * @abstract\n     * @param {number} ts - Epoch milliseconds for which to get the offset\n     * @param {string} format - What style of offset to return.\n     *                          Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively\n     * @return {string}\n     */\n    ;\n\n    _proto.formatOffset = function formatOffset(ts, format) {\n      throw new ZoneIsAbstractError();\n    }\n    /**\n     * Return the offset in minutes for this zone at the specified timestamp.\n     * @abstract\n     * @param {number} ts - Epoch milliseconds for which to compute the offset\n     * @return {number}\n     */\n    ;\n\n    _proto.offset = function offset(ts) {\n      throw new ZoneIsAbstractError();\n    }\n    /**\n     * Return whether this Zone is equal to another zone\n     * @abstract\n     * @param {Zone} otherZone - the zone to compare\n     * @return {boolean}\n     */\n    ;\n\n    _proto.equals = function equals(otherZone) {\n      throw new ZoneIsAbstractError();\n    }\n    /**\n     * Return whether this Zone is valid.\n     * @abstract\n     * @type {boolean}\n     */\n    ;\n\n    _createClass(Zone, [{\n      key: \"type\",\n\n      /**\n       * The type of zone\n       * @abstract\n       * @type {string}\n       */\n      get: function get() {\n        throw new ZoneIsAbstractError();\n      }\n      /**\n       * The name of this zone.\n       * @abstract\n       * @type {string}\n       */\n\n    }, {\n      key: \"name\",\n      get: function get() {\n        throw new ZoneIsAbstractError();\n      }\n      /**\n       * Returns whether the offset is known to be fixed for the whole year.\n       * @abstract\n       * @type {boolean}\n       */\n\n    }, {\n      key: \"universal\",\n      get: function get() {\n        throw new ZoneIsAbstractError();\n      }\n    }, {\n      key: \"isValid\",\n      get: function get() {\n        throw new ZoneIsAbstractError();\n      }\n    }]);\n\n    return Zone;\n  }();\n\n  var singleton = null;\n  /**\n   * Represents the local zone for this Javascript environment.\n   * @implements {Zone}\n   */\n\n  var LocalZone = /*#__PURE__*/function (_Zone) {\n    _inheritsLoose(LocalZone, _Zone);\n\n    function LocalZone() {\n      return _Zone.apply(this, arguments) || this;\n    }\n\n    var _proto = LocalZone.prototype;\n\n    /** @override **/\n    _proto.offsetName = function offsetName(ts, _ref) {\n      var format = _ref.format,\n          locale = _ref.locale;\n      return parseZoneInfo(ts, format, locale);\n    }\n    /** @override **/\n    ;\n\n    _proto.formatOffset = function formatOffset$1(ts, format) {\n      return formatOffset(this.offset(ts), format);\n    }\n    /** @override **/\n    ;\n\n    _proto.offset = function offset(ts) {\n      return -new Date(ts).getTimezoneOffset();\n    }\n    /** @override **/\n    ;\n\n    _proto.equals = function equals(otherZone) {\n      return otherZone.type === \"local\";\n    }\n    /** @override **/\n    ;\n\n    _createClass(LocalZone, [{\n      key: \"type\",\n\n      /** @override **/\n      get: function get() {\n        return \"local\";\n      }\n      /** @override **/\n\n    }, {\n      key: \"name\",\n      get: function get() {\n        if (hasIntl()) {\n          return new Intl.DateTimeFormat().resolvedOptions().timeZone;\n        } else return \"local\";\n      }\n      /** @override **/\n\n    }, {\n      key: \"universal\",\n      get: function get() {\n        return false;\n      }\n    }, {\n      key: \"isValid\",\n      get: function get() {\n        return true;\n      }\n    }], [{\n      key: \"instance\",\n\n      /**\n       * Get a singleton instance of the local zone\n       * @return {LocalZone}\n       */\n      get: function get() {\n        if (singleton === null) {\n          singleton = new LocalZone();\n        }\n\n        return singleton;\n      }\n    }]);\n\n    return LocalZone;\n  }(Zone);\n\n  var matchingRegex = RegExp(\"^\" + ianaRegex.source + \"$\");\n  var dtfCache = {};\n\n  function makeDTF(zone) {\n    if (!dtfCache[zone]) {\n      dtfCache[zone] = new Intl.DateTimeFormat(\"en-US\", {\n        hour12: false,\n        timeZone: zone,\n        year: \"numeric\",\n        month: \"2-digit\",\n        day: \"2-digit\",\n        hour: \"2-digit\",\n        minute: \"2-digit\",\n        second: \"2-digit\"\n      });\n    }\n\n    return dtfCache[zone];\n  }\n\n  var typeToPos = {\n    year: 0,\n    month: 1,\n    day: 2,\n    hour: 3,\n    minute: 4,\n    second: 5\n  };\n\n  function hackyOffset(dtf, date) {\n    var formatted = dtf.format(date).replace(/\\u200E/g, \"\"),\n        parsed = /(\\d+)\\/(\\d+)\\/(\\d+),? (\\d+):(\\d+):(\\d+)/.exec(formatted),\n        fMonth = parsed[1],\n        fDay = parsed[2],\n        fYear = parsed[3],\n        fHour = parsed[4],\n        fMinute = parsed[5],\n        fSecond = parsed[6];\n    return [fYear, fMonth, fDay, fHour, fMinute, fSecond];\n  }\n\n  function partsOffset(dtf, date) {\n    var formatted = dtf.formatToParts(date),\n        filled = [];\n\n    for (var i = 0; i < formatted.length; i++) {\n      var _formatted$i = formatted[i],\n          type = _formatted$i.type,\n          value = _formatted$i.value,\n          pos = typeToPos[type];\n\n      if (!isUndefined(pos)) {\n        filled[pos] = parseInt(value, 10);\n      }\n    }\n\n    return filled;\n  }\n\n  var ianaZoneCache = {};\n  /**\n   * A zone identified by an IANA identifier, like America/New_York\n   * @implements {Zone}\n   */\n\n  var IANAZone = /*#__PURE__*/function (_Zone) {\n    _inheritsLoose(IANAZone, _Zone);\n\n    /**\n     * @param {string} name - Zone name\n     * @return {IANAZone}\n     */\n    IANAZone.create = function create(name) {\n      if (!ianaZoneCache[name]) {\n        ianaZoneCache[name] = new IANAZone(name);\n      }\n\n      return ianaZoneCache[name];\n    }\n    /**\n     * Reset local caches. Should only be necessary in testing scenarios.\n     * @return {void}\n     */\n    ;\n\n    IANAZone.resetCache = function resetCache() {\n      ianaZoneCache = {};\n      dtfCache = {};\n    }\n    /**\n     * Returns whether the provided string is a valid specifier. This only checks the string's format, not that the specifier identifies a known zone; see isValidZone for that.\n     * @param {string} s - The string to check validity on\n     * @example IANAZone.isValidSpecifier(\"America/New_York\") //=> true\n     * @example IANAZone.isValidSpecifier(\"Fantasia/Castle\") //=> true\n     * @example IANAZone.isValidSpecifier(\"Sport~~blorp\") //=> false\n     * @return {boolean}\n     */\n    ;\n\n    IANAZone.isValidSpecifier = function isValidSpecifier(s) {\n      return !!(s && s.match(matchingRegex));\n    }\n    /**\n     * Returns whether the provided string identifies a real zone\n     * @param {string} zone - The string to check\n     * @example IANAZone.isValidZone(\"America/New_York\") //=> true\n     * @example IANAZone.isValidZone(\"Fantasia/Castle\") //=> false\n     * @example IANAZone.isValidZone(\"Sport~~blorp\") //=> false\n     * @return {boolean}\n     */\n    ;\n\n    IANAZone.isValidZone = function isValidZone(zone) {\n      try {\n        new Intl.DateTimeFormat(\"en-US\", {\n          timeZone: zone\n        }).format();\n        return true;\n      } catch (e) {\n        return false;\n      }\n    } // Etc/GMT+8 -> -480\n\n    /** @ignore */\n    ;\n\n    IANAZone.parseGMTOffset = function parseGMTOffset(specifier) {\n      if (specifier) {\n        var match = specifier.match(/^Etc\\/GMT([+-]\\d{1,2})$/i);\n\n        if (match) {\n          return -60 * parseInt(match[1]);\n        }\n      }\n\n      return null;\n    };\n\n    function IANAZone(name) {\n      var _this;\n\n      _this = _Zone.call(this) || this;\n      /** @private **/\n\n      _this.zoneName = name;\n      /** @private **/\n\n      _this.valid = IANAZone.isValidZone(name);\n      return _this;\n    }\n    /** @override **/\n\n\n    var _proto = IANAZone.prototype;\n\n    /** @override **/\n    _proto.offsetName = function offsetName(ts, _ref) {\n      var format = _ref.format,\n          locale = _ref.locale;\n      return parseZoneInfo(ts, format, locale, this.name);\n    }\n    /** @override **/\n    ;\n\n    _proto.formatOffset = function formatOffset$1(ts, format) {\n      return formatOffset(this.offset(ts), format);\n    }\n    /** @override **/\n    ;\n\n    _proto.offset = function offset(ts) {\n      var date = new Date(ts),\n          dtf = makeDTF(this.name),\n          _ref2 = dtf.formatToParts ? partsOffset(dtf, date) : hackyOffset(dtf, date),\n          year = _ref2[0],\n          month = _ref2[1],\n          day = _ref2[2],\n          hour = _ref2[3],\n          minute = _ref2[4],\n          second = _ref2[5],\n          adjustedHour = hour === 24 ? 0 : hour;\n\n      var asUTC = objToLocalTS({\n        year: year,\n        month: month,\n        day: day,\n        hour: adjustedHour,\n        minute: minute,\n        second: second,\n        millisecond: 0\n      });\n      var asTS = +date;\n      var over = asTS % 1000;\n      asTS -= over >= 0 ? over : 1000 + over;\n      return (asUTC - asTS) / (60 * 1000);\n    }\n    /** @override **/\n    ;\n\n    _proto.equals = function equals(otherZone) {\n      return otherZone.type === \"iana\" && otherZone.name === this.name;\n    }\n    /** @override **/\n    ;\n\n    _createClass(IANAZone, [{\n      key: \"type\",\n      get: function get() {\n        return \"iana\";\n      }\n      /** @override **/\n\n    }, {\n      key: \"name\",\n      get: function get() {\n        return this.zoneName;\n      }\n      /** @override **/\n\n    }, {\n      key: \"universal\",\n      get: function get() {\n        return false;\n      }\n    }, {\n      key: \"isValid\",\n      get: function get() {\n        return this.valid;\n      }\n    }]);\n\n    return IANAZone;\n  }(Zone);\n\n  var singleton$1 = null;\n  /**\n   * A zone with a fixed offset (meaning no DST)\n   * @implements {Zone}\n   */\n\n  var FixedOffsetZone = /*#__PURE__*/function (_Zone) {\n    _inheritsLoose(FixedOffsetZone, _Zone);\n\n    /**\n     * Get an instance with a specified offset\n     * @param {number} offset - The offset in minutes\n     * @return {FixedOffsetZone}\n     */\n    FixedOffsetZone.instance = function instance(offset) {\n      return offset === 0 ? FixedOffsetZone.utcInstance : new FixedOffsetZone(offset);\n    }\n    /**\n     * Get an instance of FixedOffsetZone from a UTC offset string, like \"UTC+6\"\n     * @param {string} s - The offset string to parse\n     * @example FixedOffsetZone.parseSpecifier(\"UTC+6\")\n     * @example FixedOffsetZone.parseSpecifier(\"UTC+06\")\n     * @example FixedOffsetZone.parseSpecifier(\"UTC-6:00\")\n     * @return {FixedOffsetZone}\n     */\n    ;\n\n    FixedOffsetZone.parseSpecifier = function parseSpecifier(s) {\n      if (s) {\n        var r = s.match(/^utc(?:([+-]\\d{1,2})(?::(\\d{2}))?)?$/i);\n\n        if (r) {\n          return new FixedOffsetZone(signedOffset(r[1], r[2]));\n        }\n      }\n\n      return null;\n    };\n\n    _createClass(FixedOffsetZone, null, [{\n      key: \"utcInstance\",\n\n      /**\n       * Get a singleton instance of UTC\n       * @return {FixedOffsetZone}\n       */\n      get: function get() {\n        if (singleton$1 === null) {\n          singleton$1 = new FixedOffsetZone(0);\n        }\n\n        return singleton$1;\n      }\n    }]);\n\n    function FixedOffsetZone(offset) {\n      var _this;\n\n      _this = _Zone.call(this) || this;\n      /** @private **/\n\n      _this.fixed = offset;\n      return _this;\n    }\n    /** @override **/\n\n\n    var _proto = FixedOffsetZone.prototype;\n\n    /** @override **/\n    _proto.offsetName = function offsetName() {\n      return this.name;\n    }\n    /** @override **/\n    ;\n\n    _proto.formatOffset = function formatOffset$1(ts, format) {\n      return formatOffset(this.fixed, format);\n    }\n    /** @override **/\n    ;\n\n    /** @override **/\n    _proto.offset = function offset() {\n      return this.fixed;\n    }\n    /** @override **/\n    ;\n\n    _proto.equals = function equals(otherZone) {\n      return otherZone.type === \"fixed\" && otherZone.fixed === this.fixed;\n    }\n    /** @override **/\n    ;\n\n    _createClass(FixedOffsetZone, [{\n      key: \"type\",\n      get: function get() {\n        return \"fixed\";\n      }\n      /** @override **/\n\n    }, {\n      key: \"name\",\n      get: function get() {\n        return this.fixed === 0 ? \"UTC\" : \"UTC\" + formatOffset(this.fixed, \"narrow\");\n      }\n    }, {\n      key: \"universal\",\n      get: function get() {\n        return true;\n      }\n    }, {\n      key: \"isValid\",\n      get: function get() {\n        return true;\n      }\n    }]);\n\n    return FixedOffsetZone;\n  }(Zone);\n\n  /**\n   * A zone that failed to parse. You should never need to instantiate this.\n   * @implements {Zone}\n   */\n\n  var InvalidZone = /*#__PURE__*/function (_Zone) {\n    _inheritsLoose(InvalidZone, _Zone);\n\n    function InvalidZone(zoneName) {\n      var _this;\n\n      _this = _Zone.call(this) || this;\n      /**  @private */\n\n      _this.zoneName = zoneName;\n      return _this;\n    }\n    /** @override **/\n\n\n    var _proto = InvalidZone.prototype;\n\n    /** @override **/\n    _proto.offsetName = function offsetName() {\n      return null;\n    }\n    /** @override **/\n    ;\n\n    _proto.formatOffset = function formatOffset() {\n      return \"\";\n    }\n    /** @override **/\n    ;\n\n    _proto.offset = function offset() {\n      return NaN;\n    }\n    /** @override **/\n    ;\n\n    _proto.equals = function equals() {\n      return false;\n    }\n    /** @override **/\n    ;\n\n    _createClass(InvalidZone, [{\n      key: \"type\",\n      get: function get() {\n        return \"invalid\";\n      }\n      /** @override **/\n\n    }, {\n      key: \"name\",\n      get: function get() {\n        return this.zoneName;\n      }\n      /** @override **/\n\n    }, {\n      key: \"universal\",\n      get: function get() {\n        return false;\n      }\n    }, {\n      key: \"isValid\",\n      get: function get() {\n        return false;\n      }\n    }]);\n\n    return InvalidZone;\n  }(Zone);\n\n  /**\n   * @private\n   */\n  function normalizeZone(input, defaultZone) {\n    var offset;\n\n    if (isUndefined(input) || input === null) {\n      return defaultZone;\n    } else if (input instanceof Zone) {\n      return input;\n    } else if (isString(input)) {\n      var lowered = input.toLowerCase();\n      if (lowered === \"local\") return defaultZone;else if (lowered === \"utc\" || lowered === \"gmt\") return FixedOffsetZone.utcInstance;else if ((offset = IANAZone.parseGMTOffset(input)) != null) {\n        // handle Etc/GMT-4, which V8 chokes on\n        return FixedOffsetZone.instance(offset);\n      } else if (IANAZone.isValidSpecifier(lowered)) return IANAZone.create(input);else return FixedOffsetZone.parseSpecifier(lowered) || new InvalidZone(input);\n    } else if (isNumber(input)) {\n      return FixedOffsetZone.instance(input);\n    } else if (typeof input === \"object\" && input.offset && typeof input.offset === \"number\") {\n      // This is dumb, but the instanceof check above doesn't seem to really work\n      // so we're duck checking it\n      return input;\n    } else {\n      return new InvalidZone(input);\n    }\n  }\n\n  var now = function now() {\n    return Date.now();\n  },\n      defaultZone = null,\n      // not setting this directly to LocalZone.instance bc loading order issues\n  defaultLocale = null,\n      defaultNumberingSystem = null,\n      defaultOutputCalendar = null,\n      throwOnInvalid = false;\n  /**\n   * Settings contains static getters and setters that control Luxon's overall behavior. Luxon is a simple library with few options, but the ones it does have live here.\n   */\n\n\n  var Settings = /*#__PURE__*/function () {\n    function Settings() {}\n\n    /**\n     * Reset Luxon's global caches. Should only be necessary in testing scenarios.\n     * @return {void}\n     */\n    Settings.resetCaches = function resetCaches() {\n      Locale.resetCache();\n      IANAZone.resetCache();\n    };\n\n    _createClass(Settings, null, [{\n      key: \"now\",\n\n      /**\n       * Get the callback for returning the current timestamp.\n       * @type {function}\n       */\n      get: function get() {\n        return now;\n      }\n      /**\n       * Set the callback for returning the current timestamp.\n       * The function should return a number, which will be interpreted as an Epoch millisecond count\n       * @type {function}\n       * @example Settings.now = () => Date.now() + 3000 // pretend it is 3 seconds in the future\n       * @example Settings.now = () => 0 // always pretend it's Jan 1, 1970 at midnight in UTC time\n       */\n      ,\n      set: function set(n) {\n        now = n;\n      }\n      /**\n       * Get the default time zone to create DateTimes in.\n       * @type {string}\n       */\n\n    }, {\n      key: \"defaultZoneName\",\n      get: function get() {\n        return Settings.defaultZone.name;\n      }\n      /**\n       * Set the default time zone to create DateTimes in. Does not affect existing instances.\n       * @type {string}\n       */\n      ,\n      set: function set(z) {\n        if (!z) {\n          defaultZone = null;\n        } else {\n          defaultZone = normalizeZone(z);\n        }\n      }\n      /**\n       * Get the default time zone object to create DateTimes in. Does not affect existing instances.\n       * @type {Zone}\n       */\n\n    }, {\n      key: \"defaultZone\",\n      get: function get() {\n        return defaultZone || LocalZone.instance;\n      }\n      /**\n       * Get the default locale to create DateTimes with. Does not affect existing instances.\n       * @type {string}\n       */\n\n    }, {\n      key: \"defaultLocale\",\n      get: function get() {\n        return defaultLocale;\n      }\n      /**\n       * Set the default locale to create DateTimes with. Does not affect existing instances.\n       * @type {string}\n       */\n      ,\n      set: function set(locale) {\n        defaultLocale = locale;\n      }\n      /**\n       * Get the default numbering system to create DateTimes with. Does not affect existing instances.\n       * @type {string}\n       */\n\n    }, {\n      key: \"defaultNumberingSystem\",\n      get: function get() {\n        return defaultNumberingSystem;\n      }\n      /**\n       * Set the default numbering system to create DateTimes with. Does not affect existing instances.\n       * @type {string}\n       */\n      ,\n      set: function set(numberingSystem) {\n        defaultNumberingSystem = numberingSystem;\n      }\n      /**\n       * Get the default output calendar to create DateTimes with. Does not affect existing instances.\n       * @type {string}\n       */\n\n    }, {\n      key: \"defaultOutputCalendar\",\n      get: function get() {\n        return defaultOutputCalendar;\n      }\n      /**\n       * Set the default output calendar to create DateTimes with. Does not affect existing instances.\n       * @type {string}\n       */\n      ,\n      set: function set(outputCalendar) {\n        defaultOutputCalendar = outputCalendar;\n      }\n      /**\n       * Get whether Luxon will throw when it encounters invalid DateTimes, Durations, or Intervals\n       * @type {boolean}\n       */\n\n    }, {\n      key: \"throwOnInvalid\",\n      get: function get() {\n        return throwOnInvalid;\n      }\n      /**\n       * Set whether Luxon will throw when it encounters invalid DateTimes, Durations, or Intervals\n       * @type {boolean}\n       */\n      ,\n      set: function set(t) {\n        throwOnInvalid = t;\n      }\n    }]);\n\n    return Settings;\n  }();\n\n  var intlDTCache = {};\n\n  function getCachedDTF(locString, opts) {\n    if (opts === void 0) {\n      opts = {};\n    }\n\n    var key = JSON.stringify([locString, opts]);\n    var dtf = intlDTCache[key];\n\n    if (!dtf) {\n      dtf = new Intl.DateTimeFormat(locString, opts);\n      intlDTCache[key] = dtf;\n    }\n\n    return dtf;\n  }\n\n  var intlNumCache = {};\n\n  function getCachedINF(locString, opts) {\n    if (opts === void 0) {\n      opts = {};\n    }\n\n    var key = JSON.stringify([locString, opts]);\n    var inf = intlNumCache[key];\n\n    if (!inf) {\n      inf = new Intl.NumberFormat(locString, opts);\n      intlNumCache[key] = inf;\n    }\n\n    return inf;\n  }\n\n  var intlRelCache = {};\n\n  function getCachedRTF(locString, opts) {\n    if (opts === void 0) {\n      opts = {};\n    }\n\n    var _opts = opts,\n        base = _opts.base,\n        cacheKeyOpts = _objectWithoutPropertiesLoose(_opts, [\"base\"]); // exclude `base` from the options\n\n\n    var key = JSON.stringify([locString, cacheKeyOpts]);\n    var inf = intlRelCache[key];\n\n    if (!inf) {\n      inf = new Intl.RelativeTimeFormat(locString, opts);\n      intlRelCache[key] = inf;\n    }\n\n    return inf;\n  }\n\n  var sysLocaleCache = null;\n\n  function systemLocale() {\n    if (sysLocaleCache) {\n      return sysLocaleCache;\n    } else if (hasIntl()) {\n      var computedSys = new Intl.DateTimeFormat().resolvedOptions().locale; // node sometimes defaults to \"und\". Override that because that is dumb\n\n      sysLocaleCache = !computedSys || computedSys === \"und\" ? \"en-US\" : computedSys;\n      return sysLocaleCache;\n    } else {\n      sysLocaleCache = \"en-US\";\n      return sysLocaleCache;\n    }\n  }\n\n  function parseLocaleString(localeStr) {\n    // I really want to avoid writing a BCP 47 parser\n    // see, e.g. https://github.com/wooorm/bcp-47\n    // Instead, we'll do this:\n    // a) if the string has no -u extensions, just leave it alone\n    // b) if it does, use Intl to resolve everything\n    // c) if Intl fails, try again without the -u\n    var uIndex = localeStr.indexOf(\"-u-\");\n\n    if (uIndex === -1) {\n      return [localeStr];\n    } else {\n      var options;\n      var smaller = localeStr.substring(0, uIndex);\n\n      try {\n        options = getCachedDTF(localeStr).resolvedOptions();\n      } catch (e) {\n        options = getCachedDTF(smaller).resolvedOptions();\n      }\n\n      var _options = options,\n          numberingSystem = _options.numberingSystem,\n          calendar = _options.calendar; // return the smaller one so that we can append the calendar and numbering overrides to it\n\n      return [smaller, numberingSystem, calendar];\n    }\n  }\n\n  function intlConfigString(localeStr, numberingSystem, outputCalendar) {\n    if (hasIntl()) {\n      if (outputCalendar || numberingSystem) {\n        localeStr += \"-u\";\n\n        if (outputCalendar) {\n          localeStr += \"-ca-\" + outputCalendar;\n        }\n\n        if (numberingSystem) {\n          localeStr += \"-nu-\" + numberingSystem;\n        }\n\n        return localeStr;\n      } else {\n        return localeStr;\n      }\n    } else {\n      return [];\n    }\n  }\n\n  function mapMonths(f) {\n    var ms = [];\n\n    for (var i = 1; i <= 12; i++) {\n      var dt = DateTime.utc(2016, i, 1);\n      ms.push(f(dt));\n    }\n\n    return ms;\n  }\n\n  function mapWeekdays(f) {\n    var ms = [];\n\n    for (var i = 1; i <= 7; i++) {\n      var dt = DateTime.utc(2016, 11, 13 + i);\n      ms.push(f(dt));\n    }\n\n    return ms;\n  }\n\n  function listStuff(loc, length, defaultOK, englishFn, intlFn) {\n    var mode = loc.listingMode(defaultOK);\n\n    if (mode === \"error\") {\n      return null;\n    } else if (mode === \"en\") {\n      return englishFn(length);\n    } else {\n      return intlFn(length);\n    }\n  }\n\n  function supportsFastNumbers(loc) {\n    if (loc.numberingSystem && loc.numberingSystem !== \"latn\") {\n      return false;\n    } else {\n      return loc.numberingSystem === \"latn\" || !loc.locale || loc.locale.startsWith(\"en\") || hasIntl() && new Intl.DateTimeFormat(loc.intl).resolvedOptions().numberingSystem === \"latn\";\n    }\n  }\n  /**\n   * @private\n   */\n\n\n  var PolyNumberFormatter = /*#__PURE__*/function () {\n    function PolyNumberFormatter(intl, forceSimple, opts) {\n      this.padTo = opts.padTo || 0;\n      this.floor = opts.floor || false;\n\n      if (!forceSimple && hasIntl()) {\n        var intlOpts = {\n          useGrouping: false\n        };\n        if (opts.padTo > 0) intlOpts.minimumIntegerDigits = opts.padTo;\n        this.inf = getCachedINF(intl, intlOpts);\n      }\n    }\n\n    var _proto = PolyNumberFormatter.prototype;\n\n    _proto.format = function format(i) {\n      if (this.inf) {\n        var fixed = this.floor ? Math.floor(i) : i;\n        return this.inf.format(fixed);\n      } else {\n        // to match the browser's numberformatter defaults\n        var _fixed = this.floor ? Math.floor(i) : roundTo(i, 3);\n\n        return padStart(_fixed, this.padTo);\n      }\n    };\n\n    return PolyNumberFormatter;\n  }();\n  /**\n   * @private\n   */\n\n\n  var PolyDateFormatter = /*#__PURE__*/function () {\n    function PolyDateFormatter(dt, intl, opts) {\n      this.opts = opts;\n      this.hasIntl = hasIntl();\n      var z;\n\n      if (dt.zone.universal && this.hasIntl) {\n        // Chromium doesn't support fixed-offset zones like Etc/GMT+8 in its formatter,\n        // See https://bugs.chromium.org/p/chromium/issues/detail?id=364374.\n        // So we have to make do. Two cases:\n        // 1. The format options tell us to show the zone. We can't do that, so the best\n        // we can do is format the date in UTC.\n        // 2. The format options don't tell us to show the zone. Then we can adjust them\n        // the time and tell the formatter to show it to us in UTC, so that the time is right\n        // and the bad zone doesn't show up.\n        // We can clean all this up when Chrome fixes this.\n        z = \"UTC\";\n\n        if (opts.timeZoneName) {\n          this.dt = dt;\n        } else {\n          this.dt = dt.offset === 0 ? dt : DateTime.fromMillis(dt.ts + dt.offset * 60 * 1000);\n        }\n      } else if (dt.zone.type === \"local\") {\n        this.dt = dt;\n      } else {\n        this.dt = dt;\n        z = dt.zone.name;\n      }\n\n      if (this.hasIntl) {\n        var intlOpts = Object.assign({}, this.opts);\n\n        if (z) {\n          intlOpts.timeZone = z;\n        }\n\n        this.dtf = getCachedDTF(intl, intlOpts);\n      }\n    }\n\n    var _proto2 = PolyDateFormatter.prototype;\n\n    _proto2.format = function format() {\n      if (this.hasIntl) {\n        return this.dtf.format(this.dt.toJSDate());\n      } else {\n        var tokenFormat = formatString(this.opts),\n            loc = Locale.create(\"en-US\");\n        return Formatter.create(loc).formatDateTimeFromString(this.dt, tokenFormat);\n      }\n    };\n\n    _proto2.formatToParts = function formatToParts() {\n      if (this.hasIntl && hasFormatToParts()) {\n        return this.dtf.formatToParts(this.dt.toJSDate());\n      } else {\n        // This is kind of a cop out. We actually could do this for English. However, we couldn't do it for intl strings\n        // and IMO it's too weird to have an uncanny valley like that\n        return [];\n      }\n    };\n\n    _proto2.resolvedOptions = function resolvedOptions() {\n      if (this.hasIntl) {\n        return this.dtf.resolvedOptions();\n      } else {\n        return {\n          locale: \"en-US\",\n          numberingSystem: \"latn\",\n          outputCalendar: \"gregory\"\n        };\n      }\n    };\n\n    return PolyDateFormatter;\n  }();\n  /**\n   * @private\n   */\n\n\n  var PolyRelFormatter = /*#__PURE__*/function () {\n    function PolyRelFormatter(intl, isEnglish, opts) {\n      this.opts = Object.assign({\n        style: \"long\"\n      }, opts);\n\n      if (!isEnglish && hasRelative()) {\n        this.rtf = getCachedRTF(intl, opts);\n      }\n    }\n\n    var _proto3 = PolyRelFormatter.prototype;\n\n    _proto3.format = function format(count, unit) {\n      if (this.rtf) {\n        return this.rtf.format(count, unit);\n      } else {\n        return formatRelativeTime(unit, count, this.opts.numeric, this.opts.style !== \"long\");\n      }\n    };\n\n    _proto3.formatToParts = function formatToParts(count, unit) {\n      if (this.rtf) {\n        return this.rtf.formatToParts(count, unit);\n      } else {\n        return [];\n      }\n    };\n\n    return PolyRelFormatter;\n  }();\n  /**\n   * @private\n   */\n\n\n  var Locale = /*#__PURE__*/function () {\n    Locale.fromOpts = function fromOpts(opts) {\n      return Locale.create(opts.locale, opts.numberingSystem, opts.outputCalendar, opts.defaultToEN);\n    };\n\n    Locale.create = function create(locale, numberingSystem, outputCalendar, defaultToEN) {\n      if (defaultToEN === void 0) {\n        defaultToEN = false;\n      }\n\n      var specifiedLocale = locale || Settings.defaultLocale,\n          // the system locale is useful for human readable strings but annoying for parsing/formatting known formats\n      localeR = specifiedLocale || (defaultToEN ? \"en-US\" : systemLocale()),\n          numberingSystemR = numberingSystem || Settings.defaultNumberingSystem,\n          outputCalendarR = outputCalendar || Settings.defaultOutputCalendar;\n      return new Locale(localeR, numberingSystemR, outputCalendarR, specifiedLocale);\n    };\n\n    Locale.resetCache = function resetCache() {\n      sysLocaleCache = null;\n      intlDTCache = {};\n      intlNumCache = {};\n      intlRelCache = {};\n    };\n\n    Locale.fromObject = function fromObject(_temp) {\n      var _ref = _temp === void 0 ? {} : _temp,\n          locale = _ref.locale,\n          numberingSystem = _ref.numberingSystem,\n          outputCalendar = _ref.outputCalendar;\n\n      return Locale.create(locale, numberingSystem, outputCalendar);\n    };\n\n    function Locale(locale, numbering, outputCalendar, specifiedLocale) {\n      var _parseLocaleString = parseLocaleString(locale),\n          parsedLocale = _parseLocaleString[0],\n          parsedNumberingSystem = _parseLocaleString[1],\n          parsedOutputCalendar = _parseLocaleString[2];\n\n      this.locale = parsedLocale;\n      this.numberingSystem = numbering || parsedNumberingSystem || null;\n      this.outputCalendar = outputCalendar || parsedOutputCalendar || null;\n      this.intl = intlConfigString(this.locale, this.numberingSystem, this.outputCalendar);\n      this.weekdaysCache = {\n        format: {},\n        standalone: {}\n      };\n      this.monthsCache = {\n        format: {},\n        standalone: {}\n      };\n      this.meridiemCache = null;\n      this.eraCache = {};\n      this.specifiedLocale = specifiedLocale;\n      this.fastNumbersCached = null;\n    }\n\n    var _proto4 = Locale.prototype;\n\n    _proto4.listingMode = function listingMode(defaultOK) {\n      if (defaultOK === void 0) {\n        defaultOK = true;\n      }\n\n      var intl = hasIntl(),\n          hasFTP = intl && hasFormatToParts(),\n          isActuallyEn = this.isEnglish(),\n          hasNoWeirdness = (this.numberingSystem === null || this.numberingSystem === \"latn\") && (this.outputCalendar === null || this.outputCalendar === \"gregory\");\n\n      if (!hasFTP && !(isActuallyEn && hasNoWeirdness) && !defaultOK) {\n        return \"error\";\n      } else if (!hasFTP || isActuallyEn && hasNoWeirdness) {\n        return \"en\";\n      } else {\n        return \"intl\";\n      }\n    };\n\n    _proto4.clone = function clone(alts) {\n      if (!alts || Object.getOwnPropertyNames(alts).length === 0) {\n        return this;\n      } else {\n        return Locale.create(alts.locale || this.specifiedLocale, alts.numberingSystem || this.numberingSystem, alts.outputCalendar || this.outputCalendar, alts.defaultToEN || false);\n      }\n    };\n\n    _proto4.redefaultToEN = function redefaultToEN(alts) {\n      if (alts === void 0) {\n        alts = {};\n      }\n\n      return this.clone(Object.assign({}, alts, {\n        defaultToEN: true\n      }));\n    };\n\n    _proto4.redefaultToSystem = function redefaultToSystem(alts) {\n      if (alts === void 0) {\n        alts = {};\n      }\n\n      return this.clone(Object.assign({}, alts, {\n        defaultToEN: false\n      }));\n    };\n\n    _proto4.months = function months$1(length, format, defaultOK) {\n      var _this = this;\n\n      if (format === void 0) {\n        format = false;\n      }\n\n      if (defaultOK === void 0) {\n        defaultOK = true;\n      }\n\n      return listStuff(this, length, defaultOK, months, function () {\n        var intl = format ? {\n          month: length,\n          day: \"numeric\"\n        } : {\n          month: length\n        },\n            formatStr = format ? \"format\" : \"standalone\";\n\n        if (!_this.monthsCache[formatStr][length]) {\n          _this.monthsCache[formatStr][length] = mapMonths(function (dt) {\n            return _this.extract(dt, intl, \"month\");\n          });\n        }\n\n        return _this.monthsCache[formatStr][length];\n      });\n    };\n\n    _proto4.weekdays = function weekdays$1(length, format, defaultOK) {\n      var _this2 = this;\n\n      if (format === void 0) {\n        format = false;\n      }\n\n      if (defaultOK === void 0) {\n        defaultOK = true;\n      }\n\n      return listStuff(this, length, defaultOK, weekdays, function () {\n        var intl = format ? {\n          weekday: length,\n          year: \"numeric\",\n          month: \"long\",\n          day: \"numeric\"\n        } : {\n          weekday: length\n        },\n            formatStr = format ? \"format\" : \"standalone\";\n\n        if (!_this2.weekdaysCache[formatStr][length]) {\n          _this2.weekdaysCache[formatStr][length] = mapWeekdays(function (dt) {\n            return _this2.extract(dt, intl, \"weekday\");\n          });\n        }\n\n        return _this2.weekdaysCache[formatStr][length];\n      });\n    };\n\n    _proto4.meridiems = function meridiems$1(defaultOK) {\n      var _this3 = this;\n\n      if (defaultOK === void 0) {\n        defaultOK = true;\n      }\n\n      return listStuff(this, undefined, defaultOK, function () {\n        return meridiems;\n      }, function () {\n        // In theory there could be aribitrary day periods. We're gonna assume there are exactly two\n        // for AM and PM. This is probably wrong, but it's makes parsing way easier.\n        if (!_this3.meridiemCache) {\n          var intl = {\n            hour: \"numeric\",\n            hour12: true\n          };\n          _this3.meridiemCache = [DateTime.utc(2016, 11, 13, 9), DateTime.utc(2016, 11, 13, 19)].map(function (dt) {\n            return _this3.extract(dt, intl, \"dayperiod\");\n          });\n        }\n\n        return _this3.meridiemCache;\n      });\n    };\n\n    _proto4.eras = function eras$1(length, defaultOK) {\n      var _this4 = this;\n\n      if (defaultOK === void 0) {\n        defaultOK = true;\n      }\n\n      return listStuff(this, length, defaultOK, eras, function () {\n        var intl = {\n          era: length\n        }; // This is utter bullshit. Different calendars are going to define eras totally differently. What I need is the minimum set of dates\n        // to definitely enumerate them.\n\n        if (!_this4.eraCache[length]) {\n          _this4.eraCache[length] = [DateTime.utc(-40, 1, 1), DateTime.utc(2017, 1, 1)].map(function (dt) {\n            return _this4.extract(dt, intl, \"era\");\n          });\n        }\n\n        return _this4.eraCache[length];\n      });\n    };\n\n    _proto4.extract = function extract(dt, intlOpts, field) {\n      var df = this.dtFormatter(dt, intlOpts),\n          results = df.formatToParts(),\n          matching = results.find(function (m) {\n        return m.type.toLowerCase() === field;\n      });\n      return matching ? matching.value : null;\n    };\n\n    _proto4.numberFormatter = function numberFormatter(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      // this forcesimple option is never used (the only caller short-circuits on it, but it seems safer to leave)\n      // (in contrast, the rest of the condition is used heavily)\n      return new PolyNumberFormatter(this.intl, opts.forceSimple || this.fastNumbers, opts);\n    };\n\n    _proto4.dtFormatter = function dtFormatter(dt, intlOpts) {\n      if (intlOpts === void 0) {\n        intlOpts = {};\n      }\n\n      return new PolyDateFormatter(dt, this.intl, intlOpts);\n    };\n\n    _proto4.relFormatter = function relFormatter(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      return new PolyRelFormatter(this.intl, this.isEnglish(), opts);\n    };\n\n    _proto4.isEnglish = function isEnglish() {\n      return this.locale === \"en\" || this.locale.toLowerCase() === \"en-us\" || hasIntl() && new Intl.DateTimeFormat(this.intl).resolvedOptions().locale.startsWith(\"en-us\");\n    };\n\n    _proto4.equals = function equals(other) {\n      return this.locale === other.locale && this.numberingSystem === other.numberingSystem && this.outputCalendar === other.outputCalendar;\n    };\n\n    _createClass(Locale, [{\n      key: \"fastNumbers\",\n      get: function get() {\n        if (this.fastNumbersCached == null) {\n          this.fastNumbersCached = supportsFastNumbers(this);\n        }\n\n        return this.fastNumbersCached;\n      }\n    }]);\n\n    return Locale;\n  }();\n\n  /*\n   * This file handles parsing for well-specified formats. Here's how it works:\n   * Two things go into parsing: a regex to match with and an extractor to take apart the groups in the match.\n   * An extractor is just a function that takes a regex match array and returns a { year: ..., month: ... } object\n   * parse() does the work of executing the regex and applying the extractor. It takes multiple regex/extractor pairs to try in sequence.\n   * Extractors can take a \"cursor\" representing the offset in the match to look at. This makes it easy to combine extractors.\n   * combineExtractors() does the work of combining them, keeping track of the cursor through multiple extractions.\n   * Some extractions are super dumb and simpleParse and fromStrings help DRY them.\n   */\n\n  function combineRegexes() {\n    for (var _len = arguments.length, regexes = new Array(_len), _key = 0; _key < _len; _key++) {\n      regexes[_key] = arguments[_key];\n    }\n\n    var full = regexes.reduce(function (f, r) {\n      return f + r.source;\n    }, \"\");\n    return RegExp(\"^\" + full + \"$\");\n  }\n\n  function combineExtractors() {\n    for (var _len2 = arguments.length, extractors = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {\n      extractors[_key2] = arguments[_key2];\n    }\n\n    return function (m) {\n      return extractors.reduce(function (_ref, ex) {\n        var mergedVals = _ref[0],\n            mergedZone = _ref[1],\n            cursor = _ref[2];\n\n        var _ex = ex(m, cursor),\n            val = _ex[0],\n            zone = _ex[1],\n            next = _ex[2];\n\n        return [Object.assign(mergedVals, val), mergedZone || zone, next];\n      }, [{}, null, 1]).slice(0, 2);\n    };\n  }\n\n  function parse(s) {\n    if (s == null) {\n      return [null, null];\n    }\n\n    for (var _len3 = arguments.length, patterns = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {\n      patterns[_key3 - 1] = arguments[_key3];\n    }\n\n    for (var _i = 0, _patterns = patterns; _i < _patterns.length; _i++) {\n      var _patterns$_i = _patterns[_i],\n          regex = _patterns$_i[0],\n          extractor = _patterns$_i[1];\n      var m = regex.exec(s);\n\n      if (m) {\n        return extractor(m);\n      }\n    }\n\n    return [null, null];\n  }\n\n  function simpleParse() {\n    for (var _len4 = arguments.length, keys = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {\n      keys[_key4] = arguments[_key4];\n    }\n\n    return function (match, cursor) {\n      var ret = {};\n      var i;\n\n      for (i = 0; i < keys.length; i++) {\n        ret[keys[i]] = parseInteger(match[cursor + i]);\n      }\n\n      return [ret, null, cursor + i];\n    };\n  } // ISO and SQL parsing\n\n\n  var offsetRegex = /(?:(Z)|([+-]\\d\\d)(?::?(\\d\\d))?)/,\n      isoTimeBaseRegex = /(\\d\\d)(?::?(\\d\\d)(?::?(\\d\\d)(?:[.,](\\d{1,30}))?)?)?/,\n      isoTimeRegex = RegExp(\"\" + isoTimeBaseRegex.source + offsetRegex.source + \"?\"),\n      isoTimeExtensionRegex = RegExp(\"(?:T\" + isoTimeRegex.source + \")?\"),\n      isoYmdRegex = /([+-]\\d{6}|\\d{4})(?:-?(\\d\\d)(?:-?(\\d\\d))?)?/,\n      isoWeekRegex = /(\\d{4})-?W(\\d\\d)(?:-?(\\d))?/,\n      isoOrdinalRegex = /(\\d{4})-?(\\d{3})/,\n      extractISOWeekData = simpleParse(\"weekYear\", \"weekNumber\", \"weekDay\"),\n      extractISOOrdinalData = simpleParse(\"year\", \"ordinal\"),\n      sqlYmdRegex = /(\\d{4})-(\\d\\d)-(\\d\\d)/,\n      // dumbed-down version of the ISO one\n  sqlTimeRegex = RegExp(isoTimeBaseRegex.source + \" ?(?:\" + offsetRegex.source + \"|(\" + ianaRegex.source + \"))?\"),\n      sqlTimeExtensionRegex = RegExp(\"(?: \" + sqlTimeRegex.source + \")?\");\n\n  function int(match, pos, fallback) {\n    var m = match[pos];\n    return isUndefined(m) ? fallback : parseInteger(m);\n  }\n\n  function extractISOYmd(match, cursor) {\n    var item = {\n      year: int(match, cursor),\n      month: int(match, cursor + 1, 1),\n      day: int(match, cursor + 2, 1)\n    };\n    return [item, null, cursor + 3];\n  }\n\n  function extractISOTime(match, cursor) {\n    var item = {\n      hour: int(match, cursor, 0),\n      minute: int(match, cursor + 1, 0),\n      second: int(match, cursor + 2, 0),\n      millisecond: parseMillis(match[cursor + 3])\n    };\n    return [item, null, cursor + 4];\n  }\n\n  function extractISOOffset(match, cursor) {\n    var local = !match[cursor] && !match[cursor + 1],\n        fullOffset = signedOffset(match[cursor + 1], match[cursor + 2]),\n        zone = local ? null : FixedOffsetZone.instance(fullOffset);\n    return [{}, zone, cursor + 3];\n  }\n\n  function extractIANAZone(match, cursor) {\n    var zone = match[cursor] ? IANAZone.create(match[cursor]) : null;\n    return [{}, zone, cursor + 1];\n  } // ISO duration parsing\n\n\n  var isoDuration = /^-?P(?:(?:(-?\\d{1,9})Y)?(?:(-?\\d{1,9})M)?(?:(-?\\d{1,9})W)?(?:(-?\\d{1,9})D)?(?:T(?:(-?\\d{1,9})H)?(?:(-?\\d{1,9})M)?(?:(-?\\d{1,20})(?:[.,](-?\\d{1,9}))?S)?)?)$/;\n\n  function extractISODuration(match) {\n    var s = match[0],\n        yearStr = match[1],\n        monthStr = match[2],\n        weekStr = match[3],\n        dayStr = match[4],\n        hourStr = match[5],\n        minuteStr = match[6],\n        secondStr = match[7],\n        millisecondsStr = match[8];\n    var hasNegativePrefix = s[0] === \"-\";\n\n    var maybeNegate = function maybeNegate(num) {\n      return num && hasNegativePrefix ? -num : num;\n    };\n\n    return [{\n      years: maybeNegate(parseInteger(yearStr)),\n      months: maybeNegate(parseInteger(monthStr)),\n      weeks: maybeNegate(parseInteger(weekStr)),\n      days: maybeNegate(parseInteger(dayStr)),\n      hours: maybeNegate(parseInteger(hourStr)),\n      minutes: maybeNegate(parseInteger(minuteStr)),\n      seconds: maybeNegate(parseInteger(secondStr)),\n      milliseconds: maybeNegate(parseMillis(millisecondsStr))\n    }];\n  } // These are a little braindead. EDT *should* tell us that we're in, say, America/New_York\n  // and not just that we're in -240 *right now*. But since I don't think these are used that often\n  // I'm just going to ignore that\n\n\n  var obsOffsets = {\n    GMT: 0,\n    EDT: -4 * 60,\n    EST: -5 * 60,\n    CDT: -5 * 60,\n    CST: -6 * 60,\n    MDT: -6 * 60,\n    MST: -7 * 60,\n    PDT: -7 * 60,\n    PST: -8 * 60\n  };\n\n  function fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) {\n    var result = {\n      year: yearStr.length === 2 ? untruncateYear(parseInteger(yearStr)) : parseInteger(yearStr),\n      month: monthsShort.indexOf(monthStr) + 1,\n      day: parseInteger(dayStr),\n      hour: parseInteger(hourStr),\n      minute: parseInteger(minuteStr)\n    };\n    if (secondStr) result.second = parseInteger(secondStr);\n\n    if (weekdayStr) {\n      result.weekday = weekdayStr.length > 3 ? weekdaysLong.indexOf(weekdayStr) + 1 : weekdaysShort.indexOf(weekdayStr) + 1;\n    }\n\n    return result;\n  } // RFC 2822/5322\n\n\n  var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\\s)?(\\d{1,2})\\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s(\\d{2,4})\\s(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|(?:([+-]\\d\\d)(\\d\\d)))$/;\n\n  function extractRFC2822(match) {\n    var weekdayStr = match[1],\n        dayStr = match[2],\n        monthStr = match[3],\n        yearStr = match[4],\n        hourStr = match[5],\n        minuteStr = match[6],\n        secondStr = match[7],\n        obsOffset = match[8],\n        milOffset = match[9],\n        offHourStr = match[10],\n        offMinuteStr = match[11],\n        result = fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr);\n    var offset;\n\n    if (obsOffset) {\n      offset = obsOffsets[obsOffset];\n    } else if (milOffset) {\n      offset = 0;\n    } else {\n      offset = signedOffset(offHourStr, offMinuteStr);\n    }\n\n    return [result, new FixedOffsetZone(offset)];\n  }\n\n  function preprocessRFC2822(s) {\n    // Remove comments and folding whitespace and replace multiple-spaces with a single space\n    return s.replace(/\\([^)]*\\)|[\\n\\t]/g, \" \").replace(/(\\s\\s+)/g, \" \").trim();\n  } // http date\n\n\n  var rfc1123 = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\\d\\d) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\\d{4}) (\\d\\d):(\\d\\d):(\\d\\d) GMT$/,\n      rfc850 = /^(Monday|Tuesday|Wedsday|Thursday|Friday|Saturday|Sunday), (\\d\\d)-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\\d\\d) (\\d\\d):(\\d\\d):(\\d\\d) GMT$/,\n      ascii = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ( \\d|\\d\\d) (\\d\\d):(\\d\\d):(\\d\\d) (\\d{4})$/;\n\n  function extractRFC1123Or850(match) {\n    var weekdayStr = match[1],\n        dayStr = match[2],\n        monthStr = match[3],\n        yearStr = match[4],\n        hourStr = match[5],\n        minuteStr = match[6],\n        secondStr = match[7],\n        result = fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr);\n    return [result, FixedOffsetZone.utcInstance];\n  }\n\n  function extractASCII(match) {\n    var weekdayStr = match[1],\n        monthStr = match[2],\n        dayStr = match[3],\n        hourStr = match[4],\n        minuteStr = match[5],\n        secondStr = match[6],\n        yearStr = match[7],\n        result = fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr);\n    return [result, FixedOffsetZone.utcInstance];\n  }\n\n  var isoYmdWithTimeExtensionRegex = combineRegexes(isoYmdRegex, isoTimeExtensionRegex);\n  var isoWeekWithTimeExtensionRegex = combineRegexes(isoWeekRegex, isoTimeExtensionRegex);\n  var isoOrdinalWithTimeExtensionRegex = combineRegexes(isoOrdinalRegex, isoTimeExtensionRegex);\n  var isoTimeCombinedRegex = combineRegexes(isoTimeRegex);\n  var extractISOYmdTimeAndOffset = combineExtractors(extractISOYmd, extractISOTime, extractISOOffset);\n  var extractISOWeekTimeAndOffset = combineExtractors(extractISOWeekData, extractISOTime, extractISOOffset);\n  var extractISOOrdinalDataAndTime = combineExtractors(extractISOOrdinalData, extractISOTime);\n  var extractISOTimeAndOffset = combineExtractors(extractISOTime, extractISOOffset);\n  /**\n   * @private\n   */\n\n  function parseISODate(s) {\n    return parse(s, [isoYmdWithTimeExtensionRegex, extractISOYmdTimeAndOffset], [isoWeekWithTimeExtensionRegex, extractISOWeekTimeAndOffset], [isoOrdinalWithTimeExtensionRegex, extractISOOrdinalDataAndTime], [isoTimeCombinedRegex, extractISOTimeAndOffset]);\n  }\n  function parseRFC2822Date(s) {\n    return parse(preprocessRFC2822(s), [rfc2822, extractRFC2822]);\n  }\n  function parseHTTPDate(s) {\n    return parse(s, [rfc1123, extractRFC1123Or850], [rfc850, extractRFC1123Or850], [ascii, extractASCII]);\n  }\n  function parseISODuration(s) {\n    return parse(s, [isoDuration, extractISODuration]);\n  }\n  var sqlYmdWithTimeExtensionRegex = combineRegexes(sqlYmdRegex, sqlTimeExtensionRegex);\n  var sqlTimeCombinedRegex = combineRegexes(sqlTimeRegex);\n  var extractISOYmdTimeOffsetAndIANAZone = combineExtractors(extractISOYmd, extractISOTime, extractISOOffset, extractIANAZone);\n  var extractISOTimeOffsetAndIANAZone = combineExtractors(extractISOTime, extractISOOffset, extractIANAZone);\n  function parseSQL(s) {\n    return parse(s, [sqlYmdWithTimeExtensionRegex, extractISOYmdTimeOffsetAndIANAZone], [sqlTimeCombinedRegex, extractISOTimeOffsetAndIANAZone]);\n  }\n\n  var INVALID = \"Invalid Duration\"; // unit conversion constants\n\n  var lowOrderMatrix = {\n    weeks: {\n      days: 7,\n      hours: 7 * 24,\n      minutes: 7 * 24 * 60,\n      seconds: 7 * 24 * 60 * 60,\n      milliseconds: 7 * 24 * 60 * 60 * 1000\n    },\n    days: {\n      hours: 24,\n      minutes: 24 * 60,\n      seconds: 24 * 60 * 60,\n      milliseconds: 24 * 60 * 60 * 1000\n    },\n    hours: {\n      minutes: 60,\n      seconds: 60 * 60,\n      milliseconds: 60 * 60 * 1000\n    },\n    minutes: {\n      seconds: 60,\n      milliseconds: 60 * 1000\n    },\n    seconds: {\n      milliseconds: 1000\n    }\n  },\n      casualMatrix = Object.assign({\n    years: {\n      quarters: 4,\n      months: 12,\n      weeks: 52,\n      days: 365,\n      hours: 365 * 24,\n      minutes: 365 * 24 * 60,\n      seconds: 365 * 24 * 60 * 60,\n      milliseconds: 365 * 24 * 60 * 60 * 1000\n    },\n    quarters: {\n      months: 3,\n      weeks: 13,\n      days: 91,\n      hours: 91 * 24,\n      minutes: 91 * 24 * 60,\n      seconds: 91 * 24 * 60 * 60,\n      milliseconds: 91 * 24 * 60 * 60 * 1000\n    },\n    months: {\n      weeks: 4,\n      days: 30,\n      hours: 30 * 24,\n      minutes: 30 * 24 * 60,\n      seconds: 30 * 24 * 60 * 60,\n      milliseconds: 30 * 24 * 60 * 60 * 1000\n    }\n  }, lowOrderMatrix),\n      daysInYearAccurate = 146097.0 / 400,\n      daysInMonthAccurate = 146097.0 / 4800,\n      accurateMatrix = Object.assign({\n    years: {\n      quarters: 4,\n      months: 12,\n      weeks: daysInYearAccurate / 7,\n      days: daysInYearAccurate,\n      hours: daysInYearAccurate * 24,\n      minutes: daysInYearAccurate * 24 * 60,\n      seconds: daysInYearAccurate * 24 * 60 * 60,\n      milliseconds: daysInYearAccurate * 24 * 60 * 60 * 1000\n    },\n    quarters: {\n      months: 3,\n      weeks: daysInYearAccurate / 28,\n      days: daysInYearAccurate / 4,\n      hours: daysInYearAccurate * 24 / 4,\n      minutes: daysInYearAccurate * 24 * 60 / 4,\n      seconds: daysInYearAccurate * 24 * 60 * 60 / 4,\n      milliseconds: daysInYearAccurate * 24 * 60 * 60 * 1000 / 4\n    },\n    months: {\n      weeks: daysInMonthAccurate / 7,\n      days: daysInMonthAccurate,\n      hours: daysInMonthAccurate * 24,\n      minutes: daysInMonthAccurate * 24 * 60,\n      seconds: daysInMonthAccurate * 24 * 60 * 60,\n      milliseconds: daysInMonthAccurate * 24 * 60 * 60 * 1000\n    }\n  }, lowOrderMatrix); // units ordered by size\n\n  var orderedUnits = [\"years\", \"quarters\", \"months\", \"weeks\", \"days\", \"hours\", \"minutes\", \"seconds\", \"milliseconds\"];\n  var reverseUnits = orderedUnits.slice(0).reverse(); // clone really means \"create another instance just like this one, but with these changes\"\n\n  function clone(dur, alts, clear) {\n    if (clear === void 0) {\n      clear = false;\n    }\n\n    // deep merge for vals\n    var conf = {\n      values: clear ? alts.values : Object.assign({}, dur.values, alts.values || {}),\n      loc: dur.loc.clone(alts.loc),\n      conversionAccuracy: alts.conversionAccuracy || dur.conversionAccuracy\n    };\n    return new Duration(conf);\n  }\n\n  function antiTrunc(n) {\n    return n < 0 ? Math.floor(n) : Math.ceil(n);\n  } // NB: mutates parameters\n\n\n  function convert(matrix, fromMap, fromUnit, toMap, toUnit) {\n    var conv = matrix[toUnit][fromUnit],\n        raw = fromMap[fromUnit] / conv,\n        sameSign = Math.sign(raw) === Math.sign(toMap[toUnit]),\n        // ok, so this is wild, but see the matrix in the tests\n    added = !sameSign && toMap[toUnit] !== 0 && Math.abs(raw) <= 1 ? antiTrunc(raw) : Math.trunc(raw);\n    toMap[toUnit] += added;\n    fromMap[fromUnit] -= added * conv;\n  } // NB: mutates parameters\n\n\n  function normalizeValues(matrix, vals) {\n    reverseUnits.reduce(function (previous, current) {\n      if (!isUndefined(vals[current])) {\n        if (previous) {\n          convert(matrix, vals, previous, vals, current);\n        }\n\n        return current;\n      } else {\n        return previous;\n      }\n    }, null);\n  }\n  /**\n   * A Duration object represents a period of time, like \"2 months\" or \"1 day, 1 hour\". Conceptually, it's just a map of units to their quantities, accompanied by some additional configuration and methods for creating, parsing, interrogating, transforming, and formatting them. They can be used on their own or in conjunction with other Luxon types; for example, you can use {@link DateTime.plus} to add a Duration object to a DateTime, producing another DateTime.\n   *\n   * Here is a brief overview of commonly used methods and getters in Duration:\n   *\n   * * **Creation** To create a Duration, use {@link Duration.fromMillis}, {@link Duration.fromObject}, or {@link Duration.fromISO}.\n   * * **Unit values** See the {@link Duration.years}, {@link Duration.months}, {@link Duration.weeks}, {@link Duration.days}, {@link Duration.hours}, {@link Duration.minutes}, {@link Duration.seconds}, {@link Duration.milliseconds} accessors.\n   * * **Configuration** See  {@link Duration.locale} and {@link Duration.numberingSystem} accessors.\n   * * **Transformation** To create new Durations out of old ones use {@link Duration.plus}, {@link Duration.minus}, {@link Duration.normalize}, {@link Duration.set}, {@link Duration.reconfigure}, {@link Duration.shiftTo}, and {@link Duration.negate}.\n   * * **Output** To convert the Duration into other representations, see {@link Duration.as}, {@link Duration.toISO}, {@link Duration.toFormat}, and {@link Duration.toJSON}\n   *\n   * There's are more methods documented below. In addition, for more information on subtler topics like internationalization and validity, see the external documentation.\n   */\n\n\n  var Duration = /*#__PURE__*/function () {\n    /**\n     * @private\n     */\n    function Duration(config) {\n      var accurate = config.conversionAccuracy === \"longterm\" || false;\n      /**\n       * @access private\n       */\n\n      this.values = config.values;\n      /**\n       * @access private\n       */\n\n      this.loc = config.loc || Locale.create();\n      /**\n       * @access private\n       */\n\n      this.conversionAccuracy = accurate ? \"longterm\" : \"casual\";\n      /**\n       * @access private\n       */\n\n      this.invalid = config.invalid || null;\n      /**\n       * @access private\n       */\n\n      this.matrix = accurate ? accurateMatrix : casualMatrix;\n      /**\n       * @access private\n       */\n\n      this.isLuxonDuration = true;\n    }\n    /**\n     * Create Duration from a number of milliseconds.\n     * @param {number} count of milliseconds\n     * @param {Object} opts - options for parsing\n     * @param {string} [opts.locale='en-US'] - the locale to use\n     * @param {string} opts.numberingSystem - the numbering system to use\n     * @param {string} [opts.conversionAccuracy='casual'] - the conversion system to use\n     * @return {Duration}\n     */\n\n\n    Duration.fromMillis = function fromMillis(count, opts) {\n      return Duration.fromObject(Object.assign({\n        milliseconds: count\n      }, opts));\n    }\n    /**\n     * Create a Duration from a Javascript object with keys like 'years' and 'hours.\n     * If this object is empty then a zero milliseconds duration is returned.\n     * @param {Object} obj - the object to create the DateTime from\n     * @param {number} obj.years\n     * @param {number} obj.quarters\n     * @param {number} obj.months\n     * @param {number} obj.weeks\n     * @param {number} obj.days\n     * @param {number} obj.hours\n     * @param {number} obj.minutes\n     * @param {number} obj.seconds\n     * @param {number} obj.milliseconds\n     * @param {string} [obj.locale='en-US'] - the locale to use\n     * @param {string} obj.numberingSystem - the numbering system to use\n     * @param {string} [obj.conversionAccuracy='casual'] - the conversion system to use\n     * @return {Duration}\n     */\n    ;\n\n    Duration.fromObject = function fromObject(obj) {\n      if (obj == null || typeof obj !== \"object\") {\n        throw new InvalidArgumentError(\"Duration.fromObject: argument expected to be an object, got \" + (obj === null ? \"null\" : typeof obj));\n      }\n\n      return new Duration({\n        values: normalizeObject(obj, Duration.normalizeUnit, [\"locale\", \"numberingSystem\", \"conversionAccuracy\", \"zone\" // a bit of debt; it's super inconvenient internally not to be able to blindly pass this\n        ]),\n        loc: Locale.fromObject(obj),\n        conversionAccuracy: obj.conversionAccuracy\n      });\n    }\n    /**\n     * Create a Duration from an ISO 8601 duration string.\n     * @param {string} text - text to parse\n     * @param {Object} opts - options for parsing\n     * @param {string} [opts.locale='en-US'] - the locale to use\n     * @param {string} opts.numberingSystem - the numbering system to use\n     * @param {string} [opts.conversionAccuracy='casual'] - the conversion system to use\n     * @see https://en.wikipedia.org/wiki/ISO_8601#Durations\n     * @example Duration.fromISO('P3Y6M1W4DT12H30M5S').toObject() //=> { years: 3, months: 6, weeks: 1, days: 4, hours: 12, minutes: 30, seconds: 5 }\n     * @example Duration.fromISO('PT23H').toObject() //=> { hours: 23 }\n     * @example Duration.fromISO('P5Y3M').toObject() //=> { years: 5, months: 3 }\n     * @return {Duration}\n     */\n    ;\n\n    Duration.fromISO = function fromISO(text, opts) {\n      var _parseISODuration = parseISODuration(text),\n          parsed = _parseISODuration[0];\n\n      if (parsed) {\n        var obj = Object.assign(parsed, opts);\n        return Duration.fromObject(obj);\n      } else {\n        return Duration.invalid(\"unparsable\", \"the input \\\"\" + text + \"\\\" can't be parsed as ISO 8601\");\n      }\n    }\n    /**\n     * Create an invalid Duration.\n     * @param {string} reason - simple string of why this datetime is invalid. Should not contain parameters or anything else data-dependent\n     * @param {string} [explanation=null] - longer explanation, may include parameters and other useful debugging information\n     * @return {Duration}\n     */\n    ;\n\n    Duration.invalid = function invalid(reason, explanation) {\n      if (explanation === void 0) {\n        explanation = null;\n      }\n\n      if (!reason) {\n        throw new InvalidArgumentError(\"need to specify a reason the Duration is invalid\");\n      }\n\n      var invalid = reason instanceof Invalid ? reason : new Invalid(reason, explanation);\n\n      if (Settings.throwOnInvalid) {\n        throw new InvalidDurationError(invalid);\n      } else {\n        return new Duration({\n          invalid: invalid\n        });\n      }\n    }\n    /**\n     * @private\n     */\n    ;\n\n    Duration.normalizeUnit = function normalizeUnit(unit) {\n      var normalized = {\n        year: \"years\",\n        years: \"years\",\n        quarter: \"quarters\",\n        quarters: \"quarters\",\n        month: \"months\",\n        months: \"months\",\n        week: \"weeks\",\n        weeks: \"weeks\",\n        day: \"days\",\n        days: \"days\",\n        hour: \"hours\",\n        hours: \"hours\",\n        minute: \"minutes\",\n        minutes: \"minutes\",\n        second: \"seconds\",\n        seconds: \"seconds\",\n        millisecond: \"milliseconds\",\n        milliseconds: \"milliseconds\"\n      }[unit ? unit.toLowerCase() : unit];\n      if (!normalized) throw new InvalidUnitError(unit);\n      return normalized;\n    }\n    /**\n     * Check if an object is a Duration. Works across context boundaries\n     * @param {object} o\n     * @return {boolean}\n     */\n    ;\n\n    Duration.isDuration = function isDuration(o) {\n      return o && o.isLuxonDuration || false;\n    }\n    /**\n     * Get  the locale of a Duration, such 'en-GB'\n     * @type {string}\n     */\n    ;\n\n    var _proto = Duration.prototype;\n\n    /**\n     * Returns a string representation of this Duration formatted according to the specified format string. You may use these tokens:\n     * * `S` for milliseconds\n     * * `s` for seconds\n     * * `m` for minutes\n     * * `h` for hours\n     * * `d` for days\n     * * `M` for months\n     * * `y` for years\n     * Notes:\n     * * Add padding by repeating the token, e.g. \"yy\" pads the years to two digits, \"hhhh\" pads the hours out to four digits\n     * * The duration will be converted to the set of units in the format string using {@link Duration.shiftTo} and the Durations's conversion accuracy setting.\n     * @param {string} fmt - the format string\n     * @param {Object} opts - options\n     * @param {boolean} [opts.floor=true] - floor numerical values\n     * @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toFormat(\"y d s\") //=> \"1 6 2\"\n     * @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toFormat(\"yy dd sss\") //=> \"01 06 002\"\n     * @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toFormat(\"M S\") //=> \"12 518402000\"\n     * @return {string}\n     */\n    _proto.toFormat = function toFormat(fmt, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      // reverse-compat since 1.2; we always round down now, never up, and we do it by default\n      var fmtOpts = Object.assign({}, opts, {\n        floor: opts.round !== false && opts.floor !== false\n      });\n      return this.isValid ? Formatter.create(this.loc, fmtOpts).formatDurationFromString(this, fmt) : INVALID;\n    }\n    /**\n     * Returns a Javascript object with this Duration's values.\n     * @param opts - options for generating the object\n     * @param {boolean} [opts.includeConfig=false] - include configuration attributes in the output\n     * @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toObject() //=> { years: 1, days: 6, seconds: 2 }\n     * @return {Object}\n     */\n    ;\n\n    _proto.toObject = function toObject(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      if (!this.isValid) return {};\n      var base = Object.assign({}, this.values);\n\n      if (opts.includeConfig) {\n        base.conversionAccuracy = this.conversionAccuracy;\n        base.numberingSystem = this.loc.numberingSystem;\n        base.locale = this.loc.locale;\n      }\n\n      return base;\n    }\n    /**\n     * Returns an ISO 8601-compliant string representation of this Duration.\n     * @see https://en.wikipedia.org/wiki/ISO_8601#Durations\n     * @example Duration.fromObject({ years: 3, seconds: 45 }).toISO() //=> 'P3YT45S'\n     * @example Duration.fromObject({ months: 4, seconds: 45 }).toISO() //=> 'P4MT45S'\n     * @example Duration.fromObject({ months: 5 }).toISO() //=> 'P5M'\n     * @example Duration.fromObject({ minutes: 5 }).toISO() //=> 'PT5M'\n     * @example Duration.fromObject({ milliseconds: 6 }).toISO() //=> 'PT0.006S'\n     * @return {string}\n     */\n    ;\n\n    _proto.toISO = function toISO() {\n      // we could use the formatter, but this is an easier way to get the minimum string\n      if (!this.isValid) return null;\n      var s = \"P\";\n      if (this.years !== 0) s += this.years + \"Y\";\n      if (this.months !== 0 || this.quarters !== 0) s += this.months + this.quarters * 3 + \"M\";\n      if (this.weeks !== 0) s += this.weeks + \"W\";\n      if (this.days !== 0) s += this.days + \"D\";\n      if (this.hours !== 0 || this.minutes !== 0 || this.seconds !== 0 || this.milliseconds !== 0) s += \"T\";\n      if (this.hours !== 0) s += this.hours + \"H\";\n      if (this.minutes !== 0) s += this.minutes + \"M\";\n      if (this.seconds !== 0 || this.milliseconds !== 0) // this will handle \"floating point madness\" by removing extra decimal places\n        // https://stackoverflow.com/questions/588004/is-floating-point-math-broken\n        s += roundTo(this.seconds + this.milliseconds / 1000, 3) + \"S\";\n      if (s === \"P\") s += \"T0S\";\n      return s;\n    }\n    /**\n     * Returns an ISO 8601 representation of this Duration appropriate for use in JSON.\n     * @return {string}\n     */\n    ;\n\n    _proto.toJSON = function toJSON() {\n      return this.toISO();\n    }\n    /**\n     * Returns an ISO 8601 representation of this Duration appropriate for use in debugging.\n     * @return {string}\n     */\n    ;\n\n    _proto.toString = function toString() {\n      return this.toISO();\n    }\n    /**\n     * Returns an milliseconds value of this Duration.\n     * @return {number}\n     */\n    ;\n\n    _proto.valueOf = function valueOf() {\n      return this.as(\"milliseconds\");\n    }\n    /**\n     * Make this Duration longer by the specified amount. Return a newly-constructed Duration.\n     * @param {Duration|Object|number} duration - The amount to add. Either a Luxon Duration, a number of milliseconds, the object argument to Duration.fromObject()\n     * @return {Duration}\n     */\n    ;\n\n    _proto.plus = function plus(duration) {\n      if (!this.isValid) return this;\n      var dur = friendlyDuration(duration),\n          result = {};\n\n      for (var _iterator = _createForOfIteratorHelperLoose(orderedUnits), _step; !(_step = _iterator()).done;) {\n        var k = _step.value;\n\n        if (hasOwnProperty(dur.values, k) || hasOwnProperty(this.values, k)) {\n          result[k] = dur.get(k) + this.get(k);\n        }\n      }\n\n      return clone(this, {\n        values: result\n      }, true);\n    }\n    /**\n     * Make this Duration shorter by the specified amount. Return a newly-constructed Duration.\n     * @param {Duration|Object|number} duration - The amount to subtract. Either a Luxon Duration, a number of milliseconds, the object argument to Duration.fromObject()\n     * @return {Duration}\n     */\n    ;\n\n    _proto.minus = function minus(duration) {\n      if (!this.isValid) return this;\n      var dur = friendlyDuration(duration);\n      return this.plus(dur.negate());\n    }\n    /**\n     * Scale this Duration by the specified amount. Return a newly-constructed Duration.\n     * @param {function} fn - The function to apply to each unit. Arity is 1 or 2: the value of the unit and, optionally, the unit name. Must return a number.\n     * @example Duration.fromObject({ hours: 1, minutes: 30 }).mapUnit(x => x * 2) //=> { hours: 2, minutes: 60 }\n     * @example Duration.fromObject({ hours: 1, minutes: 30 }).mapUnit((x, u) => u === \"hour\" ? x * 2 : x) //=> { hours: 2, minutes: 30 }\n     * @return {Duration}\n     */\n    ;\n\n    _proto.mapUnits = function mapUnits(fn) {\n      if (!this.isValid) return this;\n      var result = {};\n\n      for (var _i = 0, _Object$keys = Object.keys(this.values); _i < _Object$keys.length; _i++) {\n        var k = _Object$keys[_i];\n        result[k] = asNumber(fn(this.values[k], k));\n      }\n\n      return clone(this, {\n        values: result\n      }, true);\n    }\n    /**\n     * Get the value of unit.\n     * @param {string} unit - a unit such as 'minute' or 'day'\n     * @example Duration.fromObject({years: 2, days: 3}).years //=> 2\n     * @example Duration.fromObject({years: 2, days: 3}).months //=> 0\n     * @example Duration.fromObject({years: 2, days: 3}).days //=> 3\n     * @return {number}\n     */\n    ;\n\n    _proto.get = function get(unit) {\n      return this[Duration.normalizeUnit(unit)];\n    }\n    /**\n     * \"Set\" the values of specified units. Return a newly-constructed Duration.\n     * @param {Object} values - a mapping of units to numbers\n     * @example dur.set({ years: 2017 })\n     * @example dur.set({ hours: 8, minutes: 30 })\n     * @return {Duration}\n     */\n    ;\n\n    _proto.set = function set(values) {\n      if (!this.isValid) return this;\n      var mixed = Object.assign(this.values, normalizeObject(values, Duration.normalizeUnit, []));\n      return clone(this, {\n        values: mixed\n      });\n    }\n    /**\n     * \"Set\" the locale and/or numberingSystem.  Returns a newly-constructed Duration.\n     * @example dur.reconfigure({ locale: 'en-GB' })\n     * @return {Duration}\n     */\n    ;\n\n    _proto.reconfigure = function reconfigure(_temp) {\n      var _ref = _temp === void 0 ? {} : _temp,\n          locale = _ref.locale,\n          numberingSystem = _ref.numberingSystem,\n          conversionAccuracy = _ref.conversionAccuracy;\n\n      var loc = this.loc.clone({\n        locale: locale,\n        numberingSystem: numberingSystem\n      }),\n          opts = {\n        loc: loc\n      };\n\n      if (conversionAccuracy) {\n        opts.conversionAccuracy = conversionAccuracy;\n      }\n\n      return clone(this, opts);\n    }\n    /**\n     * Return the length of the duration in the specified unit.\n     * @param {string} unit - a unit such as 'minutes' or 'days'\n     * @example Duration.fromObject({years: 1}).as('days') //=> 365\n     * @example Duration.fromObject({years: 1}).as('months') //=> 12\n     * @example Duration.fromObject({hours: 60}).as('days') //=> 2.5\n     * @return {number}\n     */\n    ;\n\n    _proto.as = function as(unit) {\n      return this.isValid ? this.shiftTo(unit).get(unit) : NaN;\n    }\n    /**\n     * Reduce this Duration to its canonical representation in its current units.\n     * @example Duration.fromObject({ years: 2, days: 5000 }).normalize().toObject() //=> { years: 15, days: 255 }\n     * @example Duration.fromObject({ hours: 12, minutes: -45 }).normalize().toObject() //=> { hours: 11, minutes: 15 }\n     * @return {Duration}\n     */\n    ;\n\n    _proto.normalize = function normalize() {\n      if (!this.isValid) return this;\n      var vals = this.toObject();\n      normalizeValues(this.matrix, vals);\n      return clone(this, {\n        values: vals\n      }, true);\n    }\n    /**\n     * Convert this Duration into its representation in a different set of units.\n     * @example Duration.fromObject({ hours: 1, seconds: 30 }).shiftTo('minutes', 'milliseconds').toObject() //=> { minutes: 60, milliseconds: 30000 }\n     * @return {Duration}\n     */\n    ;\n\n    _proto.shiftTo = function shiftTo() {\n      for (var _len = arguments.length, units = new Array(_len), _key = 0; _key < _len; _key++) {\n        units[_key] = arguments[_key];\n      }\n\n      if (!this.isValid) return this;\n\n      if (units.length === 0) {\n        return this;\n      }\n\n      units = units.map(function (u) {\n        return Duration.normalizeUnit(u);\n      });\n      var built = {},\n          accumulated = {},\n          vals = this.toObject();\n      var lastUnit;\n\n      for (var _iterator2 = _createForOfIteratorHelperLoose(orderedUnits), _step2; !(_step2 = _iterator2()).done;) {\n        var k = _step2.value;\n\n        if (units.indexOf(k) >= 0) {\n          lastUnit = k;\n          var own = 0; // anything we haven't boiled down yet should get boiled to this unit\n\n          for (var ak in accumulated) {\n            own += this.matrix[ak][k] * accumulated[ak];\n            accumulated[ak] = 0;\n          } // plus anything that's already in this unit\n\n\n          if (isNumber(vals[k])) {\n            own += vals[k];\n          }\n\n          var i = Math.trunc(own);\n          built[k] = i;\n          accumulated[k] = own - i; // we'd like to absorb these fractions in another unit\n          // plus anything further down the chain that should be rolled up in to this\n\n          for (var down in vals) {\n            if (orderedUnits.indexOf(down) > orderedUnits.indexOf(k)) {\n              convert(this.matrix, vals, down, built, k);\n            }\n          } // otherwise, keep it in the wings to boil it later\n\n        } else if (isNumber(vals[k])) {\n          accumulated[k] = vals[k];\n        }\n      } // anything leftover becomes the decimal for the last unit\n      // lastUnit must be defined since units is not empty\n\n\n      for (var key in accumulated) {\n        if (accumulated[key] !== 0) {\n          built[lastUnit] += key === lastUnit ? accumulated[key] : accumulated[key] / this.matrix[lastUnit][key];\n        }\n      }\n\n      return clone(this, {\n        values: built\n      }, true).normalize();\n    }\n    /**\n     * Return the negative of this Duration.\n     * @example Duration.fromObject({ hours: 1, seconds: 30 }).negate().toObject() //=> { hours: -1, seconds: -30 }\n     * @return {Duration}\n     */\n    ;\n\n    _proto.negate = function negate() {\n      if (!this.isValid) return this;\n      var negated = {};\n\n      for (var _i2 = 0, _Object$keys2 = Object.keys(this.values); _i2 < _Object$keys2.length; _i2++) {\n        var k = _Object$keys2[_i2];\n        negated[k] = -this.values[k];\n      }\n\n      return clone(this, {\n        values: negated\n      }, true);\n    }\n    /**\n     * Get the years.\n     * @type {number}\n     */\n    ;\n\n    /**\n     * Equality check\n     * Two Durations are equal iff they have the same units and the same values for each unit.\n     * @param {Duration} other\n     * @return {boolean}\n     */\n    _proto.equals = function equals(other) {\n      if (!this.isValid || !other.isValid) {\n        return false;\n      }\n\n      if (!this.loc.equals(other.loc)) {\n        return false;\n      }\n\n      for (var _iterator3 = _createForOfIteratorHelperLoose(orderedUnits), _step3; !(_step3 = _iterator3()).done;) {\n        var u = _step3.value;\n\n        if (this.values[u] !== other.values[u]) {\n          return false;\n        }\n      }\n\n      return true;\n    };\n\n    _createClass(Duration, [{\n      key: \"locale\",\n      get: function get() {\n        return this.isValid ? this.loc.locale : null;\n      }\n      /**\n       * Get the numbering system of a Duration, such 'beng'. The numbering system is used when formatting the Duration\n       *\n       * @type {string}\n       */\n\n    }, {\n      key: \"numberingSystem\",\n      get: function get() {\n        return this.isValid ? this.loc.numberingSystem : null;\n      }\n    }, {\n      key: \"years\",\n      get: function get() {\n        return this.isValid ? this.values.years || 0 : NaN;\n      }\n      /**\n       * Get the quarters.\n       * @type {number}\n       */\n\n    }, {\n      key: \"quarters\",\n      get: function get() {\n        return this.isValid ? this.values.quarters || 0 : NaN;\n      }\n      /**\n       * Get the months.\n       * @type {number}\n       */\n\n    }, {\n      key: \"months\",\n      get: function get() {\n        return this.isValid ? this.values.months || 0 : NaN;\n      }\n      /**\n       * Get the weeks\n       * @type {number}\n       */\n\n    }, {\n      key: \"weeks\",\n      get: function get() {\n        return this.isValid ? this.values.weeks || 0 : NaN;\n      }\n      /**\n       * Get the days.\n       * @type {number}\n       */\n\n    }, {\n      key: \"days\",\n      get: function get() {\n        return this.isValid ? this.values.days || 0 : NaN;\n      }\n      /**\n       * Get the hours.\n       * @type {number}\n       */\n\n    }, {\n      key: \"hours\",\n      get: function get() {\n        return this.isValid ? this.values.hours || 0 : NaN;\n      }\n      /**\n       * Get the minutes.\n       * @type {number}\n       */\n\n    }, {\n      key: \"minutes\",\n      get: function get() {\n        return this.isValid ? this.values.minutes || 0 : NaN;\n      }\n      /**\n       * Get the seconds.\n       * @return {number}\n       */\n\n    }, {\n      key: \"seconds\",\n      get: function get() {\n        return this.isValid ? this.values.seconds || 0 : NaN;\n      }\n      /**\n       * Get the milliseconds.\n       * @return {number}\n       */\n\n    }, {\n      key: \"milliseconds\",\n      get: function get() {\n        return this.isValid ? this.values.milliseconds || 0 : NaN;\n      }\n      /**\n       * Returns whether the Duration is invalid. Invalid durations are returned by diff operations\n       * on invalid DateTimes or Intervals.\n       * @return {boolean}\n       */\n\n    }, {\n      key: \"isValid\",\n      get: function get() {\n        return this.invalid === null;\n      }\n      /**\n       * Returns an error code if this Duration became invalid, or null if the Duration is valid\n       * @return {string}\n       */\n\n    }, {\n      key: \"invalidReason\",\n      get: function get() {\n        return this.invalid ? this.invalid.reason : null;\n      }\n      /**\n       * Returns an explanation of why this Duration became invalid, or null if the Duration is valid\n       * @type {string}\n       */\n\n    }, {\n      key: \"invalidExplanation\",\n      get: function get() {\n        return this.invalid ? this.invalid.explanation : null;\n      }\n    }]);\n\n    return Duration;\n  }();\n  function friendlyDuration(durationish) {\n    if (isNumber(durationish)) {\n      return Duration.fromMillis(durationish);\n    } else if (Duration.isDuration(durationish)) {\n      return durationish;\n    } else if (typeof durationish === \"object\") {\n      return Duration.fromObject(durationish);\n    } else {\n      throw new InvalidArgumentError(\"Unknown duration argument \" + durationish + \" of type \" + typeof durationish);\n    }\n  }\n\n  var INVALID$1 = \"Invalid Interval\"; // checks if the start is equal to or before the end\n\n  function validateStartEnd(start, end) {\n    if (!start || !start.isValid) {\n      return Interval.invalid(\"missing or invalid start\");\n    } else if (!end || !end.isValid) {\n      return Interval.invalid(\"missing or invalid end\");\n    } else if (end < start) {\n      return Interval.invalid(\"end before start\", \"The end of an interval must be after its start, but you had start=\" + start.toISO() + \" and end=\" + end.toISO());\n    } else {\n      return null;\n    }\n  }\n  /**\n   * An Interval object represents a half-open interval of time, where each endpoint is a {@link DateTime}. Conceptually, it's a container for those two endpoints, accompanied by methods for creating, parsing, interrogating, comparing, transforming, and formatting them.\n   *\n   * Here is a brief overview of the most commonly used methods and getters in Interval:\n   *\n   * * **Creation** To create an Interval, use {@link fromDateTimes}, {@link after}, {@link before}, or {@link fromISO}.\n   * * **Accessors** Use {@link start} and {@link end} to get the start and end.\n   * * **Interrogation** To analyze the Interval, use {@link count}, {@link length}, {@link hasSame}, {@link contains}, {@link isAfter}, or {@link isBefore}.\n   * * **Transformation** To create other Intervals out of this one, use {@link set}, {@link splitAt}, {@link splitBy}, {@link divideEqually}, {@link merge}, {@link xor}, {@link union}, {@link intersection}, or {@link difference}.\n   * * **Comparison** To compare this Interval to another one, use {@link equals}, {@link overlaps}, {@link abutsStart}, {@link abutsEnd}, {@link engulfs}.\n   * * **Output** To convert the Interval into other representations, see {@link toString}, {@link toISO}, {@link toISODate}, {@link toISOTime}, {@link toFormat}, and {@link toDuration}.\n   */\n\n\n  var Interval = /*#__PURE__*/function () {\n    /**\n     * @private\n     */\n    function Interval(config) {\n      /**\n       * @access private\n       */\n      this.s = config.start;\n      /**\n       * @access private\n       */\n\n      this.e = config.end;\n      /**\n       * @access private\n       */\n\n      this.invalid = config.invalid || null;\n      /**\n       * @access private\n       */\n\n      this.isLuxonInterval = true;\n    }\n    /**\n     * Create an invalid Interval.\n     * @param {string} reason - simple string of why this Interval is invalid. Should not contain parameters or anything else data-dependent\n     * @param {string} [explanation=null] - longer explanation, may include parameters and other useful debugging information\n     * @return {Interval}\n     */\n\n\n    Interval.invalid = function invalid(reason, explanation) {\n      if (explanation === void 0) {\n        explanation = null;\n      }\n\n      if (!reason) {\n        throw new InvalidArgumentError(\"need to specify a reason the Interval is invalid\");\n      }\n\n      var invalid = reason instanceof Invalid ? reason : new Invalid(reason, explanation);\n\n      if (Settings.throwOnInvalid) {\n        throw new InvalidIntervalError(invalid);\n      } else {\n        return new Interval({\n          invalid: invalid\n        });\n      }\n    }\n    /**\n     * Create an Interval from a start DateTime and an end DateTime. Inclusive of the start but not the end.\n     * @param {DateTime|Date|Object} start\n     * @param {DateTime|Date|Object} end\n     * @return {Interval}\n     */\n    ;\n\n    Interval.fromDateTimes = function fromDateTimes(start, end) {\n      var builtStart = friendlyDateTime(start),\n          builtEnd = friendlyDateTime(end);\n      var validateError = validateStartEnd(builtStart, builtEnd);\n\n      if (validateError == null) {\n        return new Interval({\n          start: builtStart,\n          end: builtEnd\n        });\n      } else {\n        return validateError;\n      }\n    }\n    /**\n     * Create an Interval from a start DateTime and a Duration to extend to.\n     * @param {DateTime|Date|Object} start\n     * @param {Duration|Object|number} duration - the length of the Interval.\n     * @return {Interval}\n     */\n    ;\n\n    Interval.after = function after(start, duration) {\n      var dur = friendlyDuration(duration),\n          dt = friendlyDateTime(start);\n      return Interval.fromDateTimes(dt, dt.plus(dur));\n    }\n    /**\n     * Create an Interval from an end DateTime and a Duration to extend backwards to.\n     * @param {DateTime|Date|Object} end\n     * @param {Duration|Object|number} duration - the length of the Interval.\n     * @return {Interval}\n     */\n    ;\n\n    Interval.before = function before(end, duration) {\n      var dur = friendlyDuration(duration),\n          dt = friendlyDateTime(end);\n      return Interval.fromDateTimes(dt.minus(dur), dt);\n    }\n    /**\n     * Create an Interval from an ISO 8601 string.\n     * Accepts `<start>/<end>`, `<start>/<duration>`, and `<duration>/<end>` formats.\n     * @param {string} text - the ISO string to parse\n     * @param {Object} [opts] - options to pass {@link DateTime.fromISO} and optionally {@link Duration.fromISO}\n     * @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals\n     * @return {Interval}\n     */\n    ;\n\n    Interval.fromISO = function fromISO(text, opts) {\n      var _split = (text || \"\").split(\"/\", 2),\n          s = _split[0],\n          e = _split[1];\n\n      if (s && e) {\n        var start, startIsValid;\n\n        try {\n          start = DateTime.fromISO(s, opts);\n          startIsValid = start.isValid;\n        } catch (e) {\n          startIsValid = false;\n        }\n\n        var end, endIsValid;\n\n        try {\n          end = DateTime.fromISO(e, opts);\n          endIsValid = end.isValid;\n        } catch (e) {\n          endIsValid = false;\n        }\n\n        if (startIsValid && endIsValid) {\n          return Interval.fromDateTimes(start, end);\n        }\n\n        if (startIsValid) {\n          var dur = Duration.fromISO(e, opts);\n\n          if (dur.isValid) {\n            return Interval.after(start, dur);\n          }\n        } else if (endIsValid) {\n          var _dur = Duration.fromISO(s, opts);\n\n          if (_dur.isValid) {\n            return Interval.before(end, _dur);\n          }\n        }\n      }\n\n      return Interval.invalid(\"unparsable\", \"the input \\\"\" + text + \"\\\" can't be parsed as ISO 8601\");\n    }\n    /**\n     * Check if an object is an Interval. Works across context boundaries\n     * @param {object} o\n     * @return {boolean}\n     */\n    ;\n\n    Interval.isInterval = function isInterval(o) {\n      return o && o.isLuxonInterval || false;\n    }\n    /**\n     * Returns the start of the Interval\n     * @type {DateTime}\n     */\n    ;\n\n    var _proto = Interval.prototype;\n\n    /**\n     * Returns the length of the Interval in the specified unit.\n     * @param {string} unit - the unit (such as 'hours' or 'days') to return the length in.\n     * @return {number}\n     */\n    _proto.length = function length(unit) {\n      if (unit === void 0) {\n        unit = \"milliseconds\";\n      }\n\n      return this.isValid ? this.toDuration.apply(this, [unit]).get(unit) : NaN;\n    }\n    /**\n     * Returns the count of minutes, hours, days, months, or years included in the Interval, even in part.\n     * Unlike {@link length} this counts sections of the calendar, not periods of time, e.g. specifying 'day'\n     * asks 'what dates are included in this interval?', not 'how many days long is this interval?'\n     * @param {string} [unit='milliseconds'] - the unit of time to count.\n     * @return {number}\n     */\n    ;\n\n    _proto.count = function count(unit) {\n      if (unit === void 0) {\n        unit = \"milliseconds\";\n      }\n\n      if (!this.isValid) return NaN;\n      var start = this.start.startOf(unit),\n          end = this.end.startOf(unit);\n      return Math.floor(end.diff(start, unit).get(unit)) + 1;\n    }\n    /**\n     * Returns whether this Interval's start and end are both in the same unit of time\n     * @param {string} unit - the unit of time to check sameness on\n     * @return {boolean}\n     */\n    ;\n\n    _proto.hasSame = function hasSame(unit) {\n      return this.isValid ? this.isEmpty() || this.e.minus(1).hasSame(this.s, unit) : false;\n    }\n    /**\n     * Return whether this Interval has the same start and end DateTimes.\n     * @return {boolean}\n     */\n    ;\n\n    _proto.isEmpty = function isEmpty() {\n      return this.s.valueOf() === this.e.valueOf();\n    }\n    /**\n     * Return whether this Interval's start is after the specified DateTime.\n     * @param {DateTime} dateTime\n     * @return {boolean}\n     */\n    ;\n\n    _proto.isAfter = function isAfter(dateTime) {\n      if (!this.isValid) return false;\n      return this.s > dateTime;\n    }\n    /**\n     * Return whether this Interval's end is before the specified DateTime.\n     * @param {DateTime} dateTime\n     * @return {boolean}\n     */\n    ;\n\n    _proto.isBefore = function isBefore(dateTime) {\n      if (!this.isValid) return false;\n      return this.e <= dateTime;\n    }\n    /**\n     * Return whether this Interval contains the specified DateTime.\n     * @param {DateTime} dateTime\n     * @return {boolean}\n     */\n    ;\n\n    _proto.contains = function contains(dateTime) {\n      if (!this.isValid) return false;\n      return this.s <= dateTime && this.e > dateTime;\n    }\n    /**\n     * \"Sets\" the start and/or end dates. Returns a newly-constructed Interval.\n     * @param {Object} values - the values to set\n     * @param {DateTime} values.start - the starting DateTime\n     * @param {DateTime} values.end - the ending DateTime\n     * @return {Interval}\n     */\n    ;\n\n    _proto.set = function set(_temp) {\n      var _ref = _temp === void 0 ? {} : _temp,\n          start = _ref.start,\n          end = _ref.end;\n\n      if (!this.isValid) return this;\n      return Interval.fromDateTimes(start || this.s, end || this.e);\n    }\n    /**\n     * Split this Interval at each of the specified DateTimes\n     * @param {...[DateTime]} dateTimes - the unit of time to count.\n     * @return {[Interval]}\n     */\n    ;\n\n    _proto.splitAt = function splitAt() {\n      var _this = this;\n\n      if (!this.isValid) return [];\n\n      for (var _len = arguments.length, dateTimes = new Array(_len), _key = 0; _key < _len; _key++) {\n        dateTimes[_key] = arguments[_key];\n      }\n\n      var sorted = dateTimes.map(friendlyDateTime).filter(function (d) {\n        return _this.contains(d);\n      }).sort(),\n          results = [];\n      var s = this.s,\n          i = 0;\n\n      while (s < this.e) {\n        var added = sorted[i] || this.e,\n            next = +added > +this.e ? this.e : added;\n        results.push(Interval.fromDateTimes(s, next));\n        s = next;\n        i += 1;\n      }\n\n      return results;\n    }\n    /**\n     * Split this Interval into smaller Intervals, each of the specified length.\n     * Left over time is grouped into a smaller interval\n     * @param {Duration|Object|number} duration - The length of each resulting interval.\n     * @return {[Interval]}\n     */\n    ;\n\n    _proto.splitBy = function splitBy(duration) {\n      var dur = friendlyDuration(duration);\n\n      if (!this.isValid || !dur.isValid || dur.as(\"milliseconds\") === 0) {\n        return [];\n      }\n\n      var s = this.s,\n          added,\n          next;\n      var results = [];\n\n      while (s < this.e) {\n        added = s.plus(dur);\n        next = +added > +this.e ? this.e : added;\n        results.push(Interval.fromDateTimes(s, next));\n        s = next;\n      }\n\n      return results;\n    }\n    /**\n     * Split this Interval into the specified number of smaller intervals.\n     * @param {number} numberOfParts - The number of Intervals to divide the Interval into.\n     * @return {[Interval]}\n     */\n    ;\n\n    _proto.divideEqually = function divideEqually(numberOfParts) {\n      if (!this.isValid) return [];\n      return this.splitBy(this.length() / numberOfParts).slice(0, numberOfParts);\n    }\n    /**\n     * Return whether this Interval overlaps with the specified Interval\n     * @param {Interval} other\n     * @return {boolean}\n     */\n    ;\n\n    _proto.overlaps = function overlaps(other) {\n      return this.e > other.s && this.s < other.e;\n    }\n    /**\n     * Return whether this Interval's end is adjacent to the specified Interval's start.\n     * @param {Interval} other\n     * @return {boolean}\n     */\n    ;\n\n    _proto.abutsStart = function abutsStart(other) {\n      if (!this.isValid) return false;\n      return +this.e === +other.s;\n    }\n    /**\n     * Return whether this Interval's start is adjacent to the specified Interval's end.\n     * @param {Interval} other\n     * @return {boolean}\n     */\n    ;\n\n    _proto.abutsEnd = function abutsEnd(other) {\n      if (!this.isValid) return false;\n      return +other.e === +this.s;\n    }\n    /**\n     * Return whether this Interval engulfs the start and end of the specified Interval.\n     * @param {Interval} other\n     * @return {boolean}\n     */\n    ;\n\n    _proto.engulfs = function engulfs(other) {\n      if (!this.isValid) return false;\n      return this.s <= other.s && this.e >= other.e;\n    }\n    /**\n     * Return whether this Interval has the same start and end as the specified Interval.\n     * @param {Interval} other\n     * @return {boolean}\n     */\n    ;\n\n    _proto.equals = function equals(other) {\n      if (!this.isValid || !other.isValid) {\n        return false;\n      }\n\n      return this.s.equals(other.s) && this.e.equals(other.e);\n    }\n    /**\n     * Return an Interval representing the intersection of this Interval and the specified Interval.\n     * Specifically, the resulting Interval has the maximum start time and the minimum end time of the two Intervals.\n     * Returns null if the intersection is empty, meaning, the intervals don't intersect.\n     * @param {Interval} other\n     * @return {Interval}\n     */\n    ;\n\n    _proto.intersection = function intersection(other) {\n      if (!this.isValid) return this;\n      var s = this.s > other.s ? this.s : other.s,\n          e = this.e < other.e ? this.e : other.e;\n\n      if (s > e) {\n        return null;\n      } else {\n        return Interval.fromDateTimes(s, e);\n      }\n    }\n    /**\n     * Return an Interval representing the union of this Interval and the specified Interval.\n     * Specifically, the resulting Interval has the minimum start time and the maximum end time of the two Intervals.\n     * @param {Interval} other\n     * @return {Interval}\n     */\n    ;\n\n    _proto.union = function union(other) {\n      if (!this.isValid) return this;\n      var s = this.s < other.s ? this.s : other.s,\n          e = this.e > other.e ? this.e : other.e;\n      return Interval.fromDateTimes(s, e);\n    }\n    /**\n     * Merge an array of Intervals into a equivalent minimal set of Intervals.\n     * Combines overlapping and adjacent Intervals.\n     * @param {[Interval]} intervals\n     * @return {[Interval]}\n     */\n    ;\n\n    Interval.merge = function merge(intervals) {\n      var _intervals$sort$reduc = intervals.sort(function (a, b) {\n        return a.s - b.s;\n      }).reduce(function (_ref2, item) {\n        var sofar = _ref2[0],\n            current = _ref2[1];\n\n        if (!current) {\n          return [sofar, item];\n        } else if (current.overlaps(item) || current.abutsStart(item)) {\n          return [sofar, current.union(item)];\n        } else {\n          return [sofar.concat([current]), item];\n        }\n      }, [[], null]),\n          found = _intervals$sort$reduc[0],\n          final = _intervals$sort$reduc[1];\n\n      if (final) {\n        found.push(final);\n      }\n\n      return found;\n    }\n    /**\n     * Return an array of Intervals representing the spans of time that only appear in one of the specified Intervals.\n     * @param {[Interval]} intervals\n     * @return {[Interval]}\n     */\n    ;\n\n    Interval.xor = function xor(intervals) {\n      var _Array$prototype;\n\n      var start = null,\n          currentCount = 0;\n\n      var results = [],\n          ends = intervals.map(function (i) {\n        return [{\n          time: i.s,\n          type: \"s\"\n        }, {\n          time: i.e,\n          type: \"e\"\n        }];\n      }),\n          flattened = (_Array$prototype = Array.prototype).concat.apply(_Array$prototype, ends),\n          arr = flattened.sort(function (a, b) {\n        return a.time - b.time;\n      });\n\n      for (var _iterator = _createForOfIteratorHelperLoose(arr), _step; !(_step = _iterator()).done;) {\n        var i = _step.value;\n        currentCount += i.type === \"s\" ? 1 : -1;\n\n        if (currentCount === 1) {\n          start = i.time;\n        } else {\n          if (start && +start !== +i.time) {\n            results.push(Interval.fromDateTimes(start, i.time));\n          }\n\n          start = null;\n        }\n      }\n\n      return Interval.merge(results);\n    }\n    /**\n     * Return an Interval representing the span of time in this Interval that doesn't overlap with any of the specified Intervals.\n     * @param {...Interval} intervals\n     * @return {[Interval]}\n     */\n    ;\n\n    _proto.difference = function difference() {\n      var _this2 = this;\n\n      for (var _len2 = arguments.length, intervals = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {\n        intervals[_key2] = arguments[_key2];\n      }\n\n      return Interval.xor([this].concat(intervals)).map(function (i) {\n        return _this2.intersection(i);\n      }).filter(function (i) {\n        return i && !i.isEmpty();\n      });\n    }\n    /**\n     * Returns a string representation of this Interval appropriate for debugging.\n     * @return {string}\n     */\n    ;\n\n    _proto.toString = function toString() {\n      if (!this.isValid) return INVALID$1;\n      return \"[\" + this.s.toISO() + \" \\u2013 \" + this.e.toISO() + \")\";\n    }\n    /**\n     * Returns an ISO 8601-compliant string representation of this Interval.\n     * @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals\n     * @param {Object} opts - The same options as {@link DateTime.toISO}\n     * @return {string}\n     */\n    ;\n\n    _proto.toISO = function toISO(opts) {\n      if (!this.isValid) return INVALID$1;\n      return this.s.toISO(opts) + \"/\" + this.e.toISO(opts);\n    }\n    /**\n     * Returns an ISO 8601-compliant string representation of date of this Interval.\n     * The time components are ignored.\n     * @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals\n     * @return {string}\n     */\n    ;\n\n    _proto.toISODate = function toISODate() {\n      if (!this.isValid) return INVALID$1;\n      return this.s.toISODate() + \"/\" + this.e.toISODate();\n    }\n    /**\n     * Returns an ISO 8601-compliant string representation of time of this Interval.\n     * The date components are ignored.\n     * @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals\n     * @param {Object} opts - The same options as {@link DateTime.toISO}\n     * @return {string}\n     */\n    ;\n\n    _proto.toISOTime = function toISOTime(opts) {\n      if (!this.isValid) return INVALID$1;\n      return this.s.toISOTime(opts) + \"/\" + this.e.toISOTime(opts);\n    }\n    /**\n     * Returns a string representation of this Interval formatted according to the specified format string.\n     * @param {string} dateFormat - the format string. This string formats the start and end time. See {@link DateTime.toFormat} for details.\n     * @param {Object} opts - options\n     * @param {string} [opts.separator =  ' \u2013 '] - a separator to place between the start and end representations\n     * @return {string}\n     */\n    ;\n\n    _proto.toFormat = function toFormat(dateFormat, _temp2) {\n      var _ref3 = _temp2 === void 0 ? {} : _temp2,\n          _ref3$separator = _ref3.separator,\n          separator = _ref3$separator === void 0 ? \" \u2013 \" : _ref3$separator;\n\n      if (!this.isValid) return INVALID$1;\n      return \"\" + this.s.toFormat(dateFormat) + separator + this.e.toFormat(dateFormat);\n    }\n    /**\n     * Return a Duration representing the time spanned by this interval.\n     * @param {string|string[]} [unit=['milliseconds']] - the unit or units (such as 'hours' or 'days') to include in the duration.\n     * @param {Object} opts - options that affect the creation of the Duration\n     * @param {string} [opts.conversionAccuracy='casual'] - the conversion system to use\n     * @example Interval.fromDateTimes(dt1, dt2).toDuration().toObject() //=> { milliseconds: 88489257 }\n     * @example Interval.fromDateTimes(dt1, dt2).toDuration('days').toObject() //=> { days: 1.0241812152777778 }\n     * @example Interval.fromDateTimes(dt1, dt2).toDuration(['hours', 'minutes']).toObject() //=> { hours: 24, minutes: 34.82095 }\n     * @example Interval.fromDateTimes(dt1, dt2).toDuration(['hours', 'minutes', 'seconds']).toObject() //=> { hours: 24, minutes: 34, seconds: 49.257 }\n     * @example Interval.fromDateTimes(dt1, dt2).toDuration('seconds').toObject() //=> { seconds: 88489.257 }\n     * @return {Duration}\n     */\n    ;\n\n    _proto.toDuration = function toDuration(unit, opts) {\n      if (!this.isValid) {\n        return Duration.invalid(this.invalidReason);\n      }\n\n      return this.e.diff(this.s, unit, opts);\n    }\n    /**\n     * Run mapFn on the interval start and end, returning a new Interval from the resulting DateTimes\n     * @param {function} mapFn\n     * @return {Interval}\n     * @example Interval.fromDateTimes(dt1, dt2).mapEndpoints(endpoint => endpoint.toUTC())\n     * @example Interval.fromDateTimes(dt1, dt2).mapEndpoints(endpoint => endpoint.plus({ hours: 2 }))\n     */\n    ;\n\n    _proto.mapEndpoints = function mapEndpoints(mapFn) {\n      return Interval.fromDateTimes(mapFn(this.s), mapFn(this.e));\n    };\n\n    _createClass(Interval, [{\n      key: \"start\",\n      get: function get() {\n        return this.isValid ? this.s : null;\n      }\n      /**\n       * Returns the end of the Interval\n       * @type {DateTime}\n       */\n\n    }, {\n      key: \"end\",\n      get: function get() {\n        return this.isValid ? this.e : null;\n      }\n      /**\n       * Returns whether this Interval's end is at least its start, meaning that the Interval isn't 'backwards'.\n       * @type {boolean}\n       */\n\n    }, {\n      key: \"isValid\",\n      get: function get() {\n        return this.invalidReason === null;\n      }\n      /**\n       * Returns an error code if this Interval is invalid, or null if the Interval is valid\n       * @type {string}\n       */\n\n    }, {\n      key: \"invalidReason\",\n      get: function get() {\n        return this.invalid ? this.invalid.reason : null;\n      }\n      /**\n       * Returns an explanation of why this Interval became invalid, or null if the Interval is valid\n       * @type {string}\n       */\n\n    }, {\n      key: \"invalidExplanation\",\n      get: function get() {\n        return this.invalid ? this.invalid.explanation : null;\n      }\n    }]);\n\n    return Interval;\n  }();\n\n  /**\n   * The Info class contains static methods for retrieving general time and date related data. For example, it has methods for finding out if a time zone has a DST, for listing the months in any supported locale, and for discovering which of Luxon features are available in the current environment.\n   */\n\n  var Info = /*#__PURE__*/function () {\n    function Info() {}\n\n    /**\n     * Return whether the specified zone contains a DST.\n     * @param {string|Zone} [zone='local'] - Zone to check. Defaults to the environment's local zone.\n     * @return {boolean}\n     */\n    Info.hasDST = function hasDST(zone) {\n      if (zone === void 0) {\n        zone = Settings.defaultZone;\n      }\n\n      var proto = DateTime.local().setZone(zone).set({\n        month: 12\n      });\n      return !zone.universal && proto.offset !== proto.set({\n        month: 6\n      }).offset;\n    }\n    /**\n     * Return whether the specified zone is a valid IANA specifier.\n     * @param {string} zone - Zone to check\n     * @return {boolean}\n     */\n    ;\n\n    Info.isValidIANAZone = function isValidIANAZone(zone) {\n      return IANAZone.isValidSpecifier(zone) && IANAZone.isValidZone(zone);\n    }\n    /**\n     * Converts the input into a {@link Zone} instance.\n     *\n     * * If `input` is already a Zone instance, it is returned unchanged.\n     * * If `input` is a string containing a valid time zone name, a Zone instance\n     *   with that name is returned.\n     * * If `input` is a string that doesn't refer to a known time zone, a Zone\n     *   instance with {@link Zone.isValid} == false is returned.\n     * * If `input is a number, a Zone instance with the specified fixed offset\n     *   in minutes is returned.\n     * * If `input` is `null` or `undefined`, the default zone is returned.\n     * @param {string|Zone|number} [input] - the value to be converted\n     * @return {Zone}\n     */\n    ;\n\n    Info.normalizeZone = function normalizeZone$1(input) {\n      return normalizeZone(input, Settings.defaultZone);\n    }\n    /**\n     * Return an array of standalone month names.\n     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat\n     * @param {string} [length='long'] - the length of the month representation, such as \"numeric\", \"2-digit\", \"narrow\", \"short\", \"long\"\n     * @param {Object} opts - options\n     * @param {string} [opts.locale] - the locale code\n     * @param {string} [opts.numberingSystem=null] - the numbering system\n     * @param {string} [opts.outputCalendar='gregory'] - the calendar\n     * @example Info.months()[0] //=> 'January'\n     * @example Info.months('short')[0] //=> 'Jan'\n     * @example Info.months('numeric')[0] //=> '1'\n     * @example Info.months('short', { locale: 'fr-CA' } )[0] //=> 'janv.'\n     * @example Info.months('numeric', { locale: 'ar' })[0] //=> '\u0661'\n     * @example Info.months('long', { outputCalendar: 'islamic' })[0] //=> 'Rabi\u02bb I'\n     * @return {[string]}\n     */\n    ;\n\n    Info.months = function months(length, _temp) {\n      if (length === void 0) {\n        length = \"long\";\n      }\n\n      var _ref = _temp === void 0 ? {} : _temp,\n          _ref$locale = _ref.locale,\n          locale = _ref$locale === void 0 ? null : _ref$locale,\n          _ref$numberingSystem = _ref.numberingSystem,\n          numberingSystem = _ref$numberingSystem === void 0 ? null : _ref$numberingSystem,\n          _ref$outputCalendar = _ref.outputCalendar,\n          outputCalendar = _ref$outputCalendar === void 0 ? \"gregory\" : _ref$outputCalendar;\n\n      return Locale.create(locale, numberingSystem, outputCalendar).months(length);\n    }\n    /**\n     * Return an array of format month names.\n     * Format months differ from standalone months in that they're meant to appear next to the day of the month. In some languages, that\n     * changes the string.\n     * See {@link months}\n     * @param {string} [length='long'] - the length of the month representation, such as \"numeric\", \"2-digit\", \"narrow\", \"short\", \"long\"\n     * @param {Object} opts - options\n     * @param {string} [opts.locale] - the locale code\n     * @param {string} [opts.numberingSystem=null] - the numbering system\n     * @param {string} [opts.outputCalendar='gregory'] - the calendar\n     * @return {[string]}\n     */\n    ;\n\n    Info.monthsFormat = function monthsFormat(length, _temp2) {\n      if (length === void 0) {\n        length = \"long\";\n      }\n\n      var _ref2 = _temp2 === void 0 ? {} : _temp2,\n          _ref2$locale = _ref2.locale,\n          locale = _ref2$locale === void 0 ? null : _ref2$locale,\n          _ref2$numberingSystem = _ref2.numberingSystem,\n          numberingSystem = _ref2$numberingSystem === void 0 ? null : _ref2$numberingSystem,\n          _ref2$outputCalendar = _ref2.outputCalendar,\n          outputCalendar = _ref2$outputCalendar === void 0 ? \"gregory\" : _ref2$outputCalendar;\n\n      return Locale.create(locale, numberingSystem, outputCalendar).months(length, true);\n    }\n    /**\n     * Return an array of standalone week names.\n     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat\n     * @param {string} [length='long'] - the length of the weekday representation, such as \"narrow\", \"short\", \"long\".\n     * @param {Object} opts - options\n     * @param {string} [opts.locale] - the locale code\n     * @param {string} [opts.numberingSystem=null] - the numbering system\n     * @example Info.weekdays()[0] //=> 'Monday'\n     * @example Info.weekdays('short')[0] //=> 'Mon'\n     * @example Info.weekdays('short', { locale: 'fr-CA' })[0] //=> 'lun.'\n     * @example Info.weekdays('short', { locale: 'ar' })[0] //=> '\u0627\u0644\u0627\u062b\u0646\u064a\u0646'\n     * @return {[string]}\n     */\n    ;\n\n    Info.weekdays = function weekdays(length, _temp3) {\n      if (length === void 0) {\n        length = \"long\";\n      }\n\n      var _ref3 = _temp3 === void 0 ? {} : _temp3,\n          _ref3$locale = _ref3.locale,\n          locale = _ref3$locale === void 0 ? null : _ref3$locale,\n          _ref3$numberingSystem = _ref3.numberingSystem,\n          numberingSystem = _ref3$numberingSystem === void 0 ? null : _ref3$numberingSystem;\n\n      return Locale.create(locale, numberingSystem, null).weekdays(length);\n    }\n    /**\n     * Return an array of format week names.\n     * Format weekdays differ from standalone weekdays in that they're meant to appear next to more date information. In some languages, that\n     * changes the string.\n     * See {@link weekdays}\n     * @param {string} [length='long'] - the length of the weekday representation, such as \"narrow\", \"short\", \"long\".\n     * @param {Object} opts - options\n     * @param {string} [opts.locale=null] - the locale code\n     * @param {string} [opts.numberingSystem=null] - the numbering system\n     * @return {[string]}\n     */\n    ;\n\n    Info.weekdaysFormat = function weekdaysFormat(length, _temp4) {\n      if (length === void 0) {\n        length = \"long\";\n      }\n\n      var _ref4 = _temp4 === void 0 ? {} : _temp4,\n          _ref4$locale = _ref4.locale,\n          locale = _ref4$locale === void 0 ? null : _ref4$locale,\n          _ref4$numberingSystem = _ref4.numberingSystem,\n          numberingSystem = _ref4$numberingSystem === void 0 ? null : _ref4$numberingSystem;\n\n      return Locale.create(locale, numberingSystem, null).weekdays(length, true);\n    }\n    /**\n     * Return an array of meridiems.\n     * @param {Object} opts - options\n     * @param {string} [opts.locale] - the locale code\n     * @example Info.meridiems() //=> [ 'AM', 'PM' ]\n     * @example Info.meridiems({ locale: 'my' }) //=> [ '\u1014\u1036\u1014\u1000\u103a', '\u100a\u1014\u1031' ]\n     * @return {[string]}\n     */\n    ;\n\n    Info.meridiems = function meridiems(_temp5) {\n      var _ref5 = _temp5 === void 0 ? {} : _temp5,\n          _ref5$locale = _ref5.locale,\n          locale = _ref5$locale === void 0 ? null : _ref5$locale;\n\n      return Locale.create(locale).meridiems();\n    }\n    /**\n     * Return an array of eras, such as ['BC', 'AD']. The locale can be specified, but the calendar system is always Gregorian.\n     * @param {string} [length='short'] - the length of the era representation, such as \"short\" or \"long\".\n     * @param {Object} opts - options\n     * @param {string} [opts.locale] - the locale code\n     * @example Info.eras() //=> [ 'BC', 'AD' ]\n     * @example Info.eras('long') //=> [ 'Before Christ', 'Anno Domini' ]\n     * @example Info.eras('long', { locale: 'fr' }) //=> [ 'avant J\u00e9sus-Christ', 'apr\u00e8s J\u00e9sus-Christ' ]\n     * @return {[string]}\n     */\n    ;\n\n    Info.eras = function eras(length, _temp6) {\n      if (length === void 0) {\n        length = \"short\";\n      }\n\n      var _ref6 = _temp6 === void 0 ? {} : _temp6,\n          _ref6$locale = _ref6.locale,\n          locale = _ref6$locale === void 0 ? null : _ref6$locale;\n\n      return Locale.create(locale, null, \"gregory\").eras(length);\n    }\n    /**\n     * Return the set of available features in this environment.\n     * Some features of Luxon are not available in all environments. For example, on older browsers, timezone support is not available. Use this function to figure out if that's the case.\n     * Keys:\n     * * `zones`: whether this environment supports IANA timezones\n     * * `intlTokens`: whether this environment supports internationalized token-based formatting/parsing\n     * * `intl`: whether this environment supports general internationalization\n     * * `relative`: whether this environment supports relative time formatting\n     * @example Info.features() //=> { intl: true, intlTokens: false, zones: true, relative: false }\n     * @return {Object}\n     */\n    ;\n\n    Info.features = function features() {\n      var intl = false,\n          intlTokens = false,\n          zones = false,\n          relative = false;\n\n      if (hasIntl()) {\n        intl = true;\n        intlTokens = hasFormatToParts();\n        relative = hasRelative();\n\n        try {\n          zones = new Intl.DateTimeFormat(\"en\", {\n            timeZone: \"America/New_York\"\n          }).resolvedOptions().timeZone === \"America/New_York\";\n        } catch (e) {\n          zones = false;\n        }\n      }\n\n      return {\n        intl: intl,\n        intlTokens: intlTokens,\n        zones: zones,\n        relative: relative\n      };\n    };\n\n    return Info;\n  }();\n\n  function dayDiff(earlier, later) {\n    var utcDayStart = function utcDayStart(dt) {\n      return dt.toUTC(0, {\n        keepLocalTime: true\n      }).startOf(\"day\").valueOf();\n    },\n        ms = utcDayStart(later) - utcDayStart(earlier);\n\n    return Math.floor(Duration.fromMillis(ms).as(\"days\"));\n  }\n\n  function highOrderDiffs(cursor, later, units) {\n    var differs = [[\"years\", function (a, b) {\n      return b.year - a.year;\n    }], [\"months\", function (a, b) {\n      return b.month - a.month + (b.year - a.year) * 12;\n    }], [\"weeks\", function (a, b) {\n      var days = dayDiff(a, b);\n      return (days - days % 7) / 7;\n    }], [\"days\", dayDiff]];\n    var results = {};\n    var lowestOrder, highWater;\n\n    for (var _i = 0, _differs = differs; _i < _differs.length; _i++) {\n      var _differs$_i = _differs[_i],\n          unit = _differs$_i[0],\n          differ = _differs$_i[1];\n\n      if (units.indexOf(unit) >= 0) {\n        var _cursor$plus;\n\n        lowestOrder = unit;\n        var delta = differ(cursor, later);\n        highWater = cursor.plus((_cursor$plus = {}, _cursor$plus[unit] = delta, _cursor$plus));\n\n        if (highWater > later) {\n          var _cursor$plus2;\n\n          cursor = cursor.plus((_cursor$plus2 = {}, _cursor$plus2[unit] = delta - 1, _cursor$plus2));\n          delta -= 1;\n        } else {\n          cursor = highWater;\n        }\n\n        results[unit] = delta;\n      }\n    }\n\n    return [cursor, results, highWater, lowestOrder];\n  }\n\n  function _diff (earlier, later, units, opts) {\n    var _highOrderDiffs = highOrderDiffs(earlier, later, units),\n        cursor = _highOrderDiffs[0],\n        results = _highOrderDiffs[1],\n        highWater = _highOrderDiffs[2],\n        lowestOrder = _highOrderDiffs[3];\n\n    var remainingMillis = later - cursor;\n    var lowerOrderUnits = units.filter(function (u) {\n      return [\"hours\", \"minutes\", \"seconds\", \"milliseconds\"].indexOf(u) >= 0;\n    });\n\n    if (lowerOrderUnits.length === 0) {\n      if (highWater < later) {\n        var _cursor$plus3;\n\n        highWater = cursor.plus((_cursor$plus3 = {}, _cursor$plus3[lowestOrder] = 1, _cursor$plus3));\n      }\n\n      if (highWater !== cursor) {\n        results[lowestOrder] = (results[lowestOrder] || 0) + remainingMillis / (highWater - cursor);\n      }\n    }\n\n    var duration = Duration.fromObject(Object.assign(results, opts));\n\n    if (lowerOrderUnits.length > 0) {\n      var _Duration$fromMillis;\n\n      return (_Duration$fromMillis = Duration.fromMillis(remainingMillis, opts)).shiftTo.apply(_Duration$fromMillis, lowerOrderUnits).plus(duration);\n    } else {\n      return duration;\n    }\n  }\n\n  var numberingSystems = {\n    arab: \"[\\u0660-\\u0669]\",\n    arabext: \"[\\u06F0-\\u06F9]\",\n    bali: \"[\\u1B50-\\u1B59]\",\n    beng: \"[\\u09E6-\\u09EF]\",\n    deva: \"[\\u0966-\\u096F]\",\n    fullwide: \"[\\uFF10-\\uFF19]\",\n    gujr: \"[\\u0AE6-\\u0AEF]\",\n    hanidec: \"[\u3007|\u4e00|\u4e8c|\u4e09|\u56db|\u4e94|\u516d|\u4e03|\u516b|\u4e5d]\",\n    khmr: \"[\\u17E0-\\u17E9]\",\n    knda: \"[\\u0CE6-\\u0CEF]\",\n    laoo: \"[\\u0ED0-\\u0ED9]\",\n    limb: \"[\\u1946-\\u194F]\",\n    mlym: \"[\\u0D66-\\u0D6F]\",\n    mong: \"[\\u1810-\\u1819]\",\n    mymr: \"[\\u1040-\\u1049]\",\n    orya: \"[\\u0B66-\\u0B6F]\",\n    tamldec: \"[\\u0BE6-\\u0BEF]\",\n    telu: \"[\\u0C66-\\u0C6F]\",\n    thai: \"[\\u0E50-\\u0E59]\",\n    tibt: \"[\\u0F20-\\u0F29]\",\n    latn: \"\\\\d\"\n  };\n  var numberingSystemsUTF16 = {\n    arab: [1632, 1641],\n    arabext: [1776, 1785],\n    bali: [6992, 7001],\n    beng: [2534, 2543],\n    deva: [2406, 2415],\n    fullwide: [65296, 65303],\n    gujr: [2790, 2799],\n    khmr: [6112, 6121],\n    knda: [3302, 3311],\n    laoo: [3792, 3801],\n    limb: [6470, 6479],\n    mlym: [3430, 3439],\n    mong: [6160, 6169],\n    mymr: [4160, 4169],\n    orya: [2918, 2927],\n    tamldec: [3046, 3055],\n    telu: [3174, 3183],\n    thai: [3664, 3673],\n    tibt: [3872, 3881]\n  }; // eslint-disable-next-line\n\n  var hanidecChars = numberingSystems.hanidec.replace(/[\\[|\\]]/g, \"\").split(\"\");\n  function parseDigits(str) {\n    var value = parseInt(str, 10);\n\n    if (isNaN(value)) {\n      value = \"\";\n\n      for (var i = 0; i < str.length; i++) {\n        var code = str.charCodeAt(i);\n\n        if (str[i].search(numberingSystems.hanidec) !== -1) {\n          value += hanidecChars.indexOf(str[i]);\n        } else {\n          for (var key in numberingSystemsUTF16) {\n            var _numberingSystemsUTF = numberingSystemsUTF16[key],\n                min = _numberingSystemsUTF[0],\n                max = _numberingSystemsUTF[1];\n\n            if (code >= min && code <= max) {\n              value += code - min;\n            }\n          }\n        }\n      }\n\n      return parseInt(value, 10);\n    } else {\n      return value;\n    }\n  }\n  function digitRegex(_ref, append) {\n    var numberingSystem = _ref.numberingSystem;\n\n    if (append === void 0) {\n      append = \"\";\n    }\n\n    return new RegExp(\"\" + numberingSystems[numberingSystem || \"latn\"] + append);\n  }\n\n  var MISSING_FTP = \"missing Intl.DateTimeFormat.formatToParts support\";\n\n  function intUnit(regex, post) {\n    if (post === void 0) {\n      post = function post(i) {\n        return i;\n      };\n    }\n\n    return {\n      regex: regex,\n      deser: function deser(_ref) {\n        var s = _ref[0];\n        return post(parseDigits(s));\n      }\n    };\n  }\n\n  var NBSP = String.fromCharCode(160);\n  var spaceOrNBSP = \"[ \" + NBSP + \"]\";\n  var spaceOrNBSPRegExp = new RegExp(spaceOrNBSP, \"g\");\n\n  function fixListRegex(s) {\n    // make dots optional and also make them literal\n    // make space and non breakable space characters interchangeable\n    return s.replace(/\\./g, \"\\\\.?\").replace(spaceOrNBSPRegExp, spaceOrNBSP);\n  }\n\n  function stripInsensitivities(s) {\n    return s.replace(/\\./g, \"\") // ignore dots that were made optional\n    .replace(spaceOrNBSPRegExp, \" \") // interchange space and nbsp\n    .toLowerCase();\n  }\n\n  function oneOf(strings, startIndex) {\n    if (strings === null) {\n      return null;\n    } else {\n      return {\n        regex: RegExp(strings.map(fixListRegex).join(\"|\")),\n        deser: function deser(_ref2) {\n          var s = _ref2[0];\n          return strings.findIndex(function (i) {\n            return stripInsensitivities(s) === stripInsensitivities(i);\n          }) + startIndex;\n        }\n      };\n    }\n  }\n\n  function offset(regex, groups) {\n    return {\n      regex: regex,\n      deser: function deser(_ref3) {\n        var h = _ref3[1],\n            m = _ref3[2];\n        return signedOffset(h, m);\n      },\n      groups: groups\n    };\n  }\n\n  function simple(regex) {\n    return {\n      regex: regex,\n      deser: function deser(_ref4) {\n        var s = _ref4[0];\n        return s;\n      }\n    };\n  }\n\n  function escapeToken(value) {\n    // eslint-disable-next-line no-useless-escape\n    return value.replace(/[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g, \"\\\\$&\");\n  }\n\n  function unitForToken(token, loc) {\n    var one = digitRegex(loc),\n        two = digitRegex(loc, \"{2}\"),\n        three = digitRegex(loc, \"{3}\"),\n        four = digitRegex(loc, \"{4}\"),\n        six = digitRegex(loc, \"{6}\"),\n        oneOrTwo = digitRegex(loc, \"{1,2}\"),\n        oneToThree = digitRegex(loc, \"{1,3}\"),\n        oneToSix = digitRegex(loc, \"{1,6}\"),\n        oneToNine = digitRegex(loc, \"{1,9}\"),\n        twoToFour = digitRegex(loc, \"{2,4}\"),\n        fourToSix = digitRegex(loc, \"{4,6}\"),\n        literal = function literal(t) {\n      return {\n        regex: RegExp(escapeToken(t.val)),\n        deser: function deser(_ref5) {\n          var s = _ref5[0];\n          return s;\n        },\n        literal: true\n      };\n    },\n        unitate = function unitate(t) {\n      if (token.literal) {\n        return literal(t);\n      }\n\n      switch (t.val) {\n        // era\n        case \"G\":\n          return oneOf(loc.eras(\"short\", false), 0);\n\n        case \"GG\":\n          return oneOf(loc.eras(\"long\", false), 0);\n        // years\n\n        case \"y\":\n          return intUnit(oneToSix);\n\n        case \"yy\":\n          return intUnit(twoToFour, untruncateYear);\n\n        case \"yyyy\":\n          return intUnit(four);\n\n        case \"yyyyy\":\n          return intUnit(fourToSix);\n\n        case \"yyyyyy\":\n          return intUnit(six);\n        // months\n\n        case \"M\":\n          return intUnit(oneOrTwo);\n\n        case \"MM\":\n          return intUnit(two);\n\n        case \"MMM\":\n          return oneOf(loc.months(\"short\", true, false), 1);\n\n        case \"MMMM\":\n          return oneOf(loc.months(\"long\", true, false), 1);\n\n        case \"L\":\n          return intUnit(oneOrTwo);\n\n        case \"LL\":\n          return intUnit(two);\n\n        case \"LLL\":\n          return oneOf(loc.months(\"short\", false, false), 1);\n\n        case \"LLLL\":\n          return oneOf(loc.months(\"long\", false, false), 1);\n        // dates\n\n        case \"d\":\n          return intUnit(oneOrTwo);\n\n        case \"dd\":\n          return intUnit(two);\n        // ordinals\n\n        case \"o\":\n          return intUnit(oneToThree);\n\n        case \"ooo\":\n          return intUnit(three);\n        // time\n\n        case \"HH\":\n          return intUnit(two);\n\n        case \"H\":\n          return intUnit(oneOrTwo);\n\n        case \"hh\":\n          return intUnit(two);\n\n        case \"h\":\n          return intUnit(oneOrTwo);\n\n        case \"mm\":\n          return intUnit(two);\n\n        case \"m\":\n          return intUnit(oneOrTwo);\n\n        case \"q\":\n          return intUnit(oneOrTwo);\n\n        case \"qq\":\n          return intUnit(two);\n\n        case \"s\":\n          return intUnit(oneOrTwo);\n\n        case \"ss\":\n          return intUnit(two);\n\n        case \"S\":\n          return intUnit(oneToThree);\n\n        case \"SSS\":\n          return intUnit(three);\n\n        case \"u\":\n          return simple(oneToNine);\n        // meridiem\n\n        case \"a\":\n          return oneOf(loc.meridiems(), 0);\n        // weekYear (k)\n\n        case \"kkkk\":\n          return intUnit(four);\n\n        case \"kk\":\n          return intUnit(twoToFour, untruncateYear);\n        // weekNumber (W)\n\n        case \"W\":\n          return intUnit(oneOrTwo);\n\n        case \"WW\":\n          return intUnit(two);\n        // weekdays\n\n        case \"E\":\n        case \"c\":\n          return intUnit(one);\n\n        case \"EEE\":\n          return oneOf(loc.weekdays(\"short\", false, false), 1);\n\n        case \"EEEE\":\n          return oneOf(loc.weekdays(\"long\", false, false), 1);\n\n        case \"ccc\":\n          return oneOf(loc.weekdays(\"short\", true, false), 1);\n\n        case \"cccc\":\n          return oneOf(loc.weekdays(\"long\", true, false), 1);\n        // offset/zone\n\n        case \"Z\":\n        case \"ZZ\":\n          return offset(new RegExp(\"([+-]\" + oneOrTwo.source + \")(?::(\" + two.source + \"))?\"), 2);\n\n        case \"ZZZ\":\n          return offset(new RegExp(\"([+-]\" + oneOrTwo.source + \")(\" + two.source + \")?\"), 2);\n        // we don't support ZZZZ (PST) or ZZZZZ (Pacific Standard Time) in parsing\n        // because we don't have any way to figure out what they are\n\n        case \"z\":\n          return simple(/[a-z_+-/]{1,256}?/i);\n\n        default:\n          return literal(t);\n      }\n    };\n\n    var unit = unitate(token) || {\n      invalidReason: MISSING_FTP\n    };\n    unit.token = token;\n    return unit;\n  }\n\n  var partTypeStyleToTokenVal = {\n    year: {\n      \"2-digit\": \"yy\",\n      numeric: \"yyyyy\"\n    },\n    month: {\n      numeric: \"M\",\n      \"2-digit\": \"MM\",\n      short: \"MMM\",\n      long: \"MMMM\"\n    },\n    day: {\n      numeric: \"d\",\n      \"2-digit\": \"dd\"\n    },\n    weekday: {\n      short: \"EEE\",\n      long: \"EEEE\"\n    },\n    dayperiod: \"a\",\n    dayPeriod: \"a\",\n    hour: {\n      numeric: \"h\",\n      \"2-digit\": \"hh\"\n    },\n    minute: {\n      numeric: \"m\",\n      \"2-digit\": \"mm\"\n    },\n    second: {\n      numeric: \"s\",\n      \"2-digit\": \"ss\"\n    }\n  };\n\n  function tokenForPart(part, locale, formatOpts) {\n    var type = part.type,\n        value = part.value;\n\n    if (type === \"literal\") {\n      return {\n        literal: true,\n        val: value\n      };\n    }\n\n    var style = formatOpts[type];\n    var val = partTypeStyleToTokenVal[type];\n\n    if (typeof val === \"object\") {\n      val = val[style];\n    }\n\n    if (val) {\n      return {\n        literal: false,\n        val: val\n      };\n    }\n\n    return undefined;\n  }\n\n  function buildRegex(units) {\n    var re = units.map(function (u) {\n      return u.regex;\n    }).reduce(function (f, r) {\n      return f + \"(\" + r.source + \")\";\n    }, \"\");\n    return [\"^\" + re + \"$\", units];\n  }\n\n  function match(input, regex, handlers) {\n    var matches = input.match(regex);\n\n    if (matches) {\n      var all = {};\n      var matchIndex = 1;\n\n      for (var i in handlers) {\n        if (hasOwnProperty(handlers, i)) {\n          var h = handlers[i],\n              groups = h.groups ? h.groups + 1 : 1;\n\n          if (!h.literal && h.token) {\n            all[h.token.val[0]] = h.deser(matches.slice(matchIndex, matchIndex + groups));\n          }\n\n          matchIndex += groups;\n        }\n      }\n\n      return [matches, all];\n    } else {\n      return [matches, {}];\n    }\n  }\n\n  function dateTimeFromMatches(matches) {\n    var toField = function toField(token) {\n      switch (token) {\n        case \"S\":\n          return \"millisecond\";\n\n        case \"s\":\n          return \"second\";\n\n        case \"m\":\n          return \"minute\";\n\n        case \"h\":\n        case \"H\":\n          return \"hour\";\n\n        case \"d\":\n          return \"day\";\n\n        case \"o\":\n          return \"ordinal\";\n\n        case \"L\":\n        case \"M\":\n          return \"month\";\n\n        case \"y\":\n          return \"year\";\n\n        case \"E\":\n        case \"c\":\n          return \"weekday\";\n\n        case \"W\":\n          return \"weekNumber\";\n\n        case \"k\":\n          return \"weekYear\";\n\n        case \"q\":\n          return \"quarter\";\n\n        default:\n          return null;\n      }\n    };\n\n    var zone;\n\n    if (!isUndefined(matches.Z)) {\n      zone = new FixedOffsetZone(matches.Z);\n    } else if (!isUndefined(matches.z)) {\n      zone = IANAZone.create(matches.z);\n    } else {\n      zone = null;\n    }\n\n    if (!isUndefined(matches.q)) {\n      matches.M = (matches.q - 1) * 3 + 1;\n    }\n\n    if (!isUndefined(matches.h)) {\n      if (matches.h < 12 && matches.a === 1) {\n        matches.h += 12;\n      } else if (matches.h === 12 && matches.a === 0) {\n        matches.h = 0;\n      }\n    }\n\n    if (matches.G === 0 && matches.y) {\n      matches.y = -matches.y;\n    }\n\n    if (!isUndefined(matches.u)) {\n      matches.S = parseMillis(matches.u);\n    }\n\n    var vals = Object.keys(matches).reduce(function (r, k) {\n      var f = toField(k);\n\n      if (f) {\n        r[f] = matches[k];\n      }\n\n      return r;\n    }, {});\n    return [vals, zone];\n  }\n\n  var dummyDateTimeCache = null;\n\n  function getDummyDateTime() {\n    if (!dummyDateTimeCache) {\n      dummyDateTimeCache = DateTime.fromMillis(1555555555555);\n    }\n\n    return dummyDateTimeCache;\n  }\n\n  function maybeExpandMacroToken(token, locale) {\n    if (token.literal) {\n      return token;\n    }\n\n    var formatOpts = Formatter.macroTokenToFormatOpts(token.val);\n\n    if (!formatOpts) {\n      return token;\n    }\n\n    var formatter = Formatter.create(locale, formatOpts);\n    var parts = formatter.formatDateTimeParts(getDummyDateTime());\n    var tokens = parts.map(function (p) {\n      return tokenForPart(p, locale, formatOpts);\n    });\n\n    if (tokens.includes(undefined)) {\n      return token;\n    }\n\n    return tokens;\n  }\n\n  function expandMacroTokens(tokens, locale) {\n    var _Array$prototype;\n\n    return (_Array$prototype = Array.prototype).concat.apply(_Array$prototype, tokens.map(function (t) {\n      return maybeExpandMacroToken(t, locale);\n    }));\n  }\n  /**\n   * @private\n   */\n\n\n  function explainFromTokens(locale, input, format) {\n    var tokens = expandMacroTokens(Formatter.parseFormat(format), locale),\n        units = tokens.map(function (t) {\n      return unitForToken(t, locale);\n    }),\n        disqualifyingUnit = units.find(function (t) {\n      return t.invalidReason;\n    });\n\n    if (disqualifyingUnit) {\n      return {\n        input: input,\n        tokens: tokens,\n        invalidReason: disqualifyingUnit.invalidReason\n      };\n    } else {\n      var _buildRegex = buildRegex(units),\n          regexString = _buildRegex[0],\n          handlers = _buildRegex[1],\n          regex = RegExp(regexString, \"i\"),\n          _match = match(input, regex, handlers),\n          rawMatches = _match[0],\n          matches = _match[1],\n          _ref6 = matches ? dateTimeFromMatches(matches) : [null, null],\n          result = _ref6[0],\n          zone = _ref6[1];\n\n      if (hasOwnProperty(matches, \"a\") && hasOwnProperty(matches, \"H\")) {\n        throw new ConflictingSpecificationError(\"Can't include meridiem when specifying 24-hour format\");\n      }\n\n      return {\n        input: input,\n        tokens: tokens,\n        regex: regex,\n        rawMatches: rawMatches,\n        matches: matches,\n        result: result,\n        zone: zone\n      };\n    }\n  }\n  function parseFromTokens(locale, input, format) {\n    var _explainFromTokens = explainFromTokens(locale, input, format),\n        result = _explainFromTokens.result,\n        zone = _explainFromTokens.zone,\n        invalidReason = _explainFromTokens.invalidReason;\n\n    return [result, zone, invalidReason];\n  }\n\n  var nonLeapLadder = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334],\n      leapLadder = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335];\n\n  function unitOutOfRange(unit, value) {\n    return new Invalid(\"unit out of range\", \"you specified \" + value + \" (of type \" + typeof value + \") as a \" + unit + \", which is invalid\");\n  }\n\n  function dayOfWeek(year, month, day) {\n    var js = new Date(Date.UTC(year, month - 1, day)).getUTCDay();\n    return js === 0 ? 7 : js;\n  }\n\n  function computeOrdinal(year, month, day) {\n    return day + (isLeapYear(year) ? leapLadder : nonLeapLadder)[month - 1];\n  }\n\n  function uncomputeOrdinal(year, ordinal) {\n    var table = isLeapYear(year) ? leapLadder : nonLeapLadder,\n        month0 = table.findIndex(function (i) {\n      return i < ordinal;\n    }),\n        day = ordinal - table[month0];\n    return {\n      month: month0 + 1,\n      day: day\n    };\n  }\n  /**\n   * @private\n   */\n\n\n  function gregorianToWeek(gregObj) {\n    var year = gregObj.year,\n        month = gregObj.month,\n        day = gregObj.day,\n        ordinal = computeOrdinal(year, month, day),\n        weekday = dayOfWeek(year, month, day);\n    var weekNumber = Math.floor((ordinal - weekday + 10) / 7),\n        weekYear;\n\n    if (weekNumber < 1) {\n      weekYear = year - 1;\n      weekNumber = weeksInWeekYear(weekYear);\n    } else if (weekNumber > weeksInWeekYear(year)) {\n      weekYear = year + 1;\n      weekNumber = 1;\n    } else {\n      weekYear = year;\n    }\n\n    return Object.assign({\n      weekYear: weekYear,\n      weekNumber: weekNumber,\n      weekday: weekday\n    }, timeObject(gregObj));\n  }\n  function weekToGregorian(weekData) {\n    var weekYear = weekData.weekYear,\n        weekNumber = weekData.weekNumber,\n        weekday = weekData.weekday,\n        weekdayOfJan4 = dayOfWeek(weekYear, 1, 4),\n        yearInDays = daysInYear(weekYear);\n    var ordinal = weekNumber * 7 + weekday - weekdayOfJan4 - 3,\n        year;\n\n    if (ordinal < 1) {\n      year = weekYear - 1;\n      ordinal += daysInYear(year);\n    } else if (ordinal > yearInDays) {\n      year = weekYear + 1;\n      ordinal -= daysInYear(weekYear);\n    } else {\n      year = weekYear;\n    }\n\n    var _uncomputeOrdinal = uncomputeOrdinal(year, ordinal),\n        month = _uncomputeOrdinal.month,\n        day = _uncomputeOrdinal.day;\n\n    return Object.assign({\n      year: year,\n      month: month,\n      day: day\n    }, timeObject(weekData));\n  }\n  function gregorianToOrdinal(gregData) {\n    var year = gregData.year,\n        month = gregData.month,\n        day = gregData.day,\n        ordinal = computeOrdinal(year, month, day);\n    return Object.assign({\n      year: year,\n      ordinal: ordinal\n    }, timeObject(gregData));\n  }\n  function ordinalToGregorian(ordinalData) {\n    var year = ordinalData.year,\n        ordinal = ordinalData.ordinal,\n        _uncomputeOrdinal2 = uncomputeOrdinal(year, ordinal),\n        month = _uncomputeOrdinal2.month,\n        day = _uncomputeOrdinal2.day;\n\n    return Object.assign({\n      year: year,\n      month: month,\n      day: day\n    }, timeObject(ordinalData));\n  }\n  function hasInvalidWeekData(obj) {\n    var validYear = isInteger(obj.weekYear),\n        validWeek = integerBetween(obj.weekNumber, 1, weeksInWeekYear(obj.weekYear)),\n        validWeekday = integerBetween(obj.weekday, 1, 7);\n\n    if (!validYear) {\n      return unitOutOfRange(\"weekYear\", obj.weekYear);\n    } else if (!validWeek) {\n      return unitOutOfRange(\"week\", obj.week);\n    } else if (!validWeekday) {\n      return unitOutOfRange(\"weekday\", obj.weekday);\n    } else return false;\n  }\n  function hasInvalidOrdinalData(obj) {\n    var validYear = isInteger(obj.year),\n        validOrdinal = integerBetween(obj.ordinal, 1, daysInYear(obj.year));\n\n    if (!validYear) {\n      return unitOutOfRange(\"year\", obj.year);\n    } else if (!validOrdinal) {\n      return unitOutOfRange(\"ordinal\", obj.ordinal);\n    } else return false;\n  }\n  function hasInvalidGregorianData(obj) {\n    var validYear = isInteger(obj.year),\n        validMonth = integerBetween(obj.month, 1, 12),\n        validDay = integerBetween(obj.day, 1, daysInMonth(obj.year, obj.month));\n\n    if (!validYear) {\n      return unitOutOfRange(\"year\", obj.year);\n    } else if (!validMonth) {\n      return unitOutOfRange(\"month\", obj.month);\n    } else if (!validDay) {\n      return unitOutOfRange(\"day\", obj.day);\n    } else return false;\n  }\n  function hasInvalidTimeData(obj) {\n    var hour = obj.hour,\n        minute = obj.minute,\n        second = obj.second,\n        millisecond = obj.millisecond;\n    var validHour = integerBetween(hour, 0, 23) || hour === 24 && minute === 0 && second === 0 && millisecond === 0,\n        validMinute = integerBetween(minute, 0, 59),\n        validSecond = integerBetween(second, 0, 59),\n        validMillisecond = integerBetween(millisecond, 0, 999);\n\n    if (!validHour) {\n      return unitOutOfRange(\"hour\", hour);\n    } else if (!validMinute) {\n      return unitOutOfRange(\"minute\", minute);\n    } else if (!validSecond) {\n      return unitOutOfRange(\"second\", second);\n    } else if (!validMillisecond) {\n      return unitOutOfRange(\"millisecond\", millisecond);\n    } else return false;\n  }\n\n  var INVALID$2 = \"Invalid DateTime\";\n  var MAX_DATE = 8.64e15;\n\n  function unsupportedZone(zone) {\n    return new Invalid(\"unsupported zone\", \"the zone \\\"\" + zone.name + \"\\\" is not supported\");\n  } // we cache week data on the DT object and this intermediates the cache\n\n\n  function possiblyCachedWeekData(dt) {\n    if (dt.weekData === null) {\n      dt.weekData = gregorianToWeek(dt.c);\n    }\n\n    return dt.weekData;\n  } // clone really means, \"make a new object with these modifications\". all \"setters\" really use this\n  // to create a new object while only changing some of the properties\n\n\n  function clone$1(inst, alts) {\n    var current = {\n      ts: inst.ts,\n      zone: inst.zone,\n      c: inst.c,\n      o: inst.o,\n      loc: inst.loc,\n      invalid: inst.invalid\n    };\n    return new DateTime(Object.assign({}, current, alts, {\n      old: current\n    }));\n  } // find the right offset a given local time. The o input is our guess, which determines which\n  // offset we'll pick in ambiguous cases (e.g. there are two 3 AMs b/c Fallback DST)\n\n\n  function fixOffset(localTS, o, tz) {\n    // Our UTC time is just a guess because our offset is just a guess\n    var utcGuess = localTS - o * 60 * 1000; // Test whether the zone matches the offset for this ts\n\n    var o2 = tz.offset(utcGuess); // If so, offset didn't change and we're done\n\n    if (o === o2) {\n      return [utcGuess, o];\n    } // If not, change the ts by the difference in the offset\n\n\n    utcGuess -= (o2 - o) * 60 * 1000; // If that gives us the local time we want, we're done\n\n    var o3 = tz.offset(utcGuess);\n\n    if (o2 === o3) {\n      return [utcGuess, o2];\n    } // If it's different, we're in a hole time. The offset has changed, but the we don't adjust the time\n\n\n    return [localTS - Math.min(o2, o3) * 60 * 1000, Math.max(o2, o3)];\n  } // convert an epoch timestamp into a calendar object with the given offset\n\n\n  function tsToObj(ts, offset) {\n    ts += offset * 60 * 1000;\n    var d = new Date(ts);\n    return {\n      year: d.getUTCFullYear(),\n      month: d.getUTCMonth() + 1,\n      day: d.getUTCDate(),\n      hour: d.getUTCHours(),\n      minute: d.getUTCMinutes(),\n      second: d.getUTCSeconds(),\n      millisecond: d.getUTCMilliseconds()\n    };\n  } // convert a calendar object to a epoch timestamp\n\n\n  function objToTS(obj, offset, zone) {\n    return fixOffset(objToLocalTS(obj), offset, zone);\n  } // create a new DT instance by adding a duration, adjusting for DSTs\n\n\n  function adjustTime(inst, dur) {\n    var oPre = inst.o,\n        year = inst.c.year + Math.trunc(dur.years),\n        month = inst.c.month + Math.trunc(dur.months) + Math.trunc(dur.quarters) * 3,\n        c = Object.assign({}, inst.c, {\n      year: year,\n      month: month,\n      day: Math.min(inst.c.day, daysInMonth(year, month)) + Math.trunc(dur.days) + Math.trunc(dur.weeks) * 7\n    }),\n        millisToAdd = Duration.fromObject({\n      years: dur.years - Math.trunc(dur.years),\n      quarters: dur.quarters - Math.trunc(dur.quarters),\n      months: dur.months - Math.trunc(dur.months),\n      weeks: dur.weeks - Math.trunc(dur.weeks),\n      days: dur.days - Math.trunc(dur.days),\n      hours: dur.hours,\n      minutes: dur.minutes,\n      seconds: dur.seconds,\n      milliseconds: dur.milliseconds\n    }).as(\"milliseconds\"),\n        localTS = objToLocalTS(c);\n\n    var _fixOffset = fixOffset(localTS, oPre, inst.zone),\n        ts = _fixOffset[0],\n        o = _fixOffset[1];\n\n    if (millisToAdd !== 0) {\n      ts += millisToAdd; // that could have changed the offset by going over a DST, but we want to keep the ts the same\n\n      o = inst.zone.offset(ts);\n    }\n\n    return {\n      ts: ts,\n      o: o\n    };\n  } // helper useful in turning the results of parsing into real dates\n  // by handling the zone options\n\n\n  function parseDataToDateTime(parsed, parsedZone, opts, format, text) {\n    var setZone = opts.setZone,\n        zone = opts.zone;\n\n    if (parsed && Object.keys(parsed).length !== 0) {\n      var interpretationZone = parsedZone || zone,\n          inst = DateTime.fromObject(Object.assign(parsed, opts, {\n        zone: interpretationZone,\n        // setZone is a valid option in the calling methods, but not in fromObject\n        setZone: undefined\n      }));\n      return setZone ? inst : inst.setZone(zone);\n    } else {\n      return DateTime.invalid(new Invalid(\"unparsable\", \"the input \\\"\" + text + \"\\\" can't be parsed as \" + format));\n    }\n  } // if you want to output a technical format (e.g. RFC 2822), this helper\n  // helps handle the details\n\n\n  function toTechFormat(dt, format, allowZ) {\n    if (allowZ === void 0) {\n      allowZ = true;\n    }\n\n    return dt.isValid ? Formatter.create(Locale.create(\"en-US\"), {\n      allowZ: allowZ,\n      forceSimple: true\n    }).formatDateTimeFromString(dt, format) : null;\n  } // technical time formats (e.g. the time part of ISO 8601), take some options\n  // and this commonizes their handling\n\n\n  function toTechTimeFormat(dt, _ref) {\n    var _ref$suppressSeconds = _ref.suppressSeconds,\n        suppressSeconds = _ref$suppressSeconds === void 0 ? false : _ref$suppressSeconds,\n        _ref$suppressMillisec = _ref.suppressMilliseconds,\n        suppressMilliseconds = _ref$suppressMillisec === void 0 ? false : _ref$suppressMillisec,\n        includeOffset = _ref.includeOffset,\n        _ref$includeZone = _ref.includeZone,\n        includeZone = _ref$includeZone === void 0 ? false : _ref$includeZone,\n        _ref$spaceZone = _ref.spaceZone,\n        spaceZone = _ref$spaceZone === void 0 ? false : _ref$spaceZone,\n        _ref$format = _ref.format,\n        format = _ref$format === void 0 ? \"extended\" : _ref$format;\n    var fmt = format === \"basic\" ? \"HHmm\" : \"HH:mm\";\n\n    if (!suppressSeconds || dt.second !== 0 || dt.millisecond !== 0) {\n      fmt += format === \"basic\" ? \"ss\" : \":ss\";\n\n      if (!suppressMilliseconds || dt.millisecond !== 0) {\n        fmt += \".SSS\";\n      }\n    }\n\n    if ((includeZone || includeOffset) && spaceZone) {\n      fmt += \" \";\n    }\n\n    if (includeZone) {\n      fmt += \"z\";\n    } else if (includeOffset) {\n      fmt += format === \"basic\" ? \"ZZZ\" : \"ZZ\";\n    }\n\n    return toTechFormat(dt, fmt);\n  } // defaults for unspecified units in the supported calendars\n\n\n  var defaultUnitValues = {\n    month: 1,\n    day: 1,\n    hour: 0,\n    minute: 0,\n    second: 0,\n    millisecond: 0\n  },\n      defaultWeekUnitValues = {\n    weekNumber: 1,\n    weekday: 1,\n    hour: 0,\n    minute: 0,\n    second: 0,\n    millisecond: 0\n  },\n      defaultOrdinalUnitValues = {\n    ordinal: 1,\n    hour: 0,\n    minute: 0,\n    second: 0,\n    millisecond: 0\n  }; // Units in the supported calendars, sorted by bigness\n\n  var orderedUnits$1 = [\"year\", \"month\", \"day\", \"hour\", \"minute\", \"second\", \"millisecond\"],\n      orderedWeekUnits = [\"weekYear\", \"weekNumber\", \"weekday\", \"hour\", \"minute\", \"second\", \"millisecond\"],\n      orderedOrdinalUnits = [\"year\", \"ordinal\", \"hour\", \"minute\", \"second\", \"millisecond\"]; // standardize case and plurality in units\n\n  function normalizeUnit(unit) {\n    var normalized = {\n      year: \"year\",\n      years: \"year\",\n      month: \"month\",\n      months: \"month\",\n      day: \"day\",\n      days: \"day\",\n      hour: \"hour\",\n      hours: \"hour\",\n      minute: \"minute\",\n      minutes: \"minute\",\n      quarter: \"quarter\",\n      quarters: \"quarter\",\n      second: \"second\",\n      seconds: \"second\",\n      millisecond: \"millisecond\",\n      milliseconds: \"millisecond\",\n      weekday: \"weekday\",\n      weekdays: \"weekday\",\n      weeknumber: \"weekNumber\",\n      weeksnumber: \"weekNumber\",\n      weeknumbers: \"weekNumber\",\n      weekyear: \"weekYear\",\n      weekyears: \"weekYear\",\n      ordinal: \"ordinal\"\n    }[unit.toLowerCase()];\n    if (!normalized) throw new InvalidUnitError(unit);\n    return normalized;\n  } // this is a dumbed down version of fromObject() that runs about 60% faster\n  // but doesn't do any validation, makes a bunch of assumptions about what units\n  // are present, and so on.\n\n\n  function quickDT(obj, zone) {\n    // assume we have the higher-order units\n    for (var _iterator = _createForOfIteratorHelperLoose(orderedUnits$1), _step; !(_step = _iterator()).done;) {\n      var u = _step.value;\n\n      if (isUndefined(obj[u])) {\n        obj[u] = defaultUnitValues[u];\n      }\n    }\n\n    var invalid = hasInvalidGregorianData(obj) || hasInvalidTimeData(obj);\n\n    if (invalid) {\n      return DateTime.invalid(invalid);\n    }\n\n    var tsNow = Settings.now(),\n        offsetProvis = zone.offset(tsNow),\n        _objToTS = objToTS(obj, offsetProvis, zone),\n        ts = _objToTS[0],\n        o = _objToTS[1];\n\n    return new DateTime({\n      ts: ts,\n      zone: zone,\n      o: o\n    });\n  }\n\n  function diffRelative(start, end, opts) {\n    var round = isUndefined(opts.round) ? true : opts.round,\n        format = function format(c, unit) {\n      c = roundTo(c, round || opts.calendary ? 0 : 2, true);\n      var formatter = end.loc.clone(opts).relFormatter(opts);\n      return formatter.format(c, unit);\n    },\n        differ = function differ(unit) {\n      if (opts.calendary) {\n        if (!end.hasSame(start, unit)) {\n          return end.startOf(unit).diff(start.startOf(unit), unit).get(unit);\n        } else return 0;\n      } else {\n        return end.diff(start, unit).get(unit);\n      }\n    };\n\n    if (opts.unit) {\n      return format(differ(opts.unit), opts.unit);\n    }\n\n    for (var _iterator2 = _createForOfIteratorHelperLoose(opts.units), _step2; !(_step2 = _iterator2()).done;) {\n      var unit = _step2.value;\n      var count = differ(unit);\n\n      if (Math.abs(count) >= 1) {\n        return format(count, unit);\n      }\n    }\n\n    return format(0, opts.units[opts.units.length - 1]);\n  }\n  /**\n   * A DateTime is an immutable data structure representing a specific date and time and accompanying methods. It contains class and instance methods for creating, parsing, interrogating, transforming, and formatting them.\n   *\n   * A DateTime comprises of:\n   * * A timestamp. Each DateTime instance refers to a specific millisecond of the Unix epoch.\n   * * A time zone. Each instance is considered in the context of a specific zone (by default the local system's zone).\n   * * Configuration properties that effect how output strings are formatted, such as `locale`, `numberingSystem`, and `outputCalendar`.\n   *\n   * Here is a brief overview of the most commonly used functionality it provides:\n   *\n   * * **Creation**: To create a DateTime from its components, use one of its factory class methods: {@link local}, {@link utc}, and (most flexibly) {@link fromObject}. To create one from a standard string format, use {@link fromISO}, {@link fromHTTP}, and {@link fromRFC2822}. To create one from a custom string format, use {@link fromFormat}. To create one from a native JS date, use {@link fromJSDate}.\n   * * **Gregorian calendar and time**: To examine the Gregorian properties of a DateTime individually (i.e as opposed to collectively through {@link toObject}), use the {@link year}, {@link month},\n   * {@link day}, {@link hour}, {@link minute}, {@link second}, {@link millisecond} accessors.\n   * * **Week calendar**: For ISO week calendar attributes, see the {@link weekYear}, {@link weekNumber}, and {@link weekday} accessors.\n   * * **Configuration** See the {@link locale} and {@link numberingSystem} accessors.\n   * * **Transformation**: To transform the DateTime into other DateTimes, use {@link set}, {@link reconfigure}, {@link setZone}, {@link setLocale}, {@link plus}, {@link minus}, {@link endOf}, {@link startOf}, {@link toUTC}, and {@link toLocal}.\n   * * **Output**: To convert the DateTime to other representations, use the {@link toRelative}, {@link toRelativeCalendar}, {@link toJSON}, {@link toISO}, {@link toHTTP}, {@link toObject}, {@link toRFC2822}, {@link toString}, {@link toLocaleString}, {@link toFormat}, {@link toMillis} and {@link toJSDate}.\n   *\n   * There's plenty others documented below. In addition, for more information on subtler topics like internationalization, time zones, alternative calendars, validity, and so on, see the external documentation.\n   */\n\n\n  var DateTime = /*#__PURE__*/function () {\n    /**\n     * @access private\n     */\n    function DateTime(config) {\n      var zone = config.zone || Settings.defaultZone;\n      var invalid = config.invalid || (Number.isNaN(config.ts) ? new Invalid(\"invalid input\") : null) || (!zone.isValid ? unsupportedZone(zone) : null);\n      /**\n       * @access private\n       */\n\n      this.ts = isUndefined(config.ts) ? Settings.now() : config.ts;\n      var c = null,\n          o = null;\n\n      if (!invalid) {\n        var unchanged = config.old && config.old.ts === this.ts && config.old.zone.equals(zone);\n\n        if (unchanged) {\n          var _ref2 = [config.old.c, config.old.o];\n          c = _ref2[0];\n          o = _ref2[1];\n        } else {\n          var ot = zone.offset(this.ts);\n          c = tsToObj(this.ts, ot);\n          invalid = Number.isNaN(c.year) ? new Invalid(\"invalid input\") : null;\n          c = invalid ? null : c;\n          o = invalid ? null : ot;\n        }\n      }\n      /**\n       * @access private\n       */\n\n\n      this._zone = zone;\n      /**\n       * @access private\n       */\n\n      this.loc = config.loc || Locale.create();\n      /**\n       * @access private\n       */\n\n      this.invalid = invalid;\n      /**\n       * @access private\n       */\n\n      this.weekData = null;\n      /**\n       * @access private\n       */\n\n      this.c = c;\n      /**\n       * @access private\n       */\n\n      this.o = o;\n      /**\n       * @access private\n       */\n\n      this.isLuxonDateTime = true;\n    } // CONSTRUCT\n\n    /**\n     * Create a local DateTime\n     * @param {number} [year] - The calendar year. If omitted (as in, call `local()` with no arguments), the current time will be used\n     * @param {number} [month=1] - The month, 1-indexed\n     * @param {number} [day=1] - The day of the month\n     * @param {number} [hour=0] - The hour of the day, in 24-hour time\n     * @param {number} [minute=0] - The minute of the hour, meaning a number between 0 and 59\n     * @param {number} [second=0] - The second of the minute, meaning a number between 0 and 59\n     * @param {number} [millisecond=0] - The millisecond of the second, meaning a number between 0 and 999\n     * @example DateTime.local()                            //~> now\n     * @example DateTime.local(2017)                        //~> 2017-01-01T00:00:00\n     * @example DateTime.local(2017, 3)                     //~> 2017-03-01T00:00:00\n     * @example DateTime.local(2017, 3, 12)                 //~> 2017-03-12T00:00:00\n     * @example DateTime.local(2017, 3, 12, 5)              //~> 2017-03-12T05:00:00\n     * @example DateTime.local(2017, 3, 12, 5, 45)          //~> 2017-03-12T05:45:00\n     * @example DateTime.local(2017, 3, 12, 5, 45, 10)      //~> 2017-03-12T05:45:10\n     * @example DateTime.local(2017, 3, 12, 5, 45, 10, 765) //~> 2017-03-12T05:45:10.765\n     * @return {DateTime}\n     */\n\n\n    DateTime.local = function local(year, month, day, hour, minute, second, millisecond) {\n      if (isUndefined(year)) {\n        return new DateTime({\n          ts: Settings.now()\n        });\n      } else {\n        return quickDT({\n          year: year,\n          month: month,\n          day: day,\n          hour: hour,\n          minute: minute,\n          second: second,\n          millisecond: millisecond\n        }, Settings.defaultZone);\n      }\n    }\n    /**\n     * Create a DateTime in UTC\n     * @param {number} [year] - The calendar year. If omitted (as in, call `utc()` with no arguments), the current time will be used\n     * @param {number} [month=1] - The month, 1-indexed\n     * @param {number} [day=1] - The day of the month\n     * @param {number} [hour=0] - The hour of the day, in 24-hour time\n     * @param {number} [minute=0] - The minute of the hour, meaning a number between 0 and 59\n     * @param {number} [second=0] - The second of the minute, meaning a number between 0 and 59\n     * @param {number} [millisecond=0] - The millisecond of the second, meaning a number between 0 and 999\n     * @example DateTime.utc()                            //~> now\n     * @example DateTime.utc(2017)                        //~> 2017-01-01T00:00:00Z\n     * @example DateTime.utc(2017, 3)                     //~> 2017-03-01T00:00:00Z\n     * @example DateTime.utc(2017, 3, 12)                 //~> 2017-03-12T00:00:00Z\n     * @example DateTime.utc(2017, 3, 12, 5)              //~> 2017-03-12T05:00:00Z\n     * @example DateTime.utc(2017, 3, 12, 5, 45)          //~> 2017-03-12T05:45:00Z\n     * @example DateTime.utc(2017, 3, 12, 5, 45, 10)      //~> 2017-03-12T05:45:10Z\n     * @example DateTime.utc(2017, 3, 12, 5, 45, 10, 765) //~> 2017-03-12T05:45:10.765Z\n     * @return {DateTime}\n     */\n    ;\n\n    DateTime.utc = function utc(year, month, day, hour, minute, second, millisecond) {\n      if (isUndefined(year)) {\n        return new DateTime({\n          ts: Settings.now(),\n          zone: FixedOffsetZone.utcInstance\n        });\n      } else {\n        return quickDT({\n          year: year,\n          month: month,\n          day: day,\n          hour: hour,\n          minute: minute,\n          second: second,\n          millisecond: millisecond\n        }, FixedOffsetZone.utcInstance);\n      }\n    }\n    /**\n     * Create a DateTime from a Javascript Date object. Uses the default zone.\n     * @param {Date} date - a Javascript Date object\n     * @param {Object} options - configuration options for the DateTime\n     * @param {string|Zone} [options.zone='local'] - the zone to place the DateTime into\n     * @return {DateTime}\n     */\n    ;\n\n    DateTime.fromJSDate = function fromJSDate(date, options) {\n      if (options === void 0) {\n        options = {};\n      }\n\n      var ts = isDate(date) ? date.valueOf() : NaN;\n\n      if (Number.isNaN(ts)) {\n        return DateTime.invalid(\"invalid input\");\n      }\n\n      var zoneToUse = normalizeZone(options.zone, Settings.defaultZone);\n\n      if (!zoneToUse.isValid) {\n        return DateTime.invalid(unsupportedZone(zoneToUse));\n      }\n\n      return new DateTime({\n        ts: ts,\n        zone: zoneToUse,\n        loc: Locale.fromObject(options)\n      });\n    }\n    /**\n     * Create a DateTime from a number of milliseconds since the epoch (meaning since 1 January 1970 00:00:00 UTC). Uses the default zone.\n     * @param {number} milliseconds - a number of milliseconds since 1970 UTC\n     * @param {Object} options - configuration options for the DateTime\n     * @param {string|Zone} [options.zone='local'] - the zone to place the DateTime into\n     * @param {string} [options.locale] - a locale to set on the resulting DateTime instance\n     * @param {string} options.outputCalendar - the output calendar to set on the resulting DateTime instance\n     * @param {string} options.numberingSystem - the numbering system to set on the resulting DateTime instance\n     * @return {DateTime}\n     */\n    ;\n\n    DateTime.fromMillis = function fromMillis(milliseconds, options) {\n      if (options === void 0) {\n        options = {};\n      }\n\n      if (!isNumber(milliseconds)) {\n        throw new InvalidArgumentError(\"fromMillis requires a numerical input, but received a \" + typeof milliseconds + \" with value \" + milliseconds);\n      } else if (milliseconds < -MAX_DATE || milliseconds > MAX_DATE) {\n        // this isn't perfect because because we can still end up out of range because of additional shifting, but it's a start\n        return DateTime.invalid(\"Timestamp out of range\");\n      } else {\n        return new DateTime({\n          ts: milliseconds,\n          zone: normalizeZone(options.zone, Settings.defaultZone),\n          loc: Locale.fromObject(options)\n        });\n      }\n    }\n    /**\n     * Create a DateTime from a number of seconds since the epoch (meaning since 1 January 1970 00:00:00 UTC). Uses the default zone.\n     * @param {number} seconds - a number of seconds since 1970 UTC\n     * @param {Object} options - configuration options for the DateTime\n     * @param {string|Zone} [options.zone='local'] - the zone to place the DateTime into\n     * @param {string} [options.locale] - a locale to set on the resulting DateTime instance\n     * @param {string} options.outputCalendar - the output calendar to set on the resulting DateTime instance\n     * @param {string} options.numberingSystem - the numbering system to set on the resulting DateTime instance\n     * @return {DateTime}\n     */\n    ;\n\n    DateTime.fromSeconds = function fromSeconds(seconds, options) {\n      if (options === void 0) {\n        options = {};\n      }\n\n      if (!isNumber(seconds)) {\n        throw new InvalidArgumentError(\"fromSeconds requires a numerical input\");\n      } else {\n        return new DateTime({\n          ts: seconds * 1000,\n          zone: normalizeZone(options.zone, Settings.defaultZone),\n          loc: Locale.fromObject(options)\n        });\n      }\n    }\n    /**\n     * Create a DateTime from a Javascript object with keys like 'year' and 'hour' with reasonable defaults.\n     * @param {Object} obj - the object to create the DateTime from\n     * @param {number} obj.year - a year, such as 1987\n     * @param {number} obj.month - a month, 1-12\n     * @param {number} obj.day - a day of the month, 1-31, depending on the month\n     * @param {number} obj.ordinal - day of the year, 1-365 or 366\n     * @param {number} obj.weekYear - an ISO week year\n     * @param {number} obj.weekNumber - an ISO week number, between 1 and 52 or 53, depending on the year\n     * @param {number} obj.weekday - an ISO weekday, 1-7, where 1 is Monday and 7 is Sunday\n     * @param {number} obj.hour - hour of the day, 0-23\n     * @param {number} obj.minute - minute of the hour, 0-59\n     * @param {number} obj.second - second of the minute, 0-59\n     * @param {number} obj.millisecond - millisecond of the second, 0-999\n     * @param {string|Zone} [obj.zone='local'] - interpret the numbers in the context of a particular zone. Can take any value taken as the first argument to setZone()\n     * @param {string} [obj.locale='system's locale'] - a locale to set on the resulting DateTime instance\n     * @param {string} obj.outputCalendar - the output calendar to set on the resulting DateTime instance\n     * @param {string} obj.numberingSystem - the numbering system to set on the resulting DateTime instance\n     * @example DateTime.fromObject({ year: 1982, month: 5, day: 25}).toISODate() //=> '1982-05-25'\n     * @example DateTime.fromObject({ year: 1982 }).toISODate() //=> '1982-01-01'\n     * @example DateTime.fromObject({ hour: 10, minute: 26, second: 6 }) //~> today at 10:26:06\n     * @example DateTime.fromObject({ hour: 10, minute: 26, second: 6, zone: 'utc' }),\n     * @example DateTime.fromObject({ hour: 10, minute: 26, second: 6, zone: 'local' })\n     * @example DateTime.fromObject({ hour: 10, minute: 26, second: 6, zone: 'America/New_York' })\n     * @example DateTime.fromObject({ weekYear: 2016, weekNumber: 2, weekday: 3 }).toISODate() //=> '2016-01-13'\n     * @return {DateTime}\n     */\n    ;\n\n    DateTime.fromObject = function fromObject(obj) {\n      var zoneToUse = normalizeZone(obj.zone, Settings.defaultZone);\n\n      if (!zoneToUse.isValid) {\n        return DateTime.invalid(unsupportedZone(zoneToUse));\n      }\n\n      var tsNow = Settings.now(),\n          offsetProvis = zoneToUse.offset(tsNow),\n          normalized = normalizeObject(obj, normalizeUnit, [\"zone\", \"locale\", \"outputCalendar\", \"numberingSystem\"]),\n          containsOrdinal = !isUndefined(normalized.ordinal),\n          containsGregorYear = !isUndefined(normalized.year),\n          containsGregorMD = !isUndefined(normalized.month) || !isUndefined(normalized.day),\n          containsGregor = containsGregorYear || containsGregorMD,\n          definiteWeekDef = normalized.weekYear || normalized.weekNumber,\n          loc = Locale.fromObject(obj); // cases:\n      // just a weekday -> this week's instance of that weekday, no worries\n      // (gregorian data or ordinal) + (weekYear or weekNumber) -> error\n      // (gregorian month or day) + ordinal -> error\n      // otherwise just use weeks or ordinals or gregorian, depending on what's specified\n\n      if ((containsGregor || containsOrdinal) && definiteWeekDef) {\n        throw new ConflictingSpecificationError(\"Can't mix weekYear/weekNumber units with year/month/day or ordinals\");\n      }\n\n      if (containsGregorMD && containsOrdinal) {\n        throw new ConflictingSpecificationError(\"Can't mix ordinal dates with month/day\");\n      }\n\n      var useWeekData = definiteWeekDef || normalized.weekday && !containsGregor; // configure ourselves to deal with gregorian dates or week stuff\n\n      var units,\n          defaultValues,\n          objNow = tsToObj(tsNow, offsetProvis);\n\n      if (useWeekData) {\n        units = orderedWeekUnits;\n        defaultValues = defaultWeekUnitValues;\n        objNow = gregorianToWeek(objNow);\n      } else if (containsOrdinal) {\n        units = orderedOrdinalUnits;\n        defaultValues = defaultOrdinalUnitValues;\n        objNow = gregorianToOrdinal(objNow);\n      } else {\n        units = orderedUnits$1;\n        defaultValues = defaultUnitValues;\n      } // set default values for missing stuff\n\n\n      var foundFirst = false;\n\n      for (var _iterator3 = _createForOfIteratorHelperLoose(units), _step3; !(_step3 = _iterator3()).done;) {\n        var u = _step3.value;\n        var v = normalized[u];\n\n        if (!isUndefined(v)) {\n          foundFirst = true;\n        } else if (foundFirst) {\n          normalized[u] = defaultValues[u];\n        } else {\n          normalized[u] = objNow[u];\n        }\n      } // make sure the values we have are in range\n\n\n      var higherOrderInvalid = useWeekData ? hasInvalidWeekData(normalized) : containsOrdinal ? hasInvalidOrdinalData(normalized) : hasInvalidGregorianData(normalized),\n          invalid = higherOrderInvalid || hasInvalidTimeData(normalized);\n\n      if (invalid) {\n        return DateTime.invalid(invalid);\n      } // compute the actual time\n\n\n      var gregorian = useWeekData ? weekToGregorian(normalized) : containsOrdinal ? ordinalToGregorian(normalized) : normalized,\n          _objToTS2 = objToTS(gregorian, offsetProvis, zoneToUse),\n          tsFinal = _objToTS2[0],\n          offsetFinal = _objToTS2[1],\n          inst = new DateTime({\n        ts: tsFinal,\n        zone: zoneToUse,\n        o: offsetFinal,\n        loc: loc\n      }); // gregorian data + weekday serves only to validate\n\n\n      if (normalized.weekday && containsGregor && obj.weekday !== inst.weekday) {\n        return DateTime.invalid(\"mismatched weekday\", \"you can't specify both a weekday of \" + normalized.weekday + \" and a date of \" + inst.toISO());\n      }\n\n      return inst;\n    }\n    /**\n     * Create a DateTime from an ISO 8601 string\n     * @param {string} text - the ISO string\n     * @param {Object} opts - options to affect the creation\n     * @param {string|Zone} [opts.zone='local'] - use this zone if no offset is specified in the input string itself. Will also convert the time to this zone\n     * @param {boolean} [opts.setZone=false] - override the zone with a fixed-offset zone specified in the string itself, if it specifies one\n     * @param {string} [opts.locale='system's locale'] - a locale to set on the resulting DateTime instance\n     * @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance\n     * @param {string} opts.numberingSystem - the numbering system to set on the resulting DateTime instance\n     * @example DateTime.fromISO('2016-05-25T09:08:34.123')\n     * @example DateTime.fromISO('2016-05-25T09:08:34.123+06:00')\n     * @example DateTime.fromISO('2016-05-25T09:08:34.123+06:00', {setZone: true})\n     * @example DateTime.fromISO('2016-05-25T09:08:34.123', {zone: 'utc'})\n     * @example DateTime.fromISO('2016-W05-4')\n     * @return {DateTime}\n     */\n    ;\n\n    DateTime.fromISO = function fromISO(text, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      var _parseISODate = parseISODate(text),\n          vals = _parseISODate[0],\n          parsedZone = _parseISODate[1];\n\n      return parseDataToDateTime(vals, parsedZone, opts, \"ISO 8601\", text);\n    }\n    /**\n     * Create a DateTime from an RFC 2822 string\n     * @param {string} text - the RFC 2822 string\n     * @param {Object} opts - options to affect the creation\n     * @param {string|Zone} [opts.zone='local'] - convert the time to this zone. Since the offset is always specified in the string itself, this has no effect on the interpretation of string, merely the zone the resulting DateTime is expressed in.\n     * @param {boolean} [opts.setZone=false] - override the zone with a fixed-offset zone specified in the string itself, if it specifies one\n     * @param {string} [opts.locale='system's locale'] - a locale to set on the resulting DateTime instance\n     * @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance\n     * @param {string} opts.numberingSystem - the numbering system to set on the resulting DateTime instance\n     * @example DateTime.fromRFC2822('25 Nov 2016 13:23:12 GMT')\n     * @example DateTime.fromRFC2822('Fri, 25 Nov 2016 13:23:12 +0600')\n     * @example DateTime.fromRFC2822('25 Nov 2016 13:23 Z')\n     * @return {DateTime}\n     */\n    ;\n\n    DateTime.fromRFC2822 = function fromRFC2822(text, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      var _parseRFC2822Date = parseRFC2822Date(text),\n          vals = _parseRFC2822Date[0],\n          parsedZone = _parseRFC2822Date[1];\n\n      return parseDataToDateTime(vals, parsedZone, opts, \"RFC 2822\", text);\n    }\n    /**\n     * Create a DateTime from an HTTP header date\n     * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1\n     * @param {string} text - the HTTP header date\n     * @param {Object} opts - options to affect the creation\n     * @param {string|Zone} [opts.zone='local'] - convert the time to this zone. Since HTTP dates are always in UTC, this has no effect on the interpretation of string, merely the zone the resulting DateTime is expressed in.\n     * @param {boolean} [opts.setZone=false] - override the zone with the fixed-offset zone specified in the string. For HTTP dates, this is always UTC, so this option is equivalent to setting the `zone` option to 'utc', but this option is included for consistency with similar methods.\n     * @param {string} [opts.locale='system's locale'] - a locale to set on the resulting DateTime instance\n     * @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance\n     * @param {string} opts.numberingSystem - the numbering system to set on the resulting DateTime instance\n     * @example DateTime.fromHTTP('Sun, 06 Nov 1994 08:49:37 GMT')\n     * @example DateTime.fromHTTP('Sunday, 06-Nov-94 08:49:37 GMT')\n     * @example DateTime.fromHTTP('Sun Nov  6 08:49:37 1994')\n     * @return {DateTime}\n     */\n    ;\n\n    DateTime.fromHTTP = function fromHTTP(text, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      var _parseHTTPDate = parseHTTPDate(text),\n          vals = _parseHTTPDate[0],\n          parsedZone = _parseHTTPDate[1];\n\n      return parseDataToDateTime(vals, parsedZone, opts, \"HTTP\", opts);\n    }\n    /**\n     * Create a DateTime from an input string and format string.\n     * Defaults to en-US if no locale has been specified, regardless of the system's locale.\n     * @see https://moment.github.io/luxon/docs/manual/parsing.html#table-of-tokens\n     * @param {string} text - the string to parse\n     * @param {string} fmt - the format the string is expected to be in (see the link below for the formats)\n     * @param {Object} opts - options to affect the creation\n     * @param {string|Zone} [opts.zone='local'] - use this zone if no offset is specified in the input string itself. Will also convert the DateTime to this zone\n     * @param {boolean} [opts.setZone=false] - override the zone with a zone specified in the string itself, if it specifies one\n     * @param {string} [opts.locale='en-US'] - a locale string to use when parsing. Will also set the DateTime to this locale\n     * @param {string} opts.numberingSystem - the numbering system to use when parsing. Will also set the resulting DateTime to this numbering system\n     * @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance\n     * @return {DateTime}\n     */\n    ;\n\n    DateTime.fromFormat = function fromFormat(text, fmt, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      if (isUndefined(text) || isUndefined(fmt)) {\n        throw new InvalidArgumentError(\"fromFormat requires an input string and a format\");\n      }\n\n      var _opts = opts,\n          _opts$locale = _opts.locale,\n          locale = _opts$locale === void 0 ? null : _opts$locale,\n          _opts$numberingSystem = _opts.numberingSystem,\n          numberingSystem = _opts$numberingSystem === void 0 ? null : _opts$numberingSystem,\n          localeToUse = Locale.fromOpts({\n        locale: locale,\n        numberingSystem: numberingSystem,\n        defaultToEN: true\n      }),\n          _parseFromTokens = parseFromTokens(localeToUse, text, fmt),\n          vals = _parseFromTokens[0],\n          parsedZone = _parseFromTokens[1],\n          invalid = _parseFromTokens[2];\n\n      if (invalid) {\n        return DateTime.invalid(invalid);\n      } else {\n        return parseDataToDateTime(vals, parsedZone, opts, \"format \" + fmt, text);\n      }\n    }\n    /**\n     * @deprecated use fromFormat instead\n     */\n    ;\n\n    DateTime.fromString = function fromString(text, fmt, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      return DateTime.fromFormat(text, fmt, opts);\n    }\n    /**\n     * Create a DateTime from a SQL date, time, or datetime\n     * Defaults to en-US if no locale has been specified, regardless of the system's locale\n     * @param {string} text - the string to parse\n     * @param {Object} opts - options to affect the creation\n     * @param {string|Zone} [opts.zone='local'] - use this zone if no offset is specified in the input string itself. Will also convert the DateTime to this zone\n     * @param {boolean} [opts.setZone=false] - override the zone with a zone specified in the string itself, if it specifies one\n     * @param {string} [opts.locale='en-US'] - a locale string to use when parsing. Will also set the DateTime to this locale\n     * @param {string} opts.numberingSystem - the numbering system to use when parsing. Will also set the resulting DateTime to this numbering system\n     * @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance\n     * @example DateTime.fromSQL('2017-05-15')\n     * @example DateTime.fromSQL('2017-05-15 09:12:34')\n     * @example DateTime.fromSQL('2017-05-15 09:12:34.342')\n     * @example DateTime.fromSQL('2017-05-15 09:12:34.342+06:00')\n     * @example DateTime.fromSQL('2017-05-15 09:12:34.342 America/Los_Angeles')\n     * @example DateTime.fromSQL('2017-05-15 09:12:34.342 America/Los_Angeles', { setZone: true })\n     * @example DateTime.fromSQL('2017-05-15 09:12:34.342', { zone: 'America/Los_Angeles' })\n     * @example DateTime.fromSQL('09:12:34.342')\n     * @return {DateTime}\n     */\n    ;\n\n    DateTime.fromSQL = function fromSQL(text, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      var _parseSQL = parseSQL(text),\n          vals = _parseSQL[0],\n          parsedZone = _parseSQL[1];\n\n      return parseDataToDateTime(vals, parsedZone, opts, \"SQL\", text);\n    }\n    /**\n     * Create an invalid DateTime.\n     * @param {string} reason - simple string of why this DateTime is invalid. Should not contain parameters or anything else data-dependent\n     * @param {string} [explanation=null] - longer explanation, may include parameters and other useful debugging information\n     * @return {DateTime}\n     */\n    ;\n\n    DateTime.invalid = function invalid(reason, explanation) {\n      if (explanation === void 0) {\n        explanation = null;\n      }\n\n      if (!reason) {\n        throw new InvalidArgumentError(\"need to specify a reason the DateTime is invalid\");\n      }\n\n      var invalid = reason instanceof Invalid ? reason : new Invalid(reason, explanation);\n\n      if (Settings.throwOnInvalid) {\n        throw new InvalidDateTimeError(invalid);\n      } else {\n        return new DateTime({\n          invalid: invalid\n        });\n      }\n    }\n    /**\n     * Check if an object is a DateTime. Works across context boundaries\n     * @param {object} o\n     * @return {boolean}\n     */\n    ;\n\n    DateTime.isDateTime = function isDateTime(o) {\n      return o && o.isLuxonDateTime || false;\n    } // INFO\n\n    /**\n     * Get the value of unit.\n     * @param {string} unit - a unit such as 'minute' or 'day'\n     * @example DateTime.local(2017, 7, 4).get('month'); //=> 7\n     * @example DateTime.local(2017, 7, 4).get('day'); //=> 4\n     * @return {number}\n     */\n    ;\n\n    var _proto = DateTime.prototype;\n\n    _proto.get = function get(unit) {\n      return this[unit];\n    }\n    /**\n     * Returns whether the DateTime is valid. Invalid DateTimes occur when:\n     * * The DateTime was created from invalid calendar information, such as the 13th month or February 30\n     * * The DateTime was created by an operation on another invalid date\n     * @type {boolean}\n     */\n    ;\n\n    /**\n     * Returns the resolved Intl options for this DateTime.\n     * This is useful in understanding the behavior of formatting methods\n     * @param {Object} opts - the same options as toLocaleString\n     * @return {Object}\n     */\n    _proto.resolvedLocaleOpts = function resolvedLocaleOpts(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      var _Formatter$create$res = Formatter.create(this.loc.clone(opts), opts).resolvedOptions(this),\n          locale = _Formatter$create$res.locale,\n          numberingSystem = _Formatter$create$res.numberingSystem,\n          calendar = _Formatter$create$res.calendar;\n\n      return {\n        locale: locale,\n        numberingSystem: numberingSystem,\n        outputCalendar: calendar\n      };\n    } // TRANSFORM\n\n    /**\n     * \"Set\" the DateTime's zone to UTC. Returns a newly-constructed DateTime.\n     *\n     * Equivalent to {@link setZone}('utc')\n     * @param {number} [offset=0] - optionally, an offset from UTC in minutes\n     * @param {Object} [opts={}] - options to pass to `setZone()`\n     * @return {DateTime}\n     */\n    ;\n\n    _proto.toUTC = function toUTC(offset, opts) {\n      if (offset === void 0) {\n        offset = 0;\n      }\n\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      return this.setZone(FixedOffsetZone.instance(offset), opts);\n    }\n    /**\n     * \"Set\" the DateTime's zone to the host's local zone. Returns a newly-constructed DateTime.\n     *\n     * Equivalent to `setZone('local')`\n     * @return {DateTime}\n     */\n    ;\n\n    _proto.toLocal = function toLocal() {\n      return this.setZone(Settings.defaultZone);\n    }\n    /**\n     * \"Set\" the DateTime's zone to specified zone. Returns a newly-constructed DateTime.\n     *\n     * By default, the setter keeps the underlying time the same (as in, the same timestamp), but the new instance will report different local times and consider DSTs when making computations, as with {@link plus}. You may wish to use {@link toLocal} and {@link toUTC} which provide simple convenience wrappers for commonly used zones.\n     * @param {string|Zone} [zone='local'] - a zone identifier. As a string, that can be any IANA zone supported by the host environment, or a fixed-offset name of the form 'UTC+3', or the strings 'local' or 'utc'. You may also supply an instance of a {@link Zone} class.\n     * @param {Object} opts - options\n     * @param {boolean} [opts.keepLocalTime=false] - If true, adjust the underlying time so that the local time stays the same, but in the target zone. You should rarely need this.\n     * @return {DateTime}\n     */\n    ;\n\n    _proto.setZone = function setZone(zone, _temp) {\n      var _ref3 = _temp === void 0 ? {} : _temp,\n          _ref3$keepLocalTime = _ref3.keepLocalTime,\n          keepLocalTime = _ref3$keepLocalTime === void 0 ? false : _ref3$keepLocalTime,\n          _ref3$keepCalendarTim = _ref3.keepCalendarTime,\n          keepCalendarTime = _ref3$keepCalendarTim === void 0 ? false : _ref3$keepCalendarTim;\n\n      zone = normalizeZone(zone, Settings.defaultZone);\n\n      if (zone.equals(this.zone)) {\n        return this;\n      } else if (!zone.isValid) {\n        return DateTime.invalid(unsupportedZone(zone));\n      } else {\n        var newTS = this.ts;\n\n        if (keepLocalTime || keepCalendarTime) {\n          var offsetGuess = zone.offset(this.ts);\n          var asObj = this.toObject();\n\n          var _objToTS3 = objToTS(asObj, offsetGuess, zone);\n\n          newTS = _objToTS3[0];\n        }\n\n        return clone$1(this, {\n          ts: newTS,\n          zone: zone\n        });\n      }\n    }\n    /**\n     * \"Set\" the locale, numberingSystem, or outputCalendar. Returns a newly-constructed DateTime.\n     * @param {Object} properties - the properties to set\n     * @example DateTime.local(2017, 5, 25).reconfigure({ locale: 'en-GB' })\n     * @return {DateTime}\n     */\n    ;\n\n    _proto.reconfigure = function reconfigure(_temp2) {\n      var _ref4 = _temp2 === void 0 ? {} : _temp2,\n          locale = _ref4.locale,\n          numberingSystem = _ref4.numberingSystem,\n          outputCalendar = _ref4.outputCalendar;\n\n      var loc = this.loc.clone({\n        locale: locale,\n        numberingSystem: numberingSystem,\n        outputCalendar: outputCalendar\n      });\n      return clone$1(this, {\n        loc: loc\n      });\n    }\n    /**\n     * \"Set\" the locale. Returns a newly-constructed DateTime.\n     * Just a convenient alias for reconfigure({ locale })\n     * @example DateTime.local(2017, 5, 25).setLocale('en-GB')\n     * @return {DateTime}\n     */\n    ;\n\n    _proto.setLocale = function setLocale(locale) {\n      return this.reconfigure({\n        locale: locale\n      });\n    }\n    /**\n     * \"Set\" the values of specified units. Returns a newly-constructed DateTime.\n     * You can only set units with this method; for \"setting\" metadata, see {@link reconfigure} and {@link setZone}.\n     * @param {Object} values - a mapping of units to numbers\n     * @example dt.set({ year: 2017 })\n     * @example dt.set({ hour: 8, minute: 30 })\n     * @example dt.set({ weekday: 5 })\n     * @example dt.set({ year: 2005, ordinal: 234 })\n     * @return {DateTime}\n     */\n    ;\n\n    _proto.set = function set(values) {\n      if (!this.isValid) return this;\n      var normalized = normalizeObject(values, normalizeUnit, []),\n          settingWeekStuff = !isUndefined(normalized.weekYear) || !isUndefined(normalized.weekNumber) || !isUndefined(normalized.weekday);\n      var mixed;\n\n      if (settingWeekStuff) {\n        mixed = weekToGregorian(Object.assign(gregorianToWeek(this.c), normalized));\n      } else if (!isUndefined(normalized.ordinal)) {\n        mixed = ordinalToGregorian(Object.assign(gregorianToOrdinal(this.c), normalized));\n      } else {\n        mixed = Object.assign(this.toObject(), normalized); // if we didn't set the day but we ended up on an overflow date,\n        // use the last day of the right month\n\n        if (isUndefined(normalized.day)) {\n          mixed.day = Math.min(daysInMonth(mixed.year, mixed.month), mixed.day);\n        }\n      }\n\n      var _objToTS4 = objToTS(mixed, this.o, this.zone),\n          ts = _objToTS4[0],\n          o = _objToTS4[1];\n\n      return clone$1(this, {\n        ts: ts,\n        o: o\n      });\n    }\n    /**\n     * Add a period of time to this DateTime and return the resulting DateTime\n     *\n     * Adding hours, minutes, seconds, or milliseconds increases the timestamp by the right number of milliseconds. Adding days, months, or years shifts the calendar, accounting for DSTs and leap years along the way. Thus, `dt.plus({ hours: 24 })` may result in a different time than `dt.plus({ days: 1 })` if there's a DST shift in between.\n     * @param {Duration|Object|number} duration - The amount to add. Either a Luxon Duration, a number of milliseconds, the object argument to Duration.fromObject()\n     * @example DateTime.local().plus(123) //~> in 123 milliseconds\n     * @example DateTime.local().plus({ minutes: 15 }) //~> in 15 minutes\n     * @example DateTime.local().plus({ days: 1 }) //~> this time tomorrow\n     * @example DateTime.local().plus({ days: -1 }) //~> this time yesterday\n     * @example DateTime.local().plus({ hours: 3, minutes: 13 }) //~> in 3 hr, 13 min\n     * @example DateTime.local().plus(Duration.fromObject({ hours: 3, minutes: 13 })) //~> in 3 hr, 13 min\n     * @return {DateTime}\n     */\n    ;\n\n    _proto.plus = function plus(duration) {\n      if (!this.isValid) return this;\n      var dur = friendlyDuration(duration);\n      return clone$1(this, adjustTime(this, dur));\n    }\n    /**\n     * Subtract a period of time to this DateTime and return the resulting DateTime\n     * See {@link plus}\n     * @param {Duration|Object|number} duration - The amount to subtract. Either a Luxon Duration, a number of milliseconds, the object argument to Duration.fromObject()\n     @return {DateTime}\n    */\n    ;\n\n    _proto.minus = function minus(duration) {\n      if (!this.isValid) return this;\n      var dur = friendlyDuration(duration).negate();\n      return clone$1(this, adjustTime(this, dur));\n    }\n    /**\n     * \"Set\" this DateTime to the beginning of a unit of time.\n     * @param {string} unit - The unit to go to the beginning of. Can be 'year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', or 'millisecond'.\n     * @example DateTime.local(2014, 3, 3).startOf('month').toISODate(); //=> '2014-03-01'\n     * @example DateTime.local(2014, 3, 3).startOf('year').toISODate(); //=> '2014-01-01'\n     * @example DateTime.local(2014, 3, 3, 5, 30).startOf('day').toISOTime(); //=> '00:00.000-05:00'\n     * @example DateTime.local(2014, 3, 3, 5, 30).startOf('hour').toISOTime(); //=> '05:00:00.000-05:00'\n     * @return {DateTime}\n     */\n    ;\n\n    _proto.startOf = function startOf(unit) {\n      if (!this.isValid) return this;\n      var o = {},\n          normalizedUnit = Duration.normalizeUnit(unit);\n\n      switch (normalizedUnit) {\n        case \"years\":\n          o.month = 1;\n        // falls through\n\n        case \"quarters\":\n        case \"months\":\n          o.day = 1;\n        // falls through\n\n        case \"weeks\":\n        case \"days\":\n          o.hour = 0;\n        // falls through\n\n        case \"hours\":\n          o.minute = 0;\n        // falls through\n\n        case \"minutes\":\n          o.second = 0;\n        // falls through\n\n        case \"seconds\":\n          o.millisecond = 0;\n          break;\n        // no default, invalid units throw in normalizeUnit()\n      }\n\n      if (normalizedUnit === \"weeks\") {\n        o.weekday = 1;\n      }\n\n      if (normalizedUnit === \"quarters\") {\n        var q = Math.ceil(this.month / 3);\n        o.month = (q - 1) * 3 + 1;\n      }\n\n      return this.set(o);\n    }\n    /**\n     * \"Set\" this DateTime to the end (meaning the last millisecond) of a unit of time\n     * @param {string} unit - The unit to go to the end of. Can be 'year', 'month', 'day', 'hour', 'minute', 'second', or 'millisecond'.\n     * @example DateTime.local(2014, 3, 3).endOf('month').toISO(); //=> '2014-03-31T23:59:59.999-05:00'\n     * @example DateTime.local(2014, 3, 3).endOf('year').toISO(); //=> '2014-12-31T23:59:59.999-05:00'\n     * @example DateTime.local(2014, 3, 3, 5, 30).endOf('day').toISO(); //=> '2014-03-03T23:59:59.999-05:00'\n     * @example DateTime.local(2014, 3, 3, 5, 30).endOf('hour').toISO(); //=> '2014-03-03T05:59:59.999-05:00'\n     * @return {DateTime}\n     */\n    ;\n\n    _proto.endOf = function endOf(unit) {\n      var _this$plus;\n\n      return this.isValid ? this.plus((_this$plus = {}, _this$plus[unit] = 1, _this$plus)).startOf(unit).minus(1) : this;\n    } // OUTPUT\n\n    /**\n     * Returns a string representation of this DateTime formatted according to the specified format string.\n     * **You may not want this.** See {@link toLocaleString} for a more flexible formatting tool. For a table of tokens and their interpretations, see [here](https://moment.github.io/luxon/docs/manual/formatting.html#table-of-tokens).\n     * Defaults to en-US if no locale has been specified, regardless of the system's locale.\n     * @see https://moment.github.io/luxon/docs/manual/formatting.html#table-of-tokens\n     * @param {string} fmt - the format string\n     * @param {Object} opts - opts to override the configuration options\n     * @example DateTime.local().toFormat('yyyy LLL dd') //=> '2017 Apr 22'\n     * @example DateTime.local().setLocale('fr').toFormat('yyyy LLL dd') //=> '2017 avr. 22'\n     * @example DateTime.local().toFormat('yyyy LLL dd', { locale: \"fr\" }) //=> '2017 avr. 22'\n     * @example DateTime.local().toFormat(\"HH 'hours and' mm 'minutes'\") //=> '20 hours and 55 minutes'\n     * @return {string}\n     */\n    ;\n\n    _proto.toFormat = function toFormat(fmt, opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      return this.isValid ? Formatter.create(this.loc.redefaultToEN(opts)).formatDateTimeFromString(this, fmt) : INVALID$2;\n    }\n    /**\n     * Returns a localized string representing this date. Accepts the same options as the Intl.DateTimeFormat constructor and any presets defined by Luxon, such as `DateTime.DATE_FULL` or `DateTime.TIME_SIMPLE`.\n     * The exact behavior of this method is browser-specific, but in general it will return an appropriate representation\n     * of the DateTime in the assigned locale.\n     * Defaults to the system's locale if no locale has been specified\n     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat\n     * @param opts {Object} - Intl.DateTimeFormat constructor options and configuration options\n     * @example DateTime.local().toLocaleString(); //=> 4/20/2017\n     * @example DateTime.local().setLocale('en-gb').toLocaleString(); //=> '20/04/2017'\n     * @example DateTime.local().toLocaleString({ locale: 'en-gb' }); //=> '20/04/2017'\n     * @example DateTime.local().toLocaleString(DateTime.DATE_FULL); //=> 'April 20, 2017'\n     * @example DateTime.local().toLocaleString(DateTime.TIME_SIMPLE); //=> '11:32 AM'\n     * @example DateTime.local().toLocaleString(DateTime.DATETIME_SHORT); //=> '4/20/2017, 11:32 AM'\n     * @example DateTime.local().toLocaleString({ weekday: 'long', month: 'long', day: '2-digit' }); //=> 'Thursday, April 20'\n     * @example DateTime.local().toLocaleString({ weekday: 'short', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }); //=> 'Thu, Apr 20, 11:27 AM'\n     * @example DateTime.local().toLocaleString({ hour: '2-digit', minute: '2-digit', hour12: false }); //=> '11:32'\n     * @return {string}\n     */\n    ;\n\n    _proto.toLocaleString = function toLocaleString(opts) {\n      if (opts === void 0) {\n        opts = DATE_SHORT;\n      }\n\n      return this.isValid ? Formatter.create(this.loc.clone(opts), opts).formatDateTime(this) : INVALID$2;\n    }\n    /**\n     * Returns an array of format \"parts\", meaning individual tokens along with metadata. This is allows callers to post-process individual sections of the formatted output.\n     * Defaults to the system's locale if no locale has been specified\n     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat/formatToParts\n     * @param opts {Object} - Intl.DateTimeFormat constructor options, same as `toLocaleString`.\n     * @example DateTime.local().toLocaleParts(); //=> [\n     *                                   //=>   { type: 'day', value: '25' },\n     *                                   //=>   { type: 'literal', value: '/' },\n     *                                   //=>   { type: 'month', value: '05' },\n     *                                   //=>   { type: 'literal', value: '/' },\n     *                                   //=>   { type: 'year', value: '1982' }\n     *                                   //=> ]\n     */\n    ;\n\n    _proto.toLocaleParts = function toLocaleParts(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      return this.isValid ? Formatter.create(this.loc.clone(opts), opts).formatDateTimeParts(this) : [];\n    }\n    /**\n     * Returns an ISO 8601-compliant string representation of this DateTime\n     * @param {Object} opts - options\n     * @param {boolean} [opts.suppressMilliseconds=false] - exclude milliseconds from the format if they're 0\n     * @param {boolean} [opts.suppressSeconds=false] - exclude seconds from the format if they're 0\n     * @param {boolean} [opts.includeOffset=true] - include the offset, such as 'Z' or '-04:00'\n     * @param {string} [opts.format='extended'] - choose between the basic and extended format\n     * @example DateTime.utc(1982, 5, 25).toISO() //=> '1982-05-25T00:00:00.000Z'\n     * @example DateTime.local().toISO() //=> '2017-04-22T20:47:05.335-04:00'\n     * @example DateTime.local().toISO({ includeOffset: false }) //=> '2017-04-22T20:47:05.335'\n     * @example DateTime.local().toISO({ format: 'basic' }) //=> '20170422T204705.335-0400'\n     * @return {string}\n     */\n    ;\n\n    _proto.toISO = function toISO(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      if (!this.isValid) {\n        return null;\n      }\n\n      return this.toISODate(opts) + \"T\" + this.toISOTime(opts);\n    }\n    /**\n     * Returns an ISO 8601-compliant string representation of this DateTime's date component\n     * @param {Object} opts - options\n     * @param {string} [opts.format='extended'] - choose between the basic and extended format\n     * @example DateTime.utc(1982, 5, 25).toISODate() //=> '1982-05-25'\n     * @example DateTime.utc(1982, 5, 25).toISODate({ format: 'basic' }) //=> '19820525'\n     * @return {string}\n     */\n    ;\n\n    _proto.toISODate = function toISODate(_temp3) {\n      var _ref5 = _temp3 === void 0 ? {} : _temp3,\n          _ref5$format = _ref5.format,\n          format = _ref5$format === void 0 ? \"extended\" : _ref5$format;\n\n      var fmt = format === \"basic\" ? \"yyyyMMdd\" : \"yyyy-MM-dd\";\n\n      if (this.year > 9999) {\n        fmt = \"+\" + fmt;\n      }\n\n      return toTechFormat(this, fmt);\n    }\n    /**\n     * Returns an ISO 8601-compliant string representation of this DateTime's week date\n     * @example DateTime.utc(1982, 5, 25).toISOWeekDate() //=> '1982-W21-2'\n     * @return {string}\n     */\n    ;\n\n    _proto.toISOWeekDate = function toISOWeekDate() {\n      return toTechFormat(this, \"kkkk-'W'WW-c\");\n    }\n    /**\n     * Returns an ISO 8601-compliant string representation of this DateTime's time component\n     * @param {Object} opts - options\n     * @param {boolean} [opts.suppressMilliseconds=false] - exclude milliseconds from the format if they're 0\n     * @param {boolean} [opts.suppressSeconds=false] - exclude seconds from the format if they're 0\n     * @param {boolean} [opts.includeOffset=true] - include the offset, such as 'Z' or '-04:00'\n     * @param {string} [opts.format='extended'] - choose between the basic and extended format\n     * @example DateTime.utc().set({ hour: 7, minute: 34 }).toISOTime() //=> '07:34:19.361Z'\n     * @example DateTime.utc().set({ hour: 7, minute: 34, seconds: 0, milliseconds: 0 }).toISOTime({ suppressSeconds: true }) //=> '07:34Z'\n     * @example DateTime.utc().set({ hour: 7, minute: 34 }).toISOTime({ format: 'basic' }) //=> '073419.361Z'\n     * @return {string}\n     */\n    ;\n\n    _proto.toISOTime = function toISOTime(_temp4) {\n      var _ref6 = _temp4 === void 0 ? {} : _temp4,\n          _ref6$suppressMillise = _ref6.suppressMilliseconds,\n          suppressMilliseconds = _ref6$suppressMillise === void 0 ? false : _ref6$suppressMillise,\n          _ref6$suppressSeconds = _ref6.suppressSeconds,\n          suppressSeconds = _ref6$suppressSeconds === void 0 ? false : _ref6$suppressSeconds,\n          _ref6$includeOffset = _ref6.includeOffset,\n          includeOffset = _ref6$includeOffset === void 0 ? true : _ref6$includeOffset,\n          _ref6$format = _ref6.format,\n          format = _ref6$format === void 0 ? \"extended\" : _ref6$format;\n\n      return toTechTimeFormat(this, {\n        suppressSeconds: suppressSeconds,\n        suppressMilliseconds: suppressMilliseconds,\n        includeOffset: includeOffset,\n        format: format\n      });\n    }\n    /**\n     * Returns an RFC 2822-compatible string representation of this DateTime, always in UTC\n     * @example DateTime.utc(2014, 7, 13).toRFC2822() //=> 'Sun, 13 Jul 2014 00:00:00 +0000'\n     * @example DateTime.local(2014, 7, 13).toRFC2822() //=> 'Sun, 13 Jul 2014 00:00:00 -0400'\n     * @return {string}\n     */\n    ;\n\n    _proto.toRFC2822 = function toRFC2822() {\n      return toTechFormat(this, \"EEE, dd LLL yyyy HH:mm:ss ZZZ\", false);\n    }\n    /**\n     * Returns a string representation of this DateTime appropriate for use in HTTP headers.\n     * Specifically, the string conforms to RFC 1123.\n     * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1\n     * @example DateTime.utc(2014, 7, 13).toHTTP() //=> 'Sun, 13 Jul 2014 00:00:00 GMT'\n     * @example DateTime.utc(2014, 7, 13, 19).toHTTP() //=> 'Sun, 13 Jul 2014 19:00:00 GMT'\n     * @return {string}\n     */\n    ;\n\n    _proto.toHTTP = function toHTTP() {\n      return toTechFormat(this.toUTC(), \"EEE, dd LLL yyyy HH:mm:ss 'GMT'\");\n    }\n    /**\n     * Returns a string representation of this DateTime appropriate for use in SQL Date\n     * @example DateTime.utc(2014, 7, 13).toSQLDate() //=> '2014-07-13'\n     * @return {string}\n     */\n    ;\n\n    _proto.toSQLDate = function toSQLDate() {\n      return toTechFormat(this, \"yyyy-MM-dd\");\n    }\n    /**\n     * Returns a string representation of this DateTime appropriate for use in SQL Time\n     * @param {Object} opts - options\n     * @param {boolean} [opts.includeZone=false] - include the zone, such as 'America/New_York'. Overrides includeOffset.\n     * @param {boolean} [opts.includeOffset=true] - include the offset, such as 'Z' or '-04:00'\n     * @example DateTime.utc().toSQL() //=> '05:15:16.345'\n     * @example DateTime.local().toSQL() //=> '05:15:16.345 -04:00'\n     * @example DateTime.local().toSQL({ includeOffset: false }) //=> '05:15:16.345'\n     * @example DateTime.local().toSQL({ includeZone: false }) //=> '05:15:16.345 America/New_York'\n     * @return {string}\n     */\n    ;\n\n    _proto.toSQLTime = function toSQLTime(_temp5) {\n      var _ref7 = _temp5 === void 0 ? {} : _temp5,\n          _ref7$includeOffset = _ref7.includeOffset,\n          includeOffset = _ref7$includeOffset === void 0 ? true : _ref7$includeOffset,\n          _ref7$includeZone = _ref7.includeZone,\n          includeZone = _ref7$includeZone === void 0 ? false : _ref7$includeZone;\n\n      return toTechTimeFormat(this, {\n        includeOffset: includeOffset,\n        includeZone: includeZone,\n        spaceZone: true\n      });\n    }\n    /**\n     * Returns a string representation of this DateTime appropriate for use in SQL DateTime\n     * @param {Object} opts - options\n     * @param {boolean} [opts.includeZone=false] - include the zone, such as 'America/New_York'. Overrides includeOffset.\n     * @param {boolean} [opts.includeOffset=true] - include the offset, such as 'Z' or '-04:00'\n     * @example DateTime.utc(2014, 7, 13).toSQL() //=> '2014-07-13 00:00:00.000 Z'\n     * @example DateTime.local(2014, 7, 13).toSQL() //=> '2014-07-13 00:00:00.000 -04:00'\n     * @example DateTime.local(2014, 7, 13).toSQL({ includeOffset: false }) //=> '2014-07-13 00:00:00.000'\n     * @example DateTime.local(2014, 7, 13).toSQL({ includeZone: true }) //=> '2014-07-13 00:00:00.000 America/New_York'\n     * @return {string}\n     */\n    ;\n\n    _proto.toSQL = function toSQL(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      if (!this.isValid) {\n        return null;\n      }\n\n      return this.toSQLDate() + \" \" + this.toSQLTime(opts);\n    }\n    /**\n     * Returns a string representation of this DateTime appropriate for debugging\n     * @return {string}\n     */\n    ;\n\n    _proto.toString = function toString() {\n      return this.isValid ? this.toISO() : INVALID$2;\n    }\n    /**\n     * Returns the epoch milliseconds of this DateTime. Alias of {@link toMillis}\n     * @return {number}\n     */\n    ;\n\n    _proto.valueOf = function valueOf() {\n      return this.toMillis();\n    }\n    /**\n     * Returns the epoch milliseconds of this DateTime.\n     * @return {number}\n     */\n    ;\n\n    _proto.toMillis = function toMillis() {\n      return this.isValid ? this.ts : NaN;\n    }\n    /**\n     * Returns the epoch seconds of this DateTime.\n     * @return {number}\n     */\n    ;\n\n    _proto.toSeconds = function toSeconds() {\n      return this.isValid ? this.ts / 1000 : NaN;\n    }\n    /**\n     * Returns an ISO 8601 representation of this DateTime appropriate for use in JSON.\n     * @return {string}\n     */\n    ;\n\n    _proto.toJSON = function toJSON() {\n      return this.toISO();\n    }\n    /**\n     * Returns a BSON serializable equivalent to this DateTime.\n     * @return {Date}\n     */\n    ;\n\n    _proto.toBSON = function toBSON() {\n      return this.toJSDate();\n    }\n    /**\n     * Returns a Javascript object with this DateTime's year, month, day, and so on.\n     * @param opts - options for generating the object\n     * @param {boolean} [opts.includeConfig=false] - include configuration attributes in the output\n     * @example DateTime.local().toObject() //=> { year: 2017, month: 4, day: 22, hour: 20, minute: 49, second: 42, millisecond: 268 }\n     * @return {Object}\n     */\n    ;\n\n    _proto.toObject = function toObject(opts) {\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      if (!this.isValid) return {};\n      var base = Object.assign({}, this.c);\n\n      if (opts.includeConfig) {\n        base.outputCalendar = this.outputCalendar;\n        base.numberingSystem = this.loc.numberingSystem;\n        base.locale = this.loc.locale;\n      }\n\n      return base;\n    }\n    /**\n     * Returns a Javascript Date equivalent to this DateTime.\n     * @return {Date}\n     */\n    ;\n\n    _proto.toJSDate = function toJSDate() {\n      return new Date(this.isValid ? this.ts : NaN);\n    } // COMPARE\n\n    /**\n     * Return the difference between two DateTimes as a Duration.\n     * @param {DateTime} otherDateTime - the DateTime to compare this one to\n     * @param {string|string[]} [unit=['milliseconds']] - the unit or array of units (such as 'hours' or 'days') to include in the duration.\n     * @param {Object} opts - options that affect the creation of the Duration\n     * @param {string} [opts.conversionAccuracy='casual'] - the conversion system to use\n     * @example\n     * var i1 = DateTime.fromISO('1982-05-25T09:45'),\n     *     i2 = DateTime.fromISO('1983-10-14T10:30');\n     * i2.diff(i1).toObject() //=> { milliseconds: 43807500000 }\n     * i2.diff(i1, 'hours').toObject() //=> { hours: 12168.75 }\n     * i2.diff(i1, ['months', 'days']).toObject() //=> { months: 16, days: 19.03125 }\n     * i2.diff(i1, ['months', 'days', 'hours']).toObject() //=> { months: 16, days: 19, hours: 0.75 }\n     * @return {Duration}\n     */\n    ;\n\n    _proto.diff = function diff(otherDateTime, unit, opts) {\n      if (unit === void 0) {\n        unit = \"milliseconds\";\n      }\n\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      if (!this.isValid || !otherDateTime.isValid) {\n        return Duration.invalid(this.invalid || otherDateTime.invalid, \"created by diffing an invalid DateTime\");\n      }\n\n      var durOpts = Object.assign({\n        locale: this.locale,\n        numberingSystem: this.numberingSystem\n      }, opts);\n\n      var units = maybeArray(unit).map(Duration.normalizeUnit),\n          otherIsLater = otherDateTime.valueOf() > this.valueOf(),\n          earlier = otherIsLater ? this : otherDateTime,\n          later = otherIsLater ? otherDateTime : this,\n          diffed = _diff(earlier, later, units, durOpts);\n\n      return otherIsLater ? diffed.negate() : diffed;\n    }\n    /**\n     * Return the difference between this DateTime and right now.\n     * See {@link diff}\n     * @param {string|string[]} [unit=['milliseconds']] - the unit or units units (such as 'hours' or 'days') to include in the duration\n     * @param {Object} opts - options that affect the creation of the Duration\n     * @param {string} [opts.conversionAccuracy='casual'] - the conversion system to use\n     * @return {Duration}\n     */\n    ;\n\n    _proto.diffNow = function diffNow(unit, opts) {\n      if (unit === void 0) {\n        unit = \"milliseconds\";\n      }\n\n      if (opts === void 0) {\n        opts = {};\n      }\n\n      return this.diff(DateTime.local(), unit, opts);\n    }\n    /**\n     * Return an Interval spanning between this DateTime and another DateTime\n     * @param {DateTime} otherDateTime - the other end point of the Interval\n     * @return {Interval}\n     */\n    ;\n\n    _proto.until = function until(otherDateTime) {\n      return this.isValid ? Interval.fromDateTimes(this, otherDateTime) : this;\n    }\n    /**\n     * Return whether this DateTime is in the same unit of time as another DateTime\n     * @param {DateTime} otherDateTime - the other DateTime\n     * @param {string} unit - the unit of time to check sameness on\n     * @example DateTime.local().hasSame(otherDT, 'day'); //~> true if both the same calendar day\n     * @return {boolean}\n     */\n    ;\n\n    _proto.hasSame = function hasSame(otherDateTime, unit) {\n      if (!this.isValid) return false;\n\n      if (unit === \"millisecond\") {\n        return this.valueOf() === otherDateTime.valueOf();\n      } else {\n        var inputMs = otherDateTime.valueOf();\n        return this.startOf(unit) <= inputMs && inputMs <= this.endOf(unit);\n      }\n    }\n    /**\n     * Equality check\n     * Two DateTimes are equal iff they represent the same millisecond, have the same zone and location, and are both valid.\n     * To compare just the millisecond values, use `+dt1 === +dt2`.\n     * @param {DateTime} other - the other DateTime\n     * @return {boolean}\n     */\n    ;\n\n    _proto.equals = function equals(other) {\n      return this.isValid && other.isValid && this.valueOf() === other.valueOf() && this.zone.equals(other.zone) && this.loc.equals(other.loc);\n    }\n    /**\n     * Returns a string representation of a this time relative to now, such as \"in two days\". Can only internationalize if your\n     * platform supports Intl.RelativeTimeFormat. Rounds down by default.\n     * @param {Object} options - options that affect the output\n     * @param {DateTime} [options.base=DateTime.local()] - the DateTime to use as the basis to which this time is compared. Defaults to now.\n     * @param {string} [options.style=\"long\"] - the style of units, must be \"long\", \"short\", or \"narrow\"\n     * @param {string} options.unit - use a specific unit; if omitted, the method will pick the unit. Use one of \"years\", \"quarters\", \"months\", \"weeks\", \"days\", \"hours\", \"minutes\", or \"seconds\"\n     * @param {boolean} [options.round=true] - whether to round the numbers in the output.\n     * @param {boolean} [options.padding=0] - padding in milliseconds. This allows you to round up the result if it fits inside the threshold. Don't use in combination with {round: false} because the decimal output will include the padding.\n     * @param {string} options.locale - override the locale of this DateTime\n     * @param {string} options.numberingSystem - override the numberingSystem of this DateTime. The Intl system may choose not to honor this\n     * @example DateTime.local().plus({ days: 1 }).toRelative() //=> \"in 1 day\"\n     * @example DateTime.local().setLocale(\"es\").toRelative({ days: 1 }) //=> \"dentro de 1 d\u00eda\"\n     * @example DateTime.local().plus({ days: 1 }).toRelative({ locale: \"fr\" }) //=> \"dans 23 heures\"\n     * @example DateTime.local().minus({ days: 2 }).toRelative() //=> \"2 days ago\"\n     * @example DateTime.local().minus({ days: 2 }).toRelative({ unit: \"hours\" }) //=> \"48 hours ago\"\n     * @example DateTime.local().minus({ hours: 36 }).toRelative({ round: false }) //=> \"1.5 days ago\"\n     */\n    ;\n\n    _proto.toRelative = function toRelative(options) {\n      if (options === void 0) {\n        options = {};\n      }\n\n      if (!this.isValid) return null;\n      var base = options.base || DateTime.fromObject({\n        zone: this.zone\n      }),\n          padding = options.padding ? this < base ? -options.padding : options.padding : 0;\n      return diffRelative(base, this.plus(padding), Object.assign(options, {\n        numeric: \"always\",\n        units: [\"years\", \"months\", \"days\", \"hours\", \"minutes\", \"seconds\"]\n      }));\n    }\n    /**\n     * Returns a string representation of this date relative to today, such as \"yesterday\" or \"next month\".\n     * Only internationalizes on platforms that supports Intl.RelativeTimeFormat.\n     * @param {Object} options - options that affect the output\n     * @param {DateTime} [options.base=DateTime.local()] - the DateTime to use as the basis to which this time is compared. Defaults to now.\n     * @param {string} options.locale - override the locale of this DateTime\n     * @param {string} options.unit - use a specific unit; if omitted, the method will pick the unit. Use one of \"years\", \"quarters\", \"months\", \"weeks\", or \"days\"\n     * @param {string} options.numberingSystem - override the numberingSystem of this DateTime. The Intl system may choose not to honor this\n     * @example DateTime.local().plus({ days: 1 }).toRelativeCalendar() //=> \"tomorrow\"\n     * @example DateTime.local().setLocale(\"es\").plus({ days: 1 }).toRelative() //=> \"\"ma\u00f1ana\"\n     * @example DateTime.local().plus({ days: 1 }).toRelativeCalendar({ locale: \"fr\" }) //=> \"demain\"\n     * @example DateTime.local().minus({ days: 2 }).toRelativeCalendar() //=> \"2 days ago\"\n     */\n    ;\n\n    _proto.toRelativeCalendar = function toRelativeCalendar(options) {\n      if (options === void 0) {\n        options = {};\n      }\n\n      if (!this.isValid) return null;\n      return diffRelative(options.base || DateTime.fromObject({\n        zone: this.zone\n      }), this, Object.assign(options, {\n        numeric: \"auto\",\n        units: [\"years\", \"months\", \"days\"],\n        calendary: true\n      }));\n    }\n    /**\n     * Return the min of several date times\n     * @param {...DateTime} dateTimes - the DateTimes from which to choose the minimum\n     * @return {DateTime} the min DateTime, or undefined if called with no argument\n     */\n    ;\n\n    DateTime.min = function min() {\n      for (var _len = arguments.length, dateTimes = new Array(_len), _key = 0; _key < _len; _key++) {\n        dateTimes[_key] = arguments[_key];\n      }\n\n      if (!dateTimes.every(DateTime.isDateTime)) {\n        throw new InvalidArgumentError(\"min requires all arguments be DateTimes\");\n      }\n\n      return bestBy(dateTimes, function (i) {\n        return i.valueOf();\n      }, Math.min);\n    }\n    /**\n     * Return the max of several date times\n     * @param {...DateTime} dateTimes - the DateTimes from which to choose the maximum\n     * @return {DateTime} the max DateTime, or undefined if called with no argument\n     */\n    ;\n\n    DateTime.max = function max() {\n      for (var _len2 = arguments.length, dateTimes = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {\n        dateTimes[_key2] = arguments[_key2];\n      }\n\n      if (!dateTimes.every(DateTime.isDateTime)) {\n        throw new InvalidArgumentError(\"max requires all arguments be DateTimes\");\n      }\n\n      return bestBy(dateTimes, function (i) {\n        return i.valueOf();\n      }, Math.max);\n    } // MISC\n\n    /**\n     * Explain how a string would be parsed by fromFormat()\n     * @param {string} text - the string to parse\n     * @param {string} fmt - the format the string is expected to be in (see description)\n     * @param {Object} options - options taken by fromFormat()\n     * @return {Object}\n     */\n    ;\n\n    DateTime.fromFormatExplain = function fromFormatExplain(text, fmt, options) {\n      if (options === void 0) {\n        options = {};\n      }\n\n      var _options = options,\n          _options$locale = _options.locale,\n          locale = _options$locale === void 0 ? null : _options$locale,\n          _options$numberingSys = _options.numberingSystem,\n          numberingSystem = _options$numberingSys === void 0 ? null : _options$numberingSys,\n          localeToUse = Locale.fromOpts({\n        locale: locale,\n        numberingSystem: numberingSystem,\n        defaultToEN: true\n      });\n      return explainFromTokens(localeToUse, text, fmt);\n    }\n    /**\n     * @deprecated use fromFormatExplain instead\n     */\n    ;\n\n    DateTime.fromStringExplain = function fromStringExplain(text, fmt, options) {\n      if (options === void 0) {\n        options = {};\n      }\n\n      return DateTime.fromFormatExplain(text, fmt, options);\n    } // FORMAT PRESETS\n\n    /**\n     * {@link toLocaleString} format like 10/14/1983\n     * @type {Object}\n     */\n    ;\n\n    _createClass(DateTime, [{\n      key: \"isValid\",\n      get: function get() {\n        return this.invalid === null;\n      }\n      /**\n       * Returns an error code if this DateTime is invalid, or null if the DateTime is valid\n       * @type {string}\n       */\n\n    }, {\n      key: \"invalidReason\",\n      get: function get() {\n        return this.invalid ? this.invalid.reason : null;\n      }\n      /**\n       * Returns an explanation of why this DateTime became invalid, or null if the DateTime is valid\n       * @type {string}\n       */\n\n    }, {\n      key: \"invalidExplanation\",\n      get: function get() {\n        return this.invalid ? this.invalid.explanation : null;\n      }\n      /**\n       * Get the locale of a DateTime, such 'en-GB'. The locale is used when formatting the DateTime\n       *\n       * @type {string}\n       */\n\n    }, {\n      key: \"locale\",\n      get: function get() {\n        return this.isValid ? this.loc.locale : null;\n      }\n      /**\n       * Get the numbering system of a DateTime, such 'beng'. The numbering system is used when formatting the DateTime\n       *\n       * @type {string}\n       */\n\n    }, {\n      key: \"numberingSystem\",\n      get: function get() {\n        return this.isValid ? this.loc.numberingSystem : null;\n      }\n      /**\n       * Get the output calendar of a DateTime, such 'islamic'. The output calendar is used when formatting the DateTime\n       *\n       * @type {string}\n       */\n\n    }, {\n      key: \"outputCalendar\",\n      get: function get() {\n        return this.isValid ? this.loc.outputCalendar : null;\n      }\n      /**\n       * Get the time zone associated with this DateTime.\n       * @type {Zone}\n       */\n\n    }, {\n      key: \"zone\",\n      get: function get() {\n        return this._zone;\n      }\n      /**\n       * Get the name of the time zone.\n       * @type {string}\n       */\n\n    }, {\n      key: \"zoneName\",\n      get: function get() {\n        return this.isValid ? this.zone.name : null;\n      }\n      /**\n       * Get the year\n       * @example DateTime.local(2017, 5, 25).year //=> 2017\n       * @type {number}\n       */\n\n    }, {\n      key: \"year\",\n      get: function get() {\n        return this.isValid ? this.c.year : NaN;\n      }\n      /**\n       * Get the quarter\n       * @example DateTime.local(2017, 5, 25).quarter //=> 2\n       * @type {number}\n       */\n\n    }, {\n      key: \"quarter\",\n      get: function get() {\n        return this.isValid ? Math.ceil(this.c.month / 3) : NaN;\n      }\n      /**\n       * Get the month (1-12).\n       * @example DateTime.local(2017, 5, 25).month //=> 5\n       * @type {number}\n       */\n\n    }, {\n      key: \"month\",\n      get: function get() {\n        return this.isValid ? this.c.month : NaN;\n      }\n      /**\n       * Get the day of the month (1-30ish).\n       * @example DateTime.local(2017, 5, 25).day //=> 25\n       * @type {number}\n       */\n\n    }, {\n      key: \"day\",\n      get: function get() {\n        return this.isValid ? this.c.day : NaN;\n      }\n      /**\n       * Get the hour of the day (0-23).\n       * @example DateTime.local(2017, 5, 25, 9).hour //=> 9\n       * @type {number}\n       */\n\n    }, {\n      key: \"hour\",\n      get: function get() {\n        return this.isValid ? this.c.hour : NaN;\n      }\n      /**\n       * Get the minute of the hour (0-59).\n       * @example DateTime.local(2017, 5, 25, 9, 30).minute //=> 30\n       * @type {number}\n       */\n\n    }, {\n      key: \"minute\",\n      get: function get() {\n        return this.isValid ? this.c.minute : NaN;\n      }\n      /**\n       * Get the second of the minute (0-59).\n       * @example DateTime.local(2017, 5, 25, 9, 30, 52).second //=> 52\n       * @type {number}\n       */\n\n    }, {\n      key: \"second\",\n      get: function get() {\n        return this.isValid ? this.c.second : NaN;\n      }\n      /**\n       * Get the millisecond of the second (0-999).\n       * @example DateTime.local(2017, 5, 25, 9, 30, 52, 654).millisecond //=> 654\n       * @type {number}\n       */\n\n    }, {\n      key: \"millisecond\",\n      get: function get() {\n        return this.isValid ? this.c.millisecond : NaN;\n      }\n      /**\n       * Get the week year\n       * @see https://en.wikipedia.org/wiki/ISO_week_date\n       * @example DateTime.local(2014, 11, 31).weekYear //=> 2015\n       * @type {number}\n       */\n\n    }, {\n      key: \"weekYear\",\n      get: function get() {\n        return this.isValid ? possiblyCachedWeekData(this).weekYear : NaN;\n      }\n      /**\n       * Get the week number of the week year (1-52ish).\n       * @see https://en.wikipedia.org/wiki/ISO_week_date\n       * @example DateTime.local(2017, 5, 25).weekNumber //=> 21\n       * @type {number}\n       */\n\n    }, {\n      key: \"weekNumber\",\n      get: function get() {\n        return this.isValid ? possiblyCachedWeekData(this).weekNumber : NaN;\n      }\n      /**\n       * Get the day of the week.\n       * 1 is Monday and 7 is Sunday\n       * @see https://en.wikipedia.org/wiki/ISO_week_date\n       * @example DateTime.local(2014, 11, 31).weekday //=> 4\n       * @type {number}\n       */\n\n    }, {\n      key: \"weekday\",\n      get: function get() {\n        return this.isValid ? possiblyCachedWeekData(this).weekday : NaN;\n      }\n      /**\n       * Get the ordinal (meaning the day of the year)\n       * @example DateTime.local(2017, 5, 25).ordinal //=> 145\n       * @type {number|DateTime}\n       */\n\n    }, {\n      key: \"ordinal\",\n      get: function get() {\n        return this.isValid ? gregorianToOrdinal(this.c).ordinal : NaN;\n      }\n      /**\n       * Get the human readable short month name, such as 'Oct'.\n       * Defaults to the system's locale if no locale has been specified\n       * @example DateTime.local(2017, 10, 30).monthShort //=> Oct\n       * @type {string}\n       */\n\n    }, {\n      key: \"monthShort\",\n      get: function get() {\n        return this.isValid ? Info.months(\"short\", {\n          locale: this.locale\n        })[this.month - 1] : null;\n      }\n      /**\n       * Get the human readable long month name, such as 'October'.\n       * Defaults to the system's locale if no locale has been specified\n       * @example DateTime.local(2017, 10, 30).monthLong //=> October\n       * @type {string}\n       */\n\n    }, {\n      key: \"monthLong\",\n      get: function get() {\n        return this.isValid ? Info.months(\"long\", {\n          locale: this.locale\n        })[this.month - 1] : null;\n      }\n      /**\n       * Get the human readable short weekday, such as 'Mon'.\n       * Defaults to the system's locale if no locale has been specified\n       * @example DateTime.local(2017, 10, 30).weekdayShort //=> Mon\n       * @type {string}\n       */\n\n    }, {\n      key: \"weekdayShort\",\n      get: function get() {\n        return this.isValid ? Info.weekdays(\"short\", {\n          locale: this.locale\n        })[this.weekday - 1] : null;\n      }\n      /**\n       * Get the human readable long weekday, such as 'Monday'.\n       * Defaults to the system's locale if no locale has been specified\n       * @example DateTime.local(2017, 10, 30).weekdayLong //=> Monday\n       * @type {string}\n       */\n\n    }, {\n      key: \"weekdayLong\",\n      get: function get() {\n        return this.isValid ? Info.weekdays(\"long\", {\n          locale: this.locale\n        })[this.weekday - 1] : null;\n      }\n      /**\n       * Get the UTC offset of this DateTime in minutes\n       * @example DateTime.local().offset //=> -240\n       * @example DateTime.utc().offset //=> 0\n       * @type {number}\n       */\n\n    }, {\n      key: \"offset\",\n      get: function get() {\n        return this.isValid ? +this.o : NaN;\n      }\n      /**\n       * Get the short human name for the zone's current offset, for example \"EST\" or \"EDT\".\n       * Defaults to the system's locale if no locale has been specified\n       * @type {string}\n       */\n\n    }, {\n      key: \"offsetNameShort\",\n      get: function get() {\n        if (this.isValid) {\n          return this.zone.offsetName(this.ts, {\n            format: \"short\",\n            locale: this.locale\n          });\n        } else {\n          return null;\n        }\n      }\n      /**\n       * Get the long human name for the zone's current offset, for example \"Eastern Standard Time\" or \"Eastern Daylight Time\".\n       * Defaults to the system's locale if no locale has been specified\n       * @type {string}\n       */\n\n    }, {\n      key: \"offsetNameLong\",\n      get: function get() {\n        if (this.isValid) {\n          return this.zone.offsetName(this.ts, {\n            format: \"long\",\n            locale: this.locale\n          });\n        } else {\n          return null;\n        }\n      }\n      /**\n       * Get whether this zone's offset ever changes, as in a DST.\n       * @type {boolean}\n       */\n\n    }, {\n      key: \"isOffsetFixed\",\n      get: function get() {\n        return this.isValid ? this.zone.universal : null;\n      }\n      /**\n       * Get whether the DateTime is in a DST.\n       * @type {boolean}\n       */\n\n    }, {\n      key: \"isInDST\",\n      get: function get() {\n        if (this.isOffsetFixed) {\n          return false;\n        } else {\n          return this.offset > this.set({\n            month: 1\n          }).offset || this.offset > this.set({\n            month: 5\n          }).offset;\n        }\n      }\n      /**\n       * Returns true if this DateTime is in a leap year, false otherwise\n       * @example DateTime.local(2016).isInLeapYear //=> true\n       * @example DateTime.local(2013).isInLeapYear //=> false\n       * @type {boolean}\n       */\n\n    }, {\n      key: \"isInLeapYear\",\n      get: function get() {\n        return isLeapYear(this.year);\n      }\n      /**\n       * Returns the number of days in this DateTime's month\n       * @example DateTime.local(2016, 2).daysInMonth //=> 29\n       * @example DateTime.local(2016, 3).daysInMonth //=> 31\n       * @type {number}\n       */\n\n    }, {\n      key: \"daysInMonth\",\n      get: function get() {\n        return daysInMonth(this.year, this.month);\n      }\n      /**\n       * Returns the number of days in this DateTime's year\n       * @example DateTime.local(2016).daysInYear //=> 366\n       * @example DateTime.local(2013).daysInYear //=> 365\n       * @type {number}\n       */\n\n    }, {\n      key: \"daysInYear\",\n      get: function get() {\n        return this.isValid ? daysInYear(this.year) : NaN;\n      }\n      /**\n       * Returns the number of weeks in this DateTime's year\n       * @see https://en.wikipedia.org/wiki/ISO_week_date\n       * @example DateTime.local(2004).weeksInWeekYear //=> 53\n       * @example DateTime.local(2013).weeksInWeekYear //=> 52\n       * @type {number}\n       */\n\n    }, {\n      key: \"weeksInWeekYear\",\n      get: function get() {\n        return this.isValid ? weeksInWeekYear(this.weekYear) : NaN;\n      }\n    }], [{\n      key: \"DATE_SHORT\",\n      get: function get() {\n        return DATE_SHORT;\n      }\n      /**\n       * {@link toLocaleString} format like 'Oct 14, 1983'\n       * @type {Object}\n       */\n\n    }, {\n      key: \"DATE_MED\",\n      get: function get() {\n        return DATE_MED;\n      }\n      /**\n       * {@link toLocaleString} format like 'Fri, Oct 14, 1983'\n       * @type {Object}\n       */\n\n    }, {\n      key: \"DATE_MED_WITH_WEEKDAY\",\n      get: function get() {\n        return DATE_MED_WITH_WEEKDAY;\n      }\n      /**\n       * {@link toLocaleString} format like 'October 14, 1983'\n       * @type {Object}\n       */\n\n    }, {\n      key: \"DATE_FULL\",\n      get: function get() {\n        return DATE_FULL;\n      }\n      /**\n       * {@link toLocaleString} format like 'Tuesday, October 14, 1983'\n       * @type {Object}\n       */\n\n    }, {\n      key: \"DATE_HUGE\",\n      get: function get() {\n        return DATE_HUGE;\n      }\n      /**\n       * {@link toLocaleString} format like '09:30 AM'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"TIME_SIMPLE\",\n      get: function get() {\n        return TIME_SIMPLE;\n      }\n      /**\n       * {@link toLocaleString} format like '09:30:23 AM'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"TIME_WITH_SECONDS\",\n      get: function get() {\n        return TIME_WITH_SECONDS;\n      }\n      /**\n       * {@link toLocaleString} format like '09:30:23 AM EDT'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"TIME_WITH_SHORT_OFFSET\",\n      get: function get() {\n        return TIME_WITH_SHORT_OFFSET;\n      }\n      /**\n       * {@link toLocaleString} format like '09:30:23 AM Eastern Daylight Time'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"TIME_WITH_LONG_OFFSET\",\n      get: function get() {\n        return TIME_WITH_LONG_OFFSET;\n      }\n      /**\n       * {@link toLocaleString} format like '09:30', always 24-hour.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"TIME_24_SIMPLE\",\n      get: function get() {\n        return TIME_24_SIMPLE;\n      }\n      /**\n       * {@link toLocaleString} format like '09:30:23', always 24-hour.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"TIME_24_WITH_SECONDS\",\n      get: function get() {\n        return TIME_24_WITH_SECONDS;\n      }\n      /**\n       * {@link toLocaleString} format like '09:30:23 EDT', always 24-hour.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"TIME_24_WITH_SHORT_OFFSET\",\n      get: function get() {\n        return TIME_24_WITH_SHORT_OFFSET;\n      }\n      /**\n       * {@link toLocaleString} format like '09:30:23 Eastern Daylight Time', always 24-hour.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"TIME_24_WITH_LONG_OFFSET\",\n      get: function get() {\n        return TIME_24_WITH_LONG_OFFSET;\n      }\n      /**\n       * {@link toLocaleString} format like '10/14/1983, 9:30 AM'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"DATETIME_SHORT\",\n      get: function get() {\n        return DATETIME_SHORT;\n      }\n      /**\n       * {@link toLocaleString} format like '10/14/1983, 9:30:33 AM'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"DATETIME_SHORT_WITH_SECONDS\",\n      get: function get() {\n        return DATETIME_SHORT_WITH_SECONDS;\n      }\n      /**\n       * {@link toLocaleString} format like 'Oct 14, 1983, 9:30 AM'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"DATETIME_MED\",\n      get: function get() {\n        return DATETIME_MED;\n      }\n      /**\n       * {@link toLocaleString} format like 'Oct 14, 1983, 9:30:33 AM'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"DATETIME_MED_WITH_SECONDS\",\n      get: function get() {\n        return DATETIME_MED_WITH_SECONDS;\n      }\n      /**\n       * {@link toLocaleString} format like 'Fri, 14 Oct 1983, 9:30 AM'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"DATETIME_MED_WITH_WEEKDAY\",\n      get: function get() {\n        return DATETIME_MED_WITH_WEEKDAY;\n      }\n      /**\n       * {@link toLocaleString} format like 'October 14, 1983, 9:30 AM EDT'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"DATETIME_FULL\",\n      get: function get() {\n        return DATETIME_FULL;\n      }\n      /**\n       * {@link toLocaleString} format like 'October 14, 1983, 9:30:33 AM EDT'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"DATETIME_FULL_WITH_SECONDS\",\n      get: function get() {\n        return DATETIME_FULL_WITH_SECONDS;\n      }\n      /**\n       * {@link toLocaleString} format like 'Friday, October 14, 1983, 9:30 AM Eastern Daylight Time'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"DATETIME_HUGE\",\n      get: function get() {\n        return DATETIME_HUGE;\n      }\n      /**\n       * {@link toLocaleString} format like 'Friday, October 14, 1983, 9:30:33 AM Eastern Daylight Time'. Only 12-hour if the locale is.\n       * @type {Object}\n       */\n\n    }, {\n      key: \"DATETIME_HUGE_WITH_SECONDS\",\n      get: function get() {\n        return DATETIME_HUGE_WITH_SECONDS;\n      }\n    }]);\n\n    return DateTime;\n  }();\n  function friendlyDateTime(dateTimeish) {\n    if (DateTime.isDateTime(dateTimeish)) {\n      return dateTimeish;\n    } else if (dateTimeish && dateTimeish.valueOf && isNumber(dateTimeish.valueOf())) {\n      return DateTime.fromJSDate(dateTimeish);\n    } else if (dateTimeish && typeof dateTimeish === \"object\") {\n      return DateTime.fromObject(dateTimeish);\n    } else {\n      throw new InvalidArgumentError(\"Unknown datetime argument: \" + dateTimeish + \", of type \" + typeof dateTimeish);\n    }\n  }\n\n  exports.DateTime = DateTime;\n  exports.Duration = Duration;\n  exports.FixedOffsetZone = FixedOffsetZone;\n  exports.IANAZone = IANAZone;\n  exports.Info = Info;\n  exports.Interval = Interval;\n  exports.InvalidZone = InvalidZone;\n  exports.LocalZone = LocalZone;\n  exports.Settings = Settings;\n  exports.Zone = Zone;\n\n  return exports;\n\n}({}));\n//# sourceMappingURL=luxon.js.map", "/** @odoo-module **/\n\nimport { registry } from \"./core/registry\";\n\n// -----------------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------------\n\n/**\n * @typedef {Object} OdooEnv\n * @property {Object} services\n * @property {owl.core.EventBus} bus\n * @property {owl.QWeb} qweb\n * @property {string} debug\n * @property {(str: string) => string} _t\n * @property {boolean} [isSmall]\n */\n\n// -----------------------------------------------------------------------------\n// makeEnv\n// -----------------------------------------------------------------------------\n\n/**\n * Return a value Odoo Env object\n *\n * @returns {OdooEnv}\n */\nexport function makeEnv() {\n    return {\n        qweb: new owl.QWeb(),\n        bus: new owl.core.EventBus(),\n        services: {},\n        debug: odoo.debug,\n        _t: () => {\n            throw new Error(\"Translations are not ready yet. Maybe use _lt instead?\");\n        },\n        get isSmall() {\n            throw new Error(\"UI service not initialized!\");\n        },\n    };\n}\n\n// -----------------------------------------------------------------------------\n// Service Launcher\n// -----------------------------------------------------------------------------\n\nconst serviceRegistry = registry.category(\"services\");\n\nexport const SERVICES_METADATA = {};\n\n/**\n * Start all services registered in the service registry, while making sure\n * each service dependencies are properly fulfilled.\n *\n * @param {OdooEnv} env\n * @returns {Promise<void>}\n */\nexport async function startServices(env) {\n    const toStart = new Set();\n    serviceRegistry.on(\"UPDATE\", null, async (payload) => {\n        // Wait for all synchronous code so that if new services that depend on\n        // one another are added to the registry, they're all present before we\n        // start them regardless of the order they're added to the registry.\n        await Promise.resolve();\n        const { operation, key: name, value: service } = payload;\n        if (operation === \"delete\") {\n            // We hardly see why it would be usefull to remove a service.\n            // Furthermore we could encounter problems with dependencies.\n            // Keep it simple!\n            return;\n        }\n        if (toStart.size) {\n            const namedService = Object.assign(Object.create(service), { name });\n            toStart.add(namedService);\n        } else {\n            await _startServices(env, toStart);\n        }\n    });\n    // Wait for all synchronous code so that if new services that depend on\n    // one another are added to the registry, they're all present before we\n    // start them regardless of the order they're added to the registry.\n    await Promise.resolve();\n    await _startServices(env, toStart);\n}\n\nasync function _startServices(env, toStart) {\n    const services = env.services;\n    for (const [name, service] of serviceRegistry.getEntries()) {\n        if (!(name in services)) {\n            const namedService = Object.assign(Object.create(service), { name });\n            toStart.add(namedService);\n        }\n    }\n\n    // start as many services in parallel as possible\n    async function start() {\n        let service = null;\n        const proms = [];\n        while ((service = findNext())) {\n            let name = service.name;\n            toStart.delete(service);\n            const entries = (service.dependencies || []).map((dep) => [dep, services[dep]]);\n            const dependencies = Object.fromEntries(entries);\n            let value;\n            try {\n                value = service.start(env, dependencies);\n            } catch (e) {\n                value = e;\n                console.error(e);\n            }\n            if (\"async\" in service) {\n                SERVICES_METADATA[name] = service.async;\n            }\n            if (value instanceof Promise) {\n                proms.push(\n                    new Promise(async (resolve) => {\n                        value\n                            .then((val) => {\n                                services[name] = val || null;\n                            })\n                            .catch((error) => {\n                                services[name] = error;\n                                console.error(error);\n                            })\n                            .finally(resolve);\n                    })\n                );\n            } else {\n                services[service.name] = value || null;\n            }\n        }\n        await Promise.all(proms);\n        if (proms.length) {\n            return start();\n        }\n    }\n    await start();\n    if (toStart.size) {\n        const names = [...toStart].map((s) => s.name);\n        const missingDeps = new Set();\n        [...toStart].forEach((s) =>\n            s.dependencies.forEach((dep) => {\n                if (!(dep in services) && !names.includes(dep)) {\n                    missingDeps.add(dep);\n                }\n            })\n        );\n        const depNames = [...missingDeps].join(\", \");\n        throw new Error(\n            `Some services could not be started: ${names}. Missing dependencies: ${depNames}`\n        );\n    }\n\n    function findNext() {\n        for (let s of toStart) {\n            if (s.dependencies) {\n                if (s.dependencies.every((d) => d in services)) {\n                    return s;\n                }\n            } else {\n                return s;\n            }\n        }\n        return null;\n    }\n}\n", "/** @odoo-module **/\n\nconst { useEnv, onWillStart } = owl.hooks;\nimport { memoize } from \"./utils/functions\";\nimport { browser } from \"./browser/browser\";\n\nclass AssetsLoadingError extends Error {}\n\n//------------------------------------------------------------------------------\n// Types\n//------------------------------------------------------------------------------\n\n/**\n * An object describing a bundle to load\n * @typedef {Object} Bundle\n * @property {boolean} [templates] whether to load the qweb templates\n * @property {boolean} [js] whether to load the js from the bundle. Currently\n *      not implemented.\n * @property {boolean} [css] whether to load the css from the bundle. Currently\n *      not implemented.\n */\n\n/**\n * An object describing a bundle to load. The keys are bundle names.\n * @typedef {Object<string, Bundle>} Bundles\n */\n\n/**\n * An object describing a loaded bundle\n * @typedef {Object} LoadedBundle\n * @property {XMLDocument} templates an XML document containing the owl\n *      templates defined in that bundle\n */\n\n/**\n * An object describing a loaded bundle. The keys are bundle names.\n * @typedef {Object<string, LoadedBundle>} LoadedBundles\n */\n\n//------------------------------------------------------------------------------\n// Helpers\n//------------------------------------------------------------------------------\n\n/**\n * Loads the given url inside a script tag.\n *\n * @param {string} url the url of the script\n * @returns {Promise} resolved when the script has been loaded\n */\nconst loadJS = memoize(function loadJS(url) {\n    if (document.querySelector(`script[src=\"${url}\"]`)) {\n        // Already in the DOM and wasn't loaded through this function\n        // Unfortunately there is no way to check whether a script has loaded\n        // or not (which may not be the case for async/defer scripts)\n        // so we assume it is.\n        return Promise.resolve();\n    }\n    const scriptEl = document.createElement(\"script\");\n    scriptEl.type = \"text/javascript\";\n    scriptEl.src = url;\n    document.head.appendChild(scriptEl);\n    return new Promise((resolve, reject) => {\n        scriptEl.addEventListener(\"load\", resolve);\n        scriptEl.addEventListener(\"error\", () => {\n            reject(new AssetsLoadingError(`The loading of ${url} failed`));\n        });\n    });\n});\n/**\n * Loads the given url as a stylesheet.\n *\n * @param {string} url the url of the stylesheet\n * @returns {Promise} resolved when the stylesheet has been loaded\n */\nconst loadCSS = memoize(function loadCSS(url) {\n    if (document.querySelector(`link[href=\"${url}\"]`)) {\n        // Already in the DOM and wasn't loaded through this function\n        // Unfortunately there is no way to check whether a link has loaded\n        // or not (which may not be the case for async/defer stylesheets)\n        // so we assume it is.\n        return Promise.resolve();\n    }\n    const linkEl = document.createElement(\"link\");\n    linkEl.type = \"text/css\";\n    linkEl.rel = \"stylesheet\";\n    linkEl.href = url;\n    document.head.appendChild(linkEl);\n    return new Promise(function (resolve, reject) {\n        linkEl.addEventListener(\"load\", resolve);\n        linkEl.addEventListener(\"error\", () => {\n            reject(new AssetsLoadingError(`The loading of ${url} failed`));\n        });\n    });\n});\n/**\n * Loads the qweb templates from a given bundle name.\n *\n * @param {string} name the name of the bundle as declared in the manifest.\n * @returns {Promise<XMLDocument>} A Promise of an XML document containing the\n *      owl templates.\n */\nexport const loadBundleTemplates = memoize(async function loadBundleTemplates(name) {\n    // TODO: quid of the \"unique\" in the URL? We can\"t have one cache_hash\n    // for each and every bundle I\"m guessing.\n    const bundleURL = `/web/webclient/qweb/${Date.now()}?bundle=${name}`;\n    const templates = await (await browser.fetch(bundleURL)).text();\n    return processTemplates(templates);\n});\n\nconst bundlesCache = {};\n/**\n * Loads a bundle.\n *\n * @param {string} name the name of the bundle to load\n * @param {Bundle} options parts of the bundle to load (see Bundle typedef)\n * @returns {Promise<LoadedBundle>}\n */\nasync function loadBundle(name, options) {\n    const { templates } = options;\n    if (!bundlesCache[name]) {\n        bundlesCache[name] = { name };\n    }\n    if (templates && !bundlesCache[name].templates) {\n        bundlesCache[name].templates = loadBundleTemplates(name);\n    }\n    // TODO: if (\"js/css\") {...} to support lazy loading js/css from bundles\n\n    // Wait only for the requested keys\n    const entries = await Promise.all(\n        Object.keys(options).map(async (key) => [key, await bundlesCache[name][key]])\n    );\n    return Object.fromEntries(entries);\n}\n\n//------------------------------------------------------------------------------\n// Exports\n//------------------------------------------------------------------------------\n\n/**\n * Process the qweb templates to obtain only the owl templates. This function\n * does NOT register the templates into Owl.\n *\n * @param {string} templates An xml string describing templates\n * @returns {XMLDocument} An xml document containing only the owl templates\n */\nexport function processTemplates(templates) {\n    const doc = new DOMParser().parseFromString(templates, \"text/xml\");\n    // as we currently have two qweb engines (owl and legacy), owl templates are\n    // flagged with attribute `owl=\"1\"`. The following lines removes the \"owl\"\n    // attribute from the templates, so that it doesn't appear in the DOM. We\n    // also remove the non-owl templates, as those shouldn't be loaded in the\n    // owl environment's QWeb, and will be loaded separately.\n    for (const template of [...doc.querySelector(\"templates\").children]) {\n        if (template.hasAttribute(\"owl\")) {\n            template.removeAttribute(\"owl\");\n        } else {\n            template.remove();\n        }\n    }\n    return doc;\n}\n/**\n * Renders a public asset template and loads the libraries defined inside of it.\n * Only loads js and css, template declarations will be ignored. Only loads\n * scripts and styles that are defined in script src and link href, ignores\n * inline scripts and styles.\n *\n * @deprecated\n * @param {string} xmlid The xmlid of the template that defines the public asset\n * @param {ORM} orm An ORM object capable of calling methods on models\n * @returns {Promise} Resolved when the contents of the asset is loaded\n */\nexport const loadPublicAsset = memoize(async function loadPublicAsset(xmlid, orm) {\n    const xml = await orm.call(\"ir.ui.view\", \"render_public_asset\", [xmlid]);\n    const doc = new DOMParser().parseFromString(`<xml>${xml}</xml>`, \"text/xml\");\n    return loadAssets({\n        cssLibs: [...doc.querySelectorAll(\"link[href]\")].map((node) => node.getAttribute(\"href\")),\n        jsLibs: [...doc.querySelectorAll(\"script[src]\")].map((node) => node.getAttribute(\"src\")),\n    });\n});\n/**\n * Loads the given assets. Currently, when passing bundles, only the templates\n * key is supported, as loading a bundle's JS/CSS asynchronously requires some\n * groundwork.\n *\n * @param {Object} assets\n * @param {Bundles} [assets.bundles] an object describing the bundles to load.\n *      The keys are bundle names, and the values describe whether to load the\n *      templates, js and/or css from that bundle.\n * @param {string[]} [assets.jsLibs] urls of the javascript libraries to load\n * @param {string[]} [assets.cssLibs] urls of the css libraries to load\n * @returns {Promise<{[bundles: LoadedBundle[]]}>} An object describing the loaded\n *      assets. The js and css libs are loaded globally and will be loaded when\n *      this promise is resolved, the owl xml templates will be loaded in the\n *      `templates` key of each LoadedBundle object.\n */\nexport async function loadAssets(assets) {\n    const proms = [];\n    const loadedAssets = {};\n    if (\"bundles\" in assets) {\n        const bundles = Object.entries(assets.bundles);\n        const bundlesProm = Promise.all(\n            bundles.map(async ([name, options]) => [name, await loadBundle(name, options)])\n        ).then((loadedBundles) => {\n            loadedAssets.bundles = Object.fromEntries(loadedBundles);\n        });\n        proms.push(bundlesProm);\n    }\n    if (\"jsLibs\" in assets) {\n        proms.push(Promise.all(assets.jsLibs.map(loadJS)));\n    }\n    if (\"cssLibs\" in assets) {\n        proms.push(Promise.all(assets.cssLibs.map(loadCSS)));\n    }\n    await Promise.all(proms);\n    return loadedAssets;\n}\n/**\n * Loads the given assets, and adds the loaded owl templates into the current\n * environment's qweb instance.\n *\n * @param {Object} assets\n * @param {Bundles} [assets.bundles] the bundles to load. See above typedef for\n *      details.\n * @param {string[]} [assets.jsLibs] urls of the javascript libraries to load\n * @param {string[]} [assets.cssLibs] urls of the css libraries to load\n */\nexport function useAssets(assets) {\n    const env = useEnv();\n    onWillStart(async () => {\n        const loadedAssets = await loadAssets(assets);\n        // TODO: { js, css } = loadedAssets when we support lazy loading js/css from bundles\n        const { bundles } = loadedAssets;\n        if (bundles) {\n            const templateDocs = Object.values(bundles).map(({ templates }) => templates);\n            // Some templates might already be defined by another bundle, but need\n            // to be included in the current bundle for primary inherits to work.\n            // We don't want to add those again. We also have to add them to qweb\n            // one bundle at a time in case multiple bundles were specified that\n            // include the same template (e.g. for inheritance)\n            for (const xmlDoc of templateDocs) {\n                for (const template of [...xmlDoc.querySelector(\"templates\").children]) {\n                    if (template.getAttribute(\"t-name\") in env.qweb.templates) {\n                        template.remove();\n                    }\n                }\n                env.qweb.addTemplates(xmlDoc);\n            }\n        }\n    });\n}\n", "/** @odoo-module **/\n\n/**\n * Browser\n *\n * This file exports an object containing common browser API. It may not look\n * incredibly useful, but it is very convenient when one needs to test code using\n * these methods. With this indirection, it is possible to patch the browser\n * object for a test.\n */\n\nlet sessionStorage = window.sessionStorage;\nlet localStorage = owl.browser.localStorage;\ntry {\n    // Safari crashes in Private Browsing\n    localStorage.setItem(\"__localStorage__\", \"true\");\n    localStorage.removeItem(\"__localStorage__\");\n} catch (e) {\n    localStorage = makeRAMLocalStorage();\n    sessionStorage = makeRAMLocalStorage();\n}\n\nexport const browser = Object.assign({}, owl.browser, {\n    addEventListener: window.addEventListener.bind(window),\n    removeEventListener: window.removeEventListener.bind(window),\n    requestAnimationFrame: window.requestAnimationFrame.bind(window),\n    cancelAnimationFrame: window.cancelAnimationFrame.bind(window),\n    console: window.console,\n    history: window.history,\n    navigator: navigator,\n    open: window.open.bind(window),\n    XMLHttpRequest: window.XMLHttpRequest,\n    localStorage,\n    sessionStorage,\n});\n\nObject.defineProperty(browser, \"location\", {\n    set(val) {\n        window.location = val;\n    },\n    get() {\n        return window.location;\n    },\n    configurable: true,\n});\n\n// -----------------------------------------------------------------------------\n// memory localStorage\n// -----------------------------------------------------------------------------\n\n/**\n * @returns {typeof window[\"localStorage\"]}\n */\nexport function makeRAMLocalStorage() {\n    let store = {};\n    return {\n        setItem(key, value) {\n            store[key] = value;\n        },\n        getItem(key) {\n            return store[key];\n        },\n        clear() {\n            store = {};\n        },\n        removeItem(key) {\n            delete store[key];\n        },\n        get length() {\n            return Object.keys(store).length;\n        },\n        key() {\n            return \"\";\n        },\n    };\n}\n", "/** @odoo-module **/\n\nimport { registry } from \"../registry\";\n\n/**\n * Service to make use of document.cookie\n * https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies\n * As recommended, storage should not be done by the cookie\n * but with localStorage/sessionStorage\n */\n\nconst COOKIE_TTL = 24 * 60 * 60 * 365;\n\nfunction parseCookieString(str) {\n    const cookie = {};\n    const parts = str.split(\"; \");\n    for (let part of parts) {\n        const [key, value] = part.split(\"=\");\n        cookie[key] = value || \"\";\n    }\n    return cookie;\n}\n\nfunction cookieToString(key, value, ttl = COOKIE_TTL) {\n    let fullCookie = [];\n    if (value !== undefined) {\n        fullCookie.push(`${key}=${value}`);\n    }\n    fullCookie = fullCookie.concat([\"path=/\", `max-age=${ttl}`]);\n    return fullCookie.join(\";\");\n}\n\nfunction makeCookieService() {\n    function getCurrent() {\n        return parseCookieString(document.cookie);\n    }\n    let cookie = getCurrent();\n    function setCookie(key, value, ttl) {\n        document.cookie = cookieToString(key, value, ttl);\n        cookie = getCurrent();\n    }\n    return {\n        get current() {\n            return cookie;\n        },\n        setCookie,\n        deleteCookie(key) {\n            setCookie(key, \"kill\", 0);\n        },\n    };\n}\n\nexport const cookieService = {\n    start() {\n        return makeCookieService();\n    },\n};\n\nregistry.category(\"services\").add(\"cookie\", cookieService);\n", "/** @odoo-module **/\n\nimport { browser } from \"./browser\";\n\n// -----------------------------------------------------------------------------\n// Feature detection\n// -----------------------------------------------------------------------------\n\n/**\n * true if the browser is based on Chromium (Google Chrome, Opera, Edge)\n *\n * @returns {boolean}\n */\nexport function isBrowserChrome() {\n    return browser.navigator.userAgent.includes(\"Chrome\");\n}\n\nexport function isAndroid() {\n    return /Android/i.test(browser.navigator.userAgent);\n}\n\nexport function isIOS() {\n    return /(iPad|iPhone|iPod)/i.test(browser.navigator.userAgent) ||\n        (browser.navigator.platform === 'MacIntel' && maxTouchPoints() > 1);\n}\n\nexport function isOtherMobileOS() {\n    return /(webOS|BlackBerry|Windows Phone)/i.test(browser.navigator.userAgent);\n}\n\nexport function isMacOS() {\n    return Boolean(browser.navigator.userAgent.match(/Mac/i));\n}\n\nexport function isMobileOS() {\n    return isAndroid() || isIOS() || isOtherMobileOS();\n}\n\nexport function isIosApp() {\n    return /OdooMobile \\(iOS\\)/i.test(browser.navigator.userAgent);\n}\n\nexport function hasTouch() {\n    return \"ontouchstart\" in window || \"onmsgesturechange\" in window;\n}\n\nexport function maxTouchPoints() {\n    return browser.navigator.maxTouchPoints || 1;\n}\n", "/** @odoo-module **/\n\nimport { registry } from \"../registry\";\nimport { shallowEqual } from \"../utils/objects\";\nimport { objectToUrlEncodedString } from \"../utils/urls\";\nimport { browser } from \"./browser\";\n\n/**\n * Casts the given string to a number if possible.\n *\n * @param {string} value\n * @returns {string|number}\n */\nfunction cast(value) {\n    return !value || isNaN(value) ? value : Number(value);\n}\n\n/**\n * @typedef {{ [key: string]: string }} Query\n * @typedef {{ [key: string]: any }} Route\n */\n\nfunction parseString(str) {\n    const parts = str.split(\"&\");\n    const result = {};\n    for (let part of parts) {\n        const [key, value] = part.split(\"=\");\n        const decoded = decodeURIComponent(value || \"\");\n        result[key] = cast(decoded);\n    }\n    return result;\n}\n\n/**\n * For each push request (replaceState or pushState), filterout keys that have been locked before\n * overrides locked keys that are explicitly re-locked or unlocked\n * registers keys in \"hash\" in \"lockedKeys\" according to the \"lock\" Boolean\n *\n * @param {Set<string>} lockedKeys A set containing all keys that were locked\n * @param {Query} hash An Object representing the pushed url hash\n * @param {Query} currentHash The current hash compare against\n * @param  {Object} [options={}] Whether to lock all hash keys in \"hash\" to prevent them from being changed afterwards\n * @param  {Boolean} [options.lock] Whether to lock all hash keys in \"hash\" to prevent them from being changed afterwards\n * @return {Query} The resulting \"hash\" where previous locking has been applied\n */\nfunction applyLocking(lockedKeys, hash, currentHash, options = {}) {\n    const newHash = {};\n    for (const key in hash) {\n        if (\"lock\" in options) {\n            options.lock ? lockedKeys.add(key) : lockedKeys.delete(key);\n        } else if (lockedKeys.has(key)) {\n            // forbid implicit override of key\n            continue;\n        }\n        newHash[key] = hash[key];\n    }\n    for (const key in currentHash) {\n        if (lockedKeys.has(key) && !(key in newHash)) {\n            newHash[key] = currentHash[key];\n        }\n    }\n    return newHash;\n}\n\nfunction computeNewRoute(hash, replace, currentRoute) {\n    if (!replace) {\n        hash = Object.assign({}, currentRoute.hash, hash);\n    }\n    hash = sanitizeHash(hash);\n    if (!shallowEqual(currentRoute.hash, hash)) {\n        return Object.assign({}, currentRoute, { hash });\n    }\n    return false;\n}\n\nfunction sanitizeHash(hash) {\n    return Object.fromEntries(\n        Object.entries(hash)\n            .filter(([, v]) => v !== undefined)\n            .map(([k, v]) => [k, cast(v)])\n    );\n}\n\n/**\n * @param {string} hash\n * @returns {any}\n */\nexport function parseHash(hash) {\n    return hash && hash !== \"#\" ? parseString(hash.slice(1)) : {};\n}\n\n/**\n * @param {string} search\n * @returns {any}\n */\nexport function parseSearchQuery(search) {\n    return search ? parseString(search.slice(1)) : {};\n}\n\n/**\n * @param {{ [key: string]: any }} route\n * @returns\n */\nexport function routeToUrl(route) {\n    const search = objectToUrlEncodedString(route.search);\n    const hash = objectToUrlEncodedString(route.hash);\n    return route.pathname + (search ? \"?\" + search : \"\") + (hash ? \"#\" + hash : \"\");\n}\n\nasync function redirect(env, url, wait = false) {\n    if (wait) {\n        await new Promise((resolve) => {\n            const waitForServer = (delay) => {\n                browser.setTimeout(async () => {\n                    env.services\n                        .rpc(\"/web/webclient/version_info\", {})\n                        .then(resolve)\n                        .catch(() => waitForServer(250));\n                }, delay);\n            };\n            waitForServer(1000);\n        });\n    }\n    browser.location.assign(url);\n}\n\nfunction getRoute(urlObj) {\n    const { pathname, search, hash } = urlObj;\n    const searchQuery = parseSearchQuery(search);\n    const hashQuery = parseHash(hash);\n    return { pathname, search: searchQuery, hash: hashQuery };\n}\n\nfunction makeRouter(env) {\n    const bus = env.bus;\n    const lockedKeys = new Set();\n    let current = getRoute(browser.location);\n    let pushTimeout;\n    browser.addEventListener(\"hashchange\", (ev) => {\n        browser.clearTimeout(pushTimeout);\n        const loc = new URL(ev.newURL);\n        current = getRoute(loc);\n        bus.trigger(\"ROUTE_CHANGE\");\n    });\n\n    /**\n     * @param {string} mode\n     * @returns {(hash: string, options: any) => any}\n     */\n    function makeDebouncedPush(mode) {\n        let allPushArgs = [];\n        function doPush() {\n            // Aggregates push/replace state arguments\n            const replace = allPushArgs.some(([, options]) => options && options.replace);\n            const newHash = allPushArgs.reduce((finalHash, [hash, options]) => {\n                hash = applyLocking(lockedKeys, hash, current.hash, options);\n                if (finalHash) {\n                    hash = applyLocking(lockedKeys, hash, finalHash, options);\n                }\n                return Object.assign(finalHash || {}, hash);\n            }, null);\n            // Calculates new route based on aggregated hash and options\n            const newRoute = computeNewRoute(newHash, replace, current);\n            if (!newRoute) {\n                return;\n            }\n            // If the route changed: pushes or replaces browser state\n            const url = browser.location.origin + routeToUrl(newRoute);\n            if (mode === \"push\") {\n                browser.history.pushState({}, \"\", url);\n            } else {\n                browser.history.replaceState({}, \"\", url);\n            }\n            current = getRoute(browser.location);\n        }\n        return function pushOrReplaceState(hash, options) {\n            allPushArgs.push([hash, options]);\n            browser.clearTimeout(pushTimeout);\n            pushTimeout = browser.setTimeout(() => {\n                doPush();\n                pushTimeout = null;\n                allPushArgs = [];\n            });\n        };\n    }\n\n    return {\n        get current() {\n            return current;\n        },\n        pushState: makeDebouncedPush(\"push\"),\n        replaceState: makeDebouncedPush(\"replace\"),\n        redirect: (url, wait) => redirect(env, url, wait),\n        cancelPushes: () => browser.clearTimeout(pushTimeout),\n    };\n}\n\nexport const routerService = {\n    start(env) {\n        return makeRouter(env);\n    },\n};\n\nexport function objectToQuery(obj) {\n    const query = {};\n    Object.entries(obj).forEach(([k, v]) => {\n        query[k] = v ? String(v) : v;\n    });\n    return query;\n}\n\nregistry.category(\"services\").add(\"router\", routerService);\n", "/** @odoo-module **/\n\nimport { registry } from \"../registry\";\n\nexport const titleService = {\n    start() {\n        const titleParts = {};\n\n        function getParts() {\n            return Object.assign({}, titleParts);\n        }\n\n        function setParts(parts) {\n            for (const key in parts) {\n                const val = parts[key];\n                if (!val) {\n                    delete titleParts[key];\n                } else {\n                    titleParts[key] = val;\n                }\n            }\n            document.title = Object.values(titleParts).join(\" - \");\n        }\n\n        return {\n            /**\n             * @returns {string}\n             */\n            get current() {\n                return document.title;\n            },\n            getParts,\n            setParts,\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"title\", titleService);\n", "/** @odoo-module **/\n\nconst { Component, QWeb } = owl;\n\n/**\n * Custom checkbox\n *\n * <CheckBox\n *    value=\"boolean\"\n *    disabled=\"boolean\"\n *    t-on-change=\"_onValueChange\"\n *    >\n *    Change the label text\n *  </CheckBox>\n *\n * @extends Component\n */\n\nexport class CheckBox extends Component {\n    setup() {\n        this.id = `checkbox-comp-${CheckBox.nextId++}`;\n    }\n}\n\nCheckBox.template = \"web.CheckBox\";\nCheckBox.nextId = 1;\n\nQWeb.registerComponent(\"CheckBox\", CheckBox);\n", "/** @odoo-module */\n\nimport { Dialog } from \"../dialog/dialog\";\nimport { _lt } from \"../l10n/translation\";\n\nexport class ConfirmationDialog extends Dialog {\n    setup() {\n        super.setup();\n        this.title = this.props.title;\n    }\n    _cancel() {\n        if (this.props.cancel) {\n            this.props.cancel();\n        }\n        this.close();\n    }\n\n    _confirm() {\n        if (this.props.confirm) {\n            this.props.confirm();\n        }\n        this.close();\n    }\n}\nConfirmationDialog.props = {\n    title: {\n        validate: (m) => {\n            return (\n                typeof m === \"string\" || (typeof m === \"object\" && typeof m.toString === \"function\")\n            );\n        },\n    },\n    body: String,\n    confirm: { type: Function, optional: true },\n    cancel: { type: Function, optional: true },\n    close: Function,\n};\nConfirmationDialog.defaultProps = {\n    title: _lt(\"Confirmation\"),\n};\n\nConfirmationDialog.bodyTemplate = \"web.ConfirmationDialogBody\";\nConfirmationDialog.footerTemplate = \"web.ConfirmationDialogFooter\";\nConfirmationDialog.size = \"modal-md\";\n\nexport class AlertDialog extends ConfirmationDialog {\n    setup() {\n        super.setup();\n        if (\"contentClass\" in this.props) {\n            this.contentClass = this.props.contentClass;\n        }\n    }\n}\nAlertDialog.size = \"modal-sm\";\nAlertDialog.props = Object.assign(Object.create(ConfirmationDialog.props), {\n    contentClass: { type: String, optional: true },\n});\n\nAlertDialog.defaultProps = {\n    title: _lt(\"Alert\"),\n};\n", "/** @odoo-module **/\n\nimport { evaluateExpr } from \"./py_js/py\";\n\n/**\n * @typedef {{[key: string]: any}} Context\n * @typedef {Context | string | undefined} ContextDescription\n */\n\n/**\n * Create an evaluated context from an arbitrary list of context representations.\n * The evaluated context in construction is used along the way to evaluate further parts.\n *\n * @param {ContextDescription[]} contexts\n * @param {Context} [initialEvaluationContext] optional evaluation context to start from.\n * @returns {Context}\n */\nexport function makeContext(contexts, initialEvaluationContext) {\n    const evaluationContext = Object.assign({}, initialEvaluationContext);\n    const context = {};\n    for (let ctx of contexts) {\n        if (ctx !== \"\") {\n            ctx = typeof ctx === \"string\" ? evaluateExpr(ctx, evaluationContext) : ctx;\n            Object.assign(context, ctx);\n            Object.assign(evaluationContext, context); // is this behavior really wanted ?\n        }\n    }\n    return context;\n}\n", "/** @odoo-module **/\n\nimport { localization } from \"@web/core/l10n/localization\";\nimport { registry } from \"@web/core/registry\";\nimport { useAutofocus } from \"@web/core/utils/hooks\";\nimport { isIOS } from \"@web/core/browser/feature_detection\";\n\nconst { Component, hooks } = owl;\nconst { DateTime } = luxon;\nconst { useExternalListener, useRef, useState } = hooks;\n\nconst formatters = registry.category(\"formatters\");\nconst parsers = registry.category(\"parsers\");\n\nlet datePickerId = 0;\n\n/**\n * @param {DateTime} date\n * @returns {moment}\n */\nconst luxonDateToMomentDate = (date) => {\n    return window.moment(String(date));\n};\n\n/**\n * @param {string} format\n * @returns {string}\n */\nconst luxonFormatToMomentFormat = (format) => {\n    return format.replace(/[dy]/g, (x) => x.toUpperCase());\n};\n\n/**\n * @param {string} format\n * @returns {boolean}\n */\nconst isValidStaticFormat = (format) => {\n    try {\n        return /^[\\d\\s\\/.,:-]+$/.test(DateTime.local().toFormat(format));\n    } catch (_err) {\n        return false;\n    }\n};\n\n/**\n * Date picker\n *\n * This component exposes the API of the tempusdominus datepicker library.\n * As such, its template is a simple input that will open the TD datepicker\n * when clicked on. The component will also synchronize any user-input value\n * with the library widget and vice-versa.\n *\n * Note that all props given to this component will be passed as arguments to\n * instantiate the picker widget. Also any luxon date is automatically\n * stringified since tempusdominus only works with moment objects.\n * @extends Component\n */\nexport class DatePicker extends Component {\n    setup() {\n        this.inputRef = useRef(\"input\");\n        this.state = useState({ warning: false });\n\n        this.datePickerId = `o_datepicker_${datePickerId++}`;\n        // Manually keep track of the \"open\" state to write the date in the\n        // static format just before bootstrap parses it.\n        this.datePickerShown = false;\n\n        this.initFormat();\n        this.setDate(this.props);\n        this.setFormat(this.props);\n\n        useAutofocus();\n        useExternalListener(window, \"scroll\", this.onWindowScroll);\n    }\n\n    mounted() {\n        this.bootstrapDateTimePicker(this.props);\n        this.updateInput();\n\n        window.$(this.el).on(\"show.datetimepicker\", () => {\n            this.datePickerShown = true;\n            this.inputRef.el.select();\n        });\n        window.$(this.el).on(\"hide.datetimepicker\", () => {\n            this.datePickerShown = false;\n            this.onDateChange({ useStatic: true });\n        });\n        window.$(this.el).on(\"error.datetimepicker\", () => false);\n    }\n\n    willUpdateProps(nextProps) {\n        const pickerParams = {};\n        let shouldUpdateInput = false;\n        for (const prop in nextProps) {\n            const prev = this.props[prop];\n            const next = nextProps[prop];\n            if ((prev instanceof DateTime && !prev.equals(next)) || prev !== next) {\n                pickerParams[prop] = nextProps[prop];\n                if (prop === \"date\") {\n                    this.setDate(nextProps);\n                    shouldUpdateInput = true;\n                } else if (prop === \"format\") {\n                    this.setFormat(nextProps);\n                    shouldUpdateInput = true;\n                }\n            }\n        }\n        if (shouldUpdateInput) {\n            this.updateInput();\n        }\n        this.bootstrapDateTimePicker(pickerParams);\n    }\n\n    willUnmount() {\n        window.$(this.el).off(); // Removes all jQuery events\n\n        this.bootstrapDateTimePicker(\"destroy\");\n    }\n\n    //---------------------------------------------------------------------\n    // Protected\n    //---------------------------------------------------------------------\n\n    getOptions(useStatic = false) {\n        return {\n            format: !useStatic || isValidStaticFormat(this.format) ? this.format : this.staticFormat,\n            locale: this.date.locale,\n            timezone: this.isLocal,\n        };\n    }\n\n    /**\n     * Initialises formatting and parsing parameters\n     */\n    initFormat() {\n        this.defaultFormat = localization.dateFormat;\n        this.staticFormat = \"yyyy/MM/dd\";\n        this.formatValue = formatters.get(\"date\");\n        this.parseValue = parsers.get(\"date\");\n        this.isLocal = false;\n    }\n\n    /**\n     * Sets the current date value. If a locale is provided, the given date\n     * will first be set in that locale.\n     * @param {Object} params\n     * @param {DateTime} params.date\n     * @param {string} [params.locale]\n     */\n    setDate({ date, locale }) {\n        this.date = locale ? date.setLocale(locale) : date;\n    }\n\n    /**\n     * Sets the current format.\n     * @param {Object} params\n     * @param {string} [params.format]\n     */\n    setFormat({ format }) {\n        // Fallback to default localization format in `@web/core/l10n/dates.js`.\n        this.format = format || this.defaultFormat;\n    }\n\n    /**\n     * Updates the input element with the current formatted date value.\n     * @param {Object} [params={}]\n     * @param {boolean} [params.useStatic]\n     */\n    updateInput({ useStatic } = {}) {\n        try {\n            this.inputRef.el.value = this.formatValue(this.date, this.getOptions(useStatic));\n        } catch (_err) {\n            // Ignore\n        }\n    }\n\n    //---------------------------------------------------------------------\n    // Bootstrap datepicker\n    //---------------------------------------------------------------------\n\n    /**\n     * Handles bootstrap datetimepicker calls.\n     * @param {string | Object} commandOrParams\n     */\n    bootstrapDateTimePicker(commandOrParams) {\n        if (typeof commandOrParams === \"object\") {\n            const format = isValidStaticFormat(this.format) ? this.format : this.staticFormat;\n            const params = { ...commandOrParams, format: luxonFormatToMomentFormat(format) };\n            if (!params.locale && commandOrParams.date) {\n                params.locale = commandOrParams.date.locale;\n            }\n            for (const prop in params) {\n                if (params[prop] instanceof DateTime) {\n                    const luxonDate = params[prop];\n                    const momentDate = luxonDateToMomentDate(luxonDate);\n                    params[prop] = momentDate;\n                }\n            }\n            commandOrParams = params;\n        }\n        return window.$(this.el).datetimepicker(commandOrParams);\n    }\n\n    //---------------------------------------------------------------------\n    // Handlers\n    //---------------------------------------------------------------------\n\n    onInputClick() {\n        if (!this.datePickerShown) {\n            this.updateInput({ useStatic: true });\n        }\n        this.bootstrapDateTimePicker(\"toggle\");\n    }\n\n    /**\n     * Called either when the input value has changed or when the boostrap\n     * datepicker is closed. The actual \"datetime-changed\" emitted by the\n     * component is only triggered if the date value has changed.\n     * @param {Object} [params={}]\n     * @param {boolean} [params.useStatic]\n     */\n    onDateChange({ useStatic } = {}) {\n        let date;\n        try {\n            date = this.parseValue(this.inputRef.el.value, this.getOptions(useStatic));\n        } catch (_err) {\n            // Reset to default (= given) date.\n        }\n        if (!date || date.equals(this.date)) {\n            this.updateInput();\n        } else {\n            this.state.warning = date > DateTime.local();\n            this.trigger(\"datetime-changed\", { date });\n        }\n    }\n\n    /**\n     * @param {Event} ev\n     */\n    onWindowScroll(ev) {\n        if (!isIOS() && ev.target !== this.inputRef.el) {\n            this.bootstrapDateTimePicker(\"hide\");\n        }\n    }\n}\n\nDatePicker.defaultProps = {\n    calendarWeeks: true,\n    icons: {\n        clear: \"fa fa-delete\",\n        close: \"fa fa-check primary\",\n        date: \"fa fa-calendar\",\n        down: \"fa fa-chevron-down\",\n        next: \"fa fa-chevron-right\",\n        previous: \"fa fa-chevron-left\",\n        time: \"fa fa-clock-o\",\n        today: \"fa fa-calendar-check-o\",\n        up: \"fa fa-chevron-up\",\n    },\n    maxDate: DateTime.fromObject({ year: 9999, month: 12, day: 31 }),\n    minDate: DateTime.fromObject({ year: 1000 }),\n    useCurrent: false,\n    widgetParent: \"body\",\n};\nDatePicker.props = {\n    // Components props\n    date: DateTime,\n    warn_future: { type: Boolean, optional: true },\n    // Bootstrap datepicker options\n    buttons: {\n        type: Object,\n        shape: {\n            showClear: Boolean,\n            showClose: Boolean,\n            showToday: Boolean,\n        },\n        optional: true,\n    },\n    calendarWeeks: Boolean,\n    format: { type: String, optional: true },\n    icons: {\n        type: Object,\n        shape: {\n            clear: String,\n            close: String,\n            date: String,\n            down: String,\n            next: String,\n            previous: String,\n            time: String,\n            today: String,\n            up: String,\n        },\n    },\n    keyBinds: { validate: (kb) => typeof kb === \"object\" || kb === null, optional: true },\n    locale: { type: String, optional: true },\n    maxDate: DateTime,\n    minDate: DateTime,\n    readonly: { type: Boolean, optional: true },\n    useCurrent: Boolean,\n    widgetParent: String,\n};\nDatePicker.template = \"web.DatePicker\";\n\n/**\n * Date/time picker\n *\n * Similar to the DatePicker component, adding the handling of more specific\n * time values: hour-minute-second.\n *\n * Once again, refer to the tempusdominus documentation for implementation\n * details.\n * @extends DatePicker\n */\nexport class DateTimePicker extends DatePicker {\n    /**\n     * @override\n     */\n    initFormat() {\n        this.defaultFormat = localization.dateTimeFormat;\n        this.staticFormat = \"yyyy/MM/dd HH:mm:ss\";\n        this.formatValue = formatters.get(\"datetime\");\n        this.parseValue = parsers.get(\"datetime\");\n        this.isLocal = true;\n    }\n}\n\nDateTimePicker.defaultProps = {\n    ...DatePicker.defaultProps,\n    buttons: {\n        showClear: false,\n        showClose: true,\n        showToday: false,\n    },\n};\n", "/** @odoo-module **/\n\nimport { registry } from \"../registry\";\nimport { memoize } from \"../utils/functions\";\nimport { useEffect } from \"@web/core/utils/hooks\";\n\nconst { useEnv, useSubEnv } = owl.hooks;\nconst debugRegistry = registry.category(\"debug\");\n\nconst getAccessRights = memoize(async function getAccessRights(orm) {\n    const rightsToCheck = {\n        \"ir.ui.view\": \"write\",\n        \"ir.rule\": \"read\",\n        \"ir.model.access\": \"read\",\n    };\n    const proms = Object.entries(rightsToCheck).map(([model, operation]) => {\n        return orm.call(model, \"check_access_rights\", [], {\n            operation,\n            raise_exception: false,\n        });\n    });\n    const [canEditView, canSeeRecordRules, canSeeModelAccess] = await Promise.all(proms);\n    const accessRights = { canEditView, canSeeRecordRules, canSeeModelAccess };\n    return accessRights;\n});\n\nclass DebugContext {\n    constructor(env, defaultCategories) {\n        this.orm = env.services.orm;\n        this.categories = new Map(defaultCategories.map((cat) => [cat, [{}]]));\n    }\n\n    activateCategory(category, context) {\n        const contexts = this.categories.get(category) || new Set();\n        contexts.add(context);\n        this.categories.set(category, contexts);\n\n        return () => {\n            contexts.delete(context);\n            if (contexts.size === 0) {\n                this.categories.delete(category);\n            }\n        };\n    }\n\n    async getItems(env) {\n        const accessRights = await getAccessRights(this.orm);\n        return [...this.categories.entries()]\n            .flatMap(([category, contexts]) => {\n                return debugRegistry\n                    .category(category)\n                    .getAll()\n                    .map((factory) => factory(Object.assign({ env, accessRights }, ...contexts)));\n            })\n            .filter(Boolean)\n            .sort((x, y) => {\n                const xSeq = x.sequence || 1000;\n                const ySeq = y.sequence || 1000;\n                return xSeq - ySeq;\n            });\n    }\n}\n\nconst debugContextSymbol = Symbol(\"debugContext\");\nexport function createDebugContext(env, { categories = [] } = {}) {\n    return { [debugContextSymbol]: new DebugContext(env, categories) };\n}\n\nexport function useOwnDebugContext({ categories = [] } = {}) {\n    useSubEnv(createDebugContext(useEnv(), { categories }));\n}\n\nexport function useEnvDebugContext() {\n    const debugContext = useEnv()[debugContextSymbol];\n    if (!debugContext) {\n        throw new Error(\"There is no debug context available in the current environment.\");\n    }\n    return debugContext;\n}\n\nexport function useDebugCategory(category, context = {}) {\n    const env = useEnv();\n    if (env.debug) {\n        const debugContext = useEnvDebugContext();\n        useEffect(\n            () => debugContext.activateCategory(category, context),\n            () => []\n        );\n    }\n}\n", "/** @odoo-module **/\n\nimport { useEnvDebugContext } from \"./debug_context\";\n\nconst { Component } = owl;\n\nexport class DebugMenuBasic extends Component {\n    setup() {\n        const debugContext = useEnvDebugContext();\n        // Needs to be bound to this for use in template\n        this.getElements = async () => {\n            this.elements = await debugContext.getItems(this.env);\n        };\n    }\n}\nDebugMenuBasic.template = \"web.DebugMenu\";\n", "/** @odoo-module **/\n\nimport { browser } from \"@web/core/browser/browser\";\nimport { routeToUrl } from \"@web/core/browser/router_service\";\nimport { registry } from \"@web/core/registry\";\n\nfunction activateAssetsDebugging({ env }) {\n    return {\n        type: \"item\",\n        description: env._t(\"Activate Assets Debugging\"),\n        callback: () => {\n            browser.location.search = \"?debug=assets\";\n        },\n        sequence: 410,\n    };\n}\n\nfunction activateTestsAssetsDebugging({ env }) {\n    return {\n        type: \"item\",\n        description: env._t(\"Activate Tests Assets Debugging\"),\n        callback: () => {\n            browser.location.search = \"?debug=assets,tests\";\n        },\n        sequence: 420,\n    };\n}\n\nexport function regenerateAssets({ env }) {\n    return {\n        type: \"item\",\n        description: env._t(\"Regenerate Assets Bundles\"),\n        callback: async () => {\n            const domain = [\n                \"&\",\n                [\"res_model\", \"=\", \"ir.ui.view\"],\n                \"|\",\n                [\"name\", \"=like\", \"%.assets_%.css\"],\n                [\"name\", \"=like\", \"%.assets_%.js\"],\n            ];\n            const ids = await env.services.orm.search(\"ir.attachment\", domain);\n            await env.services.orm.unlink(\"ir.attachment\", ids);\n            browser.location.reload();\n        },\n        sequence: 430,\n    };\n}\n\nfunction becomeSuperuser({ env }) {\n    const becomeSuperuserURL = browser.location.origin + \"/web/become\";\n    return {\n        type: \"item\",\n        description: env._t(\"Become Superuser\"),\n        hide: !env.services.user.isAdmin,\n        href: becomeSuperuserURL,\n        callback: () => {\n            browser.open(becomeSuperuserURL, \"_self\");\n        },\n        sequence: 440,\n    };\n}\n\nfunction leaveDebugMode({ env }) {\n    return {\n        type: \"item\",\n        description: env._t(\"Leave the Developer Tools\"),\n        callback: () => {\n            const route = env.services.router.current;\n            route.search.debug = \"\";\n            browser.location.href = browser.location.origin + routeToUrl(route);\n        },\n        sequence: 450,\n    };\n}\n\nregistry\n    .category(\"debug\")\n    .category(\"default\")\n    .add(\"activateAssetsDebugging\", activateAssetsDebugging)\n    .add(\"regenerateAssets\", regenerateAssets)\n    .add(\"becomeSuperuser\", becomeSuperuser)\n    .add(\"leaveDebugMode\", leaveDebugMode)\n    .add(\"activateTestsAssetsDebugging\", activateTestsAssetsDebugging);\n", "/** @odoo-module */\n\nimport { registry } from \"../registry\";\nimport { browser } from \"../browser/browser\";\nimport { routeToUrl } from \"../browser/router_service\";\n\nconst commandProviderRegistry = registry.category(\"command_provider\");\n\ncommandProviderRegistry.add(\"debug\", {\n    provide: (env, options) => {\n        const result = [];\n        if (env.debug) {\n            result.push({\n                action() {\n                    const route = env.services.router.current;\n                    route.search.debug = \"\";\n                    browser.location.href = browser.location.origin + routeToUrl(route);\n                },\n                category: \"debug\",\n                name: env._t(\"Deactivate debug mode\"),\n            });\n        } else {\n            if (options.searchValue.toLowerCase() === \"debug\") {\n                result.push({\n                    action() {\n                        browser.location.search = \"?debug=assets\";\n                    },\n                    category: \"debug\",\n                    name: env._t(\"Activate debug mode\"),\n                });\n            }\n        }\n        return result;\n    },\n});\n", "/** @odoo-module **/\n\nexport function editModelDebug(env, title, model, id) {\n    return env.services.action.doAction({\n        res_model: model,\n        res_id: id,\n        name: title,\n        type: \"ir.actions.act_window\",\n        views: [[false, \"form\"]],\n        view_mode: \"form\",\n        target: \"new\",\n        flags: { action_buttons: true, headless: true },\n    });\n}\n", "/** @odoo-module **/\n\nimport { DropdownItem } from \"@web/core/dropdown/dropdown_item\";\nimport { useBus, useService } from \"@web/core/utils/hooks\";\n\nconst { Component } = owl;\n\nexport class ProfilingItem extends Component {\n    setup() {\n        this.profiling = useService(\"profiling\");\n        useBus(this.props.bus, \"UPDATE\", this.render);\n    }\n\n    changeParam(param, ev) {\n        this.profiling.setParam(param, ev.target.value);\n    }\n    toggleParam(param, ev) {\n        const value = this.profiling.state.params.execution_context_qweb;\n        this.profiling.setParam(param, !value);\n    }\n    openProfiles() {\n        if (this.env.services.action) {\n            // using doAction in the backend to preserve breadcrumbs and stuff\n            this.env.services.action.doAction(\"base.action_menu_ir_profile\");\n        } else {\n            // No action service means we are in the frontend.\n            window.location = \"/web/#action=base.action_menu_ir_profile\";\n        }\n    }\n}\nProfilingItem.components = { DropdownItem };\nProfilingItem.template = \"web.DebugMenu.ProfilingItem\";\n", "/** @odoo-module **/\n\nimport { registry } from \"@web/core/registry\";\nimport { ProfilingItem } from \"./profiling_item\";\nimport { session } from \"@web/session\";\n\nconst { core } = owl;\n\nconst profilingService = {\n    dependencies: [\"orm\"],\n    start(env, { orm }) {\n        const state = {\n            session: session.profile_session || false,\n            collectors: session.profile_collectors || [\"sql\", \"traces_async\"],\n            params: session.profile_params || {},\n            get isEnabled() {\n                return Boolean(state.session);\n            },\n        };\n        const bus = new core.EventBus();\n\n        let recordingIcon = null;\n        function updateDebugIcon() {\n            const debugItem = document.querySelector(\".o_main_navbar .o_debug_manager .fa-bug\");\n            if (state.isEnabled) {\n                recordingIcon = document.createElement(\"span\");\n                recordingIcon.classList.add(\"o_recording\", \"text-danger\", \"fa\", \"fa-circle\");\n                debugItem.appendChild(recordingIcon);\n            } else if (recordingIcon) {\n                debugItem.removeChild(recordingIcon);\n                recordingIcon = null;\n            }\n        }\n\n        if (env.debug) {\n            env.bus.on(\"WEB_CLIENT_READY\", null, updateDebugIcon);\n        }\n\n        async function setProfiling(params) {\n            const kwargs = Object.assign(\n                {\n                    collectors: state.collectors,\n                    params: state.params,\n                    profile: state.isEnabled,\n                },\n                params\n            );\n            const resp = await orm.call(\"ir.profile\", \"set_profiling\", [], kwargs);\n            if (resp.type) {  // most likely an \"ir.actions.act_window\"\n                env.services.action.doAction(resp);\n            } else {\n                state.session = resp.session;\n                state.collectors = resp.collectors;\n                state.params = resp.params;\n                bus.trigger(\"UPDATE\");\n                updateDebugIcon();\n            }\n        }\n\n        function profilingSeparator() {\n            return {\n                type: \"separator\",\n                sequence: 500,\n            };\n        }\n\n        function profilingItem() {\n            return {\n                type: \"component\",\n                Component: ProfilingItem,\n                props: { bus },\n                sequence: 510,\n            };\n        }\n\n        registry\n            .category(\"debug\")\n            .category(\"default\")\n            .add(\"profilingSeparator\", profilingSeparator)\n            .add(\"profilingItem\", profilingItem);\n\n        return {\n            state,\n            async toggleProfiling() {\n                await setProfiling({ profile: !state.isEnabled });\n            },\n            async toggleCollector(collector) {\n                let nextCollectors = state.collectors.slice();\n                const index = nextCollectors.indexOf(collector);\n                if (index >= 0) {\n                    nextCollectors.splice(index, 1);\n                } else {\n                    nextCollectors.push(collector);\n                }\n                await setProfiling({ collectors: nextCollectors });\n            },\n            async setParam(key, value) {\n                const nextParams = Object.assign({}, state.params);\n                nextParams[key] = value;\n                await setProfiling({ params: nextParams });\n            },\n            isCollectorEnabled(collector) {\n                return state.collectors.includes(collector);\n            },\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"profiling\", profilingService);\n", "/** @odoo-module **/\n\nimport { useHotkey } from \"@web/core/hotkeys/hotkey_hook\";\nimport { useActiveElement } from \"../ui/ui_service\";\n\nconst { Component, hooks } = owl;\nconst { useRef, useSubEnv } = hooks;\n\nexport class Dialog extends Component {\n    setup() {\n        if (this.constructor === Dialog) {\n            throw new Error(\n                \"Dialog should not be used by itself. Please use the dialog service with a Dialog subclass.\"\n            );\n        }\n        this.modalRef = useRef(\"modal\");\n        useActiveElement(\"modal\");\n        useHotkey(\"escape\", () => {\n            this.close();\n        });\n        useSubEnv({ inDialog: true });\n        this.close = this.close.bind(this);\n        this.contentClass = this.constructor.contentClass;\n        this.fullscreen = this.constructor.fullscreen;\n        this.renderFooter = this.constructor.renderFooter;\n        this.renderHeader = this.constructor.renderHeader;\n        this.size = this.constructor.size;\n        this.technical = this.constructor.technical;\n        this.title = this.constructor.title;\n    }\n\n    /**\n     * Send an event signaling that the dialog should be closed.\n     * @private\n     */\n    close() {\n        this.props.close();\n    }\n}\n\nDialog.template = \"web.Dialog\";\nDialog.contentClass = null;\nDialog.fullscreen = false;\nDialog.renderFooter = true;\nDialog.renderHeader = true;\nDialog.size = \"modal-lg\";\nDialog.technical = true;\nDialog.title = \"Odoo\";\nDialog.bodyTemplate = owl.tags.xml`<div/>`;\nDialog.footerTemplate = \"web.DialogFooterDefault\";\n", "/** @odoo-module **/\n\nimport { ErrorHandler, NotUpdatable } from \"../utils/components\";\n\nconst { Component, tags } = owl;\n\nexport class DialogContainer extends Component {\n    setup() {\n        this.props.bus.on(\"UPDATE\", this, this.render);\n    }\n\n    close(id) {\n        if (this.props.dialogs[id]) {\n            this.props.dialogs[id].props.close();\n        }\n    }\n\n    handleError(error, dialogId) {\n        this.close(dialogId);\n        Promise.resolve().then(() => {\n            throw error;\n        });\n    }\n}\nDialogContainer.components = { ErrorHandler, NotUpdatable };\nDialogContainer.template = tags.xml`\n    <div class=\"o_dialog_container\" t-att-class=\"{'modal-open': Object.keys(props.dialogs).length > 0}\">\n      <t t-foreach=\"Object.values(props.dialogs)\" t-as=\"dialog\" t-key=\"dialog.id\">\n        <NotUpdatable>\n            <ErrorHandler onError=\"(error) => handleError(error, dialog.id)\">\n                <t t-component=\"dialog.class\" t-props=\"dialog.props\"\n                    t-on-dialog-closed=\"dialog.props.close()\" \n                    t-att-class=\"{o_inactive_modal: !dialog_last}\"/>\n            </ErrorHandler>\n        </NotUpdatable>\n      </t>\n    </div>\n    `;\n", "/** @odoo-module **/\n\nimport { registry } from \"../registry\";\nimport { Dialog } from \"./dialog\";\nimport { DialogContainer } from \"./dialog_container\";\n\nconst { core } = owl;\nconst { EventBus } = core;\n\n/**\n *  @typedef {{\n *      onClose?(): void;\n *  }} DialogServiceInterfaceAddOptions\n */\n/**\n *  @typedef {{\n *      add(\n *          Component: any,\n *          props: {},\n *          options?: DialogServiceInterfaceAddOptions\n *      ): () => void;\n *  }} DialogServiceInterface\n */\n\nexport const dialogService = {\n    /** @returns {DialogServiceInterface} */\n    start() {\n        const dialogs = {};\n        const bus = new EventBus();\n        let dialogId = 0;\n\n        registry.category(\"main_components\").add(\"DialogContainer\", {\n            Component: DialogContainer,\n            props: { bus, dialogs },\n        });\n\n        function add(dialogClass, props, options = {}) {\n            if (!(dialogClass.prototype instanceof Dialog)) {\n                throw new Error(`${dialogClass.name} must be a subclass of Dialog`);\n            }\n\n            const id = ++dialogId;\n            function close() {\n                if (dialogs[id]) {\n                    delete dialogs[id];\n                    if (options.onClose) {\n                        options.onClose();\n                    }\n                    bus.trigger(\"UPDATE\");\n                }\n            }\n\n            const dialog = {\n                id,\n                class: dialogClass,\n                props: { ...props, close },\n            };\n            dialogs[id] = dialog;\n            bus.trigger(\"UPDATE\");\n\n            return close;\n        }\n\n        return { add };\n    },\n};\n\nregistry.category(\"services\").add(\"dialog\", dialogService);\n", "/** @odoo-module **/\n\nimport { evaluate, formatAST, parseExpr } from \"./py_js/py\";\nimport { toPyValue } from \"./py_js/py_utils\";\n\n/**\n * @typedef {import(\"./py_js/py_parser\").AST} AST\n * @typedef {[string, string, any]} Condition\n * @typedef {(\"&\" | \"|\" | \"!\" | Condition)[]} DomainListRepr\n * @typedef {DomainListRepr | string | Domain} DomainRepr\n */\n\nclass InvalidDomainError extends Error {}\n\n/**\n * Javascript representation of an Odoo domain\n */\nexport class Domain {\n    /**\n     * Combine various domains together with a given operator\n     * @param {DomainRepr[]} domains\n     * @param {\"AND\" | \"OR\"} operator\n     * @returns {Domain}\n     */\n    static combine(domains, operator) {\n        if (domains.length === 0) {\n            return new Domain([]);\n        }\n        const domain1 = domains[0] instanceof Domain ? domains[0] : new Domain(domains[0]);\n        if (domains.length === 1) {\n            return domain1;\n        }\n        const domain2 = Domain.combine(domains.slice(1), operator);\n        const result = new Domain([]);\n        const astValues1 = domain1.ast.value;\n        const astValues2 = domain2.ast.value;\n        const op = operator === \"AND\" ? \"&\" : \"|\";\n        const combinedAST = { type: 4 /* List */, value: astValues1.concat(astValues2) };\n        result.ast = normalizeDomainAST(combinedAST, op);\n        return result;\n    }\n\n    /**\n     * Combine various domains together with `AND` operator\n     * @param {DomainRepr} domains\n     * @returns {Domain}\n     */\n    static and(domains) {\n        return Domain.combine(domains, \"AND\");\n    }\n\n    /**\n     * Combine various domains together with `OR` operator\n     * @param {DomainRepr} domains\n     * @returns {Domain}\n     */\n    static or(domains) {\n        return Domain.combine(domains, \"OR\");\n    }\n\n    /**\n     * Return the negation of the domain\n     * @returns {Domain}\n     */\n    static not(domain) {\n        const result = new Domain(domain);\n        result.ast.value.unshift({ type: 1, value: \"!\" });\n        return result;\n    }\n\n    /**\n     * @param {DomainRepr} [descr]\n     */\n    constructor(descr = []) {\n        if (descr instanceof Domain) {\n            /** @type {AST} */\n            return new Domain(descr.toString());\n        } else {\n            const rawAST = typeof descr === \"string\" ? parseExpr(descr) : toAST(descr);\n            this.ast = normalizeDomainAST(rawAST);\n        }\n    }\n\n    /**\n     * Check if the set of records represented by a domain contains a record\n     *\n     * @param {Object} record\n     * @returns {boolean}\n     */\n    contains(record) {\n        const expr = evaluate(this.ast, record);\n        return matchDomain(record, expr);\n    }\n\n    /**\n     * @returns {string}\n     */\n    toString() {\n        return formatAST(this.ast);\n    }\n\n    /**\n     * @param {Object} context\n     * @returns {DomainListRepr}\n     */\n    toList(context) {\n        return evaluate(this.ast, context);\n    }\n}\n\nconst TRUE_LEAF = [1, \"=\", 1];\nconst FALSE_LEAF = [0, \"=\", 1];\nconst TRUE_DOMAIN = new Domain([TRUE_LEAF]);\nconst FALSE_DOMAIN = new Domain([FALSE_LEAF]);\n\nDomain.TRUE = TRUE_DOMAIN;\nDomain.FALSE = FALSE_DOMAIN;\n\n// -----------------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------------\n\n/**\n * @param {DomainListRepr} domain\n * @returns {AST}\n */\nfunction toAST(domain) {\n    const elems = domain.map((elem) => {\n        switch (elem) {\n            case \"!\":\n            case \"&\":\n            case \"|\":\n                return { type: 1 /* String */, value: elem };\n            default:\n                return {\n                    type: 10 /* Tuple */,\n                    value: elem.map(toPyValue),\n                };\n        }\n    });\n    return { type: 4 /* List */, value: elems };\n}\n\n/**\n * Normalizes a domain\n *\n * @param {AST} domain\n * @param {'&' | '|'} [op]\n * @returns {AST}\n */\n\nfunction normalizeDomainAST(domain, op = \"&\") {\n    if (domain.type !== 4 /* List */) {\n        throw new InvalidDomainError(\"Invalid domain AST\");\n    }\n    if (domain.value.length === 0) {\n        return domain;\n    }\n    let expected = 1;\n    for (let child of domain.value) {\n        if (child.type === 1 /* String */ && (child.value === \"&\" || child.value === \"|\")) {\n            expected++;\n        } else if (child.type !== 1 /* String */ || child.value !== \"!\") {\n            expected--;\n        }\n    }\n    let values = domain.value.slice();\n    while (expected < 0) {\n        expected++;\n        values.unshift({ type: 1 /* String */, value: op });\n    }\n    if (expected > 0) {\n        throw new InvalidDomainError(\n            `invalid domain ${formatAST(domain)} (missing ${expected} segment(s))`\n        );\n    }\n    return { type: 4 /* List */, value: values };\n}\n\n/**\n * @param {Object} record\n * @param {Condition | boolean} condition\n * @returns {boolean}\n */\nfunction matchCondition(record, condition) {\n    if (typeof condition === \"boolean\") {\n        return condition;\n    }\n    const [field, operator, value] = condition;\n    const fieldValue = typeof field === \"number\" ? field : record[field];\n    switch (operator) {\n        case \"=?\":\n            if ([false, null].includes(value)) {\n                return true;\n            }\n        // eslint-disable-next-line no-fallthrough\n        case \"=\":\n        case \"==\":\n            return fieldValue === value;\n        case \"!=\":\n        case \"<>\":\n            return fieldValue !== value;\n        case \"<\":\n            return fieldValue < value;\n        case \"<=\":\n            return fieldValue <= value;\n        case \">\":\n            return fieldValue > value;\n        case \">=\":\n            return fieldValue >= value;\n        case \"in\": {\n            const val = Array.isArray(value) ? value : [value];\n            const fieldVal = Array.isArray(fieldValue) ? fieldValue : [fieldValue];\n            return fieldVal.some((fv) => val.includes(fv));\n        }\n        case \"not in\": {\n            const val = Array.isArray(value) ? value : [value];\n            const fieldVal = Array.isArray(fieldValue) ? fieldValue : [fieldValue];\n            return !fieldVal.some((fv) => val.includes(fv));\n        }\n        case \"like\":\n            if (fieldValue === false) {\n                return false;\n            }\n            return fieldValue.indexOf(value) >= 0;\n        case \"=like\":\n            if (fieldValue === false) {\n                return false;\n            }\n            return new RegExp(value.replace(/%/g, \".*\")).test(fieldValue);\n        case \"ilike\":\n            if (fieldValue === false) {\n                return false;\n            }\n            return fieldValue.toLowerCase().indexOf(value.toLowerCase()) >= 0;\n        case \"=ilike\":\n            if (fieldValue === false) {\n                return false;\n            }\n            return new RegExp(value.replace(/%/g, \".*\"), \"i\").test(fieldValue);\n    }\n    throw new InvalidDomainError(\"could not match domain\");\n}\n\n/**\n * @param {Object} record\n * @returns {Object}\n */\nfunction makeOperators(record) {\n    const match = matchCondition.bind(null, record);\n    return {\n        \"!\": (x) => !match(x),\n        \"&\": (a, b) => match(a) && match(b),\n        \"|\": (a, b) => match(a) || match(b),\n    };\n}\n\n/**\n *\n * @param {Object} record\n * @param {DomainListRepr} domain\n * @returns {boolean}\n */\nfunction matchDomain(record, domain) {\n    if (domain.length === 0) {\n        return true;\n    }\n    const operators = makeOperators(record);\n    const reversedDomain = Array.from(domain).reverse();\n    const condStack = [];\n    for (const item of reversedDomain) {\n        if (item in operators) {\n            const operator = operators[item];\n            const operands = condStack.splice(-operator.length);\n            condStack.push(operator(...operands));\n        } else {\n            condStack.push(item);\n        }\n    }\n    return matchCondition(record, condStack.pop());\n}\n", "/** @odoo-module **/\n\nimport { useBus, useEffect, useService } from \"@web/core/utils/hooks\";\nimport { usePosition } from \"../position/position_hook\";\nimport { useDropdownNavigation } from \"./dropdown_navigation_hook\";\nimport { ParentClosingMode } from \"./dropdown_item\";\nimport { localization } from \"../l10n/localization\";\n\nconst { Component, core, hooks, useState, QWeb } = owl;\nconst { EventBus } = core;\nconst { onWillStart, useExternalListener, useRef, useSubEnv } = hooks;\n\nconst DIRECTION_CARET_CLASS = {\n    bottom: \"dropdown\",\n    top: \"dropup\",\n    left: \"dropleft\",\n    right: \"dropright\",\n};\n\n/**\n * @typedef DropdownState\n * @property {boolean} open\n * @property {boolean} groupIsOpen\n */\n\n/**\n * @typedef DropdownStateChangedPayload\n * @property {Dropdown} emitter\n * @property {DropdownState} newState\n */\n\n/**\n * @extends Component\n */\nexport class Dropdown extends Component {\n    setup() {\n        this.state = useState({\n            open: this.props.startOpen,\n            groupIsOpen: this.props.startOpen,\n        });\n\n        // Set up beforeOpen ---------------------------------------------------\n        onWillStart(() => {\n            if (this.state.open && this.props.beforeOpen) {\n                return this.props.beforeOpen();\n            }\n        });\n\n        // Set up dynamic open/close behaviours --------------------------------\n        if (!this.props.manualOnly) {\n            // Close on outside click listener\n            useExternalListener(window, \"click\", this.onWindowClicked);\n            // Listen to all dropdowns state changes\n            useBus(Dropdown.bus, \"state-changed\", this.onDropdownStateChanged);\n        }\n\n        // Set up UI active element related behavior ---------------------------\n        this.ui = useService(\"ui\");\n        useEffect(\n            () => {\n                Promise.resolve().then(() => {\n                    this.myActiveEl = this.ui.activeElement;\n                });\n            },\n            () => []\n        );\n\n        // Set up nested dropdowns ---------------------------------------------\n        this.hasParentDropdown = this.env.inDropdown;\n        useSubEnv({ inDropdown: true });\n\n        // Set up key navigation -----------------------------------------------\n        useDropdownNavigation();\n\n        // Set up toggler and positioning --------------------------------------\n        /** @type {string} **/\n        let position =\n            this.props.position || (this.hasParentDropdown ? \"right-start\" : \"bottom-start\");\n        let [direction, variant = \"middle\"] = position.split(\"-\");\n        if (localization.direction === \"rtl\") {\n            if ([\"bottom\", \"top\"].includes(direction)) {\n                variant = variant === \"start\" ? \"end\" : \"start\";\n            } else {\n                direction = direction === \"left\" ? \"right\" : \"left\";\n            }\n            position = [direction, variant].join(\"-\");\n        }\n        const positioningOptions = {\n            popper: \"menuRef\",\n            position,\n        };\n        this.directionCaretClass = DIRECTION_CARET_CLASS[direction];\n        this.togglerRef = useRef(\"togglerRef\");\n        if (this.props.toggler === \"parent\") {\n            // Add parent click listener to handle toggling\n            useEffect(\n                () => {\n                    const onClick = (ev) => {\n                        if (this.el.contains(ev.target)) {\n                            // ignore clicks inside the dropdown\n                            return;\n                        }\n                        this.toggle();\n                    };\n                    if (this.el.parentElement.tabIndex === -1) {\n                        // If the parent is not focusable, make it focusable programmatically.\n                        // This code may look weird, but an element with a negative tabIndex is\n                        // focusable programmatically ONLY if its tabIndex is explicitly set.\n                        this.el.parentElement.tabIndex = -1;\n                    }\n                    this.el.parentElement.addEventListener(\"click\", onClick);\n                    return () => {\n                        this.el.parentElement.removeEventListener(\"click\", onClick);\n                    };\n                },\n                () => []\n            );\n\n            useEffect(\n                (open) => {\n                    this.el.parentElement.ariaExpanded = open ? \"true\" : \"false\";\n                },\n                () => [this.state.open]\n            );\n\n            // Position menu relatively to parent element\n            usePosition(() => this.el.parentElement, positioningOptions);\n        } else {\n            // Position menu relatively to inner toggler\n            const togglerRef = useRef(\"togglerRef\");\n            usePosition(() => togglerRef.el, positioningOptions);\n        }\n    }\n\n    // -------------------------------------------------------------------------\n    // Private\n    // -------------------------------------------------------------------------\n\n    /**\n     * Changes the dropdown state and notifies over the Dropdown bus.\n     *\n     * All state changes must trigger on the bus, except when reacting to\n     * another dropdown state change.\n     *\n     * @see onDropdownStateChanged()\n     *\n     * @param {Partial<DropdownState>} stateSlice\n     */\n    async changeStateAndNotify(stateSlice) {\n        if (stateSlice.open && this.props.beforeOpen) {\n            await this.props.beforeOpen();\n        }\n        // Update the state\n        Object.assign(this.state, stateSlice);\n        // Notify over the bus\n        /** @type DropdownStateChangedPayload */\n        const stateChangedPayload = {\n            emitter: this,\n            newState: { ...this.state },\n        };\n        Dropdown.bus.trigger(\"state-changed\", stateChangedPayload);\n    }\n\n    /**\n     * Closes the dropdown.\n     *\n     * @returns {Promise<void>}\n     */\n    close() {\n        return this.changeStateAndNotify({ open: false, groupIsOpen: false });\n    }\n\n    /**\n     * Opens the dropdown.\n     *\n     * @returns {Promise<void>}\n     */\n    open() {\n        return this.changeStateAndNotify({ open: true, groupIsOpen: true });\n    }\n\n    /**\n     * Toggles the dropdown open state.\n     *\n     * @returns {Promise<void>}\n     */\n    toggle() {\n        const toggled = !this.state.open;\n        return this.changeStateAndNotify({ open: toggled, groupIsOpen: toggled });\n    }\n\n    // -------------------------------------------------------------------------\n    // Handlers\n    // -------------------------------------------------------------------------\n\n    /**\n     * Checks if should close on dropdown item selection.\n     *\n     * @param {CustomEvent<import(\"./dropdown_item\").DropdownItemSelectedEventDetail>} ev\n     */\n    onItemSelected(ev) {\n        // Handle parent closing request\n        const { dropdownClosingRequest } = ev.detail;\n        const closeAll = dropdownClosingRequest.mode === ParentClosingMode.AllParents;\n        const closeSelf =\n            dropdownClosingRequest.isFresh &&\n            dropdownClosingRequest.mode === ParentClosingMode.ClosestParent;\n        if (!this.props.manualOnly && (closeAll || closeSelf)) {\n            this.close();\n        }\n        // Mark closing request as started\n        ev.detail.dropdownClosingRequest.isFresh = false;\n    }\n\n    /**\n     * Dropdowns react to each other state changes through this method.\n     *\n     * All state changes must trigger on the bus, except when reacting to\n     * another dropdown state change.\n     *\n     * @see changeStateAndNotify()\n     *\n     * @param {DropdownStateChangedPayload} args\n     */\n    onDropdownStateChanged(args) {\n        if (this.el.contains(args.emitter.el)) {\n            // Do not listen to events emitted by self or children\n            return;\n        }\n\n        // Emitted by direct siblings ?\n        if (args.emitter.el.parentElement === this.el.parentElement) {\n            // Sync the group status\n            this.state.groupIsOpen = args.newState.groupIsOpen;\n\n            // Another dropdown is now open ? Close myself without notifying siblings.\n            if (this.state.open && args.newState.open) {\n                this.state.open = false;\n            }\n        } else {\n            // Another dropdown is now open ? Close myself and notify the world (i.e. siblings).\n            if (this.state.open && args.newState.open) {\n                this.close();\n            }\n        }\n    }\n\n    /**\n     * Toggles the dropdown on its toggler click.\n     */\n    onTogglerClick() {\n        this.toggle();\n    }\n\n    /**\n     * Opens the dropdown the mouse enters its toggler.\n     * NB: only if its siblings dropdown group is opened and if not a sub dropdown.\n     */\n    onTogglerMouseEnter() {\n        if (this.state.groupIsOpen && !this.state.open) {\n            this.togglerRef.el.focus();\n            this.open();\n        }\n    }\n\n    /**\n     * Used to close ourself on outside click.\n     *\n     * @param {MouseEvent} ev\n     */\n    onWindowClicked(ev) {\n        // Return if already closed\n        if (!this.state.open) {\n            return;\n        }\n        // Return if it's a different ui active element\n        if (this.ui.activeElement !== this.myActiveEl) {\n            return;\n        }\n        // Close if we clicked outside the dropdown, or outside the parent\n        // element if it is the toggler\n        const rootEl = this.props.toggler === \"parent\" ? this.el.parentElement : this.el;\n        const gotClickedInside = rootEl.contains(ev.target);\n        if (!gotClickedInside) {\n            this.close();\n        }\n    }\n}\nDropdown.bus = new EventBus();\nDropdown.props = {\n    toggler: {\n        type: String,\n        optional: true,\n        validate: (prop) => [\"parent\"].includes(prop),\n    },\n    startOpen: {\n        type: Boolean,\n        optional: true,\n    },\n    manualOnly: {\n        type: Boolean,\n        optional: true,\n    },\n    menuClass: {\n        type: String,\n        optional: true,\n    },\n    beforeOpen: {\n        type: Function,\n        optional: true,\n    },\n    togglerClass: {\n        type: String,\n        optional: true,\n    },\n    hotkey: {\n        type: String,\n        optional: true,\n    },\n    title: {\n        type: String,\n        optional: true,\n    },\n    position: {\n        type: String,\n        optional: true,\n    },\n};\nDropdown.template = \"web.Dropdown\";\n\nQWeb.registerComponent(\"Dropdown\", Dropdown);\n", "/** @odoo-module **/\n\nconst { Component, QWeb } = owl;\n\n/**\n * @enum {string}\n */\nexport const ParentClosingMode = {\n    None: \"none\",\n    ClosestParent: \"closest\",\n    AllParents: \"all\",\n};\n\n/**\n * @typedef DropdownItemSelectedEventDetail\n * @property {*} payload\n * @property {Object} dropdownClosingRequest\n * @property {boolean} dropdownClosingRequest.isFresh\n * @property {ParentClosingMode} dropdownClosingRequest.mode\n *\n * @typedef {CustomEvent<DropdownItemSelectedEventDetail>} DropdownItemSelectedEvent\n */\n\n/**\n * @extends Component\n */\nexport class DropdownItem extends Component {\n    /**\n     * Triggers a custom DropdownItemSelectedEvent\n     * @param {MouseEvent} ev\n     */\n    onClick(ev) {\n        if (this.props.href){\n            ev.preventDefault();\n        }\n\n        /** @type DropdownItemSelectedEventDetail */\n        const detail = {\n            payload: this.props.payload,\n            dropdownClosingRequest: {\n                isFresh: true,\n                mode: this.props.parentClosingMode,\n            },\n        };\n        this.trigger(\"dropdown-item-selected\", detail);\n    }\n}\nDropdownItem.template = \"web.DropdownItem\";\nDropdownItem.props = {\n    payload: {\n        type: Object,\n        optional: true,\n    },\n    parentClosingMode: {\n        type: ParentClosingMode,\n        optional: true,\n    },\n    hotkey: {\n        type: String,\n        optional: true,\n    },\n    href: {\n        type: String,\n        optional: true,\n    },\n    title: {\n        type: String,\n        optional: true,\n    },\n};\nDropdownItem.defaultProps = {\n    parentClosingMode: ParentClosingMode.AllParents,\n};\n\nQWeb.registerComponent(\"DropdownItem\", DropdownItem);\n", "/** @odoo-module */\n\nimport { useEffect, useService } from \"@web/core/utils/hooks\";\nimport { browser } from \"../browser/browser\";\nimport { localization } from \"@web/core/l10n/localization\";\nimport { scrollTo } from \"../utils/scrolling\";\n\n/**\n * @typedef {{\n *  el: HTMLElement,\n *  isActive: boolean,\n *  makeOnlyActive: ()=>void,\n *  navTarget: HTMLElement,\n *  isSubDropdown: boolean,\n *  isSubDropdownOpen: boolean,\n *  closeSubDropdown: ()=>void,\n *  openSubDropdown: (immediate?:boolean)=>void,\n * }} MenuElement\n */\n\nconst { hooks } = owl;\nconst { useComponent, useRef } = hooks;\n\nconst ACTIVE_MENU_ELEMENT_CLASS = \"focus\";\nconst MENU_ELEMENTS_SELECTORS = [\":scope > .dropdown-item\", \":scope > .dropdown\"];\nconst NEXT_ACTIVE_INDEX_FNS = {\n    FIRST: () => 0,\n    LAST: (list) => list.length - 1,\n    NEXT: (list, prevActiveIndex) => (prevActiveIndex + 1) % list.length,\n    PREV: (list, prevActiveIndex) => (prevActiveIndex <= 0 ? list.length : prevActiveIndex) - 1,\n};\n\nexport function useDropdownNavigation() {\n    /** @type {import(\"./dropdown\").Dropdown} */\n    const comp = useComponent();\n\n    // As this navigation hook relies on clicking \".dropdown-toggle\" elements,\n    // it is incompatible with a toggler=\"parent\" strategy for subdropdowns.\n    if (comp.hasParentDropdown && comp.props.toggler === \"parent\") {\n        throw new Error(\"A nested Dropdown must use its standard toggler\");\n    }\n\n    // Needed to avoid unwanted mouseclick behavior on a subdropdown toggler.\n    const originalOnTogglerClick = comp.onTogglerClick.bind(comp);\n    comp.onTogglerClick = (ev) => {\n        if (comp.hasParentDropdown && !ev.__fromDropdownNavigation) {\n            return;\n        }\n        originalOnTogglerClick();\n    };\n\n    // Needed to avoid unwanted mouseenter behavior on a subdropdown toggler.\n    const originalOnTogglerMouseEnter = comp.onTogglerMouseEnter.bind(comp);\n    comp.onTogglerMouseEnter = () => {\n        if (comp.hasParentDropdown) {\n            return;\n        }\n        originalOnTogglerMouseEnter();\n    };\n\n    // Needed to avoid unwanted selection when the mouse pointer is not in use\n    // but still somewhere in the middle of the dropdown menu list.\n    let mouseSelectionActive = true;\n\n    // Set up menu elements logic ----------------------------------------------\n    const menuRef = useRef(\"menuRef\");\n    /** @type {MenuElement[]} */\n    let menuElements = [];\n    useEffect(() => {\n        if (!comp.state.open) {\n            return;\n        }\n        // Prepare MenuElements\n        const addedListeners = [];\n        /** @type {NodeListOf<HTMLElement>} */\n        const queryResult = menuRef.el.querySelectorAll(MENU_ELEMENTS_SELECTORS.join());\n        for (const el of queryResult) {\n            const isSubDropdown = el.classList.contains(\"dropdown\");\n            const isSubDropdownOpen = () => el.classList.contains(\"show\");\n            const navTarget = isSubDropdown ? el.querySelector(\":scope > .dropdown-toggle\") : el;\n            let subDropdownTimeout;\n            const closeSubDropdown = () => {\n                browser.clearTimeout(subDropdownTimeout);\n                subDropdownTimeout = browser.setTimeout(() => {\n                    if (isSubDropdownOpen()) {\n                        const ev = new MouseEvent(\"click\", { bubbles: false });\n                        ev.__fromDropdownNavigation = true;\n                        navTarget.dispatchEvent(ev);\n                    }\n                }, 200);\n            };\n            const openSubDropdown = (immediate = false) => {\n                browser.clearTimeout(subDropdownTimeout);\n                subDropdownTimeout = browser.setTimeout(\n                    () => {\n                        if (!isSubDropdownOpen()) {\n                            const ev = new MouseEvent(\"click\", { bubbles: false });\n                            ev.__fromDropdownNavigation = true;\n                            navTarget.dispatchEvent(ev);\n                        }\n                    },\n                    immediate ? 0 : 200\n                );\n            };\n            const makeOnlyActive = () => {\n                // Make all others inactive\n                for (const menuElement of menuElements) {\n                    if (menuElement.el === el) {\n                        continue;\n                    }\n                    menuElement.navTarget.classList.remove(ACTIVE_MENU_ELEMENT_CLASS);\n                    if (menuElement.isSubDropdown) {\n                        menuElement.closeSubDropdown();\n                    }\n                }\n                // Make myself active\n                navTarget.classList.add(ACTIVE_MENU_ELEMENT_CLASS);\n                navTarget.focus();\n            };\n\n            /** @type {MenuElement} */\n            const menuElement = {\n                el,\n                get isActive() {\n                    return navTarget.classList.contains(ACTIVE_MENU_ELEMENT_CLASS);\n                },\n                makeOnlyActive,\n                navTarget,\n                get isSubDropdownOpen() {\n                    return isSubDropdownOpen();\n                },\n                isSubDropdown,\n                closeSubDropdown,\n                openSubDropdown,\n            };\n            menuElements.push(menuElement);\n\n            // Set up selection listeners\n            const elementListeners = {\n                mouseenter: () => {\n                    if (!mouseSelectionActive) {\n                        mouseSelectionActive = true;\n                    } else {\n                        makeOnlyActive();\n                        if (isSubDropdown) {\n                            openSubDropdown();\n                        }\n                    }\n                },\n            };\n            for (const [eventType, listener] of Object.entries(elementListeners)) {\n                navTarget.addEventListener(eventType, listener);\n            }\n            addedListeners.push([navTarget, elementListeners]);\n        }\n        return () => {\n            menuElements = [];\n            mouseSelectionActive = true;\n\n            // Clear mouse selection listeners\n            for (const [navTarget, listeners] of addedListeners) {\n                for (const [eventType, listener] of Object.entries(listeners)) {\n                    navTarget.removeEventListener(eventType, listener);\n                }\n            }\n        };\n    });\n\n    // Set up active menu element helpers --------------------------------------\n    /**\n     * @returns {MenuElement|undefined}\n     */\n    const getActiveMenuElement = () => {\n        return menuElements.find((menuElement) => menuElement.isActive);\n    };\n\n    /**\n     * @param {MenuElement|keyof NEXT_ACTIVE_INDEX_FNS} menuElement\n     */\n    const setActiveMenuElement = (menuElement) => {\n        if (menuElements.length) {\n            if (typeof menuElement === \"string\") {\n                const prevIndex = menuElements.indexOf(getActiveMenuElement());\n                const nextIndex = NEXT_ACTIVE_INDEX_FNS[menuElement](menuElements, prevIndex);\n                menuElement = menuElements[nextIndex];\n            }\n            menuElement.makeOnlyActive();\n            scrollTo(menuElement.el, { scrollable: menuElement.el.parentElement });\n        }\n    };\n\n    // Set up nested dropdowns - active first menu element behavior ------------\n    useEffect(\n        (open) => {\n            // If we just opened and we are a subdropdown, make active our first menu element.\n            if (open && comp.hasParentDropdown) {\n                setActiveMenuElement(\"FIRST\");\n            }\n        },\n        () => [comp.state.open]\n    );\n\n    // Set up keyboard navigation ----------------------------------------------\n    const hotkeyService = useService(\"hotkey\");\n    const closeAndRefocus = () => {\n        const toFocus =\n            comp.props.toggler === \"parent\"\n                ? comp.el.parentElement\n                : comp.el.querySelector(\":scope > .dropdown-toggle\");\n        comp.close().then(() => {\n            toFocus.focus();\n        });\n    };\n    const closeSubDropdown = comp.hasParentDropdown ? closeAndRefocus : () => {};\n    const openSubDropdown = () => {\n        const menuElement = getActiveMenuElement();\n        // Active menu element is a sub dropdown\n        if (menuElement && menuElement.isSubDropdown) {\n            menuElement.openSubDropdown(true);\n        }\n    };\n    const selectActiveMenuElement = () => {\n        const menuElement = getActiveMenuElement();\n        if (menuElement) {\n            if (menuElement.isSubDropdown) {\n                menuElement.openSubDropdown(true);\n            } else {\n                menuElement.navTarget.click();\n            }\n        }\n    };\n    let hotkeyRemoves = [];\n    const hotkeyCallbacks = {\n        home: () => setActiveMenuElement(\"FIRST\"),\n        end: () => setActiveMenuElement(\"LAST\"),\n        tab: () => setActiveMenuElement(\"NEXT\"),\n        \"shift+tab\": () => setActiveMenuElement(\"PREV\"),\n        arrowdown: () => setActiveMenuElement(\"NEXT\"),\n        arrowup: () => setActiveMenuElement(\"PREV\"),\n        arrowleft: localization.direction === \"rtl\" ? openSubDropdown : closeSubDropdown,\n        arrowright: localization.direction === \"rtl\" ? closeSubDropdown : openSubDropdown,\n        enter: selectActiveMenuElement,\n        escape: closeAndRefocus,\n    };\n    useEffect(\n        (open) => {\n            if (!open) {\n                return;\n            }\n            // Subscribe keynav\n            for (const [hotkey, callback] of Object.entries(hotkeyCallbacks)) {\n                const callbackWrapper = () => {\n                    const hasOpenedSubDropdown = menuElements.some((m) => m.isSubDropdownOpen);\n                    // Leave priority to last opened sub dropdown\n                    if (!hasOpenedSubDropdown) {\n                        mouseSelectionActive = false;\n                        callback.call(comp);\n                    }\n                };\n                hotkeyRemoves.push(\n                    hotkeyService.add(hotkey, callbackWrapper, { allowRepeat: true })\n                );\n            }\n            return () => {\n                // Unsubscribe keynav\n                for (const removeHotkey of hotkeyRemoves) {\n                    removeHotkey();\n                }\n                hotkeyRemoves = [];\n            };\n        },\n        () => [comp.state.open]\n    );\n}\n", "/** @odoo-module **/\n\nconst { Component, tags } = owl;\n\nexport class EffectContainer extends Component {\n    setup() {\n        this.effect = null;\n        this.props.bus.on(\"UPDATE\", this, (effect) => {\n            this.effect = effect;\n            this.render();\n        });\n    }\n    removeEffect() {\n        this.effect = null;\n        this.render();\n    }\n}\n\nEffectContainer.template = tags.xml`\n  <div class=\"o_effects_manager\">\n    <t t-if=\"effect\">\n        <t t-component=\"effect.Component\" t-props=\"effect.props\" t-key=\"effect.id\" close=\"() => removeEffect()\"/>\n    </t>\n  </div>`;\n", "/** @odoo-module **/\n\nimport { registry } from \"../registry\";\nimport { EffectContainer } from \"./effect_container\";\nimport { RainbowMan } from \"./rainbow_man\";\n\nconst { EventBus } = owl.core;\n\nconst effectRegistry = registry.category(\"effects\");\n\n// -----------------------------------------------------------------------------\n// RainbowMan effect\n// -----------------------------------------------------------------------------\n\n/**\n * Handles effect of type \"rainbow_man\". If the effects aren't disabled, returns\n * the RainbowMan component to instantiate and its props. If the effects are\n * disabled, displays the message in a notification.\n *\n * @param {Object} env\n * @param {Object} [params={}]\n * @param {string} [params.message=\"Well Done\"]\n *    The message in the notice the rainbowman holds or the content of the notification if effects are disabled\n *    Can be a simple a string\n *    Can be a string representation of html (prefer component if you want interactions in the DOM)\n * @param {boolean} [params.img_url=\"/web/static/img/smile.svg\"]\n *    The url of the image to display inside the rainbow\n * @param {boolean} [params.messageIsHtml]\n *    Set to true if the message encodes html, s.t. it will be correctly inserted into the DOM.\n * @param {\"slow\"|\"medium\"|\"fast\"|\"no\"} [params.fadeout=\"medium\"]\n *    Delay for rainbowman to disappear\n *    'fast' will make rainbowman dissapear quickly\n *    'medium' and 'slow' will wait little longer before disappearing (can be used when options.message is longer)\n *    'no' will keep rainbowman on screen until user clicks anywhere outside rainbowman\n * @param {owl.Component} [params.Component=RainbowMan]\n *    Component class to instantiate (if effects aren't disabled)\n * @param {Object} [params.props]\n *    If params.Component is given, its props can be passed with this argument\n */\nfunction rainbowMan(env, params = {}) {\n    let message = params.message;\n    if (message instanceof jQuery) {\n        console.log(\n            \"Providing a jQuery element to an effect is deprecated. Note that all event handlers will be lost.\"\n        );\n        message = message.html();\n    } else if (message instanceof Element) {\n        console.log(\n            \"Providing an HTML element to an effect is deprecated. Note that all event handlers will be lost.\"\n        );\n        message = message.outerHTML;\n    } else if (!message) {\n        message = env._t(\"well Done!\");\n    }\n    if (env.services.user.showEffect) {\n        return {\n            Component: params.Component || RainbowMan,\n            props: {\n                imgUrl: params.img_url || \"/web/static/img/smile.svg\",\n                fadeout: params.fadeout,\n                message,\n                messageIsHtml: params.messageIsHtml || false,\n                ...params.props,\n            },\n        };\n    }\n    env.services.notification.add(message);\n}\neffectRegistry.add(\"rainbow_man\", rainbowMan);\n\n// -----------------------------------------------------------------------------\n// Effect service\n// -----------------------------------------------------------------------------\n\nexport const effectService = {\n    start(env) {\n        const bus = new EventBus();\n        registry.category(\"main_components\").add(\"EffectContainer\", {\n            Component: EffectContainer,\n            props: { bus },\n        });\n        let effectId = 0;\n\n        /**\n         * @param {Object} params various params depending on the type of effect\n         * @param {string} [params.type=\"rainbow_man\"] the effect to display\n         */\n        function add(params) {\n            const type = params.type || \"rainbow_man\";\n            const effect = effectRegistry.get(type);\n            const { Component, props } = effect(env, params) || {};\n            if (Component) {\n                bus.trigger(\"UPDATE\", { Component, props, id: effectId++ });\n            }\n        }\n\n        return { add };\n    },\n};\n\nregistry.category(\"services\").add(\"effect\", effectService);\n", "/** @odoo-module **/\n\nimport { browser } from \"@web/core/browser/browser\";\nimport { useEffect } from \"@web/core/utils/hooks\";\n\nconst { Component, hooks } = owl;\n\n/**\n * The RainbowMan widget is the widget displayed by default as a 'fun/rewarding'\n * effect in some cases.  For example, when the user marked a large deal as won,\n * or when he cleared its inbox.\n *\n * This widget is mostly a picture and a message with a rainbow animation around\n * If you want to display a RainbowMan, you probably do not want to do it by\n * importing this file.  The usual way to do that would be to use the effect\n * service (by triggering the 'show_effect' event)\n */\nexport class RainbowMan extends Component {\n    /**\n     * @override\n     * @constructor\n     * @param {Object} [options]\n     * @param {string} [options.message] Message to be displayed on rainbowman card\n     * @param {string} [options.fadeout='medium'] Delay for rainbowman to disappear. 'fast' will make rainbowman dissapear quickly, 'medium' and 'slow' will wait little longer before disappearing (can be used when options.message is longer), 'no' will keep rainbowman on screen until user clicks anywhere outside rainbowman\n     * @param {string} [options.img_url] URL of the image to be displayed\n     */\n    setup() {\n        hooks.useExternalListener(document.body, \"click\", this.closeRainbowMan);\n        const fadeout = \"fadeout\" in this.props ? this.props.fadeout : \"medium\";\n        const delay = fadeout ? RainbowMan.rainbowFadeouts[fadeout] : false;\n        this.delay = typeof delay === \"number\" ? delay : false;\n        if (this.delay !== false) {\n            useEffect(\n                () => {\n                    const timeout = browser.setTimeout(() => {\n                        this.el.classList.add(\"o_reward_fading\");\n                    }, this.delay);\n                    return () => browser.clearTimeout(timeout);\n                },\n                () => []\n            );\n        }\n    }\n\n    onAnimationEnd(ev) {\n        if (this.delay !== false && ev.animationName === \"reward-fading-reverse\") {\n            ev.stopPropagation();\n            this.closeRainbowMan();\n        }\n    }\n\n    closeRainbowMan() {\n        this.props.close();\n    }\n}\nRainbowMan.template = \"web.RainbowMan\";\nRainbowMan.rainbowFadeouts = { slow: 4500, medium: 3500, fast: 2000, no: false };\n", "/** @odoo-module **/\n\nimport { browser } from \"../browser/browser\";\nimport { Dialog } from \"../dialog/dialog\";\nimport { _lt } from \"../l10n/translation\";\nimport { registry } from \"../registry\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { capitalize } from \"../utils/strings\";\n\nconst { hooks } = owl;\nconst { useState } = hooks;\n\nexport const odooExceptionTitleMap = new Map(\n    Object.entries({\n        \"odoo.addons.base.models.ir_mail_server.MailDeliveryException\": _lt(\n            \"MailDeliveryException\"\n        ),\n        \"odoo.exceptions.AccessDenied\": _lt(\"Access Denied\"),\n        \"odoo.exceptions.MissingError\": _lt(\"Missing Record\"),\n        \"odoo.exceptions.UserError\": _lt(\"User Error\"),\n        \"odoo.exceptions.ValidationError\": _lt(\"Validation Error\"),\n        \"odoo.exceptions.AccessError\": _lt(\"Access Error\"),\n        \"odoo.exceptions.Warning\": _lt(\"Warning\"),\n    })\n);\n\n// -----------------------------------------------------------------------------\n// Generic Error Dialog\n// -----------------------------------------------------------------------------\nexport class ErrorDialog extends Dialog {\n    setup() {\n        super.setup();\n        this.state = useState({\n            showTraceback: false,\n        });\n    }\n    onClickClipboard() {\n        browser.navigator.clipboard.writeText(\n            `${this.props.name}\\n${this.props.message}\\n${this.props.traceback}`\n        );\n    }\n}\nErrorDialog.contentClass = \"o_dialog_error\";\nErrorDialog.bodyTemplate = \"web.ErrorDialogBody\";\nErrorDialog.title = _lt(\"Odoo Error\");\n\n// -----------------------------------------------------------------------------\n// Client Error Dialog\n// -----------------------------------------------------------------------------\nexport class ClientErrorDialog extends ErrorDialog {}\nClientErrorDialog.title = _lt(\"Odoo Client Error\");\n\n// -----------------------------------------------------------------------------\n// Network Error Dialog\n// -----------------------------------------------------------------------------\nexport class NetworkErrorDialog extends ErrorDialog {}\nNetworkErrorDialog.title = _lt(\"Odoo Network Error\");\n\n// -----------------------------------------------------------------------------\n// RPC Error Dialog\n// -----------------------------------------------------------------------------\nexport class RPCErrorDialog extends ErrorDialog {\n    setup() {\n        super.setup();\n        this.inferTitle();\n        this.traceback = this.props.traceback;\n        if (this.props.data && this.props.data.debug) {\n            this.traceback = `${this.props.data.debug}`;\n        }\n    }\n    inferTitle() {\n        // If the server provides an exception name that we have in a registry.\n        if (this.props.exceptionName && odooExceptionTitleMap.has(this.props.exceptionName)) {\n            this.title = odooExceptionTitleMap.get(this.props.exceptionName).toString();\n            return;\n        }\n        // Fall back to a name based on the error type.\n        if (!this.props.type) return;\n        switch (this.props.type) {\n            case \"server\":\n                this.title = this.env._t(\"Odoo Server Error\");\n                break;\n            case \"script\":\n                this.title = this.env._t(\"Odoo Client Error\");\n                break;\n            case \"network\":\n                this.title = this.env._t(\"Odoo Network Error\");\n                break;\n        }\n    }\n\n    onClickClipboard() {\n        browser.navigator.clipboard.writeText(\n            `${this.props.name}\\n${this.props.message}\\n${this.traceback}`\n        );\n    }\n}\n\n// -----------------------------------------------------------------------------\n// Warning Dialog\n// -----------------------------------------------------------------------------\nexport class WarningDialog extends Dialog {\n    setup() {\n        super.setup();\n        this.title = this.env._t(\"Odoo Warning\");\n        this.inferTitle();\n        const { data, message } = this.props;\n        if (data && data.arguments && data.arguments.length > 0) {\n            this.message = data.arguments[0];\n        } else {\n            this.message = message;\n        }\n    }\n    inferTitle() {\n        if (this.props.exceptionName && odooExceptionTitleMap.has(this.props.exceptionName)) {\n            this.title = odooExceptionTitleMap.get(this.props.exceptionName).toString();\n        }\n    }\n}\nWarningDialog.bodyTemplate = \"web.WarningDialogBody\";\n\n// -----------------------------------------------------------------------------\n// Redirect Warning Dialog\n// -----------------------------------------------------------------------------\nexport class RedirectWarningDialog extends Dialog {\n    setup() {\n        super.setup();\n        this.actionService = useService(\"action\");\n        const { data, subType } = this.props;\n        const [message, actionId, buttonText, additional_context] = data.arguments;\n        this.title = capitalize(subType) || this.env._t(\"Odoo Warning\");\n        this.message = message;\n        this.actionId = actionId;\n        this.buttonText = buttonText;\n        this.additionalContext = additional_context;\n    }\n    async onClick() {\n        const options = {};\n        if (this.additionalContext) {\n            options.additionalContext = this.additionalContext;\n        }\n        await this.actionService.doAction(this.actionId, options);\n        this.close();\n    }\n    onCancel() {\n        this.close();\n    }\n}\nRedirectWarningDialog.bodyTemplate = \"web.RedirectWarningDialogBody\";\nRedirectWarningDialog.footerTemplate = \"web.RedirectWarningDialogFooter\";\n\n// -----------------------------------------------------------------------------\n// Error 504 Dialog\n// -----------------------------------------------------------------------------\nexport class Error504Dialog extends Dialog {}\nError504Dialog.bodyTemplate = \"web.Error504DialogBody\";\nError504Dialog.title = _lt(\"Request timeout\");\n\n// -----------------------------------------------------------------------------\n// Expired Session Error Dialog\n// -----------------------------------------------------------------------------\nexport class SessionExpiredDialog extends Dialog {\n    onClick() {\n        browser.location.reload();\n    }\n}\nSessionExpiredDialog.bodyTemplate = \"web.SessionExpiredDialogBody\";\nSessionExpiredDialog.footerTemplate = \"web.SessionExpiredDialogFooter\";\nSessionExpiredDialog.title = _lt(\"Odoo Session Expired\");\n\nregistry\n    .category(\"error_dialogs\")\n    .add(\"odoo.exceptions.AccessDenied\", WarningDialog)\n    .add(\"odoo.exceptions.AccessError\", WarningDialog)\n    .add(\"odoo.exceptions.MissingError\", WarningDialog)\n    .add(\"odoo.exceptions.UserError\", WarningDialog)\n    .add(\"odoo.exceptions.ValidationError\", WarningDialog)\n    .add(\"odoo.exceptions.RedirectWarning\", RedirectWarningDialog)\n    .add(\"odoo.http.SessionExpiredException\", SessionExpiredDialog)\n    .add(\"werkzeug.exceptions.Forbidden\", SessionExpiredDialog)\n    .add(\"504\", Error504Dialog);\n", "/** @odoo-module **/\n\nimport { browser } from \"../browser/browser\";\nimport { isIOS } from \"../browser/feature_detection\";\nimport { session } from \"../../session\";\nimport { ConnectionLostError, RPCError } from \"../network/rpc_service\";\nimport { registry } from \"../registry\";\nimport {\n    ClientErrorDialog,\n    ErrorDialog,\n    NetworkErrorDialog,\n    RPCErrorDialog,\n} from \"./error_dialogs\";\nimport { UncaughtClientError, UncaughtCorsError, UncaughtPromiseError } from \"./error_service\";\n\n/**\n * @typedef {import(\"../../env\").OdooEnv} OdooEnv\n * @typedef {import(\"./error_service\").UncaughtError} UncaughError\n */\n\nconst errorHandlerRegistry = registry.category(\"error_handlers\");\nconst errorDialogRegistry = registry.category(\"error_dialogs\");\nconst errorNotificationRegistry = registry.category(\"error_notifications\");\n\n// -----------------------------------------------------------------------------\n// CORS errors\n// -----------------------------------------------------------------------------\n\n/**\n * @param {OdooEnv} env\n * @param {UncaughError} error\n * @returns {boolean}\n */\nfunction corsErrorHandler(env, error) {\n    if (error instanceof UncaughtCorsError) {\n        // In Safari 16.4+ (as of Jun 14th 2023), an error occurs\n        // when going back and forward through the browser when the\n        // cache is enabled. A feedback has been reported but in the\n        // meantime, hide any script error in these versions.\n        if (isIOS() && session.is_frontend && odoo.debug !== \"assets\") {\n            return true;\n        }\n        env.services.dialog.add(NetworkErrorDialog, {\n            traceback: error.traceback,\n            message: error.message,\n            name: error.name,\n        });\n        return true;\n    }\n}\nerrorHandlerRegistry.add(\"corsErrorHandler\", corsErrorHandler, { sequence: 95 });\n\n// -----------------------------------------------------------------------------\n// Client errors\n// -----------------------------------------------------------------------------\n\n/**\n * @param {OdooEnv} env\n * @param {UncaughError} error\n * @returns {boolean}\n */\nfunction clientErrorHandler(env, error) {\n    if (error instanceof UncaughtClientError) {\n        env.services.dialog.add(ClientErrorDialog, {\n            traceback: error.traceback,\n            message: error.message,\n            name: error.name,\n        });\n        return true;\n    }\n}\nerrorHandlerRegistry.add(\"clientErrorHandler\", clientErrorHandler, { sequence: 96 });\n\n// -----------------------------------------------------------------------------\n// RPC errors\n// -----------------------------------------------------------------------------\n\n/**\n * @param {OdooEnv} env\n * @param {UncaughError} error\n * @param {Error} originalError\n * @returns {boolean}\n */\nexport function rpcErrorHandler(env, error, originalError) {\n    if (!(error instanceof UncaughtPromiseError)) {\n        return false;\n    }\n    if (originalError instanceof RPCError) {\n        // When an error comes from the server, it can have an exeption name.\n        // (or any string truly). It is used as key in the error dialog from\n        // server registry to know which dialog component to use.\n        // It's how a backend dev can easily map its error to another component.\n        // Note that for a client side exception, we don't use this registry\n        // as we can directly assign a value to `component`.\n        // error is here a RPCError\n        error.unhandledRejectionEvent.preventDefault();\n        const exceptionName = originalError.exceptionName;\n        let ErrorComponent = originalError.Component;\n        if (!ErrorComponent && exceptionName) {\n            if (errorNotificationRegistry.contains(exceptionName)) {\n                const notif = errorNotificationRegistry.get(exceptionName);\n                env.services.notification.add(notif.message || originalError.data.message, notif);\n                return true;\n            }\n            if (errorDialogRegistry.contains(exceptionName)) {\n                ErrorComponent = errorDialogRegistry.get(exceptionName);\n            }\n        }\n        if (!ErrorComponent && originalError.data.context) {\n            const exceptionClass = originalError.data.context.exception_class;\n            if (errorDialogRegistry.contains(exceptionClass)) {\n                ErrorComponent = errorDialogRegistry.get(exceptionClass);\n            }\n        }\n\n        env.services.dialog.add(ErrorComponent || RPCErrorDialog, {\n            traceback: error.traceback,\n            message: originalError.message,\n            name: originalError.name,\n            exceptionName: originalError.exceptionName,\n            data: originalError.data,\n            subType: originalError.subType,\n            code: originalError.code,\n            type: originalError.type,\n        });\n        return true;\n    }\n}\nerrorHandlerRegistry.add(\"rpcErrorHandler\", rpcErrorHandler, { sequence: 97 });\n\n// -----------------------------------------------------------------------------\n// Lost connection errors\n// -----------------------------------------------------------------------------\n\nlet connectionLostNotifRemove = null;\n/**\n * @param {OdooEnv} env\n * @param {UncaughError} error\n * @param {Error} originalError\n * @returns {boolean}\n */\nexport function lostConnectionHandler(env, error, originalError) {\n    if (!(error instanceof UncaughtPromiseError)) {\n        return false;\n    }\n    if (originalError instanceof ConnectionLostError) {\n        if (connectionLostNotifRemove) {\n            // notification already displayed (can occur if there were several\n            // concurrent rpcs when the connection was lost)\n            return true;\n        }\n        connectionLostNotifRemove = env.services.notification.add(\n            env._t(\"Connection lost. Trying to reconnect...\"),\n            { sticky: true }\n        );\n        let delay = 2000;\n        browser.setTimeout(function checkConnection() {\n            env.services\n                .rpc(\"/web/webclient/version_info\", {})\n                .then(function () {\n                    if (connectionLostNotifRemove) {\n                        connectionLostNotifRemove();\n                        connectionLostNotifRemove = null;\n                    }\n                    env.services.notification.add(\n                        env._t(\"Connection restored. You are back online.\"),\n                        {\n                            type: \"info\",\n                        }\n                    );\n                })\n                .catch(() => {\n                    // exponential backoff, with some jitter\n                    delay = delay * 1.5 + 500 * Math.random();\n                    browser.setTimeout(checkConnection, delay);\n                });\n        }, delay);\n        return true;\n    }\n}\nerrorHandlerRegistry.add(\"lostConnectionHandler\", lostConnectionHandler, { sequence: 98 });\n\n// -----------------------------------------------------------------------------\n// Empty rejection errors\n// -----------------------------------------------------------------------------\n\n/**\n * @param {OdooEnv} env\n * @param {UncaughError} error\n * @returns {boolean}\n */\nfunction emptyRejectionErrorHandler(env, error) {\n    if (!(error instanceof UncaughtPromiseError)) {\n        return false;\n    }\n    env.services.dialog.add(ClientErrorDialog, {\n        traceback: error.traceback,\n        message: error.message,\n        name: error.name,\n    });\n    return true;\n}\nerrorHandlerRegistry.add(\"emptyRejectionErrorHandler\", emptyRejectionErrorHandler, {\n    sequence: 99,\n});\n\n// -----------------------------------------------------------------------------\n// Default handler\n// -----------------------------------------------------------------------------\n\n/**\n * @param {OdooEnv} env\n * @param {UncaughError} error\n * @returns {boolean}\n */\nfunction defaultHandler(env, error) {\n    env.services.dialog.add(ErrorDialog, {\n        traceback: error.traceback,\n        message: error.message,\n        name: error.name,\n    });\n    return true;\n}\nerrorHandlerRegistry.add(\"defaultHandler\", defaultHandler, { sequence: 100 });\n", "/** @odoo-module **/\n\nimport { browser } from \"../browser/browser\";\nimport { _lt } from \"../l10n/translation\";\nimport { registry } from \"../registry\";\nimport { annotateTraceback, formatTraceback, getErrorTechnicalName } from \"./error_utils\";\n\n/**\n * Uncaught Errors have 4 properties:\n * - name: technical name of the error (UncaughtError, ...)\n * - message: short user visible description of the issue (\"Uncaught Cors Error\")\n * - traceback: long description, possibly technical of the issue (such as a traceback)\n * - originalError: the error that was actually being caught. Note that it is not\n *      necessarily an error (for ex, if some code does throw \"boom\")\n */\nexport class UncaughtError extends Error {\n    constructor(message) {\n        super(message);\n        this.name = getErrorTechnicalName(this);\n        this.traceback = null;\n    }\n}\n\nexport class UncaughtClientError extends UncaughtError {\n    constructor(message = _lt(\"Uncaught Javascript Error\")) {\n        super(message);\n    }\n}\n\nexport class UncaughtPromiseError extends UncaughtError {\n    constructor(message = _lt(\"Uncaught Promise\")) {\n        super(message);\n        this.unhandledRejectionEvent = null;\n    }\n}\n\nexport class UncaughtCorsError extends UncaughtError {\n    constructor(message = _lt(\"Uncaught CORS Error\")) {\n        super(message);\n    }\n}\n\n/**\n * @param {UncaughtError} uncaughtError\n * @param {Error} originalError\n * @returns {string}\n */\nfunction combineErrorNames(uncaughtError, originalError) {\n    const originalErrorName = getErrorTechnicalName(originalError);\n    const uncaughtErrorName = getErrorTechnicalName(uncaughtError);\n    if (originalErrorName === Error.name) {\n        return uncaughtErrorName;\n    } else {\n        return `${uncaughtErrorName} > ${originalErrorName}`;\n    }\n}\n\n/**\n * @param {import(\"../../env\").OdooEnv} env\n * @param {UncaughtError} uncaughtError\n * @param {Error} originalError\n * @returns {Promise<void>}\n */\nasync function completeUncaughtError(env, uncaughtError, originalError) {\n    uncaughtError.name = combineErrorNames(uncaughtError, originalError);\n    if (env.debug.includes(\"assets\")) {\n        uncaughtError.traceback = await annotateTraceback(originalError);\n    } else {\n        uncaughtError.traceback = formatTraceback(originalError);\n    }\n    if (originalError.message) {\n        uncaughtError.message = `${uncaughtError.message} > ${originalError.message}`;\n    }\n}\n\nexport const errorService = {\n    start(env) {\n        function handleError(error, originalError, retry = true) {\n            const services = env.services;\n            if (!services.dialog || !services.notification || !services.rpc) {\n                // here, the environment is not ready to provide feedback to the user.\n                // We simply wait 1 sec and try again, just in case the application can\n                // recover.\n                if (retry) {\n                    browser.setTimeout(() => {\n                        handleError(error, originalError, false);\n                    }, 1000);\n                }\n                return;\n            }\n            for (let handler of registry.category(\"error_handlers\").getAll()) {\n                if (handler(env, error, originalError)) {\n                    break;\n                }\n            }\n        }\n\n        browser.addEventListener(\"error\", async (ev) => {\n            const { colno, error: originalError, filename, lineno, message } = ev;\n            const errorsToIgnore = [\n                // Ignore some unnecessary \"ResizeObserver loop limit exceeded\" error in Firefox.\n                \"ResizeObserver loop completed with undelivered notifications.\",\n                // ignore Chrome video internal error: https://crbug.com/809574\n                \"ResizeObserver loop limit exceeded\"\n            ]\n            if (!originalError && errorsToIgnore.includes(message)) {\n                return;\n            }\n            let uncaughtError;\n            if (!filename && !lineno && !colno) {\n                uncaughtError = new UncaughtCorsError();\n                uncaughtError.traceback = env._t(\n                    `Unknown CORS error\\n\\n` +\n                        `An unknown CORS error occured.\\n` +\n                        `The error probably originates from a JavaScript file served from a different origin.\\n` +\n                        `(Opening your browser console might give you a hint on the error.)`\n                );\n            } else {\n                uncaughtError = new UncaughtClientError();\n                await completeUncaughtError(env, uncaughtError, originalError);\n            }\n            handleError(uncaughtError, originalError);\n        });\n\n        browser.addEventListener(\"unhandledrejection\", async (ev) => {\n            const originalError = ev.reason;\n            const uncaughtError = new UncaughtPromiseError();\n            uncaughtError.unhandledRejectionEvent = ev;\n            if (originalError instanceof Error) {\n                await completeUncaughtError(env, uncaughtError, originalError);\n            }\n            handleError(uncaughtError, originalError);\n        });\n    },\n};\n\nregistry.category(\"services\").add(\"error\", errorService, { sequence: 1 });\n", "/** @odoo-module **/\n\nimport { loadAssets } from \"../assets\";\nimport { isBrowserChrome } from \"../browser/feature_detection\";\n\n/**\n * @param {Error} error\n * @returns {string}\n */\nexport function getErrorTechnicalName(error) {\n    return error.name !== Error.name ? error.name : error.constructor.name;\n}\n\n/**\n * Format the traceback of an error. Basically, we just add the error message\n * in the traceback if necessary (Chrome already does it by default, but not\n * other browser.)\n *\n * @param {Error} error\n * @returns {string}\n */\nexport function formatTraceback(error) {\n    let traceback = error.stack;\n    const errorName = getErrorTechnicalName(error);\n    if (!isBrowserChrome()) {\n        // transforms the stack into a chromium stack\n        // Chromium stack example:\n        // Error: Mock: Can't write value\n        //     _onOpenFormView@http://localhost:8069/web/content/425-baf33f1/web.assets.js:1064:30\n        //     ...\n        traceback = `${errorName}: ${error.message}\\n${error.stack}`.replace(/\\n/g, \"\\n    \");\n    } else if (error.stack) {\n        // Chromium stack starts with the error's name but the name is \"Error\" by default\n        // so we replace it to have the error type name\n        traceback = error.stack.replace(/^[^:]*/g, errorName);\n    }\n    return traceback;\n}\n\n/**\n * Returns an annotated traceback from an error. This is asynchronous because\n * it needs to fetch the sourcemaps for each script involved in the error,\n * then compute the correct file/line numbers and add the information to the\n * correct line.\n *\n * @param {Error} error\n * @returns {Promise<string>}\n */\nexport async function annotateTraceback(error) {\n    const traceback = formatTraceback(error);\n    try {\n        await loadAssets({\n            jsLibs: [\"/web/static/lib/stacktracejs/stacktrace.js\"],\n        });\n    } catch (e) {\n        return traceback;\n    }\n    // In Firefox, the error stack generated by anonymous code (example: invalid\n    // code in a template) is not compatible with the stacktrace lib. This code\n    // corrects the stack to make it compatible with the lib stacktrace.\n    if (error.stack) {\n        const regex = / line (\\d*) > (Function):(\\d*)/gm;\n        const subst = `:$1`;\n        error.stack = error.stack.replace(regex, subst);\n    }\n    // eslint-disable-next-line no-undef\n    let frames;\n    try {\n        frames = await StackTrace.fromError(error);\n    } catch (e) {\n        // This can crash if the originalError has no stack/stacktrace property\n        console.warn(\"The following error could not be annotated:\", error, e);\n        return traceback;\n    }\n    const lines = traceback.split(\"\\n\");\n    if (lines[lines.length - 1].trim() === \"\") {\n        // firefox traceback have an empty line at the end\n        lines.splice(-1);\n    }\n\n    // Chrome stacks contains some lines with (index 0) which apparently\n    // corresponds to some native functions (at least Promise.all). We need to\n    // ignore them because they will not correspond to a stackframe.\n    const skips = lines.filter((l) => l.includes(\"(index 0\")).length;\n    const offset = lines.length - frames.length - skips;\n    let lineIndex = offset;\n    let frameIndex = 0;\n    while (frameIndex < frames.length) {\n        const line = lines[lineIndex];\n        if (line.includes(\"(index 0)\")) {\n            lineIndex++;\n            continue;\n        }\n        const frame = frames[frameIndex];\n        const info = ` (${frame.fileName}:${frame.lineNumber})`;\n        lines[lineIndex] = line + info;\n        lineIndex++;\n        frameIndex++;\n    }\n    return lines.join(\"\\n\");\n}\n", "/** @odoo-module **/\n\nimport { useService } from \"@web/core/utils/hooks\";\n\nconst { Component, hooks, QWeb } = owl;\nconst { useRef } = hooks;\n\n/**\n * Custom file input\n *\n * Component representing a customized input of type file. It takes a sub-template\n * in its default t-slot and uses it as the trigger to open the file upload\n * prompt.\n * @extends Component\n *\n * Props:\n * @param {string} [props.accepted_file_extensions='*'] Comma-separated\n *      list of authorized file extensions (default to all).\n * @param {string} [props.action='/web/binary/upload'] Route called when\n *      a file is uploaded in the input.\n * @param {string} [props.id]\n * @param {string} [props.model]\n * @param {string} [props.multi_upload=false] Whether the input should allow\n *      to upload multiple files at once.\n */\nexport class FileInput extends Component {\n    setup() {\n        this.http = useService(\"http\");\n        this.fileInputRef = useRef(\"file-input\");\n    }\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Upload an attachment to the given action with the given parameters:\n     * - ufile: list of files contained in the file input\n     * - csrf_token: CSRF token provided by the odoo global object\n     * - model: a specific model which will be given when creating the attachment\n     * - id: the id of the model target instance\n     * @private\n     */\n    async onFileInputChange() {\n        const { action, model, id } = this.props;\n        const params = {\n            csrf_token: odoo.csrf_token,\n            ufile: [...this.fileInputRef.el.files],\n        };\n        if (model) {\n            params.model = model;\n        }\n        if (id) {\n            params.id = id;\n        }\n        const fileData = await this.http.post(action, params, \"text\");\n        const parsedFileData = JSON.parse(fileData);\n        if (parsedFileData.error) {\n            throw new Error(parsedFileData.error);\n        }\n        this.trigger(\"uploaded\", { files: parsedFileData });\n    }\n\n    /**\n     * Redirect clicks from the trigger element to the input.\n     * @private\n     */\n    onTriggerClicked() {\n        this.fileInputRef.el.click();\n    }\n}\n\nFileInput.defaultProps = {\n    accepted_file_extensions: \"*\",\n    action: \"/web/binary/upload\",\n    multi_upload: false,\n};\nFileInput.props = {\n    accepted_file_extensions: { type: String, optional: 1 },\n    action: { type: String, optional: 1 },\n    id: { type: Number, optional: 1 },\n    model: { type: String, optional: 1 },\n    multi_upload: { type: Boolean, optional: 1 },\n};\nFileInput.template = \"web.FileInput\";\n\nQWeb.registerComponent(\"FileInput\", FileInput);\n", "/** @odoo-module **/\n\nimport { useEffect, useService } from \"@web/core/utils/hooks\";\n\n/**\n * This hook will register/unregister the given registration\n * when the caller component will mount/unmount.\n *\n * @param {string} hotkey\n * @param {()=>void} callback\n * @param {Object} options additional options\n * @param {boolean} [options.allowRepeat=false]\n *  allow registration to perform multiple times when hotkey is held down\n * @param {boolean} [options.bypassEditableProtection=false]\n *  if true the hotkey service will call this registration\n *  even if an editable element is focused\n * @param {boolean} [options.global=false]\n *  allow registration to perform no matter the UI active element\n */\nexport function useHotkey(hotkey, callback, options = {}) {\n    const hotkeyService = useService(\"hotkey\");\n    useEffect(\n        () => hotkeyService.add(hotkey, callback, options),\n        () => []\n    );\n}\n", "/** @odoo-module **/\n\nimport { isMacOS } from \"../browser/feature_detection\";\nimport { registry } from \"../registry\";\nimport { browser } from \"../browser/browser\";\nimport { getVisibleElements } from \"../utils/ui\";\n\nconst ALPHANUM_KEYS = \"abcdefghijklmnopqrstuvwxyz0123456789\".split(\"\");\nconst NAV_KEYS = [\n    \"arrowleft\",\n    \"arrowright\",\n    \"arrowup\",\n    \"arrowdown\",\n    \"pageup\",\n    \"pagedown\",\n    \"home\",\n    \"end\",\n    \"backspace\",\n    \"enter\",\n    \"tab\",\n    \"delete\",\n];\nconst MODIFIERS = new Set([\"alt\", \"control\", \"shift\"]);\nconst AUTHORIZED_KEYS = new Set([...ALPHANUM_KEYS, ...NAV_KEYS, \"escape\"]);\n\nexport const hotkeyService = {\n    dependencies: [\"ui\"],\n    // Be aware that all odoo hotkeys are designed with this modifier in mind,\n    // so changing the overlay modifier may conflict with some shortcuts.\n    overlayModifier: \"alt\",\n    start(env, { ui }) {\n        const registrations = new Map();\n        let nextToken = 0;\n        let overlaysVisible = false;\n\n        addListeners(browser);\n\n        function addListeners(target) {\n            target.addEventListener(\"keydown\", onKeydown);\n            target.addEventListener(\"keyup\", removeHotkeyOverlays);\n            target.addEventListener(\"blur\", removeHotkeyOverlays);\n            target.addEventListener(\"click\", removeHotkeyOverlays);\n        }\n\n        /**\n         * Handler for keydown events.\n         * Verifies if the keyboard event can be dispatched or not.\n         * Rules sequence to forbid dispatching :\n         * - UI is blocked\n         * - the pressed key is not whitelisted\n         *\n         * @param {KeyboardEvent} event\n         */\n        function onKeydown(event) {\n            if (!event.key) {\n                // Chrome may trigger incomplete keydown events under certain circumstances.\n                // E.g. when using browser built-in autocomplete on an input.\n                // See https://stackoverflow.com/questions/59534586/google-chrome-fires-keydown-event-when-form-autocomplete\n                return;\n            }\n\n            if (event.code && event.code.indexOf(\"Numpad\") === 0 && /^\\d$/.test(event.key)) {\n                // Ignore all number keys from the Keypad because of a certain input method\n                // of (advance-)ASCII characters on Windows OS: ALT+[numerical code from keypad]\n                // See https://support.microsoft.com/en-us/office/insert-ascii-or-unicode-latin-based-symbols-and-characters-d13f58d3-7bcb-44a7-a4d5-972ee12e50e0#bm1\n                return;\n            }\n\n            const hotkey = getActiveHotkey(event);\n\n            // Do not dispatch if UI is blocked\n            if (ui.isBlocked) {\n                return;\n            }\n\n            // Replace all [accesskey] attrs by [data-hotkey] on all elements.\n            // This is needed to take over on the default accesskey behavior\n            // and also to avoid any conflict with it.\n            const elementsWithAccessKey = document.querySelectorAll(\"[accesskey]\");\n            for (const el of elementsWithAccessKey) {\n                if (el instanceof HTMLElement) {\n                    el.dataset.hotkey = el.accessKey;\n                    el.removeAttribute(\"accesskey\");\n                }\n            }\n\n            // Special case: open hotkey overlays\n            if (hotkey === hotkeyService.overlayModifier) {\n                addHotkeyOverlays();\n                event.preventDefault();\n                return;\n            }\n\n            // Is the pressed key NOT whitelisted ?\n            const singleKey = hotkey.split(\"+\").pop();\n            if (!AUTHORIZED_KEYS.has(singleKey)) {\n                return;\n            }\n\n            // Protect any editable target that does not explicitly accept hotkeys\n            // NB: except for ESC, which is always allowed as hotkey in editables.\n            const targetIsEditable =\n                event.target instanceof HTMLElement &&\n                (/input|textarea/i.test(event.target.tagName) || event.target.isContentEditable);\n            const shouldProtectEditable =\n                targetIsEditable && !event.target.dataset.allowHotkeys && singleKey !== \"escape\";\n\n            // Finally, prepare and dispatch.\n            const infos = {\n                hotkey,\n                shouldProtectEditable,\n                _originalEvent: event,\n            };\n            dispatch(infos);\n            removeHotkeyOverlays(event);\n        }\n\n        /**\n         * Dispatches an hotkey to first matching registration.\n         * Registrations are iterated in following order:\n         * - priority to all registrations done through the hotkeyService.add()\n         *   method (NB: in descending order of insertion = newer first)\n         * - then all registrations done through the DOM [data-hotkey] attribute\n         *\n         * @param {{\n         *  hotkey: string,\n         *  shouldProtectEditable: boolean,\n         *  _originalEvent: KeyboardEvent\n         * }} infos\n         */\n        function dispatch(infos) {\n            let dispatched = false;\n            const { hotkey, shouldProtectEditable, _originalEvent: event } = infos;\n            const activeElement = ui.activeElement;\n\n            // Dispatch actual hotkey to all matching registrations\n            for (const reg of Array.from(registrations.values()).reverse()) {\n                if (!reg.global && reg.activeElement !== activeElement) {\n                    continue;\n                }\n\n                if (reg.hotkey !== hotkey) {\n                    continue;\n                }\n\n                if (!reg.allowRepeat && event.repeat) {\n                    continue;\n                }\n\n                if (!reg.bypassEditableProtection && shouldProtectEditable) {\n                    continue;\n                }\n\n                reg.callback();\n                dispatched = true;\n                break;\n            }\n            const overlayModParts = hotkeyService.overlayModifier.split(\"+\");\n            if (\n                !dispatched &&\n                !event.repeat &&\n                overlayModParts.every((el) => hotkey.includes(el))\n            ) {\n                // Click on all elements having a data-hotkey attribute matching the actual hotkey without the overlayModifier.\n                const cleanHotkey = hotkey\n                    .split(\"+\")\n                    .filter((key) => !overlayModParts.includes(key))\n                    .join(\"+\");\n                const elems = getVisibleElements(\n                    ui.activeElement,\n                    `[data-hotkey='${cleanHotkey}' i]`\n                );\n                for (const el of elems) {\n                    // AAB: not sure it is enough, we might need to trigger all events that occur when you actually click\n                    el.focus();\n                    el.click();\n                    dispatched = true;\n                    break;\n                }\n            }\n\n            // Only if event has been handheld.\n            if (dispatched) {\n                // Purpose: prevent browser defaults\n                event.preventDefault();\n                // Purpose: stop other window keydown listeners (e.g. home menu)\n                event.stopImmediatePropagation();\n            }\n        }\n\n        /**\n         * Add the hotkey overlays respecting the ui active element.\n         */\n        function addHotkeyOverlays() {\n            if (overlaysVisible) {\n                return;\n            }\n            for (const el of getVisibleElements(ui.activeElement, \"[data-hotkey]:not(:disabled)\")) {\n                const hotkey = el.dataset.hotkey;\n                const overlay = document.createElement(\"div\");\n                overlay.className = \"o_web_hotkey_overlay\";\n                overlay.appendChild(document.createTextNode(hotkey.toUpperCase()));\n\n                let overlayParent;\n                if (el.tagName.toUpperCase() === \"INPUT\") {\n                    // special case for the search input that has an access key\n                    // defined. We cannot set the overlay on the input itself,\n                    // only on its parent.\n                    overlayParent = el.parentElement;\n                } else {\n                    overlayParent = el;\n                }\n\n                if (overlayParent.style.position !== \"absolute\") {\n                    overlayParent.style.position = \"relative\";\n                }\n                overlayParent.appendChild(overlay);\n            }\n            overlaysVisible = true;\n        }\n\n        /**\n         * Remove all the hotkey overlays.\n         */\n        function removeHotkeyOverlays(event) {\n            if (!overlaysVisible) {\n                return;\n            }\n            for (const overlay of document.querySelectorAll(\".o_web_hotkey_overlay\")) {\n                overlay.remove();\n            }\n            overlaysVisible = false;\n            event.preventDefault();\n        }\n\n        /**\n         * Get the actual hotkey being pressed.\n         *\n         * @param {KeyboardEvent} ev\n         * @returns {string} the active hotkey, in lowercase\n         */\n        function getActiveHotkey(ev) {\n            const hotkey = [];\n\n            // ------- Modifiers -------\n            // Modifiers are pushed in ascending order to the hotkey.\n            if (isMacOS() ? ev.ctrlKey : ev.altKey) {\n                hotkey.push(\"alt\");\n            }\n            if (isMacOS() ? ev.metaKey : ev.ctrlKey) {\n                hotkey.push(\"control\");\n            }\n            if (ev.shiftKey) {\n                hotkey.push(\"shift\");\n            }\n\n            // ------- Key -------\n            let key = ev.key.toLowerCase();\n            // Identify if the user has tapped on the number keys above the text keys.\n            if (ev.code && ev.code.indexOf(\"Digit\") === 0) {\n                key = ev.code.slice(-1);\n            }\n            // Prefer physical keys for non-latin keyboard layout.\n            if (!AUTHORIZED_KEYS.has(key) && ev.code && ev.code.indexOf(\"Key\") === 0) {\n                key = ev.code.slice(-1).toLowerCase();\n            }\n            // Make sure we do not duplicate a modifier key\n            if (!MODIFIERS.has(key)) {\n                hotkey.push(key);\n            }\n            return hotkey.join(\"+\");\n        }\n\n        /**\n         * Registers a new hotkey.\n         *\n         * @param {string} hotkey\n         * @param {()=>void} callback\n         * @param {Object} options additional options\n         * @param {boolean} [options.allowRepeat=false]\n         *  allow registration to perform multiple times when hotkey is held down\n         * @param {boolean} [options.bypassEditableProtection=false]\n         *  if true the hotkey service will call this registration\n         *  even if an editable element is focused\n         * @param {boolean} [options.global=false]\n         *  allow registration to perform no matter the UI active element\n         * @returns {number} registration token\n         */\n        function registerHotkey(hotkey, callback, options = {}) {\n            // Validate some informations\n            if (!hotkey || hotkey.length === 0) {\n                throw new Error(\"You must specify an hotkey when registering a registration.\");\n            }\n\n            if (!callback || typeof callback !== \"function\") {\n                throw new Error(\n                    \"You must specify a callback function when registering a registration.\"\n                );\n            }\n\n            /**\n             * An hotkey must comply to these rules:\n             *  - all parts are whitelisted\n             *  - single key part comes last\n             *  - each part is separated by the dash character: \"+\"\n             */\n            const keys = hotkey\n                .toLowerCase()\n                .split(\"+\")\n                .filter((k) => !MODIFIERS.has(k));\n            if (keys.some((k) => !AUTHORIZED_KEYS.has(k))) {\n                throw new Error(\n                    `You are trying to subscribe for an hotkey ('${hotkey}')\n            that contains parts not whitelisted: ${keys.join(\", \")}`\n                );\n            } else if (keys.length > 1) {\n                throw new Error(\n                    `You are trying to subscribe for an hotkey ('${hotkey}')\n            that contains more than one single key part: ${keys.join(\"+\")}`\n                );\n            }\n\n            // Add registration\n            const token = nextToken++;\n            const registration = {\n                hotkey: hotkey.toLowerCase(),\n                callback,\n                activeElement: null,\n                allowRepeat: options && options.allowRepeat,\n                bypassEditableProtection: options && options.bypassEditableProtection,\n                global: options && options.global,\n            };\n            registrations.set(token, registration);\n\n            // Due to the way elements are mounted in the DOM by Owl (bottom-to-top),\n            // we need to wait the next micro task tick to set the context owner of the registration.\n            Promise.resolve().then(() => {\n                registration.activeElement = ui.activeElement;\n            });\n\n            return token;\n        }\n\n        /**\n         * Unsubscribes the token corresponding registration.\n         *\n         * @param {number} token\n         */\n        function unregisterHotkey(token) {\n            registrations.delete(token);\n        }\n\n        return {\n            /**\n             * @param {string} hotkey\n             * @param {() => void} callback\n             * @param {Object} options\n             * @param {boolean} [options.allowRepeat=false]\n             * @param {boolean} [options.bypassEditableProtection=false]\n             * @param {boolean} [options.global=false]\n             * @returns {() => void}\n             */\n            add(hotkey, callback, options = {}) {\n                const token = registerHotkey(hotkey, callback, options);\n                return () => {\n                    unregisterHotkey(token);\n                };\n            },\n            /**\n             * @param {HTMLIFrameElement} iframe\n             */\n            registerIframe(iframe) {\n                addListeners(iframe.contentWindow);\n            },\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"hotkey\", hotkeyService);\n", "/** @odoo-module **/\n\nimport { localization } from \"@web/core/l10n/localization\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { memoize } from \"@web/core/utils/functions\";\nimport { sprintf } from \"@web/core/utils/strings\";\n\nconst { DateTime, Settings } = luxon;\n\nconst SERVER_DATE_FORMAT = \"yyyy-MM-dd\";\nconst SERVER_TIME_FORMAT = \"HH:mm:ss\";\n\n// -----------------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------------\n\nconst alphaRegex = /[a-zA-Z]/g;\nconst nonAlphaRegex = /[^a-zA-Z]/g;\nconst nonDigitsRegex = /[^0-9]/g;\n\nconst normalizeFormatTable = {\n    // Python strftime to luxon.js conversion table\n    // See odoo/addons/base/views/res_lang_views.xml\n    // for details about supported directives\n    a: \"ccc\",\n    A: \"cccc\",\n    b: \"MMM\",\n    B: \"MMMM\",\n    d: \"dd\",\n    H: \"HH\",\n    I: \"hh\",\n    j: \"o\",\n    m: \"MM\",\n    M: \"mm\",\n    p: \"a\",\n    S: \"ss\",\n    W: \"WW\",\n    w: \"c\",\n    y: \"yy\",\n    Y: \"yyyy\",\n    c: \"ccc MMM d HH:mm:ss yyyy\",\n    x: \"MM/dd/yy\",\n    X: \"HH:mm:ss\",\n};\n\nconst smartDateUnits = {\n    d: \"days\",\n    m: \"months\",\n    w: \"weeks\",\n    y: \"years\",\n};\nconst smartDateRegex = new RegExp(`^([+-])(\\\\d+)([${Object.keys(smartDateUnits).join(\"\")}]?)$`);\n\n/**\n * Smart date inputs are shortcuts to write dates quicker.\n * These shortcuts should respect the format ^[+-]\\d+[dmwy]?$\n *\n * e.g.\n *   \"+1d\" or \"+1\" will return now + 1 day\n *   \"-2w\" will return now - 2 weeks\n *   \"+3m\" will return now + 3 months\n *   \"-4y\" will return now + 4 years\n *\n * @param {string} value\n * @returns {DateTime|false} Luxon datetime object (in the UTC timezone)\n */\nfunction parseSmartDateInput(value) {\n    const match = smartDateRegex.exec(value);\n    if (match) {\n        let date = DateTime.utc();\n        const offset = parseInt(match[2], 10);\n        const unit = smartDateUnits[match[3] || \"d\"];\n        if (match[1] === \"+\") {\n            date = date.plus({ [unit]: offset });\n        } else {\n            date = date.minus({ [unit]: offset });\n        }\n        return date;\n    }\n    return false;\n}\n\n/**\n * Enforces some restrictions to a Luxon DateTime object.\n * Returns it if within those restrictions.\n * Returns false otherwise.\n *\n * @param {DateTime | false} date\n * @returns {DateTime | false}\n */\nfunction constrain(dt) {\n    let valid = dt !== false;\n    valid = valid && dt.isValid;\n    valid = valid && dt.year >= 1000;\n    valid = valid && dt.year < 10000;\n    return valid ? dt : false;\n}\n\n/**\n * Removes any duplicated alphabetic characters in a given string.\n * Example: \"aa-bb-CCcc-ddD xxxx-Yy-ZZ\" -> \"a-b-Cc-dD x-Yy-Z\"\n *\n * @param {string} str\n * @returns {string}\n */\nconst stripAlphaDupes = memoize(function stripAlphaDupes(str) {\n    return str.replace(alphaRegex, (letter, index, str) => {\n        return letter === str[index - 1] ? \"\" : letter;\n    });\n});\n\n/**\n * Convert Python strftime to escaped luxon.js format.\n *\n * @param {string} value original format\n * @returns {string} valid Luxon format\n */\nexport const strftimeToLuxonFormat = memoize(function strftimeToLuxonFormat(value) {\n    const output = [];\n    let inToken = false;\n    for (let index = 0; index < value.length; ++index) {\n        let character = value[index];\n        if (character === \"%\" && !inToken) {\n            inToken = true;\n            continue;\n        }\n        if (character.match(alphaRegex)) {\n            if (inToken && normalizeFormatTable[character] !== undefined) {\n                character = normalizeFormatTable[character];\n            } else {\n                character = \"'\" + character + \"'\";  // luxon escape\n            }\n        }\n        output.push(character);\n        inToken = false;\n    }\n    return output.join(\"\");\n});\n\n// -----------------------------------------------------------------------------\n// Formatting\n// -----------------------------------------------------------------------------\n\n/**\n * Formats a DateTime object to a date string\n *\n * `options.timezone` is defaulted to false on dates since we assume they\n * shouldn't be affected by timezones like datetimes.\n *\n * @see formatDateTime\n * @returns {string}\n */\nexport function formatDate(value, options = {}) {\n    value = value.setZone(\"utc\", { keepLocalTime: true });\n    return formatDateTime(value, {\n        timezone: false, // Timezone should never alter a 'date' value.\n        ...options,\n        format: options.format || localization.dateFormat,\n    });\n}\n\n/**\n * Formats a DateTime object to a datetime string\n *\n * @param {DateTime | false} value\n * @param {Object} options\n * @param {string} [options.format]\n *  Provided format used to format the input DateTime object.\n *\n *  Default=the session localization format.\n *\n * @param {boolean} [options.timezone]\n *  - True = input will be set in local time before being formatted.\n *  - False = input will be set in UTC time before being formatted.\n *\n *  Default=false.\n *\n * @param {string} [options.numberingSystem]\n *  Provided numbering system used to parse the input value.\n *\n * Default=the default numbering system assigned to luxon\n * @see localization_service.js\n *\n * @returns {string}\n */\nexport function formatDateTime(value, options = {}) {\n    if (value === false) {\n        return \"\";\n    }\n    const format = options.format || localization.dateTimeFormat;\n    const numberingSystem = options.numberingSystem || Settings.defaultNumberingSystem;\n    const zone = options.timezone ? \"local\" : \"utc\";\n    value = value.setZone(zone, { keepLocaltime: options.timezone });\n    return value.toFormat(format, { numberingSystem });\n}\n\n// -----------------------------------------------------------------------------\n// Parsing\n// -----------------------------------------------------------------------------\n\n/**\n * Parses a string value to a Luxon DateTime object.\n *\n * `options.timezone` is defaulted to false on dates since we assume they\n * shouldn't be affected by timezones like datetimes.\n *\n * @see parseDateTime (Note: since we're only interested by the date itself, the\n *  returned value will always be set at the start of the day)\n * @returns {DateTime | false} Luxon DateTime object\n */\nexport function parseDate(value, options = {}) {\n    return parseDateTime(value, { timezone: false, ...options }).startOf(\"day\");\n}\n\n/**\n * Parses a string value to a Luxon DateTime object.\n *\n * @param {string} value value to parse.\n *  - Value can take the form of a smart date:\n *    e.g. \"+3w\" for three weeks from now.\n *    (`options.format` and `options.timezone` are ignored in this case)\n *\n *  - If value cannot be parsed within the provided format,\n *    ISO8601 and SQL formats are then tried.\n *\n * @param {object} options\n * @param {string} [options.format]\n *  Provided format used to parse the input value.\n *\n *  Default=the session localization format\n *\n * @param {boolean} [options.timezone]\n *  - True = input value is considered being in localtime.\n *  - False = input value is considered being in utc time, and the returned\n *            value will have the UTC zone.\n *\n *  NB: ISO strings containing timezone information\n *      will have priority over this option.\n *\n *  Default=false.\n *\n * @param {string} [options.locale]\n *  Provided locale used to parse the input value.\n *\n * Default=the session localization locale\n *\n * @param {string} [options.numberingSystem]\n *  Provided numbering system used to parse the input value.\n *\n * Default=the default numbering system assigned to luxon\n * @see localization_service.js\n *\n * @returns {DateTime | false} Luxon DateTime object\n */\nexport function parseDateTime(value, options = {}) {\n    if (!value) {\n        return false;\n    }\n\n    const valueDigitsOnly = value.replace(nonDigitsRegex, \"\");\n    const parseOpts = {\n        setZone: true,\n        zone: options.timezone ? \"local\" : \"utc\",\n        locale: options.locale,\n        numberingSystem: options.numberingSystem || Settings.defaultNumberingSystem,\n    };\n\n    let result = constrain(parseSmartDateInput(value));\n\n    if (!result) {\n        const fmt = options.format || localization.dateTimeFormat;\n        const fmtWoZero = stripAlphaDupes(fmt);\n\n        // Luxon is not permissive regarding delimiting characters in the format.\n        // So if the value to parse has less characters than the format, we would\n        // try to parse without the delimiting characters.\n        const woSeps = {\n            val: valueDigitsOnly,\n            fmt: fmt.replace(nonAlphaRegex, \"\").slice(0, valueDigitsOnly.length),\n        };\n\n        result =\n            constrain(DateTime.fromFormat(value, fmt, parseOpts)) ||\n            constrain(DateTime.fromFormat(value, fmtWoZero, parseOpts)) ||\n            constrain(DateTime.fromFormat(woSeps.val, woSeps.fmt, parseOpts));\n    }\n\n    if (!result) {\n        if (valueDigitsOnly.length > 4) {\n            // Also try some fallback formats, but only if value counts more than\n            // four digit characters as this could get misinterpreted as the time of\n            // the actual date.\n            result =\n                constrain(DateTime.fromISO(value, parseOpts)) || // ISO8601\n                constrain(DateTime.fromSQL(value, parseOpts)); // last try: SQL\n        }\n    }\n\n    if (!result) {\n        throw new Error(sprintf(_t(\"'%s' is not a correct date or datetime\"), value));\n    }\n\n    return options.timezone ? result : result.toUTC();\n}\n\n/**\n * Returns a date object parsed from the given serialized string.\n * @param {string} value\n * @returns {DateTime | false}\n */\nexport const deserializeDate = (value) => {\n    return parseDate(value, {\n        format: SERVER_DATE_FORMAT,\n        numberingSystem: \"latn\",\n    });\n};\n\n/**\n * Returns a datetime object parsed from the given serialized string.\n * @param {string} value\n * @returns {DateTime | false}\n */\nexport const deserializeDateTime = (value) => {\n    return parseDateTime(value, {\n        format: `${SERVER_DATE_FORMAT} ${SERVER_TIME_FORMAT}`,\n        numberingSystem: \"latn\",\n    });\n};\n\n/**\n * Returns a serialized string representing the given date.\n * @param {DateTime} value\n * @returns {string}\n */\nexport const serializeDate = (value) => {\n    return formatDate(value, {\n        format: SERVER_DATE_FORMAT,\n        numberingSystem: \"latn\",\n    });\n};\n\n/**\n * Returns a serialized string representing the given datetime.\n * @param {DateTime} value\n * @returns {string}\n */\nexport const serializeDateTime = (value) => {\n    return formatDateTime(value, {\n        format: `${SERVER_DATE_FORMAT} ${SERVER_TIME_FORMAT}`,\n        numberingSystem: \"latn\",\n    });\n};\n", "/** @odoo-module **/\n\n/**\n * @type {any}\n */\nconst notReadyError = new Error(\n    \"Localization parameters not ready yet. Maybe add 'localization' to your dependencies?\"\n);\n\n/**\n * This is the main object holding user specific data about the localization. Its basically\n * the JS counterpart of the \"res.lang\" model.\n * It is useful to directly access those data anywhere, even outside Components.\n *\n * Important Note: its data are actually loaded by the localization_service,\n * so a code like the following would not work:\n *   import { localization } from \"@web/core/l10n/localization\";\n *   const dateFormat = localization.dateFormat; // dateFormat isn't set yet\n */\nexport const localization = {\n    dateFormat: notReadyError,\n    dateTimeFormat: notReadyError,\n    timeFormat: notReadyError,\n    decimalPoint: notReadyError,\n    direction: notReadyError,\n    grouping: notReadyError,\n    multiLang: notReadyError,\n    thousandsSep: notReadyError,\n    weekStart: notReadyError,\n};\n", "/** @odoo-module **/\n\nimport { session } from \"@web/session\";\nimport { browser } from \"../browser/browser\";\nimport { registry } from \"../registry\";\nimport { strftimeToLuxonFormat } from \"./dates\";\nimport { localization } from \"./localization\";\nimport { translatedTerms, _t } from \"./translation\";\n\nconst { Settings } = luxon;\n\n/** @type {[RegExp, string][]} */\nconst NUMBERING_SYSTEMS = [\n    [/^ar-(sa|sy|001)$/i, \"arab\"],\n    [/^bn/i, \"beng\"],\n    [/^bo/i, \"tibt\"],\n    // [/^fa/i, \"Farsi (Persian)\"], // No numberingSystem found in Intl\n    // [/^(hi|mr|ne)/i, \"Hindi\"], // No numberingSystem found in Intl\n    // [/^my/i, \"Burmese\"], // No numberingSystem found in Intl\n    [/^pa-in/i, \"guru\"],\n    [/^ta/i, \"tamldec\"],\n    [/.*/i, \"latn\"],\n];\n\nexport const localizationService = {\n    dependencies: [\"user\"],\n    start: async (env, { user }) => {\n        // add \"data-toolip\" to the list of translatable attributes in owl templates\n        owl.config.translatableAttributes.push(\"data-tooltip\");\n\n        const cacheHashes = session.cache_hashes || {};\n        const translationsHash = cacheHashes.translations || new Date().getTime().toString();\n        const lang = user.lang || null;\n        const translationURL = session.translationURL || \"/web/webclient/translations\";\n        let url = `${translationURL}/${translationsHash}`;\n        if (lang) {\n            url += `?lang=${lang}`;\n        }\n\n        const response = await browser.fetch(url);\n        if (!response.ok) {\n            throw new Error(\"Error while fetching translations\");\n        }\n\n        const {\n            lang_parameters: userLocalization,\n            modules: modules,\n            multi_lang: multiLang,\n        } = await response.json();\n\n        // FIXME We flatten the result of the python route.\n        // Eventually, we want a new python route to return directly the good result.\n        let terms = {};\n        for (const addon of Object.keys(modules)) {\n            for (const message of modules[addon].messages) {\n                terms[message.id] = message.string;\n            }\n        }\n\n        Object.setPrototypeOf(translatedTerms, terms);\n        env._t = _t;\n        env.qweb.translateFn = _t;\n\n        if (lang) {\n            // Setup lang inside luxon. The locale codes received from the server contain \"_\",\n            // whereas the Intl codes use \"-\" (Unicode BCP 47). There's only one exception, which\n            // is locale \"sr@latin\", for which we manually fallback to the \"sr-Latn-RS\" locale.\n            const locale = lang === \"sr@latin\" ? \"sr-Latn-RS\" : lang.replace(/_/g, \"-\");\n            Settings.defaultLocale = locale;\n            for (const [re, numberingSystem] of NUMBERING_SYSTEMS) {\n                if (re.test(locale)) {\n                    Settings.defaultNumberingSystem = numberingSystem;\n                    break;\n                }\n            }\n        }\n\n        const dateFormat = strftimeToLuxonFormat(userLocalization.date_format);\n        const timeFormat = strftimeToLuxonFormat(userLocalization.time_format);\n        const dateTimeFormat = `${dateFormat} ${timeFormat}`;\n        const grouping = JSON.parse(userLocalization.grouping);\n\n        Object.assign(localization, {\n            dateFormat,\n            timeFormat,\n            dateTimeFormat,\n            decimalPoint: userLocalization.decimal_point,\n            direction: userLocalization.direction,\n            grouping,\n            multiLang,\n            thousandsSep: userLocalization.thousands_sep,\n            weekStart: userLocalization.week_start,\n        });\n    },\n};\n\nregistry.category(\"services\").add(\"localization\", localizationService);\n", "/** @odoo-module **/\n\nexport const translatedTerms = {};\n\n/**\n * Translate a term, or return the term if no translation can be found.\n *\n * Note that it translates eagerly, which means that if the translations have\n * not been loaded yet, it will return the untranslated term. If it cannot be\n * guaranteed that translations are ready, one should use the _lt function\n * instead (see below)\n *\n * @param {string} term\n * @returns {string}\n */\nexport function _t(term) {\n    return translatedTerms[term] || term;\n}\n\nclass LazyTranslatedString extends String {\n    valueOf() {\n        const str = super.valueOf();\n        return _t(str);\n    }\n    toString() {\n        return this.valueOf();\n    }\n}\n\n/**\n * Lazy translation function, only performs the translation when actually\n * printed (e.g. inserted into a template).\n * Useful when defining translatable strings in code evaluated before the\n * translations are loaded, as class attributes or at the top-level of\n * an Odoo Web module\n *\n * @param {string} term\n * @returns {LazyTranslatedString}\n */\nexport function _lt(term) {\n    return new LazyTranslatedString(term);\n}\n\n/*\n * Setup jQuery timeago:\n * Strings in timeago are \"composed\" with prefixes, words and suffixes. This\n * makes their detection by our translating system impossible. Use all literal\n * strings we're using with a translation mark here so the extractor can do its\n * job.\n */\n_lt(\"less than a minute ago\");\n_lt(\"about a minute ago\");\n_lt(\"%d minutes ago\");\n_lt(\"about an hour ago\");\n_lt(\"%d hours ago\");\n_lt(\"a day ago\");\n_lt(\"%d days ago\");\n_lt(\"about a month ago\");\n_lt(\"%d months ago\");\n_lt(\"about a year ago\");\n_lt(\"%d years ago\");\n", "/** @odoo-module */\nimport { registry } from \"./registry\";\nimport { NotUpdatable, ErrorHandler } from \"./utils/components\";\n\nconst { Component, tags } = owl;\n\nexport class MainComponentsContainer extends Component {\n    setup() {\n        this.Components = registry.category(\"main_components\").getEntries();\n    }\n\n    handleComponentError(error, C) {\n        // remove the faulty component and rerender without it\n        this.Components.splice(this.Components.indexOf(C), 1);\n        this.render();\n        /**\n         * we rethrow the error to notify the user something bad happened.\n         * We do it after a tick to make sure owl can properly finish its\n         * rendering\n         */\n        Promise.resolve().then(() => {\n            throw error;\n        });\n    }\n}\n\nMainComponentsContainer.template = tags.xml`\n<div>\n    <t t-foreach=\"Components\" t-as=\"C\" t-key=\"C[0]\">\n        <NotUpdatable>\n            <ErrorHandler onError=\"error => handleComponentError(error, C)\">\n                <t t-component=\"C[1].Component\" t-props=\"C[1].props\"/>\n            </ErrorHandler>\n        </NotUpdatable>\n    </t>\n</div>\n`;\nMainComponentsContainer.components = { NotUpdatable, ErrorHandler };\n", "/** @odoo-module **/\n\nimport { _lt } from \"../l10n/translation\";\nimport { makeErrorFromResponse } from \"@web/core/network/rpc_service\";\nimport { ConnectionLostError } from \"@web/core/network/rpc_service\";\nimport { browser } from \"@web/core/browser/browser\";\n\n// -----------------------------------------------------------------------------\n// Content Disposition Library\n// -----------------------------------------------------------------------------\n\n/*\n(The MIT License)\nCopyright (c) 2014-2017 Douglas Christopher Wilson\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n/**\n * Stripped down to only parsing/decoding.\n * Slightly changed for export and lint compliance\n */\n\n/**\n * RegExp to match percent encoding escape.\n * @private\n */\nconst HEX_ESCAPE_REPLACE_REGEXP = /%([0-9A-Fa-f]{2})/g;\n\n/**\n * RegExp to match non-latin1 characters.\n * @private\n */\nconst NON_LATIN1_REGEXP = /[^\\x20-\\x7e\\xa0-\\xff]/g;\n\n/**\n * RegExp to match quoted-pair in RFC 2616\n *\n * quoted-pair = \"\\\" CHAR\n * CHAR        = <any US-ASCII character (octets 0 - 127)>\n * @private\n */\nconst QESC_REGEXP = /\\\\([\\u0000-\\u007f])/g;\n\n/**\n * RegExp for various RFC 2616 grammar\n *\n * parameter     = token \"=\" ( token | quoted-string )\n * token         = 1*<any CHAR except CTLs or separators>\n * separators    = \"(\" | \")\" | \"<\" | \">\" | \"@\"\n *               | \",\" | \";\" | \":\" | \"\\\" | <\">\n *               | \"/\" | \"[\" | \"]\" | \"?\" | \"=\"\n *               | \"{\" | \"}\" | SP | HT\n * quoted-string = ( <\"> *(qdtext | quoted-pair ) <\"> )\n * qdtext        = <any TEXT except <\">>\n * quoted-pair   = \"\\\" CHAR\n * CHAR          = <any US-ASCII character (octets 0 - 127)>\n * TEXT          = <any OCTET except CTLs, but including LWS>\n * LWS           = [CRLF] 1*( SP | HT )\n * CRLF          = CR LF\n * CR            = <US-ASCII CR, carriage return (13)>\n * LF            = <US-ASCII LF, linefeed (10)>\n * SP            = <US-ASCII SP, space (32)>\n * HT            = <US-ASCII HT, horizontal-tab (9)>\n * CTL           = <any US-ASCII control character (octets 0 - 31) and DEL (127)>\n * OCTET         = <any 8-bit sequence of data>\n * @private\n */\nconst PARAM_REGEXP = /;[\\x09\\x20]*([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\\x09\\x20]*=[\\x09\\x20]*(\"(?:[\\x20!\\x23-\\x5b\\x5d-\\x7e\\x80-\\xff]|\\\\[\\x20-\\x7e])*\"|[!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\\x09\\x20]*/g;\n\n/**\n * RegExp for various RFC 5987 grammar\n *\n * ext-value     = charset  \"'\" [ language ] \"'\" value-chars\n * charset       = \"UTF-8\" / \"ISO-8859-1\" / mime-charset\n * mime-charset  = 1*mime-charsetc\n * mime-charsetc = ALPHA / DIGIT\n *               / \"!\" / \"#\" / \"$\" / \"%\" / \"&\"\n *               / \"+\" / \"-\" / \"^\" / \"_\" / \"`\"\n *               / \"{\" / \"}\" / \"~\"\n * language      = ( 2*3ALPHA [ extlang ] )\n *               / 4ALPHA\n *               / 5*8ALPHA\n * extlang       = *3( \"-\" 3ALPHA )\n * value-chars   = *( pct-encoded / attr-char )\n * pct-encoded   = \"%\" HEXDIG HEXDIG\n * attr-char     = ALPHA / DIGIT\n *               / \"!\" / \"#\" / \"$\" / \"&\" / \"+\" / \"-\" / \".\"\n *               / \"^\" / \"_\" / \"`\" / \"|\" / \"~\"\n * @private\n */\nconst EXT_VALUE_REGEXP = /^([A-Za-z0-9!#$%&+\\-^_`{}~]+)'(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}){0,3}|[A-Za-z]{4,8}|)'((?:%[0-9A-Fa-f]{2}|[A-Za-z0-9!#$&+.^_`|~-])+)$/;\n\n/**\n * RegExp for various RFC 6266 grammar\n *\n * disposition-type = \"inline\" | \"attachment\" | disp-ext-type\n * disp-ext-type    = token\n * disposition-parm = filename-parm | disp-ext-parm\n * filename-parm    = \"filename\" \"=\" value\n *                  | \"filename*\" \"=\" ext-value\n * disp-ext-parm    = token \"=\" value\n *                  | ext-token \"=\" ext-value\n * ext-token        = <the characters in token, followed by \"*\">\n * @private\n */\nconst DISPOSITION_TYPE_REGEXP = /^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\\x09\\x20]*(?:$|;)/;\n\n/**\n * Decode a RFC 6987 field value (gracefully).\n *\n * @param {string} str\n * @return {string}\n * @private\n */\nfunction decodefield(str) {\n    const match = EXT_VALUE_REGEXP.exec(str);\n\n    if (!match) {\n        throw new TypeError(\"invalid extended field value\");\n    }\n\n    const charset = match[1].toLowerCase();\n    const encoded = match[2];\n\n    switch (charset) {\n        case \"iso-8859-1\":\n            return encoded\n                .replace(HEX_ESCAPE_REPLACE_REGEXP, pdecode)\n                .replace(NON_LATIN1_REGEXP, \"?\");\n        case \"utf-8\":\n            return decodeURIComponent(encoded);\n        default:\n            throw new TypeError(\"unsupported charset in extended field\");\n    }\n}\n\n/**\n * Parse Content-Disposition header string.\n *\n * @param {string} string\n * @return {ContentDisposition}\n * @public\n */\nfunction parse(string) {\n    if (!string || typeof string !== \"string\") {\n        throw new TypeError(\"argument string is required\");\n    }\n\n    let match = DISPOSITION_TYPE_REGEXP.exec(string);\n\n    if (!match) {\n        throw new TypeError(\"invalid type format\");\n    }\n\n    // normalize type\n    let index = match[0].length;\n    const type = match[1].toLowerCase();\n\n    let key;\n    const names = [];\n    const params = {};\n    let value;\n\n    // calculate index to start at\n    index = PARAM_REGEXP.lastIndex = match[0].substr(-1) === \";\" ? index - 1 : index;\n\n    // match parameters\n    while ((match = PARAM_REGEXP.exec(string))) {\n        if (match.index !== index) {\n            throw new TypeError(\"invalid parameter format\");\n        }\n\n        index += match[0].length;\n        key = match[1].toLowerCase();\n        value = match[2];\n\n        if (names.indexOf(key) !== -1) {\n            throw new TypeError(\"invalid duplicate parameter\");\n        }\n\n        names.push(key);\n\n        if (key.indexOf(\"*\") + 1 === key.length) {\n            // decode extended value\n            key = key.slice(0, -1);\n            value = decodefield(value);\n\n            // overwrite existing value\n            params[key] = value;\n            continue;\n        }\n\n        if (typeof params[key] === \"string\") {\n            continue;\n        }\n\n        if (value[0] === '\"') {\n            // remove quotes and escapes\n            value = value.substr(1, value.length - 2).replace(QESC_REGEXP, \"$1\");\n        }\n\n        params[key] = value;\n    }\n\n    if (index !== -1 && index !== string.length) {\n        throw new TypeError(\"invalid parameter format\");\n    }\n\n    return new ContentDisposition(type, params);\n}\n\n/**\n * Percent decode a single character.\n *\n * @param {string} str\n * @param {string} hex\n * @return {string}\n * @private\n */\nfunction pdecode(str, hex) {\n    return String.fromCharCode(parseInt(hex, 16));\n}\n\n/**\n * Class for parsed Content-Disposition header for v8 optimization\n *\n * @public\n * @param {string} type\n * @param {object} parameters\n * @constructor\n */\nfunction ContentDisposition(type, parameters) {\n    this.type = type;\n    this.parameters = parameters;\n}\n\n// -----------------------------------------------------------------------------\n// download.js library\n// -----------------------------------------------------------------------------\n\n/*\nMIT License\nCopyright (c) 2016 dandavis\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n */\n\n/**\n * download.js v4.2, by dandavis; 2008-2018. [MIT] see http://danml.com/download.html for tests/usage\n * v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime\n * v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs\n * v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support. 3.1 improved safari handling.\n * v4 adds AMD/UMD, commonJS, and plain browser support\n * v4.1 adds url download capability via solo URL argument (same domain/CORS only)\n * v4.2 adds semantic variable names, long (over 2MB) dataURL support, and hidden by default temp anchors\n *\n * Slightly modified for export and lint compliance\n *\n * @param {Blob | File | String} data\n * @param {String} [filename]\n * @param {String} [mimetype]\n */\nfunction _download(data, filename, mimetype) {\n    let self = window, // this script is only for browsers anyway...\n        defaultMime = \"application/octet-stream\", // this default mime also triggers iframe downloads\n        mimeType = mimetype || defaultMime,\n        payload = data,\n        url = !filename && !mimetype && payload,\n        anchor = document.createElement(\"a\"),\n        toString = function (a) {\n            return String(a);\n        },\n        myBlob = self.Blob || self.MozBlob || self.WebKitBlob || toString,\n        fileName = filename || \"download\",\n        blob,\n        reader;\n    myBlob = myBlob.call ? myBlob.bind(self) : Blob;\n\n    if (String(this) === \"true\") {\n        //reverse arguments, allowing download.bind(true, \"text/xml\", \"export.xml\") to act as a callback\n        payload = [payload, mimeType];\n        mimeType = payload[0];\n        payload = payload[1];\n    }\n\n    if (url && url.length < 2048) {\n        // if no filename and no mime, assume a url was passed as the only argument\n        fileName = url.split(\"/\").pop().split(\"?\")[0];\n        anchor.href = url; // assign href prop to temp anchor\n        if (anchor.href.indexOf(url) !== -1) {\n            // if the browser determines that it's a potentially valid url path:\n            let ajax = new XMLHttpRequest();\n            ajax.open(\"GET\", url, true);\n            ajax.responseType = \"blob\";\n            ajax.onload = function (e) {\n                _download(e.target.response, fileName, defaultMime);\n            };\n            setTimeout(() => {\n                ajax.send();\n            }, 0); // allows setting custom ajax headers using the return:\n            return ajax;\n        }\n    }\n\n    //go ahead and download dataURLs right away\n    if (/^data:[\\w+\\-]+\\/[\\w+\\-]+[,;]/.test(payload)) {\n        if (payload.length > 1024 * 1024 * 1.999 && myBlob !== toString) {\n            payload = dataUrlToBlob(payload);\n            mimeType = payload.type || defaultMime;\n        } else {\n            return navigator.msSaveBlob // IE10 can't do a[download], only Blobs:\n                ? navigator.msSaveBlob(dataUrlToBlob(payload), fileName)\n                : saver(payload); // everyone else can save dataURLs un-processed\n        }\n    }\n\n    blob = payload instanceof myBlob ? payload : new myBlob([payload], { type: mimeType });\n\n    function dataUrlToBlob(strUrl) {\n        let parts = strUrl.split(/[:;,]/),\n            type = parts[1],\n            decoder = parts[2] === \"base64\" ? atob : decodeURIComponent,\n            binData = decoder(parts.pop()),\n            mx = binData.length,\n            i = 0,\n            uiArr = new Uint8Array(mx);\n\n        for (i; i < mx; ++i) {\n            uiArr[i] = binData.charCodeAt(i);\n        }\n\n        return new myBlob([uiArr], { type });\n    }\n\n    function saver(url, winMode) {\n        if (\"download\" in anchor) {\n            //html5 A[download]\n            anchor.href = url;\n            anchor.setAttribute(\"download\", fileName);\n            anchor.className = \"download-js-link\";\n            anchor.innerText = _lt(\"downloading...\");\n            anchor.style.display = \"none\";\n            document.body.appendChild(anchor);\n            setTimeout(() => {\n                anchor.click();\n                document.body.removeChild(anchor);\n                if (winMode === true) {\n                    setTimeout(() => {\n                        self.URL.revokeObjectURL(anchor.href);\n                    }, 250);\n                }\n            }, 66);\n            return true;\n        }\n\n        // handle non-a[download] safari as best we can:\n        if (/(Version)\\/(\\d+)\\.(\\d+)(?:\\.(\\d+))?.*Safari\\//.test(navigator.userAgent)) {\n            url = url.replace(/^data:([\\w\\/\\-+]+)/, defaultMime);\n            if (!window.open(url)) {\n                // popup blocked, offer direct download:\n                if (\n                    confirm(\n                        \"Displaying New Document\\n\\nUse Save As... to download, then click back to return to this page.\"\n                    )\n                ) {\n                    location.href = url;\n                }\n            }\n            return true;\n        }\n\n        //do iframe dataURL download (old ch+FF):\n        let f = document.createElement(\"iframe\");\n        document.body.appendChild(f);\n\n        if (!winMode) {\n            // force a mime that will download:\n            url = `data:${url.replace(/^data:([\\w\\/\\-+]+)/, defaultMime)}`;\n        }\n        f.src = url;\n        setTimeout(() => {\n            document.body.removeChild(f);\n        }, 333);\n    }\n\n    if (navigator.msSaveBlob) {\n        // IE10+ : (has Blob, but not a[download] or URL)\n        return navigator.msSaveBlob(blob, fileName);\n    }\n\n    if (self.URL) {\n        // simple fast and modern way using Blob and URL:\n        saver(self.URL.createObjectURL(blob), true);\n    } else {\n        // handle non-Blob()+non-URL browsers:\n        if (typeof blob === \"string\" || blob.constructor === toString) {\n            try {\n                return saver(`data:${mimeType};base64,${self.btoa(blob)}`);\n            } catch (y) {\n                return saver(`data:${mimeType},${encodeURIComponent(blob)}`);\n            }\n        }\n\n        // Blob but not URL support:\n        reader = new FileReader();\n        reader.onload = function () {\n            saver(this.result);\n        };\n        reader.readAsDataURL(blob);\n    }\n    return true;\n}\n\n// -----------------------------------------------------------------------------\n// Exported download function\n// -----------------------------------------------------------------------------\n\n/**\n * Download a file\n *\n * Note: the actual implementation is certainly unconventional, but sadly\n * necessary to be able to test code using the download function\n *\n * @param {*} options\n * @returns {Promise<any>}\n */\nexport function download(options) {\n    return download._download(options);\n}\n\ndownload._download = (options) => {\n    return new Promise((resolve, reject) => {\n        const xhr = new browser.XMLHttpRequest();\n        let data;\n        if (Object.prototype.hasOwnProperty.call(options, \"form\")) {\n            xhr.open(options.form.method, options.form.action);\n            data = new FormData(options.form);\n        } else {\n            xhr.open(\"POST\", options.url);\n            data = new FormData();\n            Object.entries(options.data).forEach((entry) => {\n                const [key, value] = entry;\n                data.append(key, value);\n            });\n        }\n        data.append(\"token\", \"dummy-because-api-expects-one\");\n        if (odoo.csrf_token) {\n            data.append(\"csrf_token\", odoo.csrf_token);\n        }\n        // IE11 wants this after xhr.open or it throws\n        xhr.responseType = \"blob\";\n        xhr.onload = () => {\n            const mimetype = xhr.response.type;\n            // In Odoo, the default mimetype, including for JSON errors is text/html (ref: http.py:Root.get_response )\n            // in that case, we have to assume the file is not valid, hence that there was an error\n            if (xhr.status === 200 && mimetype !== \"text/html\") {\n                // replace because apparently we send some C-D headers with a trailing \";\"\n                const header = (xhr.getResponseHeader(\"Content-Disposition\") || \"\").replace(\n                    /;$/,\n                    \"\"\n                );\n                const filename = header ? parse(header).parameters.filename : null;\n                _download(xhr.response, filename, mimetype);\n                return resolve(filename);\n\n            } else if (xhr.status === 502) { // If Odoo is behind another server (nginx)\n                reject(new ConnectionLostError());\n            } else {\n                const decoder = new FileReader();\n                decoder.onload = () => {\n                    const contents = decoder.result;\n                    const doc = new DOMParser().parseFromString(contents, \"text/html\");\n                    const nodes =\n                        doc.body.children.length === 0 ? doc.body.childNodes : doc.body.children;\n\n                    let error;\n                    try { // a Serialized python Error\n                        const node = nodes[1] || nodes[0];\n                        error = JSON.parse(node.textContent);\n                    } catch (e) {\n                        error = {\n                            message: \"Arbitrary Uncaught Python Exception\",\n                            data: {\n                                debug: `${xhr.status}` + `\\n` +\n                                   `${nodes.length > 0 ? nodes[0].textContent : \"\"}\n                                    ${nodes.length > 1 ? nodes[1].textContent : \"\"}`\n                            },\n\n                        };\n                    }\n                    error = makeErrorFromResponse(error);\n                    reject(error);\n                };\n                decoder.readAsText(xhr.response);\n            }\n        };\n        xhr.onerror = () => {\n            reject(new ConnectionLostError());\n        };\n        xhr.send(data);\n    });\n};\n", "/** @odoo-module **/\n\nimport { registry } from \"../registry\";\n\nfunction checkResponseStatus(response) {\n    if (response.status === 502) {\n        throw new Error(\"Failed to fetch\");\n    }\n}\n\nexport const httpService = {\n    start() {\n        return {\n            async get(route, readMethod = \"json\") {\n                const response = await fetch(route, { method: \"GET\" });\n                checkResponseStatus(response);\n                return response[readMethod]();\n            },\n            async post(route, params = {}, readMethod = \"json\") {\n                const formData = new FormData();\n                for (const key in params) {\n                    const value = params[key];\n                    if (Array.isArray(value) && value.length) {\n                        for (const val of value) {\n                            formData.append(key, val);\n                        }\n                    } else {\n                        formData.append(key, value);\n                    }\n                }\n                const info = {\n                    body: formData,\n                    method: \"POST\",\n                };\n                const response = await fetch(route, info);\n                checkResponseStatus(response);\n                return response[readMethod]();\n            },\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"http\", httpService);\n", "/** @odoo-module **/\n\nimport { browser } from \"../browser/browser\";\nimport { registry } from \"../registry\";\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\nexport class RPCError extends Error {\n    constructor() {\n        super(...arguments);\n        this.name = \"RPC_ERROR\";\n        this.type = \"server\";\n        this.code = null;\n        this.data = null;\n        this.exceptionName = null;\n        this.subType = null;\n    }\n}\n\nexport class ConnectionLostError extends Error {}\n\nexport class ConnectionAbortedError extends Error {}\n\nexport class HTTPError extends Error {}\n\n// -----------------------------------------------------------------------------\n// Main RPC method\n// -----------------------------------------------------------------------------\nexport function makeErrorFromResponse(reponse) {\n    // Odoo returns error like this, in a error field instead of properly\n    // using http error codes...\n    const { code, data: errorData, message, type: subType } = reponse;\n    const error = new RPCError();\n    error.exceptionName = errorData.name;\n    error.subType = subType;\n    error.data = errorData;\n    error.message = message;\n    error.code = code;\n    return error;\n}\n\nexport function jsonrpc(env, rpcId, url, params, settings = {}) {\n    const bus = env.bus;\n    const XHR = browser.XMLHttpRequest;\n    const data = {\n        id: rpcId,\n        jsonrpc: \"2.0\",\n        method: \"call\",\n        params: params,\n    };\n    const request = settings.xhr || new XHR();\n    let rejectFn;\n    const promise = new Promise((resolve, reject) => {\n        rejectFn = reject;\n        if (!settings.silent) {\n            bus.trigger(\"RPC:REQUEST\", data.id);\n        }\n        // handle success\n        request.addEventListener(\"load\", () => {\n            if (request.status === 502) {\n                // If Odoo is behind another server (eg.: nginx)\n                if (!settings.silent) {\n                    bus.trigger(\"RPC:RESPONSE\", data.id);\n                }\n                reject(new ConnectionLostError());\n                return;\n            }\n            let params;\n            try {\n                params = JSON.parse(request.response);\n            } catch (_) {\n                // the response isn't json parsable, which probably means that the rpc request could\n                // not be handled by the server, e.g. PoolError('The Connection Pool Is Full')\n                if (!settings.silent) {\n                    bus.trigger(\"RPC:RESPONSE\", data.id);\n                }\n                return reject(new ConnectionLostError());\n            }\n            const { error: responseError, result: responseResult } = params;\n            if (!settings.silent) {\n                bus.trigger(\"RPC:RESPONSE\", data.id);\n            }\n            if (!responseError) {\n                return resolve(responseResult);\n            }\n            const error = makeErrorFromResponse(responseError);\n            reject(error);\n        });\n        // handle failure\n        request.addEventListener(\"error\", () => {\n            if (!settings.silent) {\n                bus.trigger(\"RPC:RESPONSE\", data.id);\n            }\n            reject(new ConnectionLostError());\n        });\n        // configure and send request\n        request.open(\"POST\", url);\n        request.setRequestHeader(\"Content-Type\", \"application/json\");\n        request.send(JSON.stringify(data));\n    });\n    /**\n     * @param {Boolean} rejectError Returns an error if true. Allows you to cancel\n     *                  ignored rpc's in order to unblock the ui and not display an error.\n     */\n    promise.abort = function (rejectError = true) {\n        if (request.abort) {\n            request.abort();\n        }\n        if (!settings.silent) {\n            bus.trigger(\"RPC:RESPONSE\", data.id);\n        }\n        if (rejectError) {\n            rejectFn(new ConnectionAbortedError(\"XmlHttpRequestError abort\"));\n        }\n    };\n    return promise;\n}\n\n// -----------------------------------------------------------------------------\n// RPC service\n// -----------------------------------------------------------------------------\nexport const rpcService = {\n    async: true,\n    start(env) {\n        let rpcId = 0;\n        return function rpc(route, params = {}, settings) {\n            return jsonrpc(env, rpcId++, route, params, settings);\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"rpc\", rpcService);\n", "/** @odoo-module **/\n\nconst { Component, hooks } = owl;\n\nexport class Notification extends Component {\n    setup() {\n        if (this.props.onClose) {\n            hooks.onWillUnmount(() => this.props.onClose());\n        }\n    }\n\n    shouldUpdate() {\n        return false;\n    }\n\n    get className() {\n        let className;\n        switch (this.props.type) {\n            case \"danger\":\n                className = \"bg-danger\";\n                break;\n            case \"warning\":\n                className = \"bg-warning\";\n                break;\n            case \"success\":\n                className = \"bg-success\";\n                break;\n            case \"info\":\n                className = \"bg-info\";\n                break;\n        }\n        return className ? `${className} ${this.props.className}` : this.props.className;\n    }\n}\n\nNotification.template = \"web.NotificationWowl\";\nNotification.props = {\n    message: {\n        validate: (m) => {\n            return (\n                typeof m === \"string\" || (typeof m === \"object\" && typeof m.toString === \"function\")\n            );\n        }\n    },\n    title: { type: [String, Boolean, { toString: Function }], optional: true },\n    type: {\n        type: String,\n        optional: true,\n        validate: (t) => [\"warning\", \"danger\", \"success\", \"info\"].includes(t)\n    },\n    messageIsHtml: { type: Boolean, optional: true },\n    className: { type: String, optional: true },\n    buttons: {\n        type: Array,\n        element: {\n            type: Object,\n            shape: {\n                name: { type: String },\n                icon: { type: String, optional: true },\n                primary: { type: Boolean, optional: true },\n                onClick: Function\n            }\n        }\n    },\n    onClose: { type: Function, optional: true }\n};\nNotification.defaultProps = {\n    buttons: [],\n    className: \"\",\n    messageIsHtml: false,\n    type: \"warning\"\n};\n", "/** @odoo-module **/\n\nimport { Notification } from \"./notification\";\n\nconst { Component, tags } = owl;\n\nexport class NotificationContainer extends Component {\n    setup() {\n        // this works, but then this component cannot be unmounted, then\n        // remounted. would need a destroyed hook different from willunmount\n        this.props.bus.on(\"UPDATE\", this, this.render);\n    }\n}\n\nNotificationContainer.template = tags.xml`\n    <div class=\"o_notification_manager\">\n        <t t-foreach=\"props.notifications\" t-as=\"notification\" t-key=\"notification.id\">\n            <Notification\n                t-props=\"notification.props\"\n                t-transition=\"o_notification_fade\"\n                t-on-close=\"notification.close()\"\n            />\n        </t>\n    </div>`;\nNotificationContainer.components = { Notification };\n", "/** @odoo-module **/\n\nimport { browser } from \"../browser/browser\";\nimport { registry } from \"../registry\";\nimport { NotificationContainer } from \"./notification_container\";\n\nconst { EventBus } = owl.core;\n\nconst AUTOCLOSE_DELAY = 4000;\n\n/**\n * @typedef {Object} NotificationButton\n * @property {string} name\n * @property {string} [icon]\n * @property {boolean} [primary=false]\n * @property {function(): void} onClick\n *\n * @typedef {Object} NotificationOptions\n * @property {string} [title]\n * @property {\"warning\" | \"danger\" | \"success\" | \"info\"} [type]\n * @property {boolean} [sticky=false]\n * @property {string} [className]\n * @property {function(): void} [onClose]\n * @property {NotificationButton[]} [buttons]\n */\n\nexport const notificationService = {\n    start() {\n        let notifId = 0;\n        let notifications = [];\n        const bus = new EventBus();\n\n        registry.category(\"main_components\").add(\"NotificationContainer\", {\n            Component: NotificationContainer,\n            props: { bus, notifications },\n        });\n\n        /**\n         * @param {string} message\n         * @param {NotificationOptions} [options]\n         */\n        function add(message, options = {}) {\n            const id = ++notifId;\n            const props = Object.assign({}, options, { message });\n            const sticky = props.sticky;\n            delete props.sticky;\n            const closeFn = () => close(id);\n            const notification = {\n                id,\n                props,\n                close: closeFn,\n            };\n            notifications.push(notification);\n            bus.trigger(\"UPDATE\");\n            if (!sticky) {\n                browser.setTimeout(closeFn, AUTOCLOSE_DELAY);\n            }\n            return closeFn;\n        }\n\n        function close(id) {\n            const index = notifications.findIndex((n) => n.id === id);\n            if (index > -1) {\n                notifications.splice(index, 1);\n                bus.trigger(\"UPDATE\");\n            }\n        }\n\n        return { add };\n    },\n};\n\nregistry.category(\"services\").add(\"notification\", notificationService);\n", "/** @odoo-module **/\n\nimport { registry } from \"./registry\";\n\n/**\n * This ORM service is the standard way to interact with the ORM in python from\n * the javascript codebase.\n */\n\n// -----------------------------------------------------------------------------\n// Helper\n// -----------------------------------------------------------------------------\nfunction assignOptions(kwargs, options, whileList) {\n    for (let elem of whileList) {\n        if (elem in options) {\n            kwargs[elem] = options[elem];\n        }\n    }\n}\n\n// -----------------------------------------------------------------------------\n// ORM\n// -----------------------------------------------------------------------------\n\n/**\n * One2many and Many2many fields expect a special command to manipulate the\n * relation they implement.\n *\n * Internally, each command is a 3-elements tuple where the first element is a\n * mandatory integer that identifies the command, the second element is either\n * the related record id to apply the command on (commands update, delete,\n * unlink and link) either 0 (commands create, clear and set), the third\n * element is either the ``values`` to write on the record (commands create\n * and update) either the new ``ids`` list of related records (command set),\n * either 0 (commands delete, unlink, link, and clear).\n */\nexport const Commands = {\n    create(virtualID, values) {\n        delete values.id;\n        return [0, virtualID || false, values];\n    },\n\n    update(id, values) {\n        delete values.id;\n        return [1, id, values];\n    },\n\n    delete(id) {\n        return [2, id, false];\n    },\n\n    forget(id) {\n        return [3, id, false];\n    },\n\n    linkTo(id) {\n        return [4, id, false];\n    },\n\n    deleteAll() {\n        return [5, false, false];\n    },\n\n    replaceWith(ids) {\n        return [6, false, ids];\n    },\n};\n\nfunction validateModel(value) {\n    if (typeof value !== \"string\" || value.length === 0) {\n        throw new Error(`Invalid model name: ${value}`);\n    }\n}\nfunction validatePrimitiveList(name, type, value) {\n    if (!Array.isArray(value) || value.some((val) => typeof val !== type)) {\n        throw new Error(`Invalid ${name} list: ${value}`);\n    }\n}\nfunction validateObject(name, obj) {\n    if (typeof obj !== \"object\" || obj === null || Array.isArray(obj)) {\n        throw new Error(`${name} should be an object`);\n    }\n}\nfunction validateArray(name, array) {\n    if (!Array.isArray(array)) {\n        throw new Error(`${name} should be an array`);\n    }\n}\n\nexport class ORM {\n    constructor(rpc, user) {\n        this.rpc = rpc;\n        this.user = user;\n        this._silent = false;\n    }\n\n    get silent() {\n        return Object.assign(Object.create(this), { _silent: true });\n    }\n\n    call(model, method, args = [], kwargs = {}) {\n        validateModel(model);\n        let url = `/web/dataset/call_kw/${model}/${method}`;\n        const fullContext = Object.assign({}, this.user.context, kwargs.context || {});\n        const fullKwargs = Object.assign({}, kwargs, { context: fullContext });\n        let params = {\n            model,\n            method,\n            args,\n            kwargs: fullKwargs,\n        };\n        return this.rpc(url, params, { silent: this._silent });\n    }\n\n    create(model, state, ctx) {\n        validateObject(\"state\", state);\n        return this.call(model, \"create\", [state], { context: ctx });\n    }\n\n    read(model, ids, fields, ctx) {\n        validatePrimitiveList(\"ids\", \"number\", ids);\n        if (fields) {\n            validatePrimitiveList(\"fields\", \"string\", fields);\n        }\n        if (!ids.length) {\n            return Promise.resolve([]);\n        }\n        return this.call(model, \"read\", [ids, fields], { context: ctx });\n    }\n\n    readGroup(model, domain, fields, groupby, options = {}, ctx = {}) {\n        validateArray(\"domain\", domain);\n        validatePrimitiveList(\"fields\", \"string\", fields);\n        validatePrimitiveList(\"groupby\", \"string\", groupby);\n        const kwargs = {\n            domain,\n            groupby,\n            fields,\n            context: ctx,\n        };\n        assignOptions(kwargs, options, [\"lazy\", \"offset\", \"orderby\", \"limit\"]);\n        return this.call(model, \"read_group\", [], kwargs);\n    }\n\n    search(model, domain, options = {}, ctx = {}) {\n        validateArray(\"domain\", domain);\n        const kwargs = {\n            context: ctx,\n        };\n        assignOptions(kwargs, options, [\"offset\", \"limit\", \"order\"]);\n        return this.call(model, \"search\", [domain], kwargs);\n    }\n\n    searchRead(model, domain, fields, options = {}, ctx = {}) {\n        validateArray(\"domain\", domain);\n        if (fields) {\n            validatePrimitiveList(\"fields\", \"string\", fields);\n        }\n        const kwargs = { context: ctx, domain, fields };\n        assignOptions(kwargs, options, [\"offset\", \"limit\", \"order\"]);\n        return this.call(model, \"search_read\", [], kwargs);\n    }\n\n    unlink(model, ids, ctx) {\n        validatePrimitiveList(\"ids\", \"number\", ids);\n        if (!ids.length) {\n            return true;\n        }\n        return this.call(model, \"unlink\", [ids], { context: ctx });\n    }\n\n    webReadGroup(model, domain, fields, groupby, options = {}, ctx = {}) {\n        validateArray(\"domain\", domain);\n        validatePrimitiveList(\"fields\", \"string\", fields);\n        validatePrimitiveList(\"groupby\", \"string\", groupby);\n        const kwargs = {\n            domain,\n            groupby,\n            fields,\n            context: ctx,\n        };\n        assignOptions(kwargs, options, [\"lazy\", \"offset\", \"orderby\", \"limit\"]);\n        return this.call(model, \"web_read_group\", [], kwargs);\n    }\n\n    webSearchRead(model, domain, fields, options = {}, ctx = {}) {\n        validateArray(\"domain\", domain);\n        validatePrimitiveList(\"fields\", \"string\", fields);\n        const kwargs = { context: ctx, domain, fields };\n        assignOptions(kwargs, options, [\"offset\", \"limit\", \"order\"]);\n        return this.call(model, \"web_search_read\", [], kwargs);\n    }\n\n    write(model, ids, data, ctx) {\n        validatePrimitiveList(\"ids\", \"number\", ids);\n        validateObject(\"data\", data);\n        return this.call(model, \"write\", [ids, data], { context: ctx });\n    }\n}\n\n/**\n * Note:\n *\n * when we will need a way to configure a rpc (for example, to setup a \"shadow\"\n * flag, or some way of not displaying errors), we can use the following api:\n *\n * this.orm = useService('orm');\n *\n * ...\n *\n * const result = await this.orm.withOption({shadow: true}).read('res.partner', [id]);\n */\nexport const ormService = {\n    dependencies: [\"rpc\", \"user\"],\n    async: [\n        \"call\",\n        \"create\",\n        \"read\",\n        \"readGroup\",\n        \"search\",\n        \"searchRead\",\n        \"unlink\",\n        \"webSearchRead\",\n        \"write\",\n    ],\n    start(env, { rpc, user }) {\n        return new ORM(rpc, user);\n    },\n};\n\nregistry.category(\"services\").add(\"orm\", ormService);\n", "/** @odoo-module **/\n\nimport { usePosition } from \"../position/position_hook\";\n\nconst { Component } = owl;\n\nexport class Popover extends Component {\n    setup() {\n        usePosition(this.props.target, {\n            margin: 16,\n            position: this.props.position,\n        });\n    }\n}\n\nPopover.template = \"web.PopoverWowl\";\nPopover.defaultProps = {\n    position: \"bottom\",\n};\nPopover.props = {\n    popoverClass: {\n        optional: true,\n        type: String,\n    },\n    position: {\n        type: String,\n        validate: (p) => [\"top\", \"bottom\", \"left\", \"right\"].includes(p),\n    },\n    target: HTMLElement,\n};\n", "/** @odoo-module **/\n\nimport { Popover } from \"./popover\";\n\nconst { Component } = owl;\nconst { useExternalListener, useState } = owl.hooks;\nconst { xml } = owl.tags;\n\nclass PopoverController extends Component {\n    setup() {\n        this.state = useState({ displayed: false });\n        this.targetObserver = new MutationObserver(this.onTargetMutate.bind(this));\n\n        useExternalListener(window, \"click\", this.onClickAway, { capture: true });\n    }\n    mounted() {\n        this.targetObserver.observe(this.target.parentElement, { childList: true });\n    }\n    willUnmount() {\n        this.targetObserver.disconnect();\n    }\n\n    shouldUpdate() {\n        return false;\n    }\n\n    get popoverProps() {\n        return {\n            target: this.target,\n            position: this.props.position,\n            popoverClass: this.props.popoverClass,\n        };\n    }\n    get target() {\n        if (typeof this.props.target === \"string\") {\n            return document.querySelector(this.props.target);\n        } else {\n            return this.props.target;\n        }\n    }\n    onClickAway(ev) {\n        if (this.target.contains(ev.target) || ev.target.closest(\".o_popover\")) {\n            return;\n        }\n        if (this.props.closeOnClickAway) {\n            this.props.close();\n        }\n    }\n    onTargetMutate() {\n        const target = this.target;\n        if (!target || !target.parentElement) {\n            this.props.close();\n        }\n    }\n}\nPopoverController.components = { Popover };\nPopoverController.defaultProps = {\n    alwaysDisplayed: false,\n    closeOnClickAway: true,\n};\nPopoverController.template = xml/*xml*/ `\n    <Popover t-props=\"popoverProps\" t-on-popover-closed=\"props.close()\">\n        <t t-component=\"props.Component\" t-props=\"props.props\" />\n    </Popover>\n`;\n\nexport class PopoverContainer extends Component {\n    setup() {\n        this.props.bus.on(\"UPDATE\", this, this.render);\n    }\n}\nPopoverContainer.components = { PopoverController };\nPopoverContainer.template = xml`\n    <div class=\"o_popover_container\">\n        <t t-foreach=\"Object.values(props.popovers)\" t-as=\"popover\" t-key=\"popover.id\">\n            <PopoverController t-props=\"popover\" />\n        </t>\n    </div>\n`;\n", "/** @odoo-module **/\n\nimport { useService } from \"@web/core/utils/hooks\";\n\nconst { onWillUnmount, useComponent } = owl.hooks;\n\nexport function usePopover() {\n    const removeFns = new Set();\n    const service = useService(\"popover\");\n    const component = useComponent();\n\n    onWillUnmount(function () {\n        for (const removeFn of removeFns) {\n            removeFn();\n        }\n        removeFns.clear();\n    });\n    return {\n        /**\n         * Signals the manager to add a popover.\n         *\n         * @param {string | HTMLElement}    target\n         * @param {any}                     Component\n         * @param {Object}                  props\n         * @param {Object}                  [options]\n         * @param {boolean}                 [options.closeOnClickAway=true]\n         * @param {function()}              [options.onClose]\n         * @param {string}                  [options.popoverClass]\n         * @param {string}                  [options.position]\n         * @returns {function()}\n         */\n        add(target, Component, props, options = {}) {\n            const newOptions = Object.create(options);\n            newOptions.onClose = function () {\n                removeFns.delete(removeFn);\n                if (options.onClose && component.__owl__.status !== 5 /* DESTROYED */) {\n                    options.onClose();\n                }\n            };\n\n            const removeFn = service.add(target, Component, props, newOptions);\n            removeFns.add(removeFn);\n            return removeFn;\n        },\n    };\n}\n", "/** @odoo-module **/\n\nimport { registry } from \"../registry\";\nimport { PopoverContainer } from \"./popover_container\";\n\nconst { EventBus } = owl.core;\n\nexport const popoverService = {\n    start() {\n        let nextId = 0;\n        const popovers = {};\n        const bus = new EventBus();\n\n        registry\n            .category(\"main_components\")\n            .add(\"PopoverContainer\", { Component: PopoverContainer, props: { bus, popovers } });\n\n        /**\n         * Signals the manager to add a popover.\n         *\n         * @param {string | HTMLElement}    target\n         * @param {any}                     Component\n         * @param {Object}                  props\n         * @param {Object}                  [options]\n         * @param {boolean}                 [options.closeOnClickAway=true]\n         * @param {function(): void}        [options.onClose]\n         * @param {string}                  [options.popoverClass]\n         * @param {string}                  [options.position]\n         * @returns {function(): void}\n         */\n        function add(target, Component, props, options = {}) {\n            const id = ++nextId;\n            const closeFn = () => close(id);\n            const popover = {\n                id,\n                target,\n                Component,\n                props,\n                close: closeFn,\n                onClose: options.onClose,\n                position: options.position,\n                popoverClass: options.popoverClass,\n                closeOnClickAway: options.closeOnClickAway,\n            };\n            popovers[id] = popover;\n            bus.trigger(\"UPDATE\");\n            return closeFn;\n        }\n\n        function close(id) {\n            if (id in popovers) {\n                const popover = popovers[id];\n                if (popover.onClose) {\n                    popover.onClose();\n                }\n                delete popovers[id];\n                bus.trigger(\"UPDATE\");\n            }\n        }\n\n        return { add };\n    },\n};\n\nregistry.category(\"services\").add(\"popover\", popoverService);\n", "/** @odoo-module */\n\nimport { onDestroyed, useEffect } from \"@web/core/utils/hooks\";\nimport { throttleForAnimation } from \"../utils/timing\";\n\nconst { core, hooks } = owl;\nconst { useComponent, useExternalListener, useRef, useSubEnv, onWillUnmount } = hooks;\nconst { EventBus } = core;\n\n/**\n * @typedef {{\n *  popper?: string;\n *  container?: HTMLElement;\n *  margin?: number;\n *  position?: Position;\n * }} Options\n *\n * @typedef {{\n *  directions: (DirectionsDataKey)[];\n *  variants: (VariantsDataKey)[];\n *  get: (d?: DirectionsDataKey, v?: VariantsDataKey, containerRestricted?: boolean) => PositioningSolution | null;\n * }} Positioning\n *\n * @typedef {keyof DirectionsData} DirectionsDataKey\n * @typedef {{\n *  t: number;\n *  b: number;\n *  l: number;\n *  r: number;\n * }} DirectionsData\n *\n * @typedef {keyof VariantsData} VariantsDataKey\n * @typedef {{\n *  vs: number;\n *  vm: number;\n *  ve: number;\n *  hs: number;\n *  hm: number;\n *  he: number;\n * }} VariantsData\n *\n * @typedef {\"top\" | \"left\" | \"bottom\" | \"right\"} Direction\n * @typedef {\"start\" | \"middle\" | \"end\"} Variant\n *\n * @typedef {{[direction in Direction]: string}} DirectionFlipOrder\n *  values are successive DirectionsDataKey represented as a single string\n *\n * @typedef {{[variant in Variant]: string}} VariantFlipOrder\n *  values are successive VariantsDataKey represented as a single string\n *\n * @typedef {Direction\n *  | \"top-start\" | \"top-middle\" | \"top-end\"\n *  | \"left-start\" | \"left-middle\" | \"left-end\"\n *  | \"right-start\" | \"right-middle\" | \"right-end\"\n *  | \"bottom-start\" | \"bottom-middle\" | \"bottom-end\"} Position\n *\n * @typedef {{ className: string, top: number, left: number }} PositioningSolution\n */\n\nconst POPPER_CLASS = \"o-popper-position\";\n/** @type DirectionFlipOrder */\nconst DIRECTION_FLIP_ORDER = { top: \"tbrl\", right: \"rltb\", bottom: \"btrl\", left: \"lrbt\" };\n/** @type VariantFlipOrder */\nconst VARIANT_FLIP_ORDER = { start: \"sme\", middle: \"mse\", end: \"ems\" };\n\n/** @type {Options} */\nexport const DEFAULTS = {\n    margin: 0,\n    position: \"bottom\",\n};\n\n/**\n * Computes positioning data used to determine each possible position\n * based on the reference, popper, and container sizes.\n * Particularly, a popper must not overflow the container in any direction,\n * it should actually stay at `margin` distance from the border to look good.\n *\n * @param {HTMLElement} reference\n * @param {HTMLElement} popper\n * @param {Options} options\n * @returns {Positioning} a positioning object containing:\n *  - ascendingly sorted directions and variants\n *  - a method returning style to apply to the popper for a given direction and variant\n */\nexport function computePositioning(reference, popper, options) {\n    const { container, margin, position } = options;\n\n    // Retrieve directions and variants\n    const [directionKey, variantKey = \"middle\"] = position.split(\"-\");\n    const directions = DIRECTION_FLIP_ORDER[directionKey];\n    const variants = VARIANT_FLIP_ORDER[variantKey];\n\n    // Boxes\n    const popBox = popper.getBoundingClientRect();\n    const refBox = reference.getBoundingClientRect();\n    const contBox = container.getBoundingClientRect();\n\n    const containerIsHTMLNode = container === document.firstElementChild;\n\n    // Compute positioning data\n    /** @type {DirectionsData} */\n    const directionsData = {\n        t: refBox.top - popBox.height - margin,\n        b: refBox.bottom + margin,\n        r: refBox.right + margin,\n        l: refBox.left - popBox.width - margin,\n    };\n    /** @type {VariantsData} */\n    const variantsData = {\n        vs: refBox.left,\n        vm: refBox.left + refBox.width / 2 + -popBox.width / 2,\n        ve: refBox.right - popBox.width,\n        hs: refBox.top,\n        hm: refBox.top + refBox.height / 2 + -popBox.height / 2,\n        he: refBox.bottom - popBox.height,\n    };\n\n    function get(d = directions[0], v = variants[0], containerRestricted = false) {\n        const vertical = [\"t\", \"b\"].includes(d);\n        const variantPrefix = vertical ? \"v\" : \"h\";\n        const directionValue = directionsData[d];\n        const variantValue = variantsData[variantPrefix + v];\n\n        if (containerRestricted) {\n            const [directionSize, variantSize] = vertical\n                ? [popBox.height + margin, popBox.width]\n                : [popBox.width, popBox.height + margin];\n            let [directionMin, directionMax] = vertical\n                ? [contBox.top, contBox.bottom]\n                : [contBox.left, contBox.right];\n            let [variantMin, variantMax] = vertical\n                ? [contBox.left, contBox.right]\n                : [contBox.top, contBox.bottom];\n\n            if (containerIsHTMLNode) {\n                if (vertical) {\n                    directionMin += container.scrollTop;\n                    directionMax += container.scrollTop;\n                } else {\n                    variantMin += container.scrollTop;\n                    variantMax += container.scrollTop;\n                }\n            }\n\n            // Abort if outside container boundaries\n            const directionOverflow =\n                directionValue < directionMin || directionValue + directionSize > directionMax;\n            const variantOverflow =\n                variantValue < variantMin || variantValue + variantSize > variantMax;\n            if (directionOverflow || variantOverflow) {\n                return null;\n            }\n        }\n\n        const positioning = vertical\n            ? {\n                  top: directionValue,\n                  left: variantValue,\n              }\n            : {\n                  top: variantValue,\n                  left: directionValue,\n              };\n        return { ...positioning, className: `${POPPER_CLASS}--${d}${v}` };\n    }\n\n    return {\n        directions,\n        variants,\n        get,\n    };\n}\n\n/**\n * This method will try to position the popper as requested (according to options).\n * If the requested position does not fit the container, other positions will be\n * tried in different direction and variant flip orders (depending on the requested position).\n * If no position is found that fits the container, the requested position stays used.\n *\n * When the final position is applied, a corresponding CSS class is also added to the popper.\n * This could be used to further styling.\n *\n * @param {HTMLElement} reference\n * @param {HTMLElement} popper\n * @param {Options} options\n */\nfunction reposition(reference, popper, options) {\n    options = {\n        container: document.documentElement,\n        ...DEFAULTS,\n        ...options,\n    };\n\n    // Reset all existing popper classes and only leave it as standalone\n    for (const popperClass of popper.classList) {\n        if (popperClass.startsWith(POPPER_CLASS)) {\n            popper.classList.remove(popperClass);\n        }\n    }\n    popper.classList.add(POPPER_CLASS);\n\n    // Compute positioning and find first match\n    const positioning = computePositioning(reference, popper, options);\n    for (const d of positioning.directions) {\n        for (const v of positioning.variants) {\n            const posData = positioning.get(d, v, true);\n            if (!posData) {\n                continue;\n            }\n\n            // From now, a position match have been found.\n            // Apply styles\n            const { className, top, left } = posData;\n            popper.classList.add(className);\n            popper.style.top = `${top}px`;\n            popper.style.left = `${left}px`;\n            return;\n        }\n    }\n\n    // use the given `position` because no position fits\n    const { className, top, left } = positioning.get();\n    popper.classList.add(className);\n    popper.style.top = `${top}px`;\n    popper.style.left = `${left}px`;\n}\n\nconst POSITION_BUS = Symbol(\"position-bus\");\n\n/**\n * Makes sure that the `popper` element is always\n * placed at `position` from the `reference` element.\n * If doing so the `popper` element is clipped off `container`,\n * sensible fallback positions are tried.\n * If all of fallback positions are also clipped off `container`,\n * the original position is used.\n *\n * @param {HTMLElement | (()=>HTMLElement)} reference\n * @param {Options} options\n */\nexport function usePosition(reference, options) {\n    options = { ...DEFAULTS, ...options };\n    const { popper } = options;\n    const popperRef = popper ? useRef(popper) : useComponent();\n    const getReference = typeof reference === \"function\" ? reference : () => reference;\n    const update = () => {\n        const ref = getReference();\n        if (popperRef.el && ref) {\n            reposition(ref, popperRef.el, options);\n        }\n    };\n    const component = useComponent();\n    const bus = component.env[POSITION_BUS] || new EventBus();\n    bus.on(\"update\", component, update);\n    onDestroyed(() => bus.off(\"update\", component));\n    useEffect(() => bus.trigger(\"update\"));\n    if (!(POSITION_BUS in component.env)) {\n        useSubEnv({ [POSITION_BUS]: bus });\n        const throttledUpdate = throttleForAnimation(() => bus.trigger(\"update\"));\n        useExternalListener(document, \"scroll\", throttledUpdate, { capture: true });\n        useExternalListener(window, \"resize\", throttledUpdate);\n        onWillUnmount(throttledUpdate.cancel);\n    }\n}\n", "/** @odoo-module **/\n\nimport { evaluate } from \"./py_interpreter\";\nimport { parse } from \"./py_parser\";\nimport { tokenize } from \"./py_tokenizer\";\n\nexport { evaluate } from \"./py_interpreter\";\nexport { parse } from \"./py_parser\";\nexport { tokenize } from \"./py_tokenizer\";\nexport { formatAST } from \"./py_utils\";\n\n/**\n * @typedef { import(\"./py_tokenizer\").Token } Token\n * @typedef { import(\"./py_parser\").AST } AST\n */\n\n/**\n * Parses an expression into a valid AST representation\n \n * @param {string} expr \n * @returns { AST }\n */\nexport function parseExpr(expr) {\n    const tokens = tokenize(expr);\n    return parse(tokens);\n}\n\n/**\n * Evaluates a python expression\n *\n * @param {string} expr\n * @param {Object} [context]\n * @returns {any}\n */\nexport function evaluateExpr(expr, context = {}) {\n    const ast = parseExpr(expr);\n    return evaluate(ast, context);\n}\n", "/** @odoo-module **/\n\nimport { PyDate, PyDateTime, PyRelativeDelta, PyTime, PyTimeDelta } from \"./py_date\";\n\nexport const BUILTINS = {\n    /**\n     * @param {any} value\n     * @returns {boolean}\n     */\n    bool(value) {\n        switch (typeof value) {\n            case \"number\":\n                return value !== 0;\n            case \"string\":\n                return value !== \"\";\n            case \"boolean\":\n                return value;\n            case \"object\":\n                if (value === null) {\n                    return false;\n                }\n                if (value.isTrue) {\n                    return value.isTrue();\n                }\n                return true;\n        }\n        return true;\n    },\n\n    time: {\n        strftime(format) {\n            return PyDateTime.now().strftime(format);\n        },\n    },\n\n    context_today() {\n        return PyDate.today();\n    },\n\n    get current_date() {\n        // deprecated: today should be prefered\n        return this.today;\n    },\n\n    get today() {\n        return PyDate.today().strftime(\"%Y-%m-%d\");\n    },\n\n    get now() {\n        return PyDateTime.now().strftime(\"%Y-%m-%d %H:%M:%S\");\n    },\n\n    datetime: {\n        time: PyTime,\n        timedelta: PyTimeDelta,\n        datetime: PyDateTime,\n        date: PyDate,\n    },\n\n    relativedelta: PyRelativeDelta,\n};\n", "/** @odoo-module **/\n\nimport { parseArgs } from \"./py_parser\";\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\nexport class AssertionError extends Error {}\nexport class ValueError extends Error {}\nexport class NotSupportedError extends Error {}\n\n// -----------------------------------------------------------------------------\n// helpers\n// -----------------------------------------------------------------------------\n\nfunction fmt2(n) {\n    return String(n).padStart(2, \"0\");\n}\nfunction fmt4(n) {\n    return String(n).padStart(4, \"0\");\n}\n\n/**\n * computes (Math.floor(a/b), a%b and passes that to the callback.\n *\n * returns the callback's result\n */\nfunction divmod(a, b, fn) {\n    let mod = a % b;\n    // in python, sign(a % b) === sign(b). Not in JS. If wrong side, add a\n    // round of b\n    if ((mod > 0 && b < 0) || (mod < 0 && b > 0)) {\n        mod += b;\n    }\n    return fn(Math.floor(a / b), mod);\n}\n\nfunction assert(bool, message = \"AssertionError\") {\n    if (!bool) {\n        throw new AssertionError(message);\n    }\n}\n\nconst DAYS_IN_MONTH = [null, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\nconst DAYS_BEFORE_MONTH = [null];\n\nfor (let dbm = 0, i = 1; i < DAYS_IN_MONTH.length; ++i) {\n    DAYS_BEFORE_MONTH.push(dbm);\n    dbm += DAYS_IN_MONTH[i];\n}\n\nfunction daysInMonth(year, month) {\n    if (month === 2 && isLeap(year)) {\n        return 29;\n    }\n    return DAYS_IN_MONTH[month];\n}\n\nfunction isLeap(year) {\n    return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);\n}\n\nfunction daysBeforeYear(year) {\n    const y = year - 1;\n    return y * 365 + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400);\n}\n\nfunction daysBeforeMonth(year, month) {\n    const postLeapFeb = month > 2 && isLeap(year);\n    return DAYS_BEFORE_MONTH[month] + (postLeapFeb ? 1 : 0);\n}\n\nfunction ymd2ord(year, month, day) {\n    const dim = daysInMonth(year, month);\n    if (!(1 <= day && day <= dim)) {\n        throw new ValueError(`day must be in 1..${dim}`);\n    }\n    return daysBeforeYear(year) + daysBeforeMonth(year, month) + day;\n}\n\nconst DI400Y = daysBeforeYear(401);\nconst DI100Y = daysBeforeYear(101);\nconst DI4Y = daysBeforeYear(5);\n\nfunction ord2ymd(n) {\n    --n;\n    let n400, n100, n4, n1, n0;\n    divmod(n, DI400Y, function (_n400, n) {\n        n400 = _n400;\n        divmod(n, DI100Y, function (_n100, n) {\n            n100 = _n100;\n            divmod(n, DI4Y, function (_n4, n) {\n                n4 = _n4;\n                divmod(n, 365, function (_n1, n) {\n                    n1 = _n1;\n                    n0 = n;\n                });\n            });\n        });\n    });\n\n    n = n0;\n    const year = n400 * 400 + 1 + n100 * 100 + n4 * 4 + n1;\n    if (n1 == 4 || n100 == 100) {\n        assert(n0 === 0);\n        return {\n            year: year - 1,\n            month: 12,\n            day: 31,\n        };\n    }\n\n    let leapyear = n1 === 3 && (n4 !== 24 || n100 == 3);\n    assert(leapyear == isLeap(year));\n    let month = (n + 50) >> 5;\n    let preceding = DAYS_BEFORE_MONTH[month] + (month > 2 && leapyear ? 1 : 0);\n    if (preceding > n) {\n        --month;\n        preceding -= DAYS_IN_MONTH[month] + (month === 2 && leapyear ? 1 : 0);\n    }\n    n -= preceding;\n    return {\n        year: year,\n        month: month,\n        day: n + 1,\n    };\n}\n\n/**\n * Converts the stuff passed in into a valid date, applying overflows as needed\n */\nfunction tmxxx(year, month, day, hour, minute, second, microsecond) {\n    hour = hour || 0;\n    minute = minute || 0;\n    second = second || 0;\n    microsecond = microsecond || 0;\n\n    if (microsecond < 0 || microsecond > 999999) {\n        divmod(microsecond, 1000000, function (carry, ms) {\n            microsecond = ms;\n            second += carry;\n        });\n    }\n    if (second < 0 || second > 59) {\n        divmod(second, 60, function (carry, s) {\n            second = s;\n            minute += carry;\n        });\n    }\n    if (minute < 0 || minute > 59) {\n        divmod(minute, 60, function (carry, m) {\n            minute = m;\n            hour += carry;\n        });\n    }\n    if (hour < 0 || hour > 23) {\n        divmod(hour, 24, function (carry, h) {\n            hour = h;\n            day += carry;\n        });\n    }\n    // That was easy.  Now it gets muddy:  the proper range for day\n    // can't be determined without knowing the correct month and year,\n    // but if day is, e.g., plus or minus a million, the current month\n    // and year values make no sense (and may also be out of bounds\n    // themselves).\n    // Saying 12 months == 1 year should be non-controversial.\n    if (month < 1 || month > 12) {\n        divmod(month - 1, 12, function (carry, m) {\n            month = m + 1;\n            year += carry;\n        });\n    }\n    // Now only day can be out of bounds (year may also be out of bounds\n    // for a datetime object, but we don't care about that here).\n    // If day is out of bounds, what to do is arguable, but at least the\n    // method here is principled and explainable.\n    let dim = daysInMonth(year, month);\n    if (day < 1 || day > dim) {\n        // Move day-1 days from the first of the month.  First try to\n        // get off cheap if we're only one day out of range (adjustments\n        // for timezone alone can't be worse than that).\n        if (day === 0) {\n            --month;\n            if (month > 0) {\n                day = daysInMonth(year, month);\n            } else {\n                --year;\n                month = 12;\n                day = 31;\n            }\n        } else if (day == dim + 1) {\n            ++month;\n            day = 1;\n            if (month > 12) {\n                month = 1;\n                ++year;\n            }\n        } else {\n            let r = ord2ymd(ymd2ord(year, month, 1) + (day - 1));\n            year = r.year;\n            month = r.month;\n            day = r.day;\n        }\n    }\n    return {\n        year: year,\n        month: month,\n        day: day,\n        hour: hour,\n        minute: minute,\n        second: second,\n        microsecond: microsecond,\n    };\n}\n\n// -----------------------------------------------------------------------------\n// Date/Time and related classes\n// -----------------------------------------------------------------------------\n\nexport class PyDate {\n    /**\n     * @returns {PyDate}\n     */\n    static today() {\n        return this.convertDate(new Date());\n    }\n\n    /**\n     * Convert a date object into PyDate\n     * @param {Date} date\n     * @returns {PyDate}\n     */\n    static convertDate(date) {\n        const year = date.getFullYear();\n        const month = date.getMonth() + 1;\n        const day = date.getDate();\n        return new PyDate(year, month, day);\n    }\n\n    /**\n     * @param {integer} year\n     * @param {integer} month\n     * @param {integer} day\n     */\n    constructor(year, month, day) {\n        this.year = year;\n        this.month = month; // 1-indexed => 1 = january, 2 = february, ...\n        this.day = day; // 1-indexed => 1 = first day of month, ...\n    }\n\n    /**\n     * @param  {...any} args\n     * @returns {PyDate}\n     */\n    static create(...args) {\n        const { year, month, day } = parseArgs(args, [\"year\", \"month\", \"day\"]);\n        return new PyDate(year, month, day);\n    }\n\n    /**\n     * @param {PyTimeDelta} timedelta\n     * @returns {PyDate}\n     */\n    add(timedelta) {\n        const s = tmxxx(this.year, this.month, this.day + timedelta.days);\n        return new PyDate(s.year, s.month, s.day);\n    }\n\n    /**\n     * @param {any} other\n     * @returns {boolean}\n     */\n    isEqual(other) {\n        if (!(other instanceof PyDate)) {\n            return false;\n        }\n        return this.year === other.year && this.month === other.month && this.day === other.day;\n    }\n\n    /**\n     * @param {string} format\n     * @returns {string}\n     */\n    strftime(format) {\n        return format.replace(/%([A-Za-z])/g, (m, c) => {\n            switch (c) {\n                case \"Y\":\n                    return fmt4(this.year);\n                case \"m\":\n                    return fmt2(this.month);\n                case \"d\":\n                    return fmt2(this.day);\n            }\n            throw new ValueError(`No known conversion for ${m}`);\n        });\n    }\n\n    /**\n     * @param {PyTimeDelta | PyDate} other\n     * @returns {PyDate | PyTimeDelta}\n     */\n    substract(other) {\n        if (other instanceof PyTimeDelta) {\n            return this.add(other.negate());\n        }\n        if (other instanceof PyDate) {\n            return PyTimeDelta.create(this.toordinal() - other.toordinal());\n        }\n        throw NotSupportedError();\n    }\n\n    /**\n     * @returns {string}\n     */\n    toJSON() {\n        return this.strftime(\"%Y-%m-%d\");\n    }\n\n    /**\n     * @returns {integer}\n     */\n    toordinal() {\n        return ymd2ord(this.year, this.month, this.day);\n    }\n}\n\nexport class PyDateTime {\n    /**\n     * @returns {PyDateTime}\n     */\n    static now() {\n        return this.convertDate(new Date());\n    }\n\n    /**\n     * Convert a date object into PyDateTime\n     * @param {Date} date\n     * @returns {PyDateTime}\n     */\n    static convertDate(date) {\n        const year = date.getFullYear();\n        const month = date.getMonth() + 1;\n        const day = date.getDate();\n        const hour = date.getHours();\n        const minute = date.getMinutes();\n        const second = date.getSeconds();\n        return new PyDateTime(year, month, day, hour, minute, second, 0);\n    }\n\n    /**\n     * @param  {...any} args\n     * @returns {PyDateTime}\n     */\n    static create(...args) {\n        const namedArgs = parseArgs(args, [\n            \"year\",\n            \"month\",\n            \"day\",\n            \"hour\",\n            \"minute\",\n            \"second\",\n            \"microsecond\",\n        ]);\n        const year = namedArgs.year;\n        const month = namedArgs.month;\n        const day = namedArgs.day;\n        const hour = namedArgs.hour || 0;\n        const minute = namedArgs.minute || 0;\n        const second = namedArgs.second || 0;\n        const ms = namedArgs.micro / 1000 || 0;\n        return new PyDateTime(year, month, day, hour, minute, second, ms);\n    }\n\n    /**\n     * @param  {...any} args\n     * @returns {PyDateTime}\n     */\n    static combine(...args) {\n        const { date, time } = parseArgs(args, [\"date\", \"time\"]);\n        // not sure. should we go through constructor instead? what about args normalization?\n        return PyDateTime.create(\n            date.year,\n            date.month,\n            date.day,\n            time.hour,\n            time.minute,\n            time.second\n        );\n    }\n\n    /**\n     * @param {integer} year\n     * @param {integer} month\n     * @param {integer} day\n     * @param {integer} hour\n     * @param {integer} minute\n     * @param {integer} second\n     * @param {integer} microsecond\n     */\n    constructor(year, month, day, hour, minute, second, microsecond) {\n        this.year = year;\n        this.month = month; // 1-indexed => 1 = january, 2 = february, ...\n        this.day = day; // 1-indexed => 1 = first day of month, ...\n        this.hour = hour;\n        this.minute = minute;\n        this.second = second;\n        this.microsecond = microsecond;\n    }\n\n    /**\n     * @param {PyTimeDelta} timedelta\n     * @returns {PyDate}\n     */\n    add(timedelta) {\n        const s = tmxxx(\n            this.year,\n            this.month,\n            this.day + timedelta.days,\n            this.hour,\n            this.minute,\n            this.second + timedelta.seconds,\n            this.microsecond + timedelta.microseconds\n        );\n        // does not seem to closely follow python implementation.\n        return new PyDateTime(s.year, s.month, s.day, s.hour, s.minute, s.second, s.microsecond);\n    }\n\n    /**\n     * @param {any} other\n     * @returns {boolean}\n     */\n    isEqual(other) {\n        if (!(other instanceof PyDateTime)) {\n            return false;\n        }\n        return (\n            this.year === other.year &&\n            this.month === other.month &&\n            this.day === other.day &&\n            this.hour === other.hour &&\n            this.minute === other.minute &&\n            this.second === other.second &&\n            this.microsecond === other.microsecond\n        );\n    }\n\n    /**\n     * @param {string} format\n     * @returns {string}\n     */\n    strftime(format) {\n        return format.replace(/%([A-Za-z])/g, (m, c) => {\n            switch (c) {\n                case \"Y\":\n                    return fmt4(this.year);\n                case \"m\":\n                    return fmt2(this.month);\n                case \"d\":\n                    return fmt2(this.day);\n                case \"H\":\n                    return fmt2(this.hour);\n                case \"M\":\n                    return fmt2(this.minute);\n                case \"S\":\n                    return fmt2(this.second);\n            }\n            throw new ValueError(`No known conversion for ${m}`);\n        });\n    }\n\n    /**\n     * @param {PyTimeDelta} timedelta\n     * @returns {PyDateTime}\n     */\n    substract(timedelta) {\n        return this.add(timedelta.negate());\n    }\n\n    /**\n     * @returns {string}\n     */\n    toJSON() {\n        return this.strftime(\"%Y-%m-%d %H:%M:%S\");\n    }\n\n    /**\n     * @returns {PyDateTime}\n     */\n    to_utc() {\n        const d = new Date(this.year, this.month -1, this.day, this.hour, this.minute, this.second);\n        const timedelta = PyTimeDelta.create({ minutes: d.getTimezoneOffset() });\n        return this.add(timedelta);\n    }\n}\n\nexport class PyTime extends PyDate {\n    /**\n     * @param  {...any} args\n     * @returns {PyTime}\n     */\n    static create(...args) {\n        const namedArgs = parseArgs(args, [\"hour\", \"minute\", \"second\"]);\n        const hour = namedArgs.hour || 0;\n        const minute = namedArgs.minute || 0;\n        const second = namedArgs.second || 0;\n        return new PyTime(hour, minute, second);\n    }\n\n    constructor(hour, minute, second) {\n        const now = new Date();\n        const year = now.getFullYear();\n        const month = now.getMonth();\n        const day = now.getDate();\n        super(year, month, day);\n        this.hour = hour;\n        this.minute = minute;\n        this.second = second;\n    }\n\n    /**\n     * @param {string} format\n     * @returns {string}\n     */\n    strftime(format) {\n        return format.replace(/%([A-Za-z])/g, (m, c) => {\n            switch (c) {\n                case \"Y\":\n                    return fmt4(this.year);\n                case \"m\":\n                    return fmt2(this.month + 1);\n                case \"d\":\n                    return fmt2(this.day);\n                case \"H\":\n                    return fmt2(this.hour);\n                case \"M\":\n                    return fmt2(this.minute);\n                case \"S\":\n                    return fmt2(this.second);\n            }\n            throw new ValueError(`No known conversion for ${m}`);\n        });\n    }\n\n    toJSON() {\n        return this.strftime(\"%H:%M:%S\");\n    }\n}\n\n/*\n * This list is intended to be of that shape (32 days in december), it is used by\n * the algorithm that computes \"relativedelta yearday\". The algorithm was adapted\n * from the one in python (https://github.com/dateutil/dateutil/blob/2.7.3/dateutil/relativedelta.py#L199)\n */\nconst DAYS_IN_YEAR = [31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 366];\n\nconst TIME_PERIODS = [\"hour\", \"minute\", \"second\"];\nconst PERIODS = [\"year\", \"month\", \"day\", ...TIME_PERIODS];\n\nconst RELATIVE_KEYS = \"years months weeks days hours minutes seconds microseconds leapdays\".split(\n    \" \"\n);\nconst ABSOLUTE_KEYS = \"year month day hour minute second microsecond weekday nlyearday yearday\".split(\n    \" \"\n);\n\nconst argsSpec = [\"dt1\", \"dt2\"]; // all other arguments are kwargs\nexport class PyRelativeDelta {\n    /**\n     * @param  {...any} args\n     * @returns {PyRelativeDelta}\n     */\n    static create(...args) {\n        const params = parseArgs(args, argsSpec);\n        if (\"dt1\" in params) {\n            throw new Error(\"relativedelta(dt1, dt2) is not supported for now\");\n        }\n        for (const period of PERIODS) {\n            if (period in params) {\n                const val = params[period];\n                assert(val >= 0, `${period} ${val} is out of range`);\n            }\n        }\n\n        for (const key of RELATIVE_KEYS) {\n            params[key] = params[key] || 0;\n        }\n        for (const key of ABSOLUTE_KEYS) {\n            params[key] = key in params ? params[key] : null;\n        }\n        params.days += 7 * params.weeks;\n\n        let yearDay = 0;\n        if (params.nlyearday) {\n            yearDay = params.nlyearday;\n        } else if (params.yearday) {\n            yearDay = params.yearday;\n            if (yearDay > 59) {\n                params.leapDays = -1;\n            }\n        }\n\n        if (yearDay) {\n            for (let monthIndex = 0; monthIndex < DAYS_IN_YEAR.length; monthIndex++) {\n                if (yearDay <= DAYS_IN_YEAR[monthIndex]) {\n                    params.month = monthIndex + 1;\n                    if (monthIndex === 0) {\n                        params.day = yearDay;\n                    } else {\n                        params.day = yearDay - DAYS_IN_YEAR[monthIndex - 1];\n                    }\n                    break;\n                }\n            }\n        }\n\n        return new PyRelativeDelta(params);\n    }\n\n    /**\n     * @param {PyDateTime|PyDate} date\n     * @param {PyRelativeDelta} delta\n     * @returns {PyDateTime|PyDate}\n     */\n    static add(date, delta) {\n        if (!(date instanceof PyDate || date instanceof PyDateTime)) {\n            throw NotSupportedError();\n        }\n\n        // First pass: we want to determine which is our target year and if we will apply leap days\n        const s = tmxxx(\n            (delta.year || date.year) + delta.years,\n            (delta.month || date.month) + delta.months,\n            delta.day || date.day,\n            delta.hour || date.hour || 0,\n            delta.minute || date.minute || 0,\n            delta.second || date.seconds || 0,\n            delta.microseconds || date.microseconds || 0\n        );\n\n        const newDateTime = new PyDateTime(\n            s.year,\n            s.month,\n            s.day,\n            s.hour,\n            s.minute,\n            s.second,\n            s.microsecond\n        );\n\n        let leapDays = 0;\n        if (delta.leapDays && newDateTime.month > 2 && isLeap(newDateTime.year)) {\n            leapDays = delta.leapDays;\n        }\n\n        // Second pass: apply the difference in days, and the difference in time values\n        const temp = newDateTime.add(\n            PyTimeDelta.create({\n                days: delta.days + leapDays,\n                hours: delta.hours,\n                minutes: delta.minutes,\n                seconds: delta.seconds,\n                microseconds: delta.microseconds,\n            })\n        );\n\n        // Determine the right return type:\n        // First we look at the type of the incoming date object,\n        // then we look at the actual time values held by the computed date.\n        const hasTime = Boolean(temp.hour || temp.minute || temp.second || temp.microsecond);\n        const returnDate =\n            !hasTime && date instanceof PyDate ? new PyDate(temp.year, temp.month, temp.day) : temp;\n\n        // Final pass: target the wanted day of the week (if necessary)\n        if (delta.weekday !== null) {\n            const wantedDow = delta.weekday + 1; // python: Monday is 0 ; JS: Monday is 1;\n            const _date = new Date(returnDate.year, returnDate.month - 1, returnDate.day);\n            const days = (7 - _date.getDay() + wantedDow) % 7;\n            return returnDate.add(new PyTimeDelta(days, 0, 0));\n        }\n        return returnDate;\n    }\n\n    /**\n     * @param {PyDateTime|PyDate} date\n     * @param {PyRelativeDelta} delta\n     * @returns {PyDateTime|PyDate}\n     */\n    static substract(date, delta) {\n        return PyRelativeDelta.add(date, delta.negate());\n    }\n\n    /**\n     * @param {Object} params\n     * @param {+1|-1} sign\n     */\n    constructor(params = {}, sign = +1) {\n        this.years = sign * params.years;\n        this.months = sign * params.months;\n        this.days = sign * params.days;\n        this.hours = sign * params.hours;\n        this.minutes = sign * params.minutes;\n        this.seconds = sign * params.seconds;\n        this.microseconds = sign * params.microseconds;\n\n        this.leapDays = params.leapDays;\n\n        this.year = params.year;\n        this.month = params.month;\n        this.day = params.day;\n        this.hour = params.hour;\n        this.minute = params.minute;\n        this.second = params.second;\n        this.microsecond = params.microsecond;\n\n        this.weekday = params.weekday;\n    }\n\n    /**\n     * @returns {PyRelativeDelta}\n     */\n    negate() {\n        return new PyRelativeDelta(this, -1);\n    }\n\n    isEqual(other) {\n        // For now we don't do normalization in the constructor (or create method).\n        // That is, we only compute the overflows at the time we add or substract.\n        // This is why we can't support isEqual for now.\n        throw new NotSupportedError();\n    }\n}\n\nconst TIME_DELTA_KEYS = \"weeks days hours minutes seconds milliseconds microseconds\".split(\" \");\n\n/**\n * Returns a \"pair\" with the fractional and integer parts of x\n * @param {float}\n * @returns {[float,integer]}\n */\nfunction modf(x) {\n    const mod = x % 1;\n    return [mod < 0 ? mod + 1 : mod, Math.floor(x)];\n}\n\nexport class PyTimeDelta {\n    /**\n     * @param  {...any} args\n     * @returns {PyTimeDelta}\n     */\n    static create(...args) {\n        const namedArgs = parseArgs(args, [\"days\", \"seconds\", \"microseconds\"]);\n        for (const key of TIME_DELTA_KEYS) {\n            namedArgs[key] = namedArgs[key] || 0;\n        }\n\n        // a timedelta can be created using TIME_DELTA_KEYS with float/integer values\n        // but only days, seconds, microseconds are kept internally.\n        // --> some normalization occurs here\n\n        let d = 0;\n        let s = 0;\n        let us = 0; // ~ \u03bcs standard notation for microseconds\n\n        const days = namedArgs.days + namedArgs.weeks * 7;\n        let seconds = namedArgs.seconds + 60 * namedArgs.minutes + 3600 * namedArgs.hours;\n        let microseconds = namedArgs.microseconds + 1000 * namedArgs.milliseconds;\n\n        const [dFrac, dInt] = modf(days);\n        d = dInt;\n        let daysecondsfrac = 0;\n        if (dFrac) {\n            const [dsFrac, dsInt] = modf(dFrac * 24 * 3600);\n            s = dsInt;\n            daysecondsfrac = dsFrac;\n        }\n\n        const [sFrac, sInt] = modf(seconds);\n        seconds = sInt;\n        const secondsfrac = sFrac + daysecondsfrac;\n\n        divmod(seconds, 24 * 3600, (days, seconds) => {\n            d += days;\n            s += seconds;\n        });\n\n        microseconds += secondsfrac * 1e6;\n        divmod(microseconds, 1000000, (seconds, microseconds) => {\n            divmod(seconds, 24 * 3600, (days, seconds) => {\n                d += days;\n                s += seconds;\n                us += Math.round(microseconds);\n            });\n        });\n\n        return new PyTimeDelta(d, s, us);\n    }\n\n    /**\n     * @param {integer} days\n     * @param {integer} seconds\n     * @param {integer} microseconds\n     */\n    constructor(days, seconds, microseconds) {\n        this.days = days;\n        this.seconds = seconds;\n        this.microseconds = microseconds;\n    }\n\n    /**\n     * @param {PyTimeDelta} other\n     * @returns {PyTimeDelta}\n     */\n    add(other) {\n        return PyTimeDelta.create({\n            days: this.days + other.days,\n            seconds: this.seconds + other.seconds,\n            microseconds: this.microseconds + other.microseconds,\n        });\n    }\n\n    /**\n     * @param {integer} n\n     * @returns {PyTimeDelta}\n     */\n    divide(n) {\n        const us = (this.days * 24 * 3600 + this.seconds) * 1e6 + this.microseconds;\n        return PyTimeDelta.create({ microseconds: Math.floor(us / n) });\n    }\n\n    /**\n     * @param {any} other\n     * @returns {boolean}\n     */\n    isEqual(other) {\n        if (!(other instanceof PyTimeDelta)) {\n            return false;\n        }\n        return (\n            this.days === other.days &&\n            this.seconds === other.seconds &&\n            this.microseconds === other.microseconds\n        );\n    }\n\n    /**\n     * @returns {boolean}\n     */\n    isTrue() {\n        return this.days !== 0 || this.seconds !== 0 || this.microseconds !== 0;\n    }\n\n    /**\n     * @param {float} n\n     * @returns {PyTimeDelta}\n     */\n    multiply(n) {\n        return PyTimeDelta.create({\n            days: n * this.days,\n            seconds: n * this.seconds,\n            microseconds: n * this.microseconds,\n        });\n    }\n\n    /**\n     * @returns {PyTimeDelta}\n     */\n    negate() {\n        return PyTimeDelta.create({\n            days: -this.days,\n            seconds: -this.seconds,\n            microseconds: -this.microseconds,\n        });\n    }\n\n    /**\n     * @param {PyTimeDelta} other\n     * @returns {PyTimeDelta}\n     */\n    substract(other) {\n        return PyTimeDelta.create({\n            days: this.days - other.days,\n            seconds: this.seconds - other.seconds,\n            microseconds: this.microseconds - other.microseconds,\n        });\n    }\n\n    /**\n     * @returns {float}\n     */\n    total_seconds() {\n        return this.days * 86400 + this.seconds + this.microseconds / 1000000;\n    }\n}\n", "/** @odoo-module **/\n\nimport { BUILTINS } from \"./py_builtin\";\nimport {\n    NotSupportedError,\n    PyDate,\n    PyDateTime,\n    PyRelativeDelta,\n    PyTime,\n    PyTimeDelta,\n} from \"./py_date\";\nimport { PY_DICT, toPyDict } from \"./py_utils\";\nimport { parseArgs } from './py_parser';\n\n// -----------------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------------\n\n/**\n * @typedef { import(\"./py_parser\").AST } AST\n */\n\nexport class EvaluationError extends Error {}\n\n// -----------------------------------------------------------------------------\n// Constants and helpers\n// -----------------------------------------------------------------------------\n\nconst isTrue = BUILTINS.bool;\n\n/**\n * @param {AST} ast\n * @param {Object} context\n * @returns {any}\n */\nfunction applyUnaryOp(ast, context) {\n    const value = evaluate(ast.right, context);\n    switch (ast.op) {\n        case \"-\":\n            if (value instanceof Object && value.negate) {\n                return value.negate();\n            }\n            return -value;\n        case \"+\":\n            return value;\n        case \"not\":\n            return !isTrue(value);\n    }\n    throw new EvaluationError(`Unknown unary operator: ${ast.op}`);\n}\n\n/**\n * We want to maintain this order:\n *   None < number (boolean) < dict < string < list < dict\n * So, each type is mapped to a number to represent that order\n *\n * @param {any} val\n * @returns {number} index type\n */\nfunction pytypeIndex(val) {\n    switch (typeof val) {\n        case \"object\":\n            // None, List, Object, Dict\n            return val === null ? 1 : Array.isArray(val) ? 5 : 3;\n        case \"number\":\n            return 2;\n        case \"string\":\n            return 4;\n    }\n    throw new EvaluationError(`Unknown type: ${typeof val}`);\n}\n\n/**\n * @param {Object} obj\n * @returns {boolean}\n */\nfunction isConstructor(obj) {\n    return !!obj.prototype && !!obj.prototype.constructor.name;\n}\n\n/**\n * Compare two values\n *\n * @param {any} left\n * @param {any} right\n * @returns {boolean}\n */\nfunction isLess(left, right) {\n    if (typeof left === \"number\" && typeof right === \"number\") {\n        return left < right;\n    }\n    if (typeof left === \"boolean\") {\n        left = left ? 1 : 0;\n    }\n    if (typeof right === \"boolean\") {\n        right = right ? 1 : 0;\n    }\n    const leftIndex = pytypeIndex(left);\n    const rightIndex = pytypeIndex(right);\n    if (leftIndex === rightIndex) {\n        return left < right;\n    }\n    return leftIndex < rightIndex;\n}\n\n/**\n * @param {any} left\n * @param {any} right\n * @returns {boolean}\n */\nfunction isEqual(left, right) {\n    if (typeof left !== typeof right) {\n        if (typeof left === \"boolean\" && typeof right === \"number\") {\n            return right === (left ? 1 : 0);\n        }\n        if (typeof left === \"number\" && typeof right === \"boolean\") {\n            return left === (right ? 1 : 0);\n        }\n        return false;\n    }\n    if (left instanceof Object && left.isEqual) {\n        return left.isEqual(right);\n    }\n    return left === right;\n}\n\n/**\n * @param {any} left\n * @param {any} right\n * @returns {boolean}\n */\nfunction isIn(left, right) {\n    if (Array.isArray(right)) {\n        return right.includes(left);\n    }\n    if (typeof right === \"string\" && typeof left === \"string\") {\n        return right.includes(left);\n    }\n    return false;\n}\n\n/**\n * @param {AST} ast\n * @param {object} context\n * @returns {any}\n */\nfunction applyBinaryOp(ast, context) {\n    const left = evaluate(ast.left, context);\n    const right = evaluate(ast.right, context);\n    switch (ast.op) {\n        case \"+\": {\n            const relativeDeltaOnLeft = left instanceof PyRelativeDelta;\n            const relativeDeltaOnRight = right instanceof PyRelativeDelta;\n            if (relativeDeltaOnLeft || relativeDeltaOnRight) {\n                const date = relativeDeltaOnLeft ? right : left;\n                const delta = relativeDeltaOnLeft ? left : right;\n                return PyRelativeDelta.add(date, delta);\n            }\n\n            const timeDeltaOnLeft = left instanceof PyTimeDelta;\n            const timeDeltaOnRight = right instanceof PyTimeDelta;\n            if (timeDeltaOnLeft && timeDeltaOnRight) {\n                return left.add(right);\n            }\n            if (timeDeltaOnLeft) {\n                if (right instanceof PyDate || right instanceof PyDateTime) {\n                    return right.add(left);\n                } else {\n                    throw NotSupportedError();\n                }\n            }\n            if (timeDeltaOnRight) {\n                if (left instanceof PyDate || left instanceof PyDateTime) {\n                    return left.add(right);\n                } else {\n                    throw NotSupportedError();\n                }\n            }\n\n            return left + right;\n        }\n        case \"-\": {\n            const isRightDelta = right instanceof PyRelativeDelta;\n            if (isRightDelta) {\n                return PyRelativeDelta.substract(left, right);\n            }\n\n            const timeDeltaOnRight = right instanceof PyTimeDelta;\n            if (timeDeltaOnRight) {\n                if (left instanceof PyTimeDelta) {\n                    return left.substract(right);\n                } else if (left instanceof PyDate || left instanceof PyDateTime) {\n                    return left.substract(right);\n                } else {\n                    throw NotSupportedError();\n                }\n            }\n\n            if (left instanceof PyDate) {\n                return left.substract(right);\n            }\n            return left - right;\n        }\n        case \"*\":\n            const timeDeltaOnLeft = left instanceof PyTimeDelta;\n            const timeDeltaOnRight = right instanceof PyTimeDelta;\n            if (timeDeltaOnLeft || timeDeltaOnRight) {\n                const number = timeDeltaOnLeft ? right : left;\n                const delta = timeDeltaOnLeft ? left : right;\n                return delta.multiply(number); // check number type?\n            }\n\n            return left * right;\n        case \"/\":\n            return left / right;\n        case \"%\":\n            return left % right;\n        case \"//\":\n            if (left instanceof PyTimeDelta) {\n                return left.divide(right); // check number type?\n            }\n            return Math.floor(left / right);\n        case \"**\":\n            return left ** right;\n        case \"==\":\n            return isEqual(left, right);\n        case \"<>\":\n        case \"!=\":\n            return !isEqual(left, right);\n        case \"<\":\n            return isLess(left, right);\n        case \">\":\n            return isLess(right, left);\n        case \">=\":\n            return isEqual(left, right) || isLess(right, left);\n        case \"<=\":\n            return isEqual(left, right) || isLess(left, right);\n        case \"in\":\n            return isIn(left, right);\n        case \"not in\":\n            return !isIn(left, right);\n    }\n    throw new EvaluationError(`Unknown binary operator: ${ast.op}`);\n}\n\nconst DICT = {\n    get(dict) {\n        return (...args) => {\n            const { key, defValue } = parseArgs(args, [\"key\", \"defValue\"]);\n            if (key in dict) {\n                return dict[key];\n            } else if (defValue) {\n                return defValue;\n            }\n            return null;\n        };\n    },\n};\n\n// -----------------------------------------------------------------------------\n// Evaluate function\n// -----------------------------------------------------------------------------\n\n/**\n * @param {AST} ast\n * @param {Object} context\n * @returns {any}\n */\nexport function evaluate(ast, context = {}) {\n    const dicts = new Set();\n    let pyContext;\n    const evalContext = Object.create(context);\n    Object.defineProperty(evalContext, \"context\", {\n        get() {\n            if (!pyContext) {\n                pyContext = toPyDict(context);\n            }\n            return pyContext;\n        },\n    });\n\n    /**\n     * @param {AST} ast\n     */\n    function _evaluate(ast) {\n        switch (ast.type) {\n            case 0 /* Number */:\n            case 1 /* String */:\n                return ast.value;\n            case 5 /* Name */:\n                if (ast.value in evalContext) {\n                    return evalContext[ast.value];\n                } else if (ast.value in BUILTINS) {\n                    return BUILTINS[ast.value];\n                } else {\n                    throw new EvaluationError(`Name '${ast.value}' is not defined`);\n                }\n            case 3 /* None */:\n                return null;\n            case 2 /* Boolean */:\n                return ast.value;\n            case 6 /* UnaryOperator */:\n                return applyUnaryOp(ast, evalContext);\n            case 7 /* BinaryOperator */:\n                return applyBinaryOp(ast, evalContext);\n            case 14 /* BooleanOperator */:\n                const left = _evaluate(ast.left);\n                if (ast.op === \"and\") {\n                    return isTrue(left) ? _evaluate(ast.right) : left;\n                } else {\n                    return isTrue(left) ? left : _evaluate(ast.right);\n                }\n            case 4 /* List */:\n            case 10 /* Tuple */:\n                return ast.value.map(_evaluate);\n            case 11 /* Dictionary */:\n                const dict = {};\n                for (let key in ast.value) {\n                    dict[key] = _evaluate(ast.value[key]);\n                }\n                dicts.add(dict);\n                return dict;\n            case 8 /* FunctionCall */:\n                const fnValue = _evaluate(ast.fn);\n                const args = ast.args.map(_evaluate);\n                const kwargs = {};\n                for (let kwarg in ast.kwargs) {\n                    kwargs[kwarg] = _evaluate(ast.kwargs[kwarg]);\n                }\n                if (\n                    fnValue === PyDate ||\n                    fnValue === PyDateTime ||\n                    fnValue === PyTime ||\n                    fnValue === PyRelativeDelta ||\n                    fnValue === PyTimeDelta\n                ) {\n                    return fnValue.create(...args, kwargs);\n                }\n                return fnValue(...args, kwargs);\n            case 12 /* Lookup */: {\n                const dict = _evaluate(ast.target);\n                const key = _evaluate(ast.key);\n                return dict[key];\n            }\n            case 13 /* If */: {\n                if (isTrue(_evaluate(ast.condition))) {\n                    return _evaluate(ast.ifTrue);\n                } else {\n                    return _evaluate(ast.ifFalse);\n                }\n            }\n            case 15 /* ObjLookup */: {\n                const left = _evaluate(ast.obj);\n                if (dicts.has(left) || Object.isPrototypeOf.call(PY_DICT, left)) {\n                    // this is a dictionary => need to apply dict methods\n                    return DICT[ast.key](left);\n                }\n                const result = left[ast.key];\n                if (typeof result === \"function\" && !isConstructor(result)) {\n                    return result.bind(left);\n                }\n                return result;\n            }\n        }\n        throw new EvaluationError(`AST of type ${ast.type} cannot be evaluated`);\n    }\n    return _evaluate(ast);\n}\n", "/** @odoo-module **/\n\nimport { binaryOperators, comparators } from \"./py_tokenizer\";\n\n// -----------------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------------\n\n/**\n * @typedef { import(\"./py_tokenizer\").Token } Token\n */\n\n/**\n * @typedef {{type: 0, value: number}} ASTNumber\n * @typedef {{type: 1, value: string}} ASTString\n * @typedef {{type: 2, value: boolean}} ASTBoolean\n * @typedef {{type: 3}} ASTNone\n * @typedef {{type: 4, value: AST[]}} ASTList\n * @typedef {{type: 5, value: string}} ASTName\n * @typedef {{type: 6, op: string, right: AST}} ASTUnaryOperator\n * @typedef {{type: 7, op: string, left: AST, right: AST}} ASTBinaryOperator\n * @typedef {{type: 8, fn: AST, args: AST[], kwargs: {[key: string]: AST}}} ASTFunctionCall\n * @typedef {{type: 9, name: ASTName, value: AST}} ASTAssignment\n * @typedef {{type: 10, value: AST[]}} ASTTuple\n * @typedef {{type: 11, value: { [key: string]: AST}}} ASTDictionary\n * @typedef {{type: 12, target: AST, key: AST}} ASTLookup\n * @typedef {{type: 13, condition: AST, ifTrue: AST, ifFalse: AST}} ASTIf\n * @typedef {{type: 14, op: string, left: AST, right: AST}} ASTBooleanOperator\n * @typedef {{type: 15, obj: AST, key: string}} ASTObjLookup\n *\n * @typedef { ASTNumber | ASTString | ASTBoolean | ASTNone | ASTList | ASTName | ASTUnaryOperator | ASTBinaryOperator | ASTFunctionCall | ASTAssignment | ASTTuple | ASTDictionary |ASTLookup | ASTIf | ASTBooleanOperator | ASTObjLookup} AST\n */\n\nexport class ParserError extends Error {}\n\n// -----------------------------------------------------------------------------\n// Constants and helpers\n// -----------------------------------------------------------------------------\n\nconst chainedOperators = new Set(comparators);\nconst infixOperators = new Set(binaryOperators.concat(comparators));\n\n/**\n * Compute the \"binding power\" of a symbol\n *\n * @param {string} symbol\n * @returns {number}\n */\nexport function bp(symbol) {\n    switch (symbol) {\n        case \"=\":\n            return 10;\n        case \"if\":\n            return 20;\n        case \"in\":\n        case \"not in\":\n        case \"is\":\n        case \"is not\":\n        case \"<\":\n        case \"<=\":\n        case \">\":\n        case \">=\":\n        case \"<>\":\n        case \"==\":\n        case \"!=\":\n            return 60;\n        case \"or\":\n            return 30;\n        case \"and\":\n            return 40;\n        case \"not\":\n            return 50;\n        case \"|\":\n            return 70;\n        case \"^\":\n            return 80;\n        case \"&\":\n            return 90;\n        case \"<<\":\n        case \">>\":\n            return 100;\n        case \"+\":\n        case \"-\":\n            return 110;\n        case \"*\":\n        case \"/\":\n        case \"//\":\n        case \"%\":\n            return 120;\n        case \"**\":\n            return 140;\n        case \".\":\n        case \"(\":\n        case \"[\":\n            return 150;\n    }\n    return 0;\n}\n\n/**\n * Compute binding power of a symbol\n *\n * @param {Token} token\n * @returns {number}\n */\nfunction bindingPower(token) {\n    return token.type === 2 /* Symbol */ ? bp(token.value) : 0;\n}\n\n/**\n * Check if a token is a symbol of a given value\n *\n * @param {Token} token\n * @param {string} value\n * @returns {boolean}\n */\nfunction isSymbol(token, value) {\n    return token.type === 2 /* Symbol */ && token.value === value;\n}\n\n/**\n * @param {Token} current\n * @param {Token[]} tokens\n * @returns {AST}\n */\nfunction parsePrefix(current, tokens) {\n    switch (current.type) {\n        case 0 /* Number */:\n            return { type: 0 /* Number */, value: current.value };\n        case 1 /* String */:\n            return { type: 1 /* String */, value: current.value };\n        case 4 /* Constant */:\n            if (current.value === \"None\") {\n                return { type: 3 /* None */ };\n            } else {\n                return { type: 2 /* Boolean */, value: current.value === \"True\" };\n            }\n        case 3 /* Name */:\n            return { type: 5 /* Name */, value: current.value };\n        case 2 /* Symbol */:\n            switch (current.value) {\n                case \"-\":\n                case \"+\":\n                case \"~\":\n                    return {\n                        type: 6 /* UnaryOperator */,\n                        op: current.value,\n                        right: _parse(tokens, 130),\n                    };\n                case \"not\":\n                    return {\n                        type: 6 /* UnaryOperator */,\n                        op: current.value,\n                        right: _parse(tokens, 50),\n                    };\n                case \"(\":\n                    const content = [];\n                    let isTuple = false;\n                    while (tokens[0] && !isSymbol(tokens[0], \")\")) {\n                        content.push(_parse(tokens, 0));\n                        if (tokens[0]) {\n                            if (tokens[0] && isSymbol(tokens[0], \",\")) {\n                                isTuple = true;\n                                tokens.shift();\n                            } else if (!isSymbol(tokens[0], \")\")) {\n                                throw new ParserError(\"parsing error\");\n                            }\n                        } else {\n                            throw new ParserError(\"parsing error\");\n                        }\n                    }\n                    if (!tokens[0] || !isSymbol(tokens[0], \")\")) {\n                        throw new ParserError(\"parsing error\");\n                    }\n                    tokens.shift();\n                    isTuple = isTuple || content.length === 0;\n                    return isTuple ? { type: 10 /* Tuple */, value: content } : content[0];\n                case \"[\":\n                    const value = [];\n                    while (tokens[0] && !isSymbol(tokens[0], \"]\")) {\n                        value.push(_parse(tokens, 0));\n                        if (tokens[0]) {\n                            if (isSymbol(tokens[0], \",\")) {\n                                tokens.shift();\n                            } else if (!isSymbol(tokens[0], \"]\")) {\n                                throw new ParserError(\"parsing error\");\n                            }\n                        }\n                    }\n                    if (!tokens[0] || !isSymbol(tokens[0], \"]\")) {\n                        throw new ParserError(\"parsing error\");\n                    }\n                    tokens.shift();\n                    return { type: 4 /* List */, value };\n                case \"{\": {\n                    const dict = {};\n                    while (tokens[0] && !isSymbol(tokens[0], \"}\")) {\n                        const key = _parse(tokens, 0);\n                        if (\n                            (key.type !== 1 /* String */ && key.type !== 0) /* Number */ ||\n                            !tokens[0] ||\n                            !isSymbol(tokens[0], \":\")\n                        ) {\n                            throw new ParserError(\"parsing error\");\n                        }\n                        tokens.shift();\n                        const value = _parse(tokens, 0);\n                        dict[key.value] = value;\n                        if (isSymbol(tokens[0], \",\")) {\n                            tokens.shift();\n                        }\n                    }\n                    // remove the } token\n                    if (!tokens.shift()) {\n                        throw new ParserError(\"parsing error\");\n                    }\n                    return { type: 11 /* Dictionary */, value: dict };\n                }\n            }\n    }\n    throw new ParserError(\"Token cannot be parsed\");\n}\n\n/**\n * @param {AST} ast\n * @param {Token} current\n * @param {Token[]} tokens\n * @returns {AST}\n */\nfunction parseInfix(left, current, tokens) {\n    switch (current.type) {\n        case 2 /* Symbol */:\n            if (infixOperators.has(current.value)) {\n                let right = _parse(tokens, bindingPower(current));\n                if (current.value === \"and\" || current.value === \"or\") {\n                    return {\n                        type: 14 /* BooleanOperator */,\n                        op: current.value,\n                        left,\n                        right,\n                    };\n                } else if (current.value === \".\") {\n                    if (right.type === 5 /* Name */) {\n                        return {\n                            type: 15 /* ObjLookup */,\n                            obj: left,\n                            key: right.value,\n                        };\n                    } else {\n                        throw new ParserError(\"invalid obj lookup\");\n                    }\n                }\n                let op = {\n                    type: 7 /* BinaryOperator */,\n                    op: current.value,\n                    left,\n                    right,\n                };\n                while (\n                    chainedOperators.has(current.value) &&\n                    tokens[0] &&\n                    tokens[0].type === 2 /* Symbol */ &&\n                    chainedOperators.has(tokens[0].value)\n                ) {\n                    const nextToken = tokens.shift();\n                    op = {\n                        type: 14 /* BooleanOperator */,\n                        op: \"and\",\n                        left: op,\n                        right: {\n                            type: 7 /* BinaryOperator */,\n                            op: nextToken.value,\n                            left: right,\n                            right: _parse(tokens, bindingPower(nextToken)),\n                        },\n                    };\n                    right = op.right.right;\n                }\n                return op;\n            }\n            switch (current.value) {\n                case \"(\":\n                    // function call\n                    const args = [];\n                    const kwargs = {};\n                    while (tokens[0] && !isSymbol(tokens[0], \")\")) {\n                        const arg = _parse(tokens, 0);\n                        if (arg.type === 9 /* Assignment */) {\n                            kwargs[arg.name.value] = arg.value;\n                        } else {\n                            args.push(arg);\n                        }\n                        if (tokens[0] && isSymbol(tokens[0], \",\")) {\n                            tokens.shift();\n                        }\n                    }\n                    if (!tokens[0] || !isSymbol(tokens[0], \")\")) {\n                        throw new ParserError(\"parsing error\");\n                    }\n                    tokens.shift();\n                    return { type: 8 /* FunctionCall */, fn: left, args, kwargs };\n                case \"=\":\n                    if (left.type === 5 /* Name */) {\n                        return {\n                            type: 9 /* Assignment */,\n                            name: left,\n                            value: _parse(tokens, 10),\n                        };\n                    }\n                case \"[\": {\n                    // lookup in dictionary\n                    const key = _parse(tokens);\n                    if (!tokens[0] || !isSymbol(tokens[0], \"]\")) {\n                        throw new ParserError(\"parsing error\");\n                    }\n                    tokens.shift();\n                    return {\n                        type: 12 /* Lookup */,\n                        target: left,\n                        key: key,\n                    };\n                }\n                case \"if\": {\n                    const condition = _parse(tokens);\n                    if (!tokens[0] || !isSymbol(tokens[0], \"else\")) {\n                        throw new ParserError(\"parsing error\");\n                    }\n                    tokens.shift();\n                    const ifFalse = _parse(tokens);\n                    return {\n                        type: 13 /* If */,\n                        condition,\n                        ifTrue: left,\n                        ifFalse,\n                    };\n                }\n            }\n    }\n    throw new ParserError(\"Token cannot be parsed\");\n}\n\n/**\n * @param {Token[]} tokens\n * @param {number} [bp]\n * @returns {AST}\n */\nfunction _parse(tokens, bp = 0) {\n    const token = tokens.shift();\n    let expr = parsePrefix(token, tokens);\n    while (tokens[0] && bindingPower(tokens[0]) > bp) {\n        expr = parseInfix(expr, tokens.shift(), tokens);\n    }\n    return expr;\n}\n\n// -----------------------------------------------------------------------------\n// Parse function\n// -----------------------------------------------------------------------------\n\n/**\n * Parse a list of tokens\n *\n * @param {Token[]} tokens\n * @returns {AST}\n */\nexport function parse(tokens) {\n    if (tokens.length) {\n        return _parse(tokens, 0);\n    }\n    throw new ParserError(\"Missing token\");\n}\n\n/**\n * @param {any[]} args\n * @param {string[]} spec\n * @returns {{[name: string]: any}}\n */\nexport function parseArgs(args, spec) {\n    const last = args[args.length - 1];\n    const unnamedArgs = typeof last === \"object\" ? args.slice(0, -1) : args;\n    const kwargs = typeof last === \"object\" ? last : {};\n    for (let [index, val] of unnamedArgs.entries()) {\n        kwargs[spec[index]] = val;\n    }\n    return kwargs;\n}\n", "/** @odoo-module **/\n\n// -----------------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------------\n\n/**\n * @typedef {{type: 0, value: number}} TokenNumber\n *\n * @typedef {{type: 1, value: string}} TokenString\n *\n * @typedef {{type: 2, value: string}} TokenSymbol\n *\n * @typedef {{type: 3, value: string}} TokenName\n *\n * @typedef {{type: 4, value: string}} TokenConstant\n *\n * @typedef {TokenNumber | TokenString | TokenSymbol | TokenName | TokenConstant} Token\n */\n\nexport class TokenizerError extends Error {}\n\n// -----------------------------------------------------------------------------\n// Helpers and Constants\n// -----------------------------------------------------------------------------\n\n/**\n * Directly maps a single escape code to an output character\n */\nconst directMap = {\n    \"\\\\\": \"\\\\\",\n    '\"': '\"',\n    \"'\": \"'\",\n    a: \"\\x07\",\n    b: \"\\x08\",\n    f: \"\\x0c\",\n    n: \"\\n\",\n    r: \"\\r\",\n    t: \"\\t\",\n    v: \"\\v\",\n};\n\n/**\n * Implements the decoding of Python string literals (embedded in\n * JS strings) into actual JS strings. This includes the decoding\n * of escapes into their corresponding JS\n * characters/codepoints/whatever.\n *\n * The ``unicode`` flags notes whether the literal should be\n * decoded as a bytestring literal or a unicode literal, which\n * pretty much only impacts decoding (or not) of unicode escapes\n * at this point since bytestrings are not technically handled\n * (everything is decoded to JS \"unicode\" strings)\n *\n * Eventurally, ``str`` could eventually use typed arrays, that'd\n * be interesting...\n *\n * @param {string} str\n * @param {boolean} unicode\n * @returns {string}\n */\nfunction decodeStringLiteral(str, unicode) {\n    let out = [];\n    let code;\n    for (var i = 0; i < str.length; ++i) {\n        if (str[i] !== \"\\\\\") {\n            out.push(str[i]);\n            continue;\n        }\n        var escape = str[i + 1];\n        if (escape in directMap) {\n            out.push(directMap[escape]);\n            ++i;\n            continue;\n        }\n        switch (escape) {\n            // Ignored\n            case \"\\n\":\n                ++i;\n                continue;\n            // Character named name in the Unicode database (Unicode only)\n            case \"N\":\n                if (!unicode) {\n                    break;\n                }\n                throw new TokenizerError(\"SyntaxError: \\\\N{} escape not implemented\");\n            case \"u\":\n                if (!unicode) {\n                    break;\n                }\n                var uni = str.slice(i + 2, i + 6);\n                if (!/[0-9a-f]{4}/i.test(uni)) {\n                    throw new TokenizerError(\n                        [\n                            \"SyntaxError: (unicode error) 'unicodeescape' codec\",\n                            \" can't decode bytes in position \",\n                            i,\n                            \"-\",\n                            i + 4,\n                            \": truncated \\\\uXXXX escape\",\n                        ].join(\"\")\n                    );\n                }\n                code = parseInt(uni, 16);\n                out.push(String.fromCharCode(code));\n                // escape + 4 hex digits\n                i += 5;\n                continue;\n            case \"U\":\n                if (!unicode) {\n                    break;\n                }\n                // TODO: String.fromCodePoint\n                throw new TokenizerError(\"SyntaxError: \\\\U escape not implemented\");\n            case \"x\":\n                // get 2 hex digits\n                var hex = str.slice(i + 2, i + 4);\n                if (!/[0-9a-f]{2}/i.test(hex)) {\n                    if (!unicode) {\n                        throw new TokenizerError(\"ValueError: invalid \\\\x escape\");\n                    }\n                    throw new TokenizerError(\n                        [\n                            \"SyntaxError: (unicode error) 'unicodeescape'\",\n                            \" codec can't decode bytes in position \",\n                            i,\n                            \"-\",\n                            i + 2,\n                            \": truncated \\\\xXX escape\",\n                        ].join(\"\")\n                    );\n                }\n                code = parseInt(hex, 16);\n                out.push(String.fromCharCode(code));\n                // skip escape + 2 hex digits\n                i += 3;\n                continue;\n            default:\n                // Check if octal\n                if (!/[0-8]/.test(escape)) {\n                    break;\n                }\n                var r = /[0-8]{1,3}/g;\n                r.lastIndex = i + 1;\n                var m = r.exec(str);\n                var oct = m[0];\n                code = parseInt(oct, 8);\n                out.push(String.fromCharCode(code));\n                // skip matchlength\n                i += oct.length;\n                continue;\n        }\n        out.push(\"\\\\\");\n    }\n    return out.join(\"\");\n}\n\nconst constants = new Set([\"None\", \"False\", \"True\"]);\n\nexport const comparators = [\n    \"in\",\n    \"not\",\n    \"not in\",\n    \"is\",\n    \"is not\",\n    \"<\",\n    \"<=\",\n    \">\",\n    \">=\",\n    \"<>\",\n    \"!=\",\n    \"==\",\n];\n\nexport const binaryOperators = [\n    \"or\",\n    \"and\",\n    \"|\",\n    \"^\",\n    \"&\",\n    \"<<\",\n    \">>\",\n    \"+\",\n    \"-\",\n    \"*\",\n    \"/\",\n    \"//\",\n    \"%\",\n    \"~\",\n    \"**\",\n    \".\",\n];\n\nexport const unaryOperators = [\"-\"];\n\nconst symbols = new Set([\n    ...[\"(\", \")\", \"[\", \"]\", \"{\", \"}\", \":\", \",\"],\n    ...[\"if\", \"else\", \"lambda\", \"=\"],\n    ...comparators,\n    ...binaryOperators,\n    ...unaryOperators,\n]);\n\n// Regexps\nfunction group(...args) {\n    return \"(\" + args.join(\"|\") + \")\";\n}\n\nconst Name = \"[a-zA-Z_]\\\\w*\";\nconst Whitespace = \"[ \\\\f\\\\t]*\";\nconst DecNumber = \"\\\\d+(L|l)?\";\nconst IntNumber = DecNumber;\n\nconst Exponent = \"[eE][+-]?\\\\d+\";\nconst PointFloat = group(`\\\\d+\\\\.\\\\d*(${Exponent})?`, `\\\\.\\\\d+(${Exponent})?`);\n// Exponent not optional when no decimal point\nconst FloatNumber = group(PointFloat, `\\\\d+${Exponent}`);\n\nconst Number = group(FloatNumber, IntNumber);\nconst Operator = group(\"\\\\*\\\\*=?\", \">>=?\", \"<<=?\", \"<>\", \"!=\", \"//=?\", \"[+\\\\-*/%&|^=<>]=?\", \"~\");\nconst Bracket = \"[\\\\[\\\\]\\\\(\\\\)\\\\{\\\\}]\";\nconst Special = \"[:;.,`@]\";\nconst Funny = group(Operator, Bracket, Special);\nconst ContStr = group(\n    \"([uU])?'([^\\n'\\\\\\\\]*(?:\\\\\\\\.[^\\n'\\\\\\\\]*)*)'\",\n    '([uU])?\"([^\\n\"\\\\\\\\]*(?:\\\\\\\\.[^\\n\"\\\\\\\\]*)*)\"'\n);\nconst PseudoToken = Whitespace + group(Number, Funny, ContStr, Name);\nconst NumberPattern = new RegExp(\"^\" + Number + \"$\");\nconst StringPattern = new RegExp(\"^\" + ContStr + \"$\");\nconst NamePattern = new RegExp(\"^\" + Name + \"$\");\nconst strip = new RegExp(\"^\" + Whitespace);\n\n// -----------------------------------------------------------------------------\n// Tokenize function\n// -----------------------------------------------------------------------------\n\n/**\n * Transform a string into a list of tokens\n *\n * @param {string} str\n * @returns {Token[]}\n */\nexport function tokenize(str) {\n    const tokens = [];\n    let max = str.length;\n    let start = 0;\n    let end = 0;\n    // /g flag makes repeated exec() have memory\n    const pseudoprog = new RegExp(PseudoToken, \"g\");\n    while (pseudoprog.lastIndex < max) {\n        const pseudomatch = pseudoprog.exec(str);\n        if (!pseudomatch) {\n            // if match failed on trailing whitespace, end tokenizing\n            if (/^\\s+$/.test(str.slice(end))) {\n                break;\n            }\n            throw new TokenizerError(\n                \"Failed to tokenize <<\" +\n                    str +\n                    \">> at index \" +\n                    (end || 0) +\n                    \"; parsed so far: \" +\n                    tokens\n            );\n        }\n        if (pseudomatch.index > end) {\n            if (str.slice(end, pseudomatch.index).trim()) {\n                throw new TokenizerError(\"Invalid expression\");\n            }\n        }\n        start = pseudomatch.index;\n        end = pseudoprog.lastIndex;\n        let token = str.slice(start, end).replace(strip, \"\");\n        if (NumberPattern.test(token)) {\n            tokens.push({\n                type: 0 /* Number */,\n                value: parseFloat(token),\n            });\n        } else if (StringPattern.test(token)) {\n            var m = StringPattern.exec(token);\n            tokens.push({\n                type: 1 /* String */,\n                value: decodeStringLiteral(m[3] !== undefined ? m[3] : m[5], !!(m[2] || m[4])),\n            });\n        } else if (symbols.has(token)) {\n            // transform 'not in' and 'is not' in a single token\n            if (token === \"in\" && tokens.length > 0 && tokens[tokens.length - 1].value === \"not\") {\n                token = \"not in\";\n                tokens.pop();\n            } else if (\n                token === \"not\" &&\n                tokens.length > 0 &&\n                tokens[tokens.length - 1].value === \"is\"\n            ) {\n                token = \"is not\";\n                tokens.pop();\n            }\n            tokens.push({\n                type: 2 /* Symbol */,\n                value: token,\n            });\n        } else if (constants.has(token)) {\n            tokens.push({\n                type: 4 /* Constant */,\n                value: token,\n            });\n        } else if (NamePattern.test(token)) {\n            tokens.push({\n                type: 3 /* Name */,\n                value: token,\n            });\n        } else {\n            throw new TokenizerError(\"Invalid expression\");\n        }\n    }\n    return tokens;\n}\n", "/** @odoo-module **/\n\nimport { bp } from \"./py_parser\";\nimport { PyDate, PyDateTime } from './py_date';\n\n// -----------------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------------\n\n/**\n * @typedef { import(\"./py_parser\").AST } AST\n */\n\n// -----------------------------------------------------------------------------\n// Utils\n// -----------------------------------------------------------------------------\n\n/**\n * Represent any value as a primitive AST\n *\n * @param {any} value\n * @returns {AST}\n */\nexport function toPyValue(value) {\n    switch (typeof value) {\n        case \"string\":\n            return { type: 1 /* String */, value };\n        case \"number\":\n            return { type: 0 /* Number */, value };\n        case \"boolean\":\n            return { type: 2 /* Boolean */, value };\n        case \"object\":\n            if (Array.isArray(value)) {\n                return { type: 4 /* List */, value: value.map(toPyValue) };\n            } else if (value === null) {\n                return { type: 3 /* None */ };\n            } else if (value instanceof Date) {\n                return { type: 1, value: PyDateTime.convertDate(value) };\n            } else if (value instanceof PyDate || value instanceof PyDateTime) {\n                return { type: 1, value };\n            } else {\n                const content = {};\n                for (let key in value) {\n                    content[key] = toPyValue(value[key]);\n                }\n                return { type: 11 /* Dictionary */, value: content };\n            }\n        default:\n            throw new Error(\"Invalid type\");\n    }\n}\n\n/**\n * @param {AST} ast\n * @param {number} [lbp] left binding power\n * @return {string}\n */\nexport function formatAST(ast, lbp = 0) {\n    switch (ast.type) {\n        case 3 /* None */:\n            return \"None\";\n        case 1 /* String */:\n            return JSON.stringify(ast.value);\n        case 0 /* Number */:\n            return String(ast.value);\n        case 2 /* Boolean */:\n            return ast.value ? \"True\" : \"False\";\n        case 4 /* List */:\n            return `[${ast.value.map(formatAST).join(\", \")}]`;\n        case 6 /* UnaryOperator */:\n            if (ast.op === \"not\") {\n                return `not ` + formatAST(ast.right, 50);\n            }\n            return ast.op + formatAST(ast.right, 130);\n        case 7 /* BinaryOperator */: {\n            const abp = bp(ast.op);\n            const str = `${formatAST(ast.left, abp)} ${ast.op} ${formatAST(ast.right, abp)}`;\n            return abp < lbp ? `(${str})` : str;\n        }\n        case 11 /* Dictionary */: {\n            const pairs = [];\n            for (let k in ast.value) {\n                pairs.push(`\"${k}\": ${formatAST(ast.value[k])}`);\n            }\n            return `{` + pairs.join(\", \") + `}`;\n        }\n        case 10 /* Tuple */:\n            return `(${ast.value.map(formatAST).join(\", \")})`;\n        case 5 /* Name */:\n            return ast.value;\n        case 12 /* Lookup */: {\n            return `${formatAST(ast.target)}[${formatAST(ast.key)}]`;\n        }\n        case 13 /* If */: {\n            const { ifTrue, condition, ifFalse } = ast;\n            return `${formatAST(ifTrue)} if ${formatAST(condition)} else ${formatAST(ifFalse)}`;\n        }\n        case 14 /* BooleanOperator */: {\n            const abp = bp(ast.op);\n            const str = `${formatAST(ast.left, abp)} ${ast.op} ${formatAST(ast.right, abp)}`;\n            return abp < lbp ? `(${str})` : str;\n        }\n        case 15 /* ObjLookup */:\n            return `${formatAST(ast.obj, 150)}.${ast.key}`;\n        case 8 /* FunctionCall */: {\n            const args = ast.args.map(formatAST);\n            const kwargs = [];\n            for (let kwarg in ast.kwargs) {\n                kwargs.push(`${kwarg} = ${formatAST(ast.kwargs[kwarg])}`);\n            }\n            const argStr = args.concat(kwargs).join(\", \");\n            return `${formatAST(ast.fn)}(${argStr})`;\n        }\n    }\n    throw new Error(\"invalid expression: \" + ast);\n}\n\nexport const PY_DICT = Object.create(null);\n\n/**\n * @param {Object} obj\n * @returns {AST} a python dictionary\n */\nexport function toPyDict(obj) {\n    const result = Object.create(PY_DICT);\n    return Object.assign(result, obj);\n}\n", "/** @odoo-module **/\n\nconst { core } = owl;\nconst { EventBus } = core;\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\nexport class KeyNotFoundError extends Error {}\n\nexport class DuplicatedKeyError extends Error {}\n\n/**\n * Registry\n *\n * The Registry class is basically just a mapping from a string key to an object.\n * It is really not much more than an object. It is however useful for the\n * following reasons:\n *\n * 1. it let us react and execute code when someone add something to the registry\n *   (for example, the FunctionRegistry subclass this for this purpose)\n * 2. it throws an error when the get operation fails\n * 3. it provides a chained API to add items to the registry.\n */\nexport class Registry extends EventBus {\n    constructor() {\n        super();\n        this.content = {};\n        this.subRegistries = {};\n        this.elements = null;\n        this.entries = null;\n\n        this.on(\"UPDATE\", this, () => {\n            this.elements = null;\n            this.entries = null;\n        });\n    }\n\n    /**\n     * Add an entry (key, value) to the registry if key is not already used. If\n     * the parameter force is set to true, an entry with same key (if any) is replaced.\n     *\n     * Note that this also returns the registry, so another add method call can\n     * be chained\n     *\n     * @param {string} key\n     * @param {any} value\n     * @param {{force?: boolean, sequence?: number}} [options]\n     * @returns {Registry}\n     */\n    add(key, value, { force, sequence } = {}) {\n        if (!force && key in this.content) {\n            throw new DuplicatedKeyError(`Cannot add '${key}' in this registry: it already exists`);\n        }\n        let previousSequence;\n        if (force) {\n            const elem = this.content[key];\n            previousSequence = elem && elem[0];\n        }\n        sequence = sequence === undefined ? previousSequence || 50 : sequence;\n        this.content[key] = [sequence, value];\n        const payload = { operation: \"add\", key, value };\n        this.trigger(\"UPDATE\", payload);\n        return this;\n    }\n\n    /**\n     * Get an item from the registry\n     *\n     * @param {string} key\n     * @returns {any}\n     */\n    get(key, defaultValue) {\n        if (arguments.length < 2 && !(key in this.content)) {\n            throw new KeyNotFoundError(`Cannot find ${key} in this registry!`);\n        }\n        const info = this.content[key];\n        return info ? info[1] : defaultValue;\n    }\n\n    /**\n     * Check the presence of a key in the registry\n     *\n     * @param {string} key\n     * @returns {boolean}\n     */\n    contains(key) {\n        return key in this.content;\n    }\n\n    /**\n     * Get a list of all elements in the registry. Note that it is ordered\n     * according to the sequence numbers.\n     *\n     * @returns {any[]}\n     */\n    getAll() {\n        if (!this.elements) {\n            const content = Object.values(this.content).sort((el1, el2) => el1[0] - el2[0]);\n            this.elements = content.map((elem) => elem[1]);\n        }\n        return this.elements.slice();\n    }\n\n    /**\n     * Return a list of all entries, ordered by sequence numbers.\n     *\n     * @returns {[string, any][]}\n     */\n    getEntries() {\n        if (!this.entries) {\n            const entries = Object.entries(this.content).sort((el1, el2) => el1[1][0] - el2[1][0]);\n            this.entries = entries.map(([str, elem]) => [str, elem[1]]);\n        }\n        return this.entries.slice();\n    }\n\n    /**\n     * Remove an item from the registry\n     *\n     * @param {string} key\n     */\n    remove(key) {\n        const value = this.content[key];\n        delete this.content[key];\n        const payload = { operation: \"delete\", key, value };\n        this.trigger(\"UPDATE\", payload);\n    }\n\n    /**\n     * Open a sub registry (and create it if necessary)\n     *\n     * @param {string} subcategory\n     * @returns {Registry}\n     */\n    category(subcategory) {\n        if (!(subcategory in this.subRegistries)) {\n            this.subRegistries[subcategory] = new Registry();\n        }\n        return this.subRegistries[subcategory];\n    }\n}\n\nexport const registry = new Registry();\n", "/** @odoo-module **/\n\nimport { browser } from \"./browser/browser\";\nimport { registry } from \"./registry\";\nimport { scrollTo } from \"./utils/scrolling\";\n\nexport const scrollerService = {\n    start(env) {\n        /**\n         * Listen to click event to allow having links with href towards an anchor.\n         * Since odoo use hashtag to represent the current state of the view, we can't\n         * easily distinguish between a link towards an anchor and a link towards\n         * another view/state. If we want to navigate towards an anchor, we must not\n         * change the hash of the url otherwise we will be redirected to the app switcher\n         * instead To check if we have an anchor, first check if we have an href\n         * attribute starting with #. Try to find a element in the DOM using JQuery\n         * selector. If we have a match, it means that it is probably a link to an\n         * anchor, so we jump to that anchor.\n         *\n         * @param {MouseEvent} ev\n         */\n        browser.addEventListener(\"click\", (ev) => {\n            const link = ev.target.closest(\"a\");\n            if (!link) {\n                return;\n            }\n            const disableAnchor = link.attributes.getNamedItem(\"disable_anchor\");\n            if (disableAnchor && disableAnchor.value === \"true\") {\n                return;\n            }\n            const href = link.attributes.getNamedItem(\"href\");\n            if (href) {\n                if (href.value[0] === \"#\") {\n                    if (href.value.length === 1) {\n                        ev.preventDefault(); // single hash in href are just a way to activate A-tags node\n                        return;\n                    }\n                    let matchingEl = null;\n                    try {\n                        matchingEl = document.querySelector(`.o_content #${href.value.substr(1)}`);\n                    } catch (e) {\n                        // Invalid selector: not an anchor anyway\n                    }\n                    const triggerEv = new CustomEvent(\"anchor-link-clicked\", {\n                        detail: {\n                            element: matchingEl,\n                            id: href.value,\n                            originalEv: ev,\n                        },\n                    });\n                    env.bus.trigger(\"SCROLLER:ANCHOR_LINK_CLICKED\", triggerEv);\n                    if (matchingEl && !triggerEv.defaultPrevented) {\n                        ev.preventDefault();\n                        scrollTo(matchingEl, { isAnchor: true });\n                    }\n                }\n            }\n        });\n    },\n};\n\nregistry.category(\"services\").add(\"scroller\", scrollerService);\n", "/** @odoo-module **/\n\nconst { Component } = owl;\n\nexport class Tooltip extends Component {}\nTooltip.template = \"web.Tooltip\";\nTooltip.props = {\n    tooltip: { type: String, optional: true },\n    template: { type: String, optional: true },\n    info: { optional: true },\n};\n", "/** @odoo-module **/\n\nimport { browser } from \"@web/core/browser/browser\";\nimport { useEffect, useListener } from \"@web/core/utils/hooks\";\nimport { usePopover } from \"@web/core/popover/popover_hook\";\nimport { Tooltip } from \"./tooltip\";\n\n/**\n * The useTooltip hook allows to display custom tooltips on every elements with\n * a \"data-tooltip\" attribute inside the Component using the hook. This attribute\n * can be set on elements for which we prefer a custom tooltip instead of the\n * native one displaying the value of the \"title\" attribute.\n *\n * Usage (with a parent somewhere using the useTooltip hook):\n *   <button data-tooltip=\"This is a tooltip\">Do something</button>\n *\n * The ideal position of the tooltip can be specified thanks to the attribute\n * \"data-tooltip-position\":\n *   <button data-tooltip=\"This is a tooltip\" data-tooltip-position=\"left\">Do something</button>\n *\n * For advanced tooltips containing dynamic and/or html content, the\n * \"data-tooltip-template\" and \"data-tooltip-info\" attributes can be used.\n * For example, let's suppose the following qweb template:\n *   <t t-name=\"some_template\">\n *     <ul>\n *       <li>info.x</li>\n *       <li>info.y</li>\n *     </ul>\n *   </t>\n * This template can then be used in a tooltip as follows:\n *   <button data-tooltip-template=\"some_template\" data-tooltip-info=\"info\">Do something</button>\n * with \"info\" being a stringified object with two keys \"x\" and \"y\".\n */\nconst OPEN_DELAY = 400;\nconst CLOSE_DELAY = 200;\nexport function useTooltip() {\n    const popover = usePopover();\n    let openTooltipTimeout;\n    let closeTooltip;\n    let target = null;\n\n    /**\n     * Closes the currently opened tooltip if any, or prevent it from opening.\n     */\n    function cleanup() {\n        browser.clearTimeout(openTooltipTimeout);\n        if (closeTooltip) {\n            closeTooltip();\n        }\n    }\n\n    /**\n     * Opens the tooltip after a delay, if the event's target has a data-tooltip\n     * attribute.\n     *\n     * @param {MouseEvent} ev a \"mouseenter\" event\n     */\n    function onMouseEnter(ev) {\n        const dataset = ev.target.dataset;\n        const tooltip = dataset.tooltip;\n        let template, info;\n        if (\"tooltipTemplate\" in dataset) {\n            template = dataset.tooltipTemplate;\n            info = dataset.tooltipInfo ? JSON.parse(dataset.tooltipInfo) : null;\n        }\n        if (tooltip || template) {\n            cleanup();\n            target = ev.target;\n            openTooltipTimeout = browser.setTimeout(() => {\n                const position = dataset.tooltipPosition;\n                closeTooltip = popover.add(\n                    target,\n                    Tooltip,\n                    { tooltip, template, info },\n                    { position }\n                );\n            }, OPEN_DELAY);\n        }\n    }\n\n    // Track mouse position to be able to detect that we are no longer hovering\n    // the target, thus that we should close the tooltip\n    let positionX;\n    let positionY;\n    useListener(\"mousemove\", (ev) => {\n        positionX = ev.x;\n        positionY = ev.y;\n    });\n\n    // Regularly check that the target is still in the DOM and we're still\n    // hovering it, because if not, we have to close the tooltip\n    useEffect(\n        () => {\n            const interval = browser.setInterval(() => {\n                if (!target) {\n                    return;\n                }\n                if (!document.body.contains(target)) {\n                    return cleanup(); // target is no longer in the DOM\n                }\n                const targetRect = target.getBoundingClientRect();\n                if (\n                    positionX < targetRect.left ||\n                    positionX > targetRect.right ||\n                    positionY < targetRect.top ||\n                    positionY > targetRect.bottom\n                ) {\n                    return cleanup(); // mouse is no longer hovering the target\n                }\n            }, CLOSE_DELAY);\n            return () => browser.clearInterval(interval);\n        },\n        () => []\n    );\n\n    // Listen (using event delegation) to \"mouseenter\" events on all nodes with\n    // the \"data-tooltip\" or \"data-tooltip-template\" attribute, to open the tooltip.\n    const selector = \"[data-tooltip], [data-tooltip-template]\";\n    useListener(\"mouseenter\", selector, onMouseEnter, { capture: true });\n}\n", "/** @odoo-module **/\n\nimport { browser } from \"@web/core/browser/browser\";\n\nconst { Component, tags, useState } = owl;\n\nexport class BlockUI extends Component {\n    setup() {\n        this.messagesByDuration = [\n            { time: 20, l1: this.env._t(\"Loading...\") },\n            { time: 40, l1: this.env._t(\"Still loading...\") },\n            {\n                time: 60,\n                l1: this.env._t(\"Still loading...\"),\n                l2: this.env._t(\"Please be patient.\"),\n            },\n            {\n                time: 180,\n                l1: this.env._t(\"Don't leave yet,\"),\n                l2: this.env._t(\"it's still loading...\"),\n            },\n            {\n                time: 120,\n                l1: this.env._t(\"You may not believe it,\"),\n                l2: this.env._t(\"but the application is actually loading...\"),\n            },\n            {\n                time: 3180,\n                l1: this.env._t(\"Take a minute to get a coffee,\"),\n                l2: this.env._t(\"because it's loading...\"),\n            },\n            {\n                time: null,\n                l1: this.env._t(\n                    \"Maybe you should consider reloading the application by pressing F5...\"\n                ),\n            },\n        ];\n        this.state = useState({\n            blockUI: false,\n            line1: \"\",\n            line2: \"\",\n        });\n\n        this.props.bus.on(\"BLOCK\", this, this.block);\n        this.props.bus.on(\"UNBLOCK\", this, this.unblock);\n    }\n\n    replaceMessage(index) {\n        const message = this.messagesByDuration[index];\n        this.state.line1 = message.l1;\n        this.state.line2 = message.l2 || \"\";\n        if (message.time !== null) {\n            this.msgTimer = browser.setTimeout(() => {\n                this.replaceMessage(index + 1);\n            }, message.time * 1000);\n        }\n    }\n\n    block() {\n        this.state.blockUI = true;\n        this.replaceMessage(0);\n    }\n\n    unblock() {\n        this.state.blockUI = false;\n        clearTimeout(this.msgTimer);\n        this.state.line1 = \"\";\n        this.state.line2 = \"\";\n    }\n}\n\nBlockUI.template = tags.xml`\n    <div t-att-class=\"state.blockUI ? 'o_blockUI' : ''\">\n      <t t-if=\"state.blockUI\">\n        <div class=\"o_spinner\">\n            <img src=\"/web/static/img/spin.png\" alt=\"Loading...\"/>\n        </div>\n        <div class=\"o_message\">\n            <t t-raw=\"state.line1\"/> <br/>\n            <t t-raw=\"state.line2\"/>\n        </div>\n      </t>\n    </div>`;\n", "/** @odoo-module **/\n\nimport { useEffect, useService } from \"@web/core/utils/hooks\";\nimport { registry } from \"@web/core/registry\";\nimport { debounce } from \"@web/core/utils/timing\";\nimport { BlockUI } from \"./block_ui\";\nimport { browser } from \"@web/core/browser/browser\";\n\nconst { Component, core, hooks } = owl;\nconst { EventBus } = core;\nconst { useRef } = hooks;\n\nexport const SIZES = { XS: 0, VSM: 1, SM: 2, MD: 3, LG: 4, XL: 5, XXL: 6 };\n\n/**\n * This hook will set the UI active element\n * when the caller component will mount/unmount.\n *\n * The caller component could pass a `t-ref` value of its template\n * to delegate the UI active element to another element than itself.\n * In that case, it is mandatory that the referenced element is fixed and\n * not dynamically attached in/detached from the DOM (e.g. with t-if directive).\n *\n * @param {string} refName\n */\nexport function useActiveElement(refName = null) {\n    const uiService = useService(\"ui\");\n    const owner = refName ? useRef(refName) : Component.current;\n    useEffect(\n        () => {\n            uiService.activateElement(owner.el);\n            return () => uiService.deactivateElement(owner.el);\n        },\n        () => []\n    );\n}\n\nexport const uiService = {\n    start(env) {\n        let ui = {};\n\n        // block/unblock code\n        const bus = new EventBus();\n        registry.category(\"main_components\").add(\"BlockUI\", { Component: BlockUI, props: { bus } });\n\n        let blockCount = 0;\n        function block() {\n            blockCount++;\n            if (blockCount === 1) {\n                bus.trigger(\"BLOCK\");\n            }\n        }\n        function unblock() {\n            blockCount--;\n            if (blockCount < 0) {\n                console.warn(\n                    \"Unblock ui was called more times than block, you should only unblock the UI if you have previously blocked it.\"\n                );\n                blockCount = 0;\n            }\n            if (blockCount === 0) {\n                bus.trigger(\"UNBLOCK\");\n            }\n        }\n\n        Object.assign(ui, {\n            bus,\n            block,\n            unblock,\n        });\n\n        Object.defineProperty(ui, \"isBlocked\", {\n            get() {\n                return blockCount > 0;\n            },\n        });\n\n        // UI active element code\n        let activeElems = [document];\n\n        function activateElement(el) {\n            activeElems.push(el);\n            bus.trigger(\"active-element-changed\", el);\n        }\n        function deactivateElement(el) {\n            activeElems = activeElems.filter((x) => x !== el);\n            bus.trigger(\"active-element-changed\", ui.activeElement);\n        }\n        function getActiveElementOf(el) {\n            for (const activeElement of [...activeElems].reverse()) {\n                if (activeElement.contains(el)) {\n                    return activeElement;\n                }\n            }\n        }\n\n        Object.assign(ui, {\n            activateElement,\n            deactivateElement,\n            getActiveElementOf,\n        });\n        Object.defineProperty(ui, \"activeElement\", {\n            get() {\n                return activeElems[activeElems.length - 1];\n            },\n        });\n\n        // window size handling\n        const MEDIAS = [\n            window.matchMedia(\"(max-width: 474px)\"),\n            window.matchMedia(\"(min-width: 475px) and (max-width: 575px)\"),\n            window.matchMedia(\"(min-width: 576px) and (max-width: 767px)\"),\n            window.matchMedia(\"(min-width: 768px) and (max-width: 991px)\"),\n            window.matchMedia(\"(min-width: 992px) and (max-width: 1199px)\"),\n            window.matchMedia(\"(min-width: 1200px) and (max-width: 1533px)\"),\n            window.matchMedia(\"(min-width: 1534px)\"),\n        ];\n        function getSize() {\n            return MEDIAS.findIndex((media) => media.matches);\n        }\n\n        // listen to media query status changes\n        function updateSize() {\n            ui.size = getSize();\n        }\n        browser.addEventListener(\"resize\", debounce(updateSize, 100));\n\n        Object.assign(ui, {\n            size: getSize(),\n        });\n        Object.defineProperty(ui, \"isSmall\", {\n            get() {\n                return ui.size <= SIZES.SM;\n            },\n        });\n        Object.defineProperty(env, \"isSmall\", {\n            get() {\n                return ui.size <= SIZES.SM;\n            },\n        });\n\n        return ui;\n    },\n};\n\nregistry.category(\"services\").add(\"ui\", uiService);\n", "/** @odoo-module **/\n\nimport { registry } from \"./registry\";\nimport { session } from \"@web/session\";\n\nexport const userService = {\n    dependencies: [\"rpc\"],\n    async: [\"hasGroup\"],\n    start(env, { rpc }) {\n        const groupProms = {};\n\n        const context = {\n            ...session.user_context,\n            uid: session.uid,\n        };\n        return {\n            get context() {\n                return Object.assign({}, context);\n            },\n            removeFromContext(key) {\n                delete context[key];\n            },\n            updateContext(update) {\n                Object.assign(context, update);\n            },\n            hasGroup(group) {\n                if (!context.uid) {\n                    return Promise.resolve(false);\n                }\n                if (!groupProms[group]) {\n                    groupProms[group] = rpc(\"/web/dataset/call_kw/res.users/has_group\", {\n                        model: \"res.users\",\n                        method: \"has_group\",\n                        args: [group],\n                        kwargs: { context },\n                    });\n                }\n                return groupProms[group];\n            },\n            name: session.name,\n            userName: session.username,\n            isAdmin: session.is_admin,\n            isSystem: session.is_system,\n            partnerId: session.partner_id,\n            home_action_id: session.home_action_id,\n            showEffect: session.show_effect,\n            get userId() {\n                return context.uid;\n            },\n            get lang() {\n                return context.lang;\n            },\n            get tz() {\n                return context.tz;\n            },\n            get db() {\n                const res = {\n                    name: session.db,\n                };\n                if (\"dbuuid\" in session) {\n                    res.uuid = session.dbuuid;\n                }\n                return res;\n            },\n        };\n    },\n};\n\nregistry.category(\"services\").add(\"user\", userService);\n", "/** @odoo-module **/\n\n/**\n * Same values returned as those returned by cartesian function for case n = 0\n * and n > 1. For n = 1, brackets are put around the unique parameter elements.\n *\n * @returns {Array}\n */\nfunction _cartesian() {\n    const args = Array.prototype.slice.call(arguments);\n    if (args.length === 0) {\n        return [undefined];\n    }\n    const firstArray = args[0].map(function (elem) {\n        return [elem];\n    });\n    if (args.length === 1) {\n        return firstArray;\n    }\n    const productOfOtherArrays = _cartesian.apply(null, args.slice(1));\n    return firstArray.reduce(function (acc, elem) {\n        return acc.concat(\n            productOfOtherArrays.map(function (tuple) {\n                return elem.concat(tuple);\n            })\n        );\n    }, []);\n}\n\n/**\n * Helper function returning an extraction handler to use on array elements to\n * return a certain attribute or mutated form of the element.\n *\n * @private\n * @param {string | function} [criterion]\n * @returns {(element: any) => any}\n */\nfunction _getExtractorFrom(criterion) {\n    if (criterion) {\n        switch (typeof criterion) {\n            case \"string\":\n                return (element) => element[criterion];\n            case \"function\":\n                return criterion;\n            default:\n                throw new Error(\n                    `Expected criterion of type 'string' or 'function' and got '${typeof criterion}'`\n                );\n        }\n    } else {\n        return (element) => element;\n    }\n}\n\n/**\n * Returns an object holding different groups defined by a given criterion\n * or a default one. Each group is a subset of the original given list.\n * The given criterion can either be:\n * - a string: a property name on the list elements which value will be the\n * group name,\n * - a function: a handler that will return the group name from a given\n * element.\n *\n * @param {any[]} array\n * @param {string | function} [criterion]\n * @returns {Object}\n */\nexport function groupBy(array, criterion) {\n    const extract = _getExtractorFrom(criterion);\n    const groups = {};\n    for (const element of array) {\n        const group = String(extract(element));\n        if (!(group in groups)) {\n            groups[group] = [];\n        }\n        groups[group].push(element);\n    }\n    return groups;\n}\n\n/**\n * Return a shallow copy of a given array sorted by a given criterion or a default one.\n * The given criterion can either be:\n * - a string: a property name on the array elements returning the sortable primitive\n * - a function: a handler that will return the sortable primitive from a given element.\n * The default order is ascending ('asc'). It can be modified by setting the extra param 'order' to 'desc'.\n *\n * @param {any[]} array\n * @param {string | function} [criterion]\n * @param {\"asc\" | \"desc\"} [order=\"asc\"]\n * @returns {any[]}\n */\nexport function sortBy(array, criterion, order = \"asc\") {\n    const extract = _getExtractorFrom(criterion);\n    return array.slice().sort((elA, elB) => {\n        const a = extract(elA);\n        const b = extract(elB);\n        let result;\n        if (isNaN(a) && isNaN(b)) {\n            result = a > b ? 1 : a < b ? -1 : 0;\n        } else {\n            result = a - b;\n        }\n        return order === \"asc\" ? result : -result;\n    });\n}\n\n/**\n * Returns an array containing all the elements of arrayA\n * that are not in arrayB and vice-versa.\n *\n * @param {any[]} arrayA\n * @param {any[]} arrayB\n * @returns an array containing all the elements of arrayA\n * that are not in arrayB and vice-versa.\n */\nexport function symmetricalDifference(arrayA, arrayB) {\n    return arrayA\n        .filter((id) => !arrayB.includes(id))\n        .concat(arrayB.filter((id) => !arrayA.includes(id)));\n}\n\n/**\n * Returns the product of any number n of arrays.\n * The internal structures of their elements is preserved.\n * For n = 1, no brackets are put around the unique parameter elements\n * For n = 0, [undefined] is returned since it is the unit\n * of the cartesian product (up to isomorphism).\n *\n * @returns {Array}\n */\nexport function cartesian() {\n    const args = Array.prototype.slice.call(arguments);\n    if (args.length === 0) {\n        return [undefined];\n    } else if (args.length === 1) {\n        return args[0];\n    } else {\n        return _cartesian.apply(null, args);\n    }\n}\n\n/**\n * Returns all initial sections of a given array, e.g. for [1, 2] the array\n * [[], [1], [1, 2]] is returned.\n *\n * @param {Array} array\n * @returns {Array[]}\n */\nexport function sections(array) {\n    const sections = [];\n    for (let i = 0; i < array.length + 1; i++) {\n        sections.push(array.slice(0, i));\n    }\n    return sections;\n}\n", "/** @odoo-module **/\n\nconst { Component, tags } = owl;\n\nexport class NotUpdatable extends Component {\n    shouldUpdate() {\n        return false;\n    }\n}\nNotUpdatable.template = tags.xml`<t t-slot=\"default\" />`;\n\nexport class ErrorHandler extends Component {\n    catchError(error) {\n        if (this.props.onError) {\n            this.props.onError(error);\n        }\n    }\n}\nErrorHandler.template = tags.xml`<t t-slot=\"default\" />`;\n", "/** @odoo-module **/\n\n/**\n * KeepLast is a concurrency primitive that manages a list of tasks, and only\n * keeps the last task active.\n */\nexport class KeepLast {\n    constructor() {\n        this._id = 0;\n    }\n    /**\n     * Register a new task\n     *\n     * @template T\n     * @param {Promise<T>} promise\n     * @returns {Promise<T>}\n     */\n    add(promise) {\n        this._id++;\n        const currentId = this._id;\n        return new Promise((resolve, reject) => {\n            promise\n                .then((value) => {\n                    if (this._id === currentId) {\n                        resolve(value);\n                    }\n                })\n                .catch((reason) => {\n                    // not sure about this part\n                    if (this._id === currentId) {\n                        reject(reason);\n                    }\n                });\n        });\n    }\n}\n\n/**\n * A (Odoo) mutex is a primitive for serializing computations.  This is\n * useful to avoid a situation where two computations modify some shared\n * state and cause some corrupted state.\n *\n * Imagine that we have a function to fetch some data _load(), which returns\n * a promise which resolves to something useful. Now, we have some code\n * looking like this::\n *\n *      return this._load().then(function (result) {\n *          this.state = result;\n *      });\n *\n * If this code is run twice, but the second execution ends before the\n * first, then the final state will be the result of the first call to\n * _load.  However, if we have a mutex::\n *\n *      this.mutex = new Mutex();\n *\n * and if we wrap the calls to _load in a mutex::\n *\n *      return this.mutex.exec(function() {\n *          return this._load().then(function (result) {\n *              this.state = result;\n *          });\n *      });\n *\n * Then, it is guaranteed that the final state will be the result of the\n * second execution.\n *\n * A Mutex has to be a class, and not a function, because we have to keep\n * track of some internal state.\n */\nexport class Mutex {\n    constructor() {\n        this._lock = Promise.resolve();\n        this._queueSize = 0;\n        this._unlockedProm = undefined;\n        this._unlock = undefined;\n    }\n    /**\n     * Add a computation to the queue, it will be executed as soon as the\n     * previous computations are completed.\n     *\n     * @param {function} action a function which may return a Promise\n     * @returns {Promise}\n     */\n    async exec(action) {\n        const currentLock = this._lock;\n        let result;\n        this._queueSize++;\n        this._unlockedProm =\n            this._unlockedProm ||\n            new Promise((resolve) => {\n                this._unlock = resolve;\n            });\n        this._lock = new Promise((unlockCurrent) => {\n            currentLock.then(() => {\n                result = action();\n                const always = (returnedResult) => {\n                    unlockCurrent();\n                    this._queueSize--;\n                    if (this._queueSize === 0) {\n                        this._unlockedProm = undefined;\n                        this._unlock();\n                    }\n                    return returnedResult;\n                };\n                Promise.resolve(result).then(always).guardedCatch(always);\n            });\n        });\n        await this._lock;\n        return result;\n    }\n    /**\n     * @returns {Promise} resolved as soon as the Mutex is unlocked\n     *   (directly if it is currently idle)\n     */\n    getUnlockedDef() {\n        return this._unlockedProm || Promise.resolve();\n    }\n}\n\n/**\n * Race is a class designed to manage concurrency problems inspired by\n * Promise.race(), except that it is dynamic in the sense that promises can be\n * added anytime to a Race instance. When a promise is added, it returns another\n * promise which resolves as soon as a promise, among all added promises, is\n * resolved. The race is thus over. From that point, a new race will begin the\n * next time a promise will be added.\n */\nexport class Race {\n    constructor() {\n        this.currentProm = null;\n        this.currentPromResolver = null;\n        this.currentPromRejecter = null;\n    }\n    /**\n     * Register a new promise. If there is an ongoing race, the promise is added\n     * to that race. Otherwise, it starts a new race. The returned promise\n     * resolves as soon as the race is over, with the value of the first resolved\n     * promise added to the race.\n     *\n     * @param {Promise} promise\n     * @returns {Promise}\n     */\n    add(promise) {\n        if (!this.currentProm) {\n            this.currentProm = new Promise((resolve, reject) => {\n                this.currentPromResolver = (value) => {\n                    this.currentProm = null;\n                    this.currentPromResolver = null;\n                    this.currentPromRejecter = null;\n                    resolve(value);\n                };\n                this.currentPromRejecter = (error) => {\n                    this.currentProm = null;\n                    this.currentPromResolver = null;\n                    this.currentPromRejecter = null;\n                    reject(error);\n                };\n            });\n        }\n        promise.then(this.currentPromResolver).catch(this.currentPromRejecter);\n        return this.currentProm;\n    }\n    /**\n     * @returns {Promise|null} promise resolved as soon as the race is over, or\n     *   null if there is no race ongoing)\n     */\n    getCurrentProm() {\n        return this.currentProm;\n    }\n}\n", "/** @odoo-module **/\n\n/**\n * Creates a version of the function that's memoized on the value of its first\n * argument, if any.\n *\n * @template T, U\n * @param {(arg: T) => U} func the function to memoize\n * @returns {(arg: T) => U} a memoized version of the original function\n */\nexport function memoize(func) {\n    const cache = new Map();\n    const funcName = func.name ? func.name + \" (memoized)\" : \"memoized\";\n    return {\n        [funcName](...args) {\n            if (!cache.has(args[0])) {\n                cache.set(args[0], func(...args));\n            }\n            return cache.get(...args);\n        },\n    }[funcName];\n}\n", "/** @odoo-module **/\n\nimport { SERVICES_METADATA } from \"@web/env\";\n\nconst { onMounted, onWillPatch, onPatched, onWillUnmount, useComponent } = owl.hooks;\n\n/**\n * This file contains various custom hooks.\n * Their inner working is rather simple:\n * Each custom hook simply hooks itself to any number of owl lifecycle hooks.\n * You can then use them just like an owl hook in any Component\n * e.g.:\n * import { useBus } from \"@web/core/utils/hooks\";\n * ...\n * setup() {\n *    ...\n *    useBus(someBus, someEvent, callback)\n *    ...\n * }\n */\n\n// -----------------------------------------------------------------------------\n// useAutofocus\n// -----------------------------------------------------------------------------\n\n/**\n * Focus a given selector as soon as it appears in the DOM and if it was not\n * displayed before. If the selected target is an input|textarea, set the selection\n * at the end.\n *\n * @param {Object} [params]\n * @param {string} [params.selector='autofocus'] default: select the first element\n *                 with an `autofocus` attribute.\n * @returns {Function} function that forces the focus on the next update if visible.\n */\nexport function useAutofocus(params = {}) {\n    const comp = useComponent();\n    // Prevent autofocus in mobile\n    if (comp.env.isSmall) {\n        return () => {};\n    }\n    const selector = params.selector || \"[autofocus]\";\n    let forceFocusCount = 0;\n    useEffect(\n        function autofocus() {\n            const target = comp.el.querySelector(selector);\n            if (target) {\n                target.focus();\n                if ([\"INPUT\", \"TEXTAREA\"].includes(target.tagName) && target.type !== 'number') {\n                    const inputEl = target;\n                    inputEl.selectionStart = inputEl.selectionEnd = inputEl.value.length;\n                }\n            }\n        },\n        () => [forceFocusCount]\n    );\n\n    return function focusOnUpdate() {\n        forceFocusCount++; // force the effect to rerun on next patch\n    };\n}\n\n// -----------------------------------------------------------------------------\n// useBus\n// -----------------------------------------------------------------------------\n\n/**\n * Ensures a bus event listener is attached and cleared the proper way.\n *\n * @param {EventBus} bus\n * @param {string} eventName\n * @param {Callback} callback\n */\nexport function useBus(bus, eventName, callback) {\n    const component = useComponent();\n    useEffect(\n        () => {\n            bus.on(eventName, component, callback);\n            return () => bus.off(eventName, component);\n        },\n        () => []\n    );\n}\n\n// -----------------------------------------------------------------------------\n// useEffect\n// -----------------------------------------------------------------------------\n\nconst NO_OP = () => {};\n/**\n * @callback Effect\n * @param {...any} dependencies the dependencies computed by computeDependencies\n * @returns {void|(()=>void)} a cleanup function that reverses the side\n *      effects of the effect callback.\n */\n\n/**\n * This hook will run a callback when a component is mounted and patched, and\n * will run a cleanup function before patching and before unmounting the\n * the component.\n *\n * @param {Effect} effect the effect to run on component mount and/or patch\n * @param {()=>any[]} [computeDependencies=()=>[NaN]] a callback to compute\n *      dependencies that will decide if the effect needs to be cleaned up and\n *      run again. If the dependencies did not change, the effect will not run\n *      again. The default value returns an array containing only NaN because\n *      NaN !== NaN, which will cause the effect to rerun on every patch.\n */\nexport function useEffect(effect, computeDependencies = () => [NaN]) {\n    let cleanup, dependencies;\n    onMounted(() => {\n        dependencies = computeDependencies();\n        cleanup = effect(...dependencies) || NO_OP;\n    });\n\n    let shouldReapplyOnPatch = false;\n    onWillPatch(() => {\n        const newDeps = computeDependencies();\n        shouldReapplyOnPatch = newDeps.some((val, i) => val !== dependencies[i]);\n        if (shouldReapplyOnPatch) {\n            cleanup();\n            dependencies = newDeps;\n        }\n    });\n    onPatched(() => {\n        if (shouldReapplyOnPatch) {\n            cleanup = effect(...dependencies) || NO_OP;\n        }\n    });\n\n    onWillUnmount(() => cleanup());\n}\n\n// -----------------------------------------------------------------------------\n// useListener\n// -----------------------------------------------------------------------------\n\n/**\n * The useListener hook offers an alternative to Owl's classical event\n * registration mechanism (with attribute 't-on-eventName' in xml).\n *\n * It is especially useful for abstract components, meant to be extended by\n * specific ones. If those abstract components need to define event handlers,\n * but don't have any template (because the template completely depends on\n * specific cases), then using the 't-on' mechanism isn't adequate, as the\n * handlers would be lost by the template override. In this case, using this\n * hook instead is more convenient.\n *\n * Usage: like all Owl hooks, this function has to be called in the\n * constructor of an Owl component:\n *\n *   useListener('click', () => { console.log('clicked'); });\n *\n * It also allows to do event delegation, by specifying a native query selector\n * as second argument. In this case, the handler is only called if the event\n * is triggered on an element matching the given selector.\n *\n *   useListener('click', 'button', () => { console.log('clicked'); });\n *\n * Note: components that alter the event's target (e.g. Portal) are not\n * expected to behave as expected with event delegation.\n *\n * @param {string} eventName the name of the event\n * @param {string} [querySelector] a JS native selector for event delegation\n * @param {function} handler the event handler (will be bound to the component)\n * @param {Object} [options] to be passed to addEventListener as options.\n *   Useful for listening in the capture phase\n */\nexport function useListener(eventName, querySelector, handler, options = {}) {\n    if (typeof arguments[1] !== \"string\") {\n        querySelector = null;\n        handler = arguments[1];\n        options = arguments[2] || {};\n    }\n    if (typeof handler !== \"function\") {\n        throw new Error(\"The handler must be a function\");\n    }\n\n    const comp = useComponent();\n    let boundHandler;\n    if (querySelector) {\n        boundHandler = function (ev) {\n            let el = ev.target;\n            let target;\n            while (el && !target) {\n                if (el.matches(querySelector)) {\n                    target = el;\n                } else if (el === comp.el) {\n                    el = null;\n                } else {\n                    el = el.parentElement;\n                }\n            }\n            if (el) {\n                handler.call(comp, ev);\n            }\n        };\n    } else {\n        boundHandler = handler.bind(comp);\n    }\n    useEffect(\n        () => {\n            comp.el.addEventListener(eventName, boundHandler, options);\n            return () => {\n                comp.el.removeEventListener(eventName, boundHandler, options);\n            };\n        },\n        () => []\n    );\n}\n\n// -----------------------------------------------------------------------------\n// useService\n// -----------------------------------------------------------------------------\n\nfunction _protectMethod(component, caller, fn) {\n    return async (...args) => {\n        if (component.__owl__.status === 5 /* DESTROYED */) {\n            throw new Error(\"Component is destroyed\");\n        }\n        const result = await fn.call(caller, ...args);\n        return component.__owl__.status === 5 ? new Promise(() => {}) : result;\n    };\n}\n\n/**\n * Import a service into a component\n *\n * @param {string} serviceName\n * @returns {any}\n */\nexport function useService(serviceName) {\n    const component = useComponent();\n    const { services } = component.env;\n    if (!(serviceName in services)) {\n        throw new Error(`Service ${serviceName} is not available`);\n    }\n    const service = services[serviceName];\n    if (serviceName in SERVICES_METADATA) {\n        if (service instanceof Function) {\n            return _protectMethod(component, null, service);\n        } else {\n            const methods = SERVICES_METADATA[serviceName];\n            const result = Object.create(service);\n            for (let method of methods) {\n                result[method] = _protectMethod(component, service, service[method]);\n            }\n            return result;\n        }\n    }\n    return service;\n}\n\n/**\n * Executes \"callback\" when the component is being destroyed\n * @param  {Function} callback\n */\nexport function onDestroyed(callback) {\n    const component = useComponent();\n    const _destroy = component.__destroy;\n    component.__destroy = (...args) => {\n        _destroy.call(component, ...args);\n        // callback is called after super to guarantee the component is actually destroyed\n        callback();\n    };\n}\n", "/** @odoo-module **/\n\n/**\n * @param {Number} value\n * @param {Number} comparisonValue\n * @returns {Number}\n */\nexport function computeVariation(value, comparisonValue) {\n    if (isNaN(value) || isNaN(comparisonValue)) {\n        return NaN;\n    }\n    if (comparisonValue === 0) {\n        if (value === 0) {\n            return 0;\n        } else if (value > 0) {\n            return 1;\n        } else {\n            return -1;\n        }\n    }\n    return (value - comparisonValue) / Math.abs(comparisonValue);\n}\n", "/** @odoo-module **/\n\n/**\n * Shallow compares two objects.\n */\nexport function shallowEqual(obj1, obj2) {\n    const obj1Keys = Object.keys(obj1);\n    return (\n        obj1Keys.length === Object.keys(obj2).length &&\n        obj1Keys.every((key) => obj1[key] === obj2[key])\n    );\n}\n\n/**\n * Deep copies an object. As it relies on JSON this function as some limitations\n * - no support for circular objects\n * - no support for specific classes, that will at best be lost and at worst crash (Map, Set etc...)\n * @param  {Object} An object that is fully JSON stringifiable\n * @return {Object}\n */\nexport function deepCopy(obj) {\n    return JSON.parse(JSON.stringify(obj));\n}\n", "/** @odoo-module **/\n\nconst patchMap = new WeakMap();\n\n/**\n * Patch an object\n *\n * If the intent is to patch a class, don't forget to patch the prototype, unless\n * you want to patch static properties/methods.\n *\n * @param {Object} obj Object to patch\n * @param {string} patchName\n * @param {Object} patchValue\n * @param {{pure?: boolean}} [options]\n */\nexport function patch(obj, patchName, patchValue, options = {}) {\n    if (typeof patchName !== \"string\") {\n        throw new Error(\"Incorrect use of patch: second argument should be the patchName\");\n    }\n    const pure = Boolean(options.pure);\n    if (!patchMap.has(obj)) {\n        patchMap.set(obj, {\n            original: {},\n            patches: [],\n        });\n    }\n    const objDesc = patchMap.get(obj);\n    if (objDesc.patches.some((p) => p.name === patchName)) {\n        throw new Error(`Class ${obj.name} already has a patch ${patchName}`);\n    }\n    objDesc.patches.push({\n        name: patchName,\n        patch: patchValue,\n        pure,\n    });\n\n    for (const k in patchValue) {\n        let prevDesc = null;\n        let proto = obj;\n        do {\n            prevDesc = Object.getOwnPropertyDescriptor(proto, k);\n            proto = Object.getPrototypeOf(proto);\n        } while (!prevDesc && proto);\n\n        let newDesc = Object.getOwnPropertyDescriptor(patchValue, k);\n        if (!Object.hasOwnProperty.call(objDesc.original, k)) {\n            objDesc.original[k] = Object.getOwnPropertyDescriptor(obj, k);\n        }\n\n        if (prevDesc) {\n            const patchedFnName = `${k} (patch ${patchName})`;\n\n            if (prevDesc.value && typeof newDesc.value === \"function\") {\n                newDesc = { ...prevDesc, value: newDesc.value };\n                makeIntermediateFunction(\"value\", prevDesc, newDesc, patchedFnName);\n            }\n            if ((newDesc.get || newDesc.set) && (prevDesc.get || prevDesc.set)) {\n                // get and set are defined together. If they are both defined\n                // in the previous descriptor but only one in the new descriptor\n                // then the other will be undefined so we need to apply the\n                // previous descriptor in the new one.\n                newDesc = {\n                    ...prevDesc,\n                    get: newDesc.get || prevDesc.get,\n                    set: newDesc.set || prevDesc.set,\n                };\n                if (prevDesc.get && typeof newDesc.get === \"function\") {\n                    makeIntermediateFunction(\"get\", prevDesc, newDesc, patchedFnName);\n                }\n                if (prevDesc.set && typeof newDesc.set === \"function\") {\n                    makeIntermediateFunction(\"set\", prevDesc, newDesc, patchedFnName);\n                }\n            }\n        }\n\n        Object.defineProperty(obj, k, newDesc);\n    }\n\n    function makeIntermediateFunction(key, prevDesc, newDesc, patchedFnName) {\n        const _superFn = prevDesc[key];\n        const patchFn = newDesc[key];\n        if (pure) {\n            newDesc[key] = patchFn;\n        } else {\n            newDesc[key] = {\n                [patchedFnName](...args) {\n                    let prevSuper;\n                    if (this) {\n                        prevSuper = this._super;\n                        Object.defineProperty(this, \"_super\", {\n                            value: _superFn.bind(this),\n                            configurable: true,\n                            writable: true,\n                        });\n                    }\n                    const result = patchFn.call(this, ...args);\n                    if (this) {\n                        Object.defineProperty(this, \"_super\", {\n                            value: prevSuper,\n                            configurable: true,\n                            writable: true,\n                        });\n                    }\n                    return result;\n                },\n            }[patchedFnName];\n        }\n    }\n}\n\n/**\n * We define here an unpatch function.  This is mostly useful if we want to\n * remove a patch.  For example, for testing purposes\n *\n * @param {Object} obj\n * @param {string} patchName\n */\nexport function unpatch(obj, patchName) {\n    const objDesc = patchMap.get(obj);\n    if (!objDesc.patches.some((p) => p.name === patchName)) {\n        throw new Error(`Class ${obj.name} does not have any patch ${patchName}`);\n    }\n    patchMap.delete(obj);\n\n    // Restore original methods on the prototype and the class.\n    for (const k in objDesc.original) {\n        if (objDesc.original[k] === undefined) {\n            delete obj[k];\n        } else {\n            Object.defineProperty(obj, k, objDesc.original[k]);\n        }\n    }\n\n    // Re-apply the patches except the one to remove.\n    for (const patchDesc of objDesc.patches) {\n        if (patchDesc.name !== patchName) {\n            patch(obj, patchDesc.name, patchDesc.patch, { pure: patchDesc.pure });\n        }\n    }\n}\n", "/** @odoo-module */\n\n/**\n * Ensures that `element` will be visible in its `scrollable`.\n *\n * @param {HTMLElement} element\n * @param {Object} options\n * @param {HTMLElement} options[scrollable] a scrollable area\n * @param {Boolean} options[isAnchor] states if the scroll is to an anchor\n */\nexport function scrollTo(element, options = { scrollable: null, isAnchor: false }) {\n    function _getScrollParent(node) {\n        if (node == null) {\n            return null;\n        }\n\n        if (node.scrollHeight > node.clientHeight && node.clientHeight > 0) {\n            return node;\n        } else {\n            return _getScrollParent(node.parentNode);\n        }\n    }\n\n    const scrollable = options.scrollable\n        ? options.scrollable\n        : _getScrollParent(element.parentNode);\n    if (scrollable) {\n        const scrollBottom = scrollable.getBoundingClientRect().bottom;\n        const scrollTop = scrollable.getBoundingClientRect().top;\n        const elementBottom = element.getBoundingClientRect().bottom;\n        const elementTop = element.getBoundingClientRect().top;\n        if (elementBottom > scrollBottom && !options.isAnchor) {\n            // The scroll place the element at the bottom border of the scrollable\n            scrollable.scrollTop +=\n                elementTop - scrollBottom + Math.ceil(element.getBoundingClientRect().height);\n        } else if (elementTop < scrollTop || options.isAnchor) {\n            // The scroll place the element at the top of the scrollable\n            scrollable.scrollTop -= scrollTop - elementTop;\n            if (options.isAnchor) {\n                // If the scrollable is within a scrollable, another scroll should be done\n                const parentScrollable = _getScrollParent(scrollable.parentNode);\n                if (parentScrollable) {\n                    scrollTo(scrollable, { isAnchor: true, scrollable: parentScrollable });\n                }\n            }\n        }\n    }\n}\n\n/*\n * Returns the main scrollable element relevant in a context of an action.\n * In desktop, the relevant element is the .o_content of the action.\n * In mobile, it is the html node itself, provided no other children is scrollable\n *   Most of the times in mobile, we don't want to scroll individual elements, so every bit\n *   of code should enforce that html is the scrollable element.\n *\n * @param {Component} The action component.\n * @return {Element}\n */\nfunction getScrollableElement(component) {\n    if (component.env.isSmall) {\n        return document.firstElementChild; // aka html node;\n    } else {\n        return component.el.querySelector(\".o_action_manager .o_content\");\n    }\n}\n\n/**\n * Retrieves the current top and left scroll position. By default, the scrolling\n * area is the '.o_content' main div. In mobile, it is the body.\n *\n * @param {Component} an action Component containing an .o_content scrollable\n *   area.\n */\nexport function getScrollPosition(component) {\n    const scrollingEl = getScrollableElement(component);\n    return {\n        left: scrollingEl ? scrollingEl.scrollLeft : 0,\n        top: scrollingEl ? scrollingEl.scrollTop : 0,\n    };\n}\n\n/**\n * Sets top and left scroll positions to the given values. By default, the\n * scrolling area is the '.o_content' main div. In mobile, it is the body.\n *\n * @param {Component} an action Component containing an .o_content scrollable\n *   area.\n * @param {Object} offset\n * @param {number} [offset.left=0]\n * @param {number} [offset.top=0]\n */\nexport function setScrollPosition(component, offset) {\n    const scrollingEl = getScrollableElement(component);\n    if (scrollingEl) {\n        scrollingEl.scrollLeft = offset.left || 0;\n        scrollingEl.scrollTop = offset.top || 0;\n    }\n}\n", "/** @odoo-module */\n\nimport { unaccent } from \"./strings\";\n\n/**\n * This private function computes a score that represent the fact that the\n * string contains the pattern, or not\n *\n * - If the score is 0, the string does not contain the letters of the pattern in\n *   the correct order.\n * - if the score is > 0, it actually contains the letters.\n *\n * Better matches will get a higher score: consecutive letters are better,\n * and a match closer to the beginning of the string is also scored higher.\n */\nfunction match(pattern, str) {\n    let totalScore = 0;\n    let currentScore = 0;\n    let len = str.length;\n    let patternIndex = 0;\n\n    pattern = unaccent(pattern, false);\n    str = unaccent(str, false);\n\n    for (let i = 0; i < len; i++) {\n        if (str[i] === pattern[patternIndex]) {\n            patternIndex++;\n            currentScore += 100 + currentScore - i / 200;\n        } else {\n            currentScore = 0;\n        }\n        totalScore = totalScore + currentScore;\n    }\n\n    return patternIndex === pattern.length ? totalScore : 0;\n}\n\n/**\n * Return a list of things that matches a pattern, ordered by their 'score' (\n * higher score first). An higher score means that the match is better. For\n * example, consecutive letters are considered a better match.\n */\nexport function fuzzyLookup(pattern, list, fn) {\n    const results = [];\n    list.forEach((data) => {\n        const score = match(pattern, fn(data));\n        if (score > 0) {\n            results.push({ score, elem: data });\n        }\n    });\n\n    // we want better matches first\n    results.sort((a, b) => b.score - a.score);\n\n    return results.map((r) => r.elem);\n}\n\n// Does `pattern` fuzzy match `string`?\nexport function fuzzyTest(pattern, string) {\n    return match(pattern, string) !== 0;\n}\n", "/** @odoo-module **/\n\n/**\n * Escapes a string to use as a RegExp.\n * @url https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping\n *\n * @param {string} str\n * @returns {string} escaped string to use as a RegExp\n */\nexport function escapeRegExp(str) {\n    return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Intersperses ``separator`` in ``str`` at the positions indicated by\n * ``indices``.\n *\n * ``indices`` is an array of relative offsets (from the previous insertion\n * position, starting from the end of the string) at which to insert\n * ``separator``.\n *\n * There are two special values:\n *\n * ``-1``\n *   indicates the insertion should end now\n * ``0``\n *   indicates that the previous section pattern should be repeated (until all\n *   of ``str`` is consumed)\n *\n * @param {string} str\n * @param {number[]} indices\n * @param {string} separator\n * @returns {string}\n */\nexport function intersperse(str, indices, separator = \"\") {\n    separator = separator || \"\";\n    const result = [];\n    let last = str.length;\n    for (let i = 0; i < indices.length; ++i) {\n        let section = indices[i];\n        if (section === -1 || last <= 0) {\n            // Done with string, or -1 (stops formatting string)\n            break;\n        } else if (section === 0 && i === 0) {\n            // repeats previous section, which there is none => stop\n            break;\n        } else if (section === 0) {\n            // repeat previous section forever\n            //noinspection AssignmentToForLoopParameterJS\n            section = indices[--i];\n        }\n        result.push(str.substring(last - section, last));\n        last -= section;\n    }\n    const s = str.substring(0, last);\n    if (s) {\n        result.push(s);\n    }\n    return result.reverse().join(separator);\n}\n\n/**\n * Returns a string formatted using given values.\n * If the value is an object, its keys will replace `%(key)s` expressions.\n * If the values are a set of strings, they will replace `%s` expressions.\n * If no value is given, the string will not be formatted.\n *\n * @param {string} s\n * @param {...string} ...values\n * @returns {string}\n */\nexport function sprintf(s, ...values) {\n    if (values.length === 1 && Object.prototype.toString.call(values[0]) === \"[object Object]\") {\n        const valuesDict = values[0];\n        s = s.replace(/\\%\\(?([^\\)]+)\\)s/g, (match, value) => valuesDict[value]);\n    } else if (values.length > 0) {\n        s = s.replace(/\\%s/g, () => values.shift());\n    }\n    return s;\n}\n\n/**\n * Capitalizes a string: \"abc def\" => \"Abc def\"\n *\n * @param {string} s the input string\n * @returns {string}\n */\nexport function capitalize(s) {\n    return s ? s[0].toUpperCase() + s.slice(1) : \"\";\n}\n\nconst diacriticsMap = {\n'\\u0041': 'A','\\u24B6': 'A','\\uFF21': 'A','\\u00C0': 'A','\\u00C1': 'A','\\u00C2': 'A','\\u1EA6': 'A','\\u1EA4': 'A','\\u1EAA': 'A','\\u1EA8': 'A',\n'\\u00C3': 'A','\\u0100': 'A','\\u0102': 'A','\\u1EB0': 'A','\\u1EAE': 'A','\\u1EB4': 'A','\\u1EB2': 'A','\\u0226': 'A','\\u01E0': 'A','\\u00C4': 'A',\n'\\u01DE': 'A','\\u1EA2': 'A','\\u00C5': 'A','\\u01FA': 'A','\\u01CD': 'A','\\u0200': 'A','\\u0202': 'A','\\u1EA0': 'A','\\u1EAC': 'A','\\u1EB6': 'A',\n'\\u1E00': 'A','\\u0104': 'A','\\u023A': 'A','\\u2C6F': 'A',\n\n'\\uA732': 'AA',\n'\\u00C6': 'AE','\\u01FC': 'AE','\\u01E2': 'AE',\n'\\uA734': 'AO',\n'\\uA736': 'AU',\n'\\uA738': 'AV','\\uA73A': 'AV',\n'\\uA73C': 'AY',\n'\\u0042': 'B','\\u24B7': 'B','\\uFF22': 'B','\\u1E02': 'B','\\u1E04': 'B','\\u1E06': 'B','\\u0243': 'B','\\u0182': 'B','\\u0181': 'B',\n\n'\\u0043': 'C','\\u24B8': 'C','\\uFF23': 'C','\\u0106': 'C','\\u0108': 'C','\\u010A': 'C','\\u010C': 'C','\\u00C7': 'C','\\u1E08': 'C','\\u0187': 'C',\n'\\u023B': 'C','\\uA73E': 'C',\n\n'\\u0044': 'D','\\u24B9': 'D','\\uFF24': 'D','\\u1E0A': 'D','\\u010E': 'D','\\u1E0C': 'D','\\u1E10': 'D','\\u1E12': 'D','\\u1E0E': 'D','\\u0110': 'D',\n'\\u018B': 'D','\\u018A': 'D','\\u0189': 'D','\\uA779': 'D',\n\n'\\u01F1': 'DZ','\\u01C4': 'DZ',\n'\\u01F2': 'Dz','\\u01C5': 'Dz',\n\n'\\u0045': 'E','\\u24BA': 'E','\\uFF25': 'E','\\u00C8': 'E','\\u00C9': 'E','\\u00CA': 'E','\\u1EC0': 'E','\\u1EBE': 'E','\\u1EC4': 'E','\\u1EC2': 'E',\n'\\u1EBC': 'E','\\u0112': 'E','\\u1E14': 'E','\\u1E16': 'E','\\u0114': 'E','\\u0116': 'E','\\u00CB': 'E','\\u1EBA': 'E','\\u011A': 'E','\\u0204': 'E',\n'\\u0206': 'E','\\u1EB8': 'E','\\u1EC6': 'E','\\u0228': 'E','\\u1E1C': 'E','\\u0118': 'E','\\u1E18': 'E','\\u1E1A': 'E','\\u0190': 'E','\\u018E': 'E',\n\n'\\u0046': 'F','\\u24BB': 'F','\\uFF26': 'F','\\u1E1E': 'F','\\u0191': 'F','\\uA77B': 'F',\n\n'\\u0047': 'G','\\u24BC': 'G','\\uFF27': 'G','\\u01F4': 'G','\\u011C': 'G','\\u1E20': 'G','\\u011E': 'G','\\u0120': 'G','\\u01E6': 'G','\\u0122': 'G',\n'\\u01E4': 'G','\\u0193': 'G','\\uA7A0': 'G','\\uA77D': 'G','\\uA77E': 'G',\n\n'\\u0048': 'H','\\u24BD': 'H','\\uFF28': 'H','\\u0124': 'H','\\u1E22': 'H','\\u1E26': 'H','\\u021E': 'H','\\u1E24': 'H','\\u1E28': 'H','\\u1E2A': 'H',\n'\\u0126': 'H','\\u2C67': 'H','\\u2C75': 'H','\\uA78D': 'H',\n\n'\\u0049': 'I','\\u24BE': 'I','\\uFF29': 'I','\\u00CC': 'I','\\u00CD': 'I','\\u00CE': 'I','\\u0128': 'I','\\u012A': 'I','\\u012C': 'I','\\u0130': 'I',\n'\\u00CF': 'I','\\u1E2E': 'I','\\u1EC8': 'I','\\u01CF': 'I','\\u0208': 'I','\\u020A': 'I','\\u1ECA': 'I','\\u012E': 'I','\\u1E2C': 'I','\\u0197': 'I',\n\n'\\u004A': 'J','\\u24BF': 'J','\\uFF2A': 'J','\\u0134': 'J','\\u0248': 'J',\n\n'\\u004B': 'K','\\u24C0': 'K','\\uFF2B': 'K','\\u1E30': 'K','\\u01E8': 'K','\\u1E32': 'K','\\u0136': 'K','\\u1E34': 'K','\\u0198': 'K','\\u2C69': 'K',\n'\\uA740': 'K','\\uA742': 'K','\\uA744': 'K','\\uA7A2': 'K',\n\n'\\u004C': 'L','\\u24C1': 'L','\\uFF2C': 'L','\\u013F': 'L','\\u0139': 'L','\\u013D': 'L','\\u1E36': 'L','\\u1E38': 'L','\\u013B': 'L','\\u1E3C': 'L',\n'\\u1E3A': 'L','\\u0141': 'L','\\u023D': 'L','\\u2C62': 'L','\\u2C60': 'L','\\uA748': 'L','\\uA746': 'L','\\uA780': 'L',\n\n'\\u01C7': 'LJ',\n'\\u01C8': 'Lj',\n'\\u004D': 'M','\\u24C2': 'M','\\uFF2D': 'M','\\u1E3E': 'M','\\u1E40': 'M','\\u1E42': 'M','\\u2C6E': 'M','\\u019C': 'M',\n\n'\\u004E': 'N','\\u24C3': 'N','\\uFF2E': 'N','\\u01F8': 'N','\\u0143': 'N','\\u00D1': 'N','\\u1E44': 'N','\\u0147': 'N','\\u1E46': 'N','\\u0145': 'N',\n'\\u1E4A': 'N','\\u1E48': 'N','\\u0220': 'N','\\u019D': 'N','\\uA790': 'N','\\uA7A4': 'N',\n\n'\\u01CA': 'NJ',\n'\\u01CB': 'Nj',\n\n'\\u004F': 'O','\\u24C4': 'O','\\uFF2F': 'O','\\u00D2': 'O','\\u00D3': 'O','\\u00D4': 'O','\\u1ED2': 'O','\\u1ED0': 'O','\\u1ED6': 'O','\\u1ED4': 'O',\n'\\u00D5': 'O','\\u1E4C': 'O','\\u022C': 'O','\\u1E4E': 'O','\\u014C': 'O','\\u1E50': 'O','\\u1E52': 'O','\\u014E': 'O','\\u022E': 'O','\\u0230': 'O',\n'\\u00D6': 'O','\\u022A': 'O','\\u1ECE': 'O','\\u0150': 'O','\\u01D1': 'O','\\u020C': 'O','\\u020E': 'O','\\u01A0': 'O','\\u1EDC': 'O','\\u1EDA': 'O',\n'\\u1EE0': 'O','\\u1EDE': 'O','\\u1EE2': 'O','\\u1ECC': 'O','\\u1ED8': 'O','\\u01EA': 'O','\\u01EC': 'O','\\u00D8': 'O','\\u01FE': 'O','\\u0186': 'O',\n'\\u019F': 'O','\\uA74A': 'O','\\uA74C': 'O',\n\n'\\u01A2': 'OI',\n'\\uA74E': 'OO',\n'\\u0222': 'OU',\n'\\u0050': 'P','\\u24C5': 'P','\\uFF30': 'P','\\u1E54': 'P','\\u1E56': 'P','\\u01A4': 'P','\\u2C63': 'P','\\uA750': 'P','\\uA752': 'P','\\uA754': 'P',\n'\\u0051': 'Q','\\u24C6': 'Q','\\uFF31': 'Q','\\uA756': 'Q','\\uA758': 'Q','\\u024A': 'Q',\n\n'\\u0052': 'R','\\u24C7': 'R','\\uFF32': 'R','\\u0154': 'R','\\u1E58': 'R','\\u0158': 'R','\\u0210': 'R','\\u0212': 'R','\\u1E5A': 'R','\\u1E5C': 'R',\n'\\u0156': 'R','\\u1E5E': 'R','\\u024C': 'R','\\u2C64': 'R','\\uA75A': 'R','\\uA7A6': 'R','\\uA782': 'R',\n\n'\\u0053': 'S','\\u24C8': 'S','\\uFF33': 'S','\\u1E9E': 'S','\\u015A': 'S','\\u1E64': 'S','\\u015C': 'S','\\u1E60': 'S','\\u0160': 'S','\\u1E66': 'S',\n'\\u1E62': 'S','\\u1E68': 'S','\\u0218': 'S','\\u015E': 'S','\\u2C7E': 'S','\\uA7A8': 'S','\\uA784': 'S',\n\n'\\u0054': 'T','\\u24C9': 'T','\\uFF34': 'T','\\u1E6A': 'T','\\u0164': 'T','\\u1E6C': 'T','\\u021A': 'T','\\u0162': 'T','\\u1E70': 'T','\\u1E6E': 'T',\n'\\u0166': 'T','\\u01AC': 'T','\\u01AE': 'T','\\u023E': 'T','\\uA786': 'T',\n\n'\\uA728': 'TZ',\n\n'\\u0055': 'U','\\u24CA': 'U','\\uFF35': 'U','\\u00D9': 'U','\\u00DA': 'U','\\u00DB': 'U','\\u0168': 'U','\\u1E78': 'U','\\u016A': 'U','\\u1E7A': 'U',\n'\\u016C': 'U','\\u00DC': 'U','\\u01DB': 'U','\\u01D7': 'U','\\u01D5': 'U','\\u01D9': 'U','\\u1EE6': 'U','\\u016E': 'U','\\u0170': 'U','\\u01D3': 'U',\n'\\u0214': 'U','\\u0216': 'U','\\u01AF': 'U','\\u1EEA': 'U','\\u1EE8': 'U','\\u1EEE': 'U','\\u1EEC': 'U','\\u1EF0': 'U','\\u1EE4': 'U','\\u1E72': 'U',\n'\\u0172': 'U','\\u1E76': 'U','\\u1E74': 'U','\\u0244': 'U',\n\n'\\u0056': 'V','\\u24CB': 'V','\\uFF36': 'V','\\u1E7C': 'V','\\u1E7E': 'V','\\u01B2': 'V','\\uA75E': 'V','\\u0245': 'V',\n'\\uA760': 'VY',\n'\\u0057': 'W','\\u24CC': 'W','\\uFF37': 'W','\\u1E80': 'W','\\u1E82': 'W','\\u0174': 'W','\\u1E86': 'W','\\u1E84': 'W','\\u1E88': 'W','\\u2C72': 'W',\n'\\u0058': 'X','\\u24CD': 'X','\\uFF38': 'X','\\u1E8A': 'X','\\u1E8C': 'X',\n\n'\\u0059': 'Y','\\u24CE': 'Y','\\uFF39': 'Y','\\u1EF2': 'Y','\\u00DD': 'Y','\\u0176': 'Y','\\u1EF8': 'Y','\\u0232': 'Y','\\u1E8E': 'Y','\\u0178': 'Y',\n'\\u1EF6': 'Y','\\u1EF4': 'Y','\\u01B3': 'Y','\\u024E': 'Y','\\u1EFE': 'Y',\n\n'\\u005A': 'Z','\\u24CF': 'Z','\\uFF3A': 'Z','\\u0179': 'Z','\\u1E90': 'Z','\\u017B': 'Z','\\u017D': 'Z','\\u1E92': 'Z','\\u1E94': 'Z','\\u01B5': 'Z',\n'\\u0224': 'Z','\\u2C7F': 'Z','\\u2C6B': 'Z','\\uA762': 'Z',\n\n'\\u0061': 'a','\\u24D0': 'a','\\uFF41': 'a','\\u1E9A': 'a','\\u00E0': 'a','\\u00E1': 'a','\\u00E2': 'a','\\u1EA7': 'a','\\u1EA5': 'a','\\u1EAB': 'a',\n'\\u1EA9': 'a','\\u00E3': 'a','\\u0101': 'a','\\u0103': 'a','\\u1EB1': 'a','\\u1EAF': 'a','\\u1EB5': 'a','\\u1EB3': 'a','\\u0227': 'a','\\u01E1': 'a',\n'\\u00E4': 'a','\\u01DF': 'a','\\u1EA3': 'a','\\u00E5': 'a','\\u01FB': 'a','\\u01CE': 'a','\\u0201': 'a','\\u0203': 'a','\\u1EA1': 'a','\\u1EAD': 'a',\n'\\u1EB7': 'a','\\u1E01': 'a','\\u0105': 'a','\\u2C65': 'a','\\u0250': 'a',\n\n'\\uA733': 'aa',\n'\\u00E6': 'ae','\\u01FD': 'ae','\\u01E3': 'ae',\n'\\uA735': 'ao',\n'\\uA737': 'au',\n'\\uA739': 'av','\\uA73B': 'av',\n'\\uA73D': 'ay',\n'\\u0062': 'b','\\u24D1': 'b','\\uFF42': 'b','\\u1E03': 'b','\\u1E05': 'b','\\u1E07': 'b','\\u0180': 'b','\\u0183': 'b','\\u0253': 'b',\n\n'\\u0063': 'c','\\u24D2': 'c','\\uFF43': 'c','\\u0107': 'c','\\u0109': 'c','\\u010B': 'c','\\u010D': 'c','\\u00E7': 'c','\\u1E09': 'c','\\u0188': 'c',\n'\\u023C': 'c','\\uA73F': 'c','\\u2184': 'c',\n\n'\\u0064': 'd','\\u24D3': 'd','\\uFF44': 'd','\\u1E0B': 'd','\\u010F': 'd','\\u1E0D': 'd','\\u1E11': 'd','\\u1E13': 'd','\\u1E0F': 'd','\\u0111': 'd',\n'\\u018C': 'd','\\u0256': 'd','\\u0257': 'd','\\uA77A': 'd',\n\n'\\u01F3': 'dz','\\u01C6': 'dz',\n\n'\\u0065': 'e','\\u24D4': 'e','\\uFF45': 'e','\\u00E8': 'e','\\u00E9': 'e','\\u00EA': 'e','\\u1EC1': 'e','\\u1EBF': 'e','\\u1EC5': 'e','\\u1EC3': 'e',\n'\\u1EBD': 'e','\\u0113': 'e','\\u1E15': 'e','\\u1E17': 'e','\\u0115': 'e','\\u0117': 'e','\\u00EB': 'e','\\u1EBB': 'e','\\u011B': 'e','\\u0205': 'e',\n'\\u0207': 'e','\\u1EB9': 'e','\\u1EC7': 'e','\\u0229': 'e','\\u1E1D': 'e','\\u0119': 'e','\\u1E19': 'e','\\u1E1B': 'e','\\u0247': 'e','\\u025B': 'e',\n'\\u01DD': 'e',\n\n'\\u0066': 'f','\\u24D5': 'f','\\uFF46': 'f','\\u1E1F': 'f','\\u0192': 'f','\\uA77C': 'f',\n\n'\\u0067': 'g','\\u24D6': 'g','\\uFF47': 'g','\\u01F5': 'g','\\u011D': 'g','\\u1E21': 'g','\\u011F': 'g','\\u0121': 'g','\\u01E7': 'g','\\u0123': 'g',\n'\\u01E5': 'g','\\u0260': 'g','\\uA7A1': 'g','\\u1D79': 'g','\\uA77F': 'g',\n\n'\\u0068': 'h','\\u24D7': 'h','\\uFF48': 'h','\\u0125': 'h','\\u1E23': 'h','\\u1E27': 'h','\\u021F': 'h','\\u1E25': 'h','\\u1E29': 'h','\\u1E2B': 'h',\n'\\u1E96': 'h','\\u0127': 'h','\\u2C68': 'h','\\u2C76': 'h','\\u0265': 'h',\n\n'\\u0195': 'hv',\n\n'\\u0069': 'i','\\u24D8': 'i','\\uFF49': 'i','\\u00EC': 'i','\\u00ED': 'i','\\u00EE': 'i','\\u0129': 'i','\\u012B': 'i','\\u012D': 'i','\\u00EF': 'i',\n'\\u1E2F': 'i','\\u1EC9': 'i','\\u01D0': 'i','\\u0209': 'i','\\u020B': 'i','\\u1ECB': 'i','\\u012F': 'i','\\u1E2D': 'i','\\u0268': 'i','\\u0131': 'i',\n\n'\\u006A': 'j','\\u24D9': 'j','\\uFF4A': 'j','\\u0135': 'j','\\u01F0': 'j','\\u0249': 'j',\n\n'\\u006B': 'k','\\u24DA': 'k','\\uFF4B': 'k','\\u1E31': 'k','\\u01E9': 'k','\\u1E33': 'k','\\u0137': 'k','\\u1E35': 'k','\\u0199': 'k','\\u2C6A': 'k',\n'\\uA741': 'k','\\uA743': 'k','\\uA745': 'k','\\uA7A3': 'k',\n\n'\\u006C': 'l','\\u24DB': 'l','\\uFF4C': 'l','\\u0140': 'l','\\u013A': 'l','\\u013E': 'l','\\u1E37': 'l','\\u1E39': 'l','\\u013C': 'l','\\u1E3D': 'l',\n'\\u1E3B': 'l','\\u017F': 'l','\\u0142': 'l','\\u019A': 'l','\\u026B': 'l','\\u2C61': 'l','\\uA749': 'l','\\uA781': 'l','\\uA747': 'l',\n\n'\\u01C9': 'lj',\n'\\u006D': 'm','\\u24DC': 'm','\\uFF4D': 'm','\\u1E3F': 'm','\\u1E41': 'm','\\u1E43': 'm','\\u0271': 'm','\\u026F': 'm',\n\n'\\u006E': 'n','\\u24DD': 'n','\\uFF4E': 'n','\\u01F9': 'n','\\u0144': 'n','\\u00F1': 'n','\\u1E45': 'n','\\u0148': 'n','\\u1E47': 'n','\\u0146': 'n',\n'\\u1E4B': 'n','\\u1E49': 'n','\\u019E': 'n','\\u0272': 'n','\\u0149': 'n','\\uA791': 'n','\\uA7A5': 'n',\n\n'\\u01CC': 'nj',\n\n'\\u006F': 'o','\\u24DE': 'o','\\uFF4F': 'o','\\u00F2': 'o','\\u00F3': 'o','\\u00F4': 'o','\\u1ED3': 'o','\\u1ED1': 'o','\\u1ED7': 'o','\\u1ED5': 'o',\n'\\u00F5': 'o','\\u1E4D': 'o','\\u022D': 'o','\\u1E4F': 'o','\\u014D': 'o','\\u1E51': 'o','\\u1E53': 'o','\\u014F': 'o','\\u022F': 'o','\\u0231': 'o',\n'\\u00F6': 'o','\\u022B': 'o','\\u1ECF': 'o','\\u0151': 'o','\\u01D2': 'o','\\u020D': 'o','\\u020F': 'o','\\u01A1': 'o','\\u1EDD': 'o','\\u1EDB': 'o',\n'\\u1EE1': 'o','\\u1EDF': 'o','\\u1EE3': 'o','\\u1ECD': 'o','\\u1ED9': 'o','\\u01EB': 'o','\\u01ED': 'o','\\u00F8': 'o','\\u01FF': 'o','\\u0254': 'o',\n'\\uA74B': 'o','\\uA74D': 'o','\\u0275': 'o',\n\n'\\u01A3': 'oi',\n'\\u0223': 'ou',\n'\\uA74F': 'oo',\n'\\u0070': 'p','\\u24DF': 'p','\\uFF50': 'p','\\u1E55': 'p','\\u1E57': 'p','\\u01A5': 'p','\\u1D7D': 'p','\\uA751': 'p','\\uA753': 'p','\\uA755': 'p',\n'\\u0071': 'q','\\u24E0': 'q','\\uFF51': 'q','\\u024B': 'q','\\uA757': 'q','\\uA759': 'q',\n\n'\\u0072': 'r','\\u24E1': 'r','\\uFF52': 'r','\\u0155': 'r','\\u1E59': 'r','\\u0159': 'r','\\u0211': 'r','\\u0213': 'r','\\u1E5B': 'r','\\u1E5D': 'r',\n'\\u0157': 'r','\\u1E5F': 'r','\\u024D': 'r','\\u027D': 'r','\\uA75B': 'r','\\uA7A7': 'r','\\uA783': 'r',\n\n'\\u0073': 's','\\u24E2': 's','\\uFF53': 's','\\u00DF': 's','\\u015B': 's','\\u1E65': 's','\\u015D': 's','\\u1E61': 's','\\u0161': 's','\\u1E67': 's',\n'\\u1E63': 's','\\u1E69': 's','\\u0219': 's','\\u015F': 's','\\u023F': 's','\\uA7A9': 's','\\uA785': 's','\\u1E9B': 's',\n\n'\\u0074': 't','\\u24E3': 't','\\uFF54': 't','\\u1E6B': 't','\\u1E97': 't','\\u0165': 't','\\u1E6D': 't','\\u021B': 't','\\u0163': 't','\\u1E71': 't',\n'\\u1E6F': 't','\\u0167': 't','\\u01AD': 't','\\u0288': 't','\\u2C66': 't','\\uA787': 't',\n\n'\\uA729': 'tz',\n\n'\\u0075': 'u','\\u24E4': 'u','\\uFF55': 'u','\\u00F9': 'u','\\u00FA': 'u','\\u00FB': 'u','\\u0169': 'u','\\u1E79': 'u','\\u016B': 'u','\\u1E7B': 'u',\n'\\u016D': 'u','\\u00FC': 'u','\\u01DC': 'u','\\u01D8': 'u','\\u01D6': 'u','\\u01DA': 'u','\\u1EE7': 'u','\\u016F': 'u','\\u0171': 'u','\\u01D4': 'u',\n'\\u0215': 'u','\\u0217': 'u','\\u01B0': 'u','\\u1EEB': 'u','\\u1EE9': 'u','\\u1EEF': 'u','\\u1EED': 'u','\\u1EF1': 'u','\\u1EE5': 'u','\\u1E73': 'u',\n'\\u0173': 'u','\\u1E77': 'u','\\u1E75': 'u','\\u0289': 'u',\n\n'\\u0076': 'v','\\u24E5': 'v','\\uFF56': 'v','\\u1E7D': 'v','\\u1E7F': 'v','\\u028B': 'v','\\uA75F': 'v','\\u028C': 'v',\n'\\uA761': 'vy',\n'\\u0077': 'w','\\u24E6': 'w','\\uFF57': 'w','\\u1E81': 'w','\\u1E83': 'w','\\u0175': 'w','\\u1E87': 'w','\\u1E85': 'w','\\u1E98': 'w','\\u1E89': 'w',\n'\\u2C73': 'w',\n'\\u0078': 'x','\\u24E7': 'x','\\uFF58': 'x','\\u1E8B': 'x','\\u1E8D': 'x',\n\n'\\u0079': 'y','\\u24E8': 'y','\\uFF59': 'y','\\u1EF3': 'y','\\u00FD': 'y','\\u0177': 'y','\\u1EF9': 'y','\\u0233': 'y','\\u1E8F': 'y','\\u00FF': 'y',\n'\\u1EF7': 'y','\\u1E99': 'y','\\u1EF5': 'y','\\u01B4': 'y','\\u024F': 'y','\\u1EFF': 'y',\n\n'\\u007A': 'z','\\u24E9': 'z','\\uFF5A': 'z','\\u017A': 'z','\\u1E91': 'z','\\u017C': 'z','\\u017E': 'z','\\u1E93': 'z','\\u1E95': 'z','\\u01B6': 'z',\n'\\u0225': 'z','\\u0240': 'z','\\u2C6C': 'z','\\uA763': 'z',\n};\n\n/**\n * Replace diacritics character with ASCII character\n *\n * @param {string} str diacritics string\n * @param {boolean} caseSensitive\n * @returns {string} ASCII string\n */\nexport function unaccent(str, caseSensitive) {\n    str = str.replace(/[^\\u0000-\\u007E]/g, function (accented) {\n        return diacriticsMap[accented] || accented;\n    });\n    return caseSensitive ? str : str.toLowerCase();\n}\n", "/** @odoo-module **/\n\nimport { browser } from \"../browser/browser\";\n\n/**\n * Creates a version of the function where only the last call between two\n * animation frames is executed before the browser's next repaint. This\n * effectively throttles the function to the display's refresh rate.\n *\n * @param {Function} func the function to throttle\n * @returns {{ (...args): void, cancel: () => void }} the throttled function\n */\nexport function throttleForAnimation(func) {\n    let handle = null;\n    const funcName = func.name ? `${func.name} (throttleForAnimation)` : \"throttleForAnimation\";\n    return Object.assign(\n        {\n            [funcName](...args) {\n                browser.cancelAnimationFrame(handle);\n                handle = browser.requestAnimationFrame(() => {\n                    handle = null;\n                    func.call(this, ...args);\n                });\n            },\n        }[funcName],\n        {\n            cancel() {\n                browser.cancelAnimationFrame(handle);\n            },\n        }\n    );\n}\n\n/**\n * Creates and returns a new debounced version of the passed function (func)\n * which will postpone its execution until after wait milliseconds\n * have elapsed since the last time it was invoked. The debounced function\n * will return a Promise that will be resolved when the function (func)\n * has been fully executed. If `immediate` is passed, trigger the function\n * on the leading edge, instead of the trailing.\n *\n * @template T the return type of the original function\n * @param {(...args) => T} func the function to debounce\n * @param {number} wait how long should elapse before the function is called\n * @param {boolean} [immediate=false] whether the function should be called on\n *      the leading edge instead of the trailing edge.\n * @returns {{ (...args): Promise<T>, cancel: () => void }} the debounced function\n */\nexport function debounce(func, wait, immediate = false) {\n    let timeout;\n    const funcName = func.name ? func.name + \" (debounce)\" : \"debounce\";\n    return Object.assign(\n        {\n            [funcName](...args) {\n                return new Promise((resolve) => {\n                    const callNow = immediate && !timeout;\n                    browser.clearTimeout(timeout);\n                    timeout = browser.setTimeout(() => {\n                        timeout = null;\n                        if (!immediate) {\n                            Promise.resolve(func.apply(this, args)).then(resolve);\n                        }\n                    }, wait);\n                    if (callNow) {\n                        Promise.resolve(func.apply(this, args)).then(resolve);\n                    }\n                });\n            },\n        }[funcName],\n        {\n            cancel() {\n                browser.clearTimeout(timeout);\n            },\n        }\n    );\n}\n\n/**\n * Returns a function, that, as long as it continues to be invoked, will be\n * triggered every N milliseconds.\n *\n * @deprecated this function has behaviour that is unexpected considering its\n *      name, prefer _.throttle until this function is rewritten\n * @param {Function} func\n * @param {number} wait\n * @returns {Function}\n */\nexport function throttle(func, wait) {\n    let waiting = false;\n    const funcName = func.name ? func.name + \" (throttle)\" : \"throttle\";\n    return {\n        [funcName](...args) {\n            const context = this;\n            if (!waiting) {\n                waiting = true;\n                browser.setTimeout(function () {\n                    waiting = false;\n                    func.call(context, ...args);\n                }, wait);\n            }\n        },\n    }[funcName];\n}\n", "/** @odoo-module **/\n\n/**\n * @param {Document} activeElement\n * @param {DOMString} selector\n * @returns all selected and visible elements present in the activeElement\n */\nexport function getVisibleElements(activeElement, selector) {\n    const visibleElements = [];\n    for (const el of activeElement.querySelectorAll(selector)) {\n        const isVisible = el.offsetWidth > 0 && el.offsetHeight > 0;\n        if (isVisible) {\n            visibleElements.push(el);\n        }\n    }\n    return visibleElements;\n}\n", "/** @odoo-module **/\n\nimport { browser } from \"../browser/browser\";\n\n/**\n * Trasnforms a key value mapping to a string formatted as url hash, e.g.\n * {a: \"x\", b: 2} -> \"a=x&b=2\"\n *\n * @param {Object} obj\n * @returns {string}\n */\nexport function objectToUrlEncodedString(obj) {\n    return Object.entries(obj)\n        .map(([k, v]) => (v ? `${k}=${encodeURIComponent(v)}` : k))\n        .join(\"&\");\n}\n\n/**\n * Gets the origin url of the page, or cleans a given one\n *\n * @param {string} [origin]: a given origin url\n * @return {string} a cleaned origin url\n */\nexport function getOrigin(origin) {\n    if (origin) {\n        // remove trailing slashes\n        origin = origin.replace(/\\/+$/, \"\");\n    } else {\n        const { host, protocol } = browser.location;\n        origin = `${protocol}//${host}`;\n    }\n    return origin;\n}\n\n/**\n * @param {string} route: the relative route, or absolute in the case of cors urls\n * @param {object} [queryParams]: parameters to be appended as the url's queryString\n * @param {object} [options]\n * @param {string} [options.origin]: a precomputed origin\n */\nexport function url(route, queryParams, options = {}) {\n    const origin = getOrigin(options.origin);\n    if (!route) {\n        return origin;\n    }\n\n    let queryString = objectToUrlEncodedString(queryParams || {});\n    queryString = queryString.length > 0 ? `?${queryString}` : queryString;\n\n    // Compare the wanted url against the current origin\n    let prefix = [\"http://\", \"https://\", \"//\"].some(\n        (el) => route.length >= el.length && route.slice(0, el.length) === el\n    );\n    prefix = prefix ? \"\" : origin;\n    return `${prefix}${route}${queryString}`;\n}\n", "/** @odoo-module **/\n\nfunction hasParsingError(parsedDocument) {\n    return parsedDocument.getElementsByTagName(\"parsererror\").length > 0;\n}\n\nexport class XMLParser {\n    /**\n     * to override. Should return the parsed content of the arch.\n     * It can call the visitArch function if desired\n     */\n    parse() {}\n\n    visitXML(xml, callback) {\n        function visit(xml) {\n            if (xml) {\n                let didVisitChildren = false;\n                const visitChildren = () => {\n                    for (let child of xml.children) {\n                        visit(child);\n                    }\n                    didVisitChildren = true;\n                };\n                const shouldVisitChildren = callback(xml, visitChildren);\n                if (shouldVisitChildren !== false && !didVisitChildren) {\n                    visitChildren();\n                }\n            }\n        }\n        const xmlDoc = typeof xml === \"string\" ? this.parseXML(xml) : xml;\n        visit(xmlDoc);\n    }\n\n    parseXML(arch) {\n        const parser = new DOMParser();\n        const xml = parser.parseFromString(arch, \"text/xml\");\n        if (hasParsingError(xml)) {\n            throw new Error(\n                `An error occured while parsing ${arch}: ${xml.getElementsByTagName(\"parsererror\")}`\n            );\n        }\n        return xml.documentElement;\n    }\n}\n", "/** @odoo-module */\n// This module makes it so that some errors only display a notification instead of an error dialog\n\nimport { registry } from \"@web/core/registry\";\nimport { odooExceptionTitleMap } from \"@web/core/errors/error_dialogs\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nodooExceptionTitleMap.forEach((title, exceptionName) => {\n    registry.category(\"error_notifications\").add(exceptionName, {\n        title: title,\n        type: \"warning\",\n        sticky: true\n    });\n});\n\nconst sessionExpired = {\n    title: _t(\"Odoo Session Expired\"),\n    message: _t(\"Your Odoo session expired. The current page is about to be refreshed.\"),\n    buttons: [\n        {\n            text: _t(\"Ok\"),\n            click: () => window.location.reload(true),\n            close: true\n        }\n    ]\n};\n\nregistry\n    .category(\"error_notifications\")\n    .add(\"odoo.http.SessionExpiredException\", sessionExpired)\n    .add(\"werkzeug.exceptions.Forbidden\", sessionExpired)\n    .add(\"504\", {\n        title: _t(\"Request timeout\"),\n        message: _t(\n            \"The operation was interrupted. This usually means that the current operation is taking too much time.\"\n        )\n    });\n", "(function () {\n'use strict';\n\n/**\n * This file makes sure textarea elements with a specific editor class are\n * tweaked as soon as the DOM is ready so that they appear to be loading.\n *\n * They must then be loaded using standard Odoo modules system. In particular,\n * @see web_editor.loader\n */\n\ndocument.addEventListener('DOMContentLoaded', () => {\n    // Standard loop for better browser support\n    var textareaEls = document.querySelectorAll('textarea.o_wysiwyg_loader');\n    for (var i = 0; i < textareaEls.length; i++) {\n        var textarea = textareaEls[i];\n        var wrapper = document.createElement('div');\n        wrapper.classList.add('position-relative', 'o_wysiwyg_textarea_wrapper');\n\n        var loadingElement = document.createElement('div');\n        loadingElement.classList.add('o_wysiwyg_loading');\n        var loadingIcon = document.createElement('i');\n        loadingIcon.classList.add('text-600', 'text-center',\n            'fa', 'fa-circle-o-notch', 'fa-spin', 'fa-2x');\n        loadingElement.appendChild(loadingIcon);\n        wrapper.appendChild(loadingElement);\n\n        textarea.parentNode.insertBefore(wrapper, textarea);\n        wrapper.insertBefore(textarea, loadingElement);\n    }\n});\n\n})();\n", "/** @odoo-module **/\n\nimport { browser } from \"../core/browser/browser\";\nimport AbstractStorageService from \"web.AbstractStorageService\";\nimport {\n    ConnectionAbortedError,\n    RPCError,\n    makeErrorFromResponse,\n    ConnectionLostError,\n} from \"../core/network/rpc_service\";\nimport { ErrorDialog } from \"../core/errors/error_dialogs\";\n\nexport function makeLegacyRpcService(legacyEnv) {\n    return {\n        start(env) {\n            legacyEnv.bus.on(\"rpc_request\", null, (rpcId) => {\n                env.bus.trigger(\"RPC:REQUEST\", rpcId);\n            });\n            legacyEnv.bus.on(\"rpc_response\", null, (rpcId) => {\n                env.bus.trigger(\"RPC:RESPONSE\", rpcId);\n            });\n            legacyEnv.bus.on(\"rpc_response_failed\", null, (rpcId) => {\n                env.bus.trigger(\"RPC:RESPONSE\", rpcId);\n            });\n        },\n    };\n}\n\n/**\n * Returns a service that maps legacy dialogs\n * to new environment services behavior.\n *\n * @param {object} legacyEnv\n * @returns a wowl deployable service\n */\nexport function makeLegacyDialogMappingService(legacyEnv) {\n    return {\n        dependencies: [\"ui\", \"hotkey\"],\n        start(env) {\n            const { ui, hotkey } = env.services;\n\n            function getModalEl(dialog) {\n                return dialog.modalRef ? dialog.modalRef.el : dialog.$modal[0];\n            }\n\n            function getCloseCallback(dialog) {\n                return dialog.modalRef ? () => dialog._close() : () => dialog.$modal.modal(\"hide\");\n            }\n\n            const dialogHotkeyRemoveMap = new Map();\n\n            function onOpenDialog(dialog) {\n                ui.activateElement(getModalEl(dialog));\n                const remove = hotkey.add(\"escape\", getCloseCallback(dialog));\n                dialogHotkeyRemoveMap.set(dialog, remove);\n            }\n\n            function onCloseDialog(dialog) {\n                ui.deactivateElement(getModalEl(dialog));\n                if (dialogHotkeyRemoveMap.has(dialog)) {\n                    const removeHotkey = dialogHotkeyRemoveMap.get(dialog);\n                    removeHotkey();\n                    dialogHotkeyRemoveMap.delete(dialog);\n                }\n            }\n\n            legacyEnv.bus.on(\"legacy_dialog_opened\", null, onOpenDialog);\n            legacyEnv.bus.on(\"legacy_dialog_destroyed\", null, onCloseDialog);\n\n            legacyEnv.bus.on(\"owl_dialog_mounted\", null, onOpenDialog);\n            legacyEnv.bus.on(\"owl_dialog_willunmount\", null, onCloseDialog);\n        },\n    };\n}\n\n/**\n * Deploys a service allowing legacy to add/remove commands.\n *\n * @param {object} legacyEnv\n * @returns a wowl deployable service\n */\nexport function makeLegacyCommandService(legacyEnv) {\n    return {\n        dependencies: [\"command\"],\n        start(env) {\n            const { command } = env.services;\n\n            const commandRemoveMap = new Map();\n\n            function setLegacyCommand(uniqueId, getCommandDefinition) {\n                const { name, action, options } = getCommandDefinition(env);\n                removeLegacyCommand(uniqueId);\n                commandRemoveMap.set(uniqueId, command.add(name, action, options));\n            }\n\n            function removeLegacyCommand(uniqueId) {\n                if (commandRemoveMap.has(uniqueId)) {\n                    const removeCommand = commandRemoveMap.get(uniqueId);\n                    removeCommand();\n                    commandRemoveMap.delete(uniqueId);\n                }\n            }\n\n            legacyEnv.bus.on(\"set_legacy_command\", null, setLegacyCommand);\n            legacyEnv.bus.on(\"remove_legacy_command\", null, removeLegacyCommand);\n        },\n    };\n}\n\nexport function makeLegacyDropdownService(legacyEnv) {\n    return {\n        dependencies: [\"ui\", \"hotkey\"],\n        start(_, { ui, hotkey }) {\n            legacyEnv.services.ui = ui;\n            legacyEnv.services.hotkey = hotkey;\n        },\n    };\n}\n\nexport function makeLegacySessionService(legacyEnv, session) {\n    return {\n        dependencies: [\"user\"],\n        start(env) {\n            // userContext, Object.create is incompatible with legacy new Context\n            function mapContext() {\n                return Object.assign({}, env.services.user.context);\n            }\n            Object.defineProperty(legacyEnv.session, \"userContext\", {\n                get: () => mapContext(),\n            });\n            Object.defineProperty(session, \"user_context\", {\n                get: () => mapContext(),\n            });\n        },\n    };\n}\n\nexport function mapLegacyEnvToWowlEnv(legacyEnv, wowlEnv) {\n    // rpc\n    legacyEnv.session.rpc = (...args) => {\n        let rejection;\n        const prom = new Promise((resolve, reject) => {\n            const [route, params, settings = {}] = args;\n            // Add user context in kwargs if there are kwargs\n            if (params && params.kwargs) {\n                params.kwargs.context = Object.assign(\n                    {},\n                    legacyEnv.session.user_context,\n                    params.kwargs.context,\n                );\n            }\n            const jsonrpc = wowlEnv.services.rpc(route, params, {\n                silent: settings.shadow,\n                xhr: settings.xhr,\n            });\n            rejection = () => {\n                jsonrpc.abort();\n            };\n            jsonrpc.then(resolve).catch((reason) => {\n                if (reason instanceof RPCError || reason instanceof ConnectionLostError) {\n                    // we do not reject an error here because we want to pass through\n                    // the legacy guardedCatch code\n                    reject({ message: reason, event: $.Event(), legacy: true });\n                } else if (reason instanceof ConnectionAbortedError) {\n                    reject({ message: reason.message, event: $.Event(\"abort\") });\n                } else {\n                    reject(reason);\n                }\n            });\n        });\n        prom.abort = rejection;\n        return prom;\n    };\n    // Storages\n    function mapStorage(storage) {\n        const StorageService = AbstractStorageService.extend({ storage });\n        return new StorageService();\n    }\n\n    legacyEnv.services.local_storage = mapStorage(browser.localStorage);\n    legacyEnv.services.session_storage = mapStorage(browser.sessionStorage);\n    // map WebClientReady\n    wowlEnv.bus.on(\"WEB_CLIENT_READY\", null, () => {\n        legacyEnv.bus.trigger(\"web_client_ready\");\n    });\n\n    wowlEnv.bus.on(\"SCROLLER:ANCHOR_LINK_CLICKED\", null, (payload) => {\n        legacyEnv.bus.trigger(\"SCROLLER:ANCHOR_LINK_CLICKED\", payload);\n    });\n\n    legacyEnv.bus.on(\"clear_cache\", null, () => {\n        wowlEnv.bus.trigger(\"CLEAR-CACHES\");\n    });\n}\n\nconst reBSTooltip = /^bs-.*$/;\n\nexport function cleanDomFromBootstrap() {\n    const body = document.body;\n    // multiple bodies in tests\n    // Bootstrap tooltips\n    const tooltips = body.querySelectorAll(\"body .tooltip\");\n    for (const tt of tooltips) {\n        if (Array.from(tt.classList).find((cls) => reBSTooltip.test(cls))) {\n            tt.parentNode.removeChild(tt);\n        }\n    }\n}\n\nexport function makeLegacyNotificationService(legacyEnv) {\n    return {\n        dependencies: [\"notification\"],\n        start(env) {\n            let notifId = 0;\n            const idsToRemoveFn = {};\n\n            function notify({\n                title,\n                message,\n                subtitle,\n                buttons = [],\n                sticky,\n                type,\n                className,\n                onClose,\n            }) {\n                if (subtitle) {\n                    title = [title, subtitle].filter(Boolean).join(\" \");\n                }\n                if (!message && title) {\n                    message = title;\n                    title = undefined;\n                }\n\n                buttons = buttons.map((button) => {\n                    return {\n                        name: button.text,\n                        icon: button.icon,\n                        primary: button.primary,\n                        onClick: button.click,\n                    };\n                });\n\n                const removeFn = env.services.notification.add(_.escape(message), {\n                    sticky,\n                    title,\n                    type,\n                    className,\n                    onClose,\n                    buttons,\n                    messageIsHtml: true,\n                });\n                const id = ++notifId;\n                idsToRemoveFn[id] = removeFn;\n                return id;\n            }\n\n            function close(id, _, wait) {\n                //the legacy close method had 3 arguments : the notification id, silent and wait.\n                //the new close method only has 2 arguments : the notification id and wait.\n                const removeFn = idsToRemoveFn[id];\n                delete idsToRemoveFn[id];\n                if (wait) {\n                    browser.setTimeout(() => {\n                        removeFn(id);\n                    }, wait);\n                } else {\n                    removeFn(id);\n                }\n            }\n\n            legacyEnv.services.notification = { notify, close };\n        },\n    };\n}\n\nexport function makeLegacyCrashManagerService(legacyEnv) {\n    return {\n        dependencies: [\"dialog\"],\n        start(env) {\n            legacyEnv.services.crash_manager = {\n                show_message(message) {\n                    env.services.dialog.add(ErrorDialog, { traceback: message });\n                },\n                rpc_error(errorResponse) {\n                    // Will be handled by error_service\n                    Promise.reject(makeErrorFromResponse(errorResponse));\n                },\n            };\n        },\n    };\n}\n\nexport function wrapSuccessOrFail(promise, { on_success, on_fail } = {}) {\n    return promise.then(on_success || (() => {})).catch((reason) => {\n        let alreadyThrown = false;\n        if (on_fail) {\n            alreadyThrown = on_fail(reason) === \"alreadyThrown\";\n        }\n        if (reason instanceof Error && !alreadyThrown) {\n            throw reason;\n        }\n    });\n}\n\nexport function makeLegacyRainbowManService(legacyEnv) {\n    return {\n        dependencies: [\"effect\"],\n        start(env, { effect }) {\n            legacyEnv.bus.on(\"show-effect\", null, (payload) => {\n                effect.add(payload);\n            });\n        },\n    };\n}\n", "odoo.define('web.OwlCompatibility', function () {\n    \"use strict\";\n\n    /**\n     * This file defines the necessary tools for the transition phase where Odoo\n     * legacy widgets and Owl components will coexist. There are two possible\n     * scenarios:\n     *  1) An Owl component has to instantiate legacy widgets\n     *  2) A legacy widget has to instantiate Owl components\n     */\n\n    const { Component, hooks, tags } = owl;\n    const { useRef, useSubEnv } = hooks;\n    const { xml } = tags;\n\n    const widgetSymbol = odoo.widgetSymbol;\n    const children = new WeakMap(); // associates legacy widgets with their Owl children\n\n    const templateForLegacy = tags.xml`<div/>`;\n    const templateForOwl = tags.xml`<t t-component=\"props.Component\" t-props=\"childProps\" />`;\n    /**\n     * Case 1) An Owl component has to instantiate legacy widgets\n     * ----------------------------------------------------------\n     *\n     * The ComponentAdapter is an Owl component meant to be used as universal\n     * adapter for Owl components that embed Odoo legacy widgets (or dynamically\n     * both Owl components and Odoo legacy widgets), e.g.:\n     *\n     *                           Owl Component\n     *                                 |\n     *                         ComponentAdapter (Owl component)\n     *                                 |\n     *                       Legacy Widget(s) (or Owl component(s))\n     *\n     *\n     * The adapter takes the component/widget class as 'Component' prop, and the\n     * arguments (except first arg 'parent') to initialize it as props.\n     * For instance:\n     *     <ComponentAdapter Component=\"LegacyWidget\" params=\"params\"/>\n     * will be translated to:\n     *     const LegacyWidget = this.props.Component;\n     *     const legacyWidget = new LegacyWidget(this, this.props.params);\n     *\n     * If more than one argument (in addition to 'parent') is given to initialize\n     * the legacy widget, the arguments order (to initialize the sub widget) has\n     * to be somehow specified. There are two alternatives. One can either (1)\n     * specify the prop 'widgetArgs', corresponding to the array of arguments,\n     * otherwise (2) a subclass of ComponentAdapter has to be defined. This\n     * subclass must override the 'widgetArgs' getter to translate arguments\n     * received as props to an array of arguments for the call to init.\n     * For instance:\n     *     (1) <ComponentAdapter Component=\"LegacyWidget\" firstArg=\"a\" secondArg=\"b\" widgetsArgs=\"[a, b]\"/>\n     *     (2) class SpecificAdapter extends ComponentAdapter {\n     *             get widgetArgs() {\n     *                 return [this.props.firstArg, this.props.secondArg];\n     *             }\n     *         }\n     *         <SpecificAdapter Component=\"LegacyWidget\" firstArg=\"a\" secondArg=\"b\"/>\n     *\n     * If the legacy widget has to be updated when props change, one must define\n     * a subclass of ComponentAdapter to override 'updateWidget' and 'renderWidget'. The\n     * 'updateWidget' function takes the nextProps as argument, and should update the\n     * internal state of the widget (might be async, and return a Promise).\n     * However, to ensure that the DOM is updated all at once, it shouldn't do\n     * a re-rendering. This is the role of function 'renderWidget', which will be\n     * called just before patching the DOM, and which thus must be synchronous.\n     * For instance:\n     *     class SpecificAdapter extends ComponentAdapter {\n     *         updateWidget(nextProps) {\n     *             return this.widget.updateState(nextProps);\n     *         }\n     *         renderWidget() {\n     *             return this.widget.render();\n     *         }\n     *     }\n     */\n    class ComponentAdapter extends Component {\n        /**\n         * Creates the template on-the-fly, depending on the type of Component\n         * (legacy widget or Owl component).\n         *\n         * @override\n         */\n        constructor(parent, props) {\n            if (!props.Component) {\n                throw Error(`ComponentAdapter: 'Component' prop is missing.`);\n            }\n            let template;\n            if (!(props.Component.prototype instanceof Component)) {\n                template = templateForLegacy;\n            } else {\n                template = templateForOwl;\n            }\n            ComponentAdapter.template = template;\n            super(...arguments);\n            this.template = template;\n            ComponentAdapter.template = null;\n\n            this.widget = null; // widget instance, if Component is a legacy widget\n        }\n\n        get childProps() {\n            if (!this._childProps) {\n                this._childProps = Object.assign({}, this.props);\n                delete this._childProps.Component;\n            }\n            return this._childProps;\n        }\n\n        /**\n         * Starts the legacy widget (not in the DOM yet)\n         *\n         * @override\n         */\n        willStart() {\n            if (!(this.props.Component.prototype instanceof Component)) {\n                this.widget = new this.props.Component(this, ...this.widgetArgs);\n                return this.widget._widgetRenderAndInsert(() => {});\n            }\n        }\n\n        /**\n         * Updates the internal state of the legacy widget (but doesn't re-render\n         * it yet).\n         *\n         * @override\n         */\n        willUpdateProps(nextProps) {\n            if (this.widget) {\n                return this.updateWidget(nextProps);\n            }\n        }\n\n        /**\n         * Hooks just before the actual patch to replace the fake div in the\n         * vnode by the actual node of the legacy widget. If the widget has to\n         * be re-render (because it has previously been updated), re-render it.\n         * This must be synchronous.\n         *\n         * @override\n         */\n        __patch(target, vnode) {\n            if (this.widget) {\n                if (this.__owl__.vnode) { // not at first rendering\n                    this.renderWidget();\n                }\n                vnode.elm = this.widget.el;\n            }\n            const result = super.__patch(...arguments);\n            if (this.widget && this.el !== this.widget.el) {\n                this.__owl__.vnode.elm = this.widget.el;\n            }\n            return result;\n        }\n\n        /**\n         * @override\n         */\n        mounted() {\n            if (this.widget && this.widget.on_attach_callback) {\n                this.widget.on_attach_callback();\n            }\n        }\n\n        /**\n         * @override\n         */\n        willUnmount() {\n            if (this.widget && this.widget.on_detach_callback) {\n                this.widget.on_detach_callback();\n            }\n        }\n\n        /**\n         * @override\n         */\n        __destroy() {\n            super.__destroy(...arguments);\n            if (this.widget) {\n                this.widget.destroy();\n            }\n        }\n\n        /**\n         * Getter that translates the props (except 'Component') into the array\n         * of arguments used to initialize the legacy widget.\n         *\n         * Must be overriden if at least two props (other that Component) are\n         * given.\n         *\n         * @returns {Array}\n         */\n        get widgetArgs() {\n            if (this.props.widgetArgs) {\n                return this.props.widgetArgs;\n            }\n            const args = Object.keys(this.props);\n            args.splice(args.indexOf('Component'), 1);\n            if (args.length > 1) {\n                throw new Error(`ComponentAdapter has more than 1 argument, 'widgetArgs' must be overriden.`);\n            }\n            return args.map(a => this.props[a]);\n        }\n\n        /**\n         * Can be overriden to update the internal state of the widget when props\n         * change. To ensure that the DOM is updated at once, this function should\n         * not do a re-rendering (which should be done by 'render' instead).\n         *\n         * @param {Object} nextProps\n         * @returns {Promise}\n         */\n        updateWidget(/*nextProps*/) {\n            if (this.env.isDebug('assets')) {\n                console.warn(`ComponentAdapter: Widget could not be updated, maybe override 'updateWidget' function?`);\n            }\n        }\n\n        /**\n         * Can be overriden to re-render the widget after an update. This\n         * function will be called just before patchin the DOM, s.t. the DOM is\n         * updated at once. It must be synchronous\n         */\n        renderWidget() {\n            if (this.env.isDebug('assets')) {\n                console.warn(`ComponentAdapter: Widget could not be re-rendered, maybe override 'renderWidget' function?`);\n            }\n        }\n\n        /**\n         * Mocks _trigger_up to redirect Odoo legacy events to OWL events.\n         *\n         * @private\n         * @param {OdooEvent} ev\n         */\n        _trigger_up(ev) {\n            const evType = ev.name;\n            const payload = ev.data;\n            if (evType === 'call_service') {\n                let args = payload.args || [];\n                if (payload.service === 'ajax' && payload.method === 'rpc') {\n                    // ajax service uses an extra 'target' argument for rpc\n                    args = args.concat(ev.target);\n                }\n                const service = this.env.services[payload.service];\n                //If the service doesn't exist it means that it was translated to Owl\n                if (service) {\n                    const result = service[payload.method].apply(service, args);\n                    payload.callback(result);\n                } else {\n                    throw new Error(\n                        `The service \"${payload.service}\" is not present in the legacy owl environment.\n                         You should probably create a mapper in @web/legacy/utils`\n                    );\n                }\n            } else if (evType === 'get_session') {\n                if (payload.callback) {\n                    payload.callback(this.env.session);\n                }\n            } else if (evType === 'load_views') {\n                const params = {\n                    model: payload.modelName,\n                    context: payload.context,\n                    views_descr: payload.views,\n                };\n                this.env.dataManager\n                    .load_views(params, payload.options || {})\n                    .then(payload.on_success);\n            } else if (evType === 'load_filters') {\n                return this.env.dataManager\n                    .load_filters(payload)\n                    .then(payload.on_success);\n            } else {\n                payload.__targetWidget = ev.target;\n                this.trigger(evType.replace(/_/g, '-'), payload);\n            }\n        }\n    }\n\n\n    /**\n     * Case 2) A legacy widget has to instantiate Owl components\n     * ---------------------------------------------------------\n     *\n     * The WidgetAdapterMixin and the ComponentWrapper are meant to be used\n     * together when an Odoo legacy widget needs to instantiate Owl components.\n     * In this case, the widgets/components hierarchy would look like:\n     *\n     *             Legacy Widget + WidgetAdapterMixin\n     *                          |\n     *                 ComponentWrapper (Owl component)\n     *                          |\n     *                    Owl Component\n     *\n     * In this case, the parent legacy widget must use the WidgetAdapterMixin,\n     * which ensures that Owl hooks (mounted, willUnmount, destroy...) are\n     * properly called on the sub components. Moreover, it must instantiate a\n     * ComponentWrapper, and provide it the Owl component class to use alongside\n     * its props. This wrapper will ensure that the Owl component will be\n     * correctly updated (with willUpdateProps) like it would be if it was embed\n     * in an Owl hierarchy. Moreover, this wrapper automatically redirects all\n     * events triggered by the Owl component (or its descendants) to legacy\n     * custom events (trigger_up) on the parent legacy widget.\n\n     * For example:\n     *      class MyComponent extends Component {}\n     *      MyComponent.template = xml`<div>Owl component with value <t t-esc=\"props.value\"/></div>`;\n     *      const MyWidget = Widget.extend(WidgetAdapterMixin, {\n     *          start() {\n     *              this.component = new ComponentWrapper(this, MyComponent, {value: 44});\n     *              return this.component.mount(this.el);\n     *          },\n     *          update() {\n     *              return this.component.update({value: 45});\n     *          },\n     *      });\n     */\n    const WidgetAdapterMixin = {\n        /**\n         * Calls on_attach_callback on each child ComponentWrapper, which will\n         * call __callMounted on each sub component (recursively), to mark them\n         * as mounted.\n         */\n        on_attach_callback() {\n            for (const component of children.get(this) || []) {\n                component.on_attach_callback();\n            }\n        },\n        /**\n         * Calls on_detach_callback on each child ComponentWrapper, which will\n         * call __callWillUnmount to mark itself and its children as no longer\n         * mounted.\n         */\n        on_detach_callback() {\n            for (const component of children.get(this) || []) {\n                component.on_detach_callback();\n            }\n        },\n        /**\n         * Destroys each sub component when the widget is destroyed. We call the\n         * private __destroy function as there is no need to remove the el from\n         * the DOM (will be removed alongside this widget).\n         */\n        destroy() {\n            for (const component of children.get(this) || []) {\n                component.env.qweb.off(\"update\", component);\n                component.__destroy();\n            }\n            children.delete(this);\n        },\n    };\n    class ComponentWrapper extends Component {\n        /**\n         * Stores the reference of the instance in the parent (in __components).\n         * Also creates a sub environment with a function that will be called\n         * just before events are triggered (see component_extension.js). This\n         * allows to add DOM event listeners on-the-fly, to redirect those Owl\n         * custom (yet DOM) events to legacy custom events (trigger_up).\n         *\n         * @override\n         * @param {Widget|null} parent\n         * @param {Component} Component this is a Class, not an instance\n         * @param {Object} props\n         */\n        constructor(parent, Component, props) {\n            if (parent instanceof Component) {\n                throw new Error('ComponentWrapper must be used with a legacy Widget as parent');\n            }\n            super(null, props);\n            if (parent) {\n                this._register(parent);\n            }\n            useSubEnv({\n                [widgetSymbol]: this._addListener.bind(this)\n            });\n\n            this.parentWidget = parent;\n            this.Component = Component;\n            this.props = props || {};\n            this._handledEvents = new Set(); // Owl events we are redirecting\n\n            this.componentRef = useRef(\"component\");\n        }\n\n        /**\n         * Calls __callMounted on itself and on each sub component (as this\n         * function isn't recursive) when the component is appended into the DOM.\n         */\n        on_attach_callback() {\n            function recursiveCallMounted(component) {\n                const { status, currentFiber } = component.__owl__;\n\n                if (status === 2 && currentFiber && !currentFiber.isCompleted) {\n                    // the component is rendered but another rendering is being done\n                    // it would be foolish to declare the component and children as mounted\n                    return;\n                }\n                if (\n                   status !== 2 /* RENDERED */ &&\n                   status !== 3 /* MOUNTED */ &&\n                   status !== 4 /* UNMOUNTED */\n                ) {\n                    // Avoid calling mounted on a component that is not even\n                    // rendered. Doing otherwise will lead to a crash if a\n                    // specific mounted callback is legitimately relying on the\n                    // component being mounted.\n                    return;\n                }\n                for (const key in component.__owl__.children) {\n                    recursiveCallMounted(component.__owl__.children[key]);\n                }\n                component.__callMounted();\n            }\n            recursiveCallMounted(this);\n        }\n        /**\n         * Calls __callWillUnmount to notify the component it will be unmounted.\n         */\n        on_detach_callback() {\n            this.__callWillUnmount();\n        }\n\n        /**\n         * Overrides to remove the reference to this component in the parent.\n         *\n         * @override\n         */\n        destroy() {\n            if (this.parentWidget) {\n                const parentChildren = children.get(this.parentWidget);\n                if (parentChildren) {\n                    const index = parentChildren.indexOf(this);\n                    children.get(this.parentWidget).splice(index, 1);\n                }\n            }\n            super.destroy();\n        }\n\n        /**\n         * Changes the parent of the wrapper component. This is a function of the\n         * legacy widgets (ParentedMixin), so we have to handle it someway.\n         * It simply removes the reference of this component in the current\n         * parent (if there was one), and adds the reference to the new one.\n         *\n         * We have at least one usecase for this: in views, the renderer is\n         * instantiated without parent, then a controller is instantiated with\n         * the renderer as argument, and finally, setParent is called to set the\n         * controller as parent of the renderer. This implies that Owl renderers\n         * can't trigger events in their constructor.\n         *\n         * @param {Widget} parent\n         */\n        setParent(parent) {\n            if (parent instanceof Component) {\n                throw new Error('ComponentWrapper must be used with a legacy Widget as parent');\n            }\n            this._register(parent);\n            if (this.parentWidget) {\n                const parentChildren = children.get(this.parentWidget);\n                parentChildren.splice(parentChildren.indexOf(this), 1);\n            }\n            this.parentWidget = parent;\n        }\n\n        /**\n         * Updates the props and re-render the component.\n         *\n         * @async\n         * @param {Object} props\n         * @return {Promise}\n         */\n        async update(props = {}) {\n            if (this.__owl__.status === 5 /* destroyed */) {\n                return new Promise(() => {});\n            }\n\n            Object.assign(this.props, props);\n\n            let prom;\n            if (this.__owl__.status === 3 /* mounted */) {\n                prom = this.render();\n            } else {\n                // we may not be in the DOM, but actually want to be redrawn\n                // (e.g. we were detached from the DOM, and now we're going to\n                // be re-attached, but we need to be reloaded first). In this\n                // case, we have to call 'mount' as Owl would skip the rendering\n                // if we simply call render.\n                prom = this.mount(...this._mountArgs);\n            }\n            return prom;\n        }\n\n        /**\n         * Adds an event handler that will redirect the given Owl event to an\n         * Odoo legacy event. This function is called just before the event is\n         * actually triggered.\n         *\n         * @private\n         * @param {string} evType\n         */\n        _addListener(evType) {\n            if (this.parentWidget && !this._handledEvents.has(evType)) {\n                this._handledEvents.add(evType);\n                this.el.addEventListener(evType, ev => {\n                    // as the WrappeComponent has the same root node as the\n                    // actual sub Component, we have to check that the event\n                    // hasn't been stopped by that component (it would naturally\n                    // call stopPropagation, whereas it should actually call\n                    // stopImmediatePropagation to prevent from getting here)\n                    if (!ev.cancelBubble) {\n                        ev.stopPropagation();\n                        const detail = Object.assign({}, ev.detail, {\n                            __originalComponent: ev.originalComponent,\n                        });\n                        this.parentWidget.trigger_up(ev.type.replace(/-/g, '_'), detail);\n                    }\n                });\n            }\n        }\n\n        /**\n         * Registers this instance as a child of the given parent in the\n         * 'children' weakMap.\n         *\n         * @private\n         * @param {Widget} parent\n         */\n        _register(parent) {\n            let parentChildren = children.get(parent);\n            if (!parentChildren) {\n                parentChildren = [];\n                children.set(parent, parentChildren);\n            }\n            parentChildren.push(this);\n        }\n        /**\n         * Stores mount target and position at first mount. That way, when updating\n         * while out of DOM, we know where and how to remount.\n         * @see update()\n         * @override\n         */\n        async mount(target, options) {\n            if (options && options.position === 'self') {\n                throw new Error(\n                    'Unsupported position: \"self\" is not allowed for wrapper components. ' +\n                    'Contact the JS Framework team or open an issue if your use case is relevant.'\n                );\n            }\n            this._mountArgs = arguments;\n            return super.mount(...arguments);\n        }\n\n        //----------------------------------------------------------------------\n        // Getters\n        //----------------------------------------------------------------------\n\n        get $el() {\n            return $(this.el);\n        }\n    }\n    ComponentWrapper.template = xml`<t t-component=\"Component\" t-props=\"props\" t-ref=\"component\"/>`;\n\n    return {\n        ComponentAdapter,\n        ComponentWrapper,\n        WidgetAdapterMixin,\n    };\n});\n", "odoo.define('web.session', function (require) {\n\"use strict\";\n\nvar Session = require('web.Session');\n\nvar session = new Session(undefined, undefined, {use_cors: false});\nsession.is_bound = session.session_bind();\n\nreturn session;\n\n});\n", "odoo.define(\"web.public_env\", function (require) {\n    \"use strict\";\n\n    /**\n     * This file defines the env to use in the public side.\n     */\n\n    const commonEnv = require(\"web.commonEnv\");\n\n    return commonEnv;\n});\n", "/** @odoo-module alias=web.public.root */\n\nimport ajax from 'web.ajax';\nimport dom from 'web.dom';\nimport env from 'web.public_env';\nimport session from 'web.session';\nimport utils from 'web.utils';\nimport publicWidget from 'web.public.widget';\nimport { registry } from '@web/core/registry';\n\nimport AbstractService from \"web.AbstractService\";\nimport legacyEnv from \"web.public_env\";\nimport lazyloader from \"web.public.lazyloader\";\n\nimport {\n    makeLegacyNotificationService,\n    makeLegacyRpcService,\n    makeLegacySessionService,\n    makeLegacyDialogMappingService,\n    mapLegacyEnvToWowlEnv,\n    makeLegacyRainbowManService,\n} from \"../../utils\";\nimport { ComponentAdapter } from \"web.OwlCompatibility\";\n\nimport { loadBundleTemplates } from \"@web/core/assets\";\nimport { makeEnv, startServices } from \"@web/env\";\nimport { MainComponentsContainer } from \"@web/core/main_components_container\";\nimport { browser } from '@web/core/browser/browser';\nimport { jsonrpc } from '@web/core/network/rpc_service';\nconst serviceRegistry = registry.category(\"services\");\n\n// Load localizations outside the PublicRoot to not wait for DOM ready (but\n// wait for them in PublicRoot)\nfunction getLang() {\n    var html = document.documentElement;\n    return (html.getAttribute('lang') || 'en_US').replace('-', '_');\n}\nvar lang = utils.get_cookie('frontend_lang') || getLang(); // FIXME the cookie value should maybe be in the ctx?\n// momentjs don't have config for en_US, so avoid useless RPC\nvar localeDef = lang !== 'en_US' ? ajax.loadJS('/web/webclient/locale/' + lang.replace('-', '_')) : Promise.resolve();\n\n\n/**\n * Element which is designed to be unique and that will be the top-most element\n * in the widget hierarchy. So, all other widgets will be indirectly linked to\n * this Class instance. Its main role will be to retrieve RPC demands from its\n * children and handle them.\n */\nexport const PublicRoot = publicWidget.RootWidget.extend({\n    events: _.extend({}, publicWidget.RootWidget.prototype.events || {}, {\n        'submit .js_website_submit_form': '_onWebsiteFormSubmit',\n        'click .js_disable_on_click': '_onDisableOnClick',\n    }),\n    custom_events: _.extend({}, publicWidget.RootWidget.prototype.custom_events || {}, {\n        call_service: '_onCallService',\n        context_get: '_onContextGet',\n        main_object_request: '_onMainObjectRequest',\n        widgets_start_request: '_onWidgetsStartRequest',\n        widgets_stop_request: '_onWidgetsStopRequest',\n    }),\n\n    /**\n     * @constructor\n     */\n    init: function () {\n        this._super.apply(this, arguments);\n        this.env = env;\n        this.publicWidgets = [];\n    },\n    /**\n     * @override\n     */\n    willStart: function () {\n        // TODO would be even greater to wait for localeDef only when necessary\n        return Promise.all([\n            this._super.apply(this, arguments),\n            session.is_bound,\n            localeDef\n        ]);\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        var defs = [\n            this._super.apply(this, arguments),\n            this._startWidgets()\n        ];\n\n        // Display image thumbnail\n        this.$(\".o_image[data-mimetype^='image']\").each(function () {\n            var $img = $(this);\n            if (/gif|jpe|jpg|png/.test($img.data('mimetype')) && $img.data('src')) {\n                $img.css('background-image', \"url('\" + $img.data('src') + \"')\");\n            }\n        });\n\n        // Auto scroll\n        if (window.location.hash.indexOf(\"scrollTop=\") > -1) {\n            this.el.scrollTop = +window.location.hash.match(/scrollTop=([0-9]+)/)[1];\n        }\n\n        // Fix for IE:\n        if ($.fn.placeholder) {\n            $('input, textarea').placeholder();\n        }\n\n        this.$el.children().on('error.datetimepicker', this._onDateTimePickerError.bind(this));\n\n        return Promise.all(defs);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Retrieves the global context of the public environment. This is the\n     * context which is automatically added to each RPC.\n     *\n     * @private\n     * @param {Object} [context]\n     * @returns {Object}\n     */\n    _getContext: function (context) {\n        return _.extend({\n            'lang': getLang(),\n        }, context || {});\n    },\n    /**\n     * Retrieves the global context of the public environment (as\n     * @see _getContext) but with extra informations that would be useless to\n     * send with each RPC.\n     *\n     * @private\n     * @param {Object} [context]\n     * @returns {Object}\n     */\n    _getExtraContext: function (context) {\n        return this._getContext(context);\n    },\n    /**\n     * @private\n     * @param {Object} [options]\n     * @returns {Object}\n     */\n    _getPublicWidgetsRegistry: function (options) {\n        return publicWidget.registry;\n    },\n    /**\n     * As the root instance is designed to be unique, the associated\n     * registry has been instantiated outside of the class and is simply\n     * returned here.\n     *\n     * @private\n     * @override\n     */\n    _getRegistry: function () {\n        return registry.category(\"public_root_widgets\");\n    },\n    /**\n     * Creates an PublicWidget instance for each DOM element which matches the\n     * `selector` key of one of the registered widgets\n     * (@see PublicWidget.selector).\n     *\n     * @private\n     * @param {jQuery} [$from]\n     *        only initialize the public widgets whose `selector` matches the\n     *        element or one of its descendant (default to the wrapwrap element)\n     * @param {Object} [options]\n     * @returns {Deferred}\n     */\n    _startWidgets: function ($from, options) {\n        var self = this;\n\n        if ($from === undefined) {\n            $from = this.$('#wrapwrap');\n            if (!$from.length) {\n                // TODO Remove this once all frontend layouts possess a\n                // #wrapwrap element (which is necessary for those pages to be\n                // adapted correctly if the user installs website).\n                $from = this.$el;\n            }\n        }\n\n        options = Object.assign({}, options, {\n            wysiwyg: $('#wrapwrap').data('wysiwyg'),\n        });\n\n        this._stopWidgets($from);\n\n        var defs = _.map(this._getPublicWidgetsRegistry(options), function (PublicWidget) {\n            var selector = PublicWidget.prototype.selector || '';\n            var $target = dom.cssFind($from, selector, true);\n\n            var defs = _.map($target, function (el) {\n                var widget = new PublicWidget(self, options);\n                self.publicWidgets.push(widget);\n                return widget.attachTo($(el));\n            });\n            return Promise.all(defs);\n        });\n        return Promise.all(defs);\n    },\n    /**\n     * Destroys all registered widget instances. Website would need this before\n     * saving while in edition mode for example.\n     *\n     * @private\n     * @param {jQuery} [$from]\n     *        only stop the public widgets linked to the given element(s) or one\n     *        of its descendants\n     */\n    _stopWidgets: function ($from) {\n        var removedWidgets = _.map(this.publicWidgets, function (widget) {\n            if (!$from\n                || $from.filter(widget.el).length\n                || $from.find(widget.el).length) {\n                widget.destroy();\n                return widget;\n            }\n            return null;\n        });\n        this.publicWidgets = _.difference(this.publicWidgets, removedWidgets);\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Calls the requested service from the env. Automatically adds the global\n     * context to RPCs.\n     *\n     * @private\n     * @param {OdooEvent} event\n     */\n    _onCallService: function (ev) {\n        function _computeContext(context, noContextKeys) {\n            context = _.extend({}, this._getContext(), context);\n            if (noContextKeys) {\n                context = _.omit(context, noContextKeys);\n            }\n            return JSON.parse(JSON.stringify(context));\n        }\n\n        const payload = ev.data;\n        let args = payload.args || [];\n        if (payload.service === 'ajax' && payload.method === 'rpc') {\n            // ajax service uses an extra 'target' argument for rpc\n            args = args.concat(ev.target);\n\n            var route = args[0];\n            if (_.str.startsWith(route, '/web/dataset/call_kw/')) {\n                var params = args[1];\n                var options = args[2];\n                var noContextKeys;\n                if (options) {\n                    noContextKeys = options.noContextKeys;\n                    args[2] = _.omit(options, 'noContextKeys');\n                }\n                params.kwargs.context = _computeContext.call(this, params.kwargs.context, noContextKeys);\n            }\n        } else if (payload.service === 'ajax' && payload.method === 'loadLibs') {\n            args[1] = _computeContext.call(this, args[1]);\n        } else {\n            return;\n        }\n\n        const service = this.env.services[payload.service];\n        const result = service[payload.method].apply(service, args);\n        payload.callback(result);\n        ev.stopPropagation();\n    },\n    /**\n     * Called when someone asked for the global public context.\n     *\n     * @private\n     * @param {OdooEvent} ev\n     */\n    _onContextGet: function (ev) {\n        if (ev.data.extra) {\n            ev.data.callback(this._getExtraContext(ev.data.context));\n        } else {\n            ev.data.callback(this._getContext(ev.data.context));\n        }\n    },\n    /**\n     * Checks information about the page main object.\n     *\n     * @private\n     * @param {OdooEvent} ev\n     */\n    _onMainObjectRequest: function (ev) {\n        var repr = $('html').data('main-object');\n        var m = repr.match(/(.+)\\((\\d+),(.*)\\)/);\n        ev.data.callback({\n            model: m[1],\n            id: m[2] | 0,\n            viewid: parseInt(document.documentElement.dataset.viewid),\n        });\n    },\n    /**\n     * Called when the root is notified that the public widgets have to be\n     * (re)started.\n     *\n     * @private\n     * @param {OdooEvent} ev\n     */\n    _onWidgetsStartRequest: function (ev) {\n        this._startWidgets(ev.data.$target, ev.data.options)\n            .then(ev.data.onSuccess)\n            .guardedCatch(ev.data.onFailure);\n    },\n    /**\n     * Called when the root is notified that the public widgets have to be\n     * stopped.\n     *\n     * @private\n     * @param {OdooEvent} ev\n     */\n    _onWidgetsStopRequest: function (ev) {\n        this._stopWidgets(ev.data.$target);\n    },\n    /**\n     * @todo review\n     * @private\n     */\n    _onWebsiteFormSubmit: function (ev) {\n        var $buttons = $(ev.currentTarget).find('button[type=\"submit\"], a.a-submit');\n        _.each($buttons, function (btn) {\n            var $btn = $(btn);\n            $btn.prepend('<i class=\"fa fa-circle-o-notch fa-spin\"></i> ');\n            $btn.prop('disabled', true);\n        });\n    },\n    /**\n     * Called when the root is notified that the button should be\n     * disabled after the first click.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    _onDisableOnClick: function (ev) {\n        $(ev.currentTarget).addClass('disabled');\n    },\n    /**\n     * Library clears the wrong date format so just ignore error\n     *\n     * @private\n     * @param {Event} ev\n     */\n    _onDateTimePickerError: function (ev) {\n        return false;\n    },\n});\n\nconst { Component, mount } = owl;\n\n/**\n * Configure Owl with the public env\n */\nowl.config.mode = legacyEnv.isDebug() ? \"dev\" : \"prod\";\nowl.Component.env = legacyEnv;\n\n/**\n * This widget is important, because the tour manager needs a root widget in\n * order to work. The root widget must be a service provider with the ajax\n * service, so that the tour manager can let the server know when tours have\n * been consumed.\n */\nexport async function createPublicRoot(RootWidget) {\n    await lazyloader.allScriptsLoaded;\n    AbstractService.prototype.deployServices(legacyEnv);\n    // add a bunch of mapping services that will redirect service calls from the legacy env\n    // to the wowl env\n    serviceRegistry.add(\"legacy_rpc\", makeLegacyRpcService(legacyEnv));\n    serviceRegistry.add(\"legacy_session\", makeLegacySessionService(legacyEnv, session));\n    serviceRegistry.add(\"legacy_notification\", makeLegacyNotificationService(legacyEnv));\n    serviceRegistry.add(\"legacy_dialog_mapping\", makeLegacyDialogMappingService(legacyEnv));\n    serviceRegistry.add(\"legacy_rainbowman_service\", makeLegacyRainbowManService(legacyEnv));\n    await Promise.all([owl.utils.whenReady(), session.is_bound]);\n\n    // Patch browser.fetch and the rpc service to use the correct base url when\n    // embeded in an external page\n    const baseUrl = session.prefix;\n    const { fetch } = browser;\n    browser.fetch = function(url, ...args) {\n        if (!url.match(/^(?:https?:)?\\/\\//)) {\n            url = baseUrl + url;\n        }\n        return fetch(url, ...args);\n    }\n    serviceRegistry.add(\"rpc\", {\n        async: true,\n        start(env) {\n            let rpcId = 0;\n            return function rpc(route, params = {}, settings) {\n                if (!route.match(/^(?:https?:)?\\/\\//)) {\n                    route = baseUrl + route;\n                }\n                return jsonrpc(env, rpcId++, route, params, settings);\n            };\n        },\n    }, { force: true });\n\n    const wowlEnv = makeEnv();\n    wowlEnv.qweb.addTemplates(await loadBundleTemplates(\"web.assets_frontend\"));\n    await startServices(wowlEnv);\n    mapLegacyEnvToWowlEnv(legacyEnv, wowlEnv);\n\n    const adapter = new ComponentAdapter(null, { Component }); // Used for _trigger_up compat layer\n    const publicRoot = new RootWidget(adapter);\n    await Promise.all([\n        mount(MainComponentsContainer, {\n            target: document.body,\n            env: wowlEnv,\n        }),\n        publicRoot.attachTo(document.body),\n    ]);\n    return publicRoot;\n}\n\nexport default { PublicRoot, createPublicRoot };\n", "/** @odoo-module alias=root.widget */\n\nimport { createPublicRoot } from \"@web/legacy/js/public/public_root\";\nimport { WebsiteRoot } from \"./website_root\";\n\nexport default createPublicRoot(WebsiteRoot);\n", "/** @odoo-module alias=web.public.widget */\n\n/**\n * Provides a way to start JS code for public contents.\n */\n\nimport dom from 'web.dom';\nimport session from 'web.session';\nimport Widget from 'web.Widget';\n\n/**\n * Specialized Widget which automatically instantiates child widgets to attach\n * to internal DOM elements once it is started. The widgets to instantiate are\n * known thanks to a linked registry which contains info about the widget\n * classes and jQuery selectors to use to find the elements to attach them to.\n *\n * @todo Merge with 'PublicWidget' ?\n */\nvar RootWidget = Widget.extend({\n    /**\n     * @constructor\n     */\n    init: function () {\n        this._super.apply(this, arguments);\n        this._widgets = [];\n    },\n    /**\n     * @override\n     * @see _attachComponents\n     */\n    start: function () {\n        var defs = [this._super.apply(this, arguments)];\n\n        defs.push(this._attachComponents());\n        this._getRegistry().on(\"UPDATE\", this, ({ operation, value }) => {\n            if (operation === \"add\") {\n                this._attachComponent(value);\n            }\n        });\n\n        return Promise.all(defs);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Instantiates a child widget according to the given registry data.\n     *\n     * @private\n     * @param {Object} childInfo\n     * @param {function} childInfo.Widget - the widget class to instantiate\n     * @param {string} childInfo.selector\n     *        the jQuery selector to use to find the internal DOM element which\n     *        needs to be attached to the instantiated widget\n     * @param {jQuery} [$from] - only check DOM elements which are descendant of\n     *                         the given one. If not given, use this.$el.\n     * @returns {Deferred}\n     */\n    _attachComponent: function (childInfo, $from) {\n        var self = this;\n        var $elements = dom.cssFind($from || this.$el, childInfo.selector);\n        var defs = _.map($elements, function (element) {\n            var w = new childInfo.Widget(self);\n            self._widgets.push(w);\n            return w.attachTo(element);\n        });\n        return Promise.all(defs);\n    },\n    /**\n     * Instantiates the child widgets that need to be according to the linked\n     * registry.\n     *\n     * @private\n     * @param {jQuery} [$from] - only check DOM elements which are descendant of\n     *                         the given one. If not given, use this.$el.\n     * @returns {Deferred}\n     */\n    _attachComponents: function ($from) {\n        var self = this;\n        var childInfos = this._getRegistry().getAll();\n        var defs = _.map(childInfos, function (childInfo) {\n            return self._attachComponent(childInfo, $from);\n        });\n        return Promise.all(defs);\n    },\n    /**\n     * Returns the `RootWidgetRegistry` instance that is linked to this\n     * `RootWidget` instance.\n     *\n     * @abstract\n     * @private\n     * @returns {RootWidgetRegistry}\n     */\n    _getRegistry: function () {},\n});\n\n//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n\n/**\n * Provides a way for executing code once a website DOM element is loaded in the\n * dom.\n */\nvar PublicWidget = Widget.extend({\n    /**\n     * The selector attribute, if defined, allows to automatically create an\n     * instance of this widget on page load for each DOM element which\n     * matches this selector. The `PublicWidget.$target` element will then be\n     * that particular DOM element. This should be the main way of instantiating\n     * `PublicWidget` elements.\n     *\n     * @todo do not make this part of the Widget but rather an info to give when\n     * registering the widget.\n     */\n    selector: false,\n    /**\n     * Extension of @see Widget.events\n     *\n     * A description of the event handlers to bind/delegate once the widget\n     * has been rendered::\n     *\n     *   'click .hello .world': 'async _onHelloWorldClick',\n     *     _^_      _^_           _^_        _^_\n     *      |        |             |          |\n     *      |  (Optional) jQuery   |  Handler method name\n     *      |  delegate selector   |\n     *      |                      |_ (Optional) space separated options\n     *      |                          * async: use the automatic system\n     *      |_ Event name with           making handlers promise-ready (see\n     *         potential jQuery          makeButtonHandler, makeAsyncHandler)\n     *         namespaces\n     *\n     * Note: the values may be replaced by a function declaration. This is\n     * however a deprecated behavior.\n     *\n     * @type {Object}\n     */\n    events: {},\n\n    /**\n     * @constructor\n     * @param {Object} parent\n     * @param {Object} [options]\n     */\n    init: function (parent, options) {\n        this._super.apply(this, arguments);\n        this.options = options || {};\n    },\n    /**\n     * Destroys the widget and basically restores the target to the state it\n     * was before the start method was called (unlike standard widget, the\n     * associated $el DOM is not removed, if this was instantiated thanks to the\n     * selector property).\n     */\n    destroy: function () {\n        if (this.selector) {\n            var $oldel = this.$el;\n            // The difference with the default behavior is that we unset the\n            // associated element first so that:\n            // 1) its events are unbinded\n            // 2) it is not removed from the DOM\n            this.setElement(null);\n        }\n\n        this._super.apply(this, arguments);\n\n        if (this.selector) {\n            // Reassign the variables afterwards to allow extensions to use them\n            // after calling the _super method\n            this.$el = $oldel;\n            this.el = $oldel[0];\n            this.$target = this.$el;\n            this.target = this.el;\n        }\n    },\n    /**\n     * @override\n     */\n    setElement: function () {\n        this._super.apply(this, arguments);\n        if (this.selector) {\n            this.$target = this.$el;\n            this.target = this.el;\n        }\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @see this.events\n     * @override\n     */\n    _delegateEvents: function () {\n        var self = this;\n        var originalEvents = this.events;\n\n        var events = {};\n        _.each(this.events, function (method, event) {\n            // If the method is a function, use the default Widget system\n            if (typeof method !== 'string') {\n                events[event] = method;\n                return;\n            }\n            // If the method is only a function name without options, use the\n            // default Widget system\n            var methodOptions = method.split(' ');\n            if (methodOptions.length <= 1) {\n                events[event] = method;\n                return;\n            }\n            // If the method has no meaningful options, use the default Widget\n            // system\n            var isAsync = _.contains(methodOptions, 'async');\n            if (!isAsync) {\n                events[event] = method;\n                return;\n            }\n\n            method = self.proxy(methodOptions[methodOptions.length - 1]);\n            if (_.str.startsWith(event, 'click')) {\n                // Protect click handler to be called multiple times by\n                // mistake by the user and add a visual disabling effect\n                // for buttons.\n                method = dom.makeButtonHandler(method);\n            } else {\n                // Protect all handlers to be recalled while the previous\n                // async handler call is not finished.\n                method = dom.makeAsyncHandler(method);\n            }\n            events[event] = method;\n        });\n\n        this.events = events;\n        this._super.apply(this, arguments);\n        this.events = originalEvents;\n    },\n    /**\n     * @private\n     * @param {boolean} [extra=false]\n     * @param {Object} [extraContext]\n     * @returns {Object}\n     */\n    _getContext: function (extra, extraContext) {\n        var context;\n        this.trigger_up('context_get', {\n            extra: extra || false,\n            context: extraContext,\n            callback: function (ctx) {\n                context = ctx;\n            },\n        });\n        return context;\n    },\n});\n\n//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n\n/**\n * The registry object contains the list of widgets that should be instantiated\n * thanks to their selector property if any.\n */\nvar registry = {};\n\n/**\n * This is a fix for apple device (<= IPhone 4, IPad 2)\n * Standard bootstrap requires data-toggle='collapse' element to be <a/> tags.\n * Unfortunatly some layouts use a <div/> tag instead. The fix forces an empty\n * click handler on these div, which allows standard bootstrap to work.\n */\nregistry._fixAppleCollapse = PublicWidget.extend({\n    selector: 'div[data-toggle=\"collapse\"]',\n    events: {\n        'click': function () {},\n    },\n});\n\n// TODO: remove this code in master and put it in its own file.\nregistry.login = PublicWidget.extend({\n    selector: '.oe_login_form',\n    events: {\n        'submit': '_onSubmit',\n    },\n\n    //-------------------------------------------------------------------------\n    // Handlers\n    //-------------------------------------------------------------------------\n\n    /**\n     * Prevents the user from crazy clicking:\n     * Gives the button a loading effect if preventDefault was not already\n     * called and modifies the preventDefault function of the event so that the\n     * loading effect is removed if preventDefault() is called in a following\n     * customization.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    _onSubmit(ev) {\n        if (!ev.isDefaultPrevented()) {\n            const btnEl = ev.currentTarget.querySelector('button[type=\"submit\"]');\n            const removeLoadingEffect = dom.addButtonLoadingEffect(btnEl);\n            const oldPreventDefault = ev.preventDefault.bind(ev);\n            ev.preventDefault = () => {\n                removeLoadingEffect();\n                oldPreventDefault();\n            };\n        }\n    },\n});\n\nexport default {\n    RootWidget: RootWidget,\n    Widget: PublicWidget,\n    registry: registry,\n};\n", "/** @odoo-module **/\n\nimport { registry } from \"@web/core/registry\";\n\n/**\n * @typedef {import(\"../env\").OdooEnv} OdooEnv\n * @typedef {import(\"../core/errors/error_service\").UncaughtPromiseError} UncaughtPromiseError\n */\n\n// -----------------------------------------------------------------------------\n// Legacy Promise error handling\n// -----------------------------------------------------------------------------\n\n/**\n * @param {OdooEnv} env\n * @param {Error} error\n * @param {Error} originalError\n * @returns {boolean}\n */\nfunction legacyRejectPromiseHandler(env, error, originalError) {\n    if (error.name === \"UncaughtPromiseError\") {\n        const isLegitError = originalError && originalError instanceof Error;\n        const isLegacyRPC = originalError && originalError.legacy;\n        if (!isLegitError && !isLegacyRPC) {\n            // we consider that a code throwing something that is not an error is\n            // a case where it is meant as an asynchronous control flow (as legacy\n            // code is sadly doing). For now, we just want to consider this as a non\n            // error, so we prevent default it.\n            error.unhandledRejectionEvent.preventDefault();\n            return true;\n        }\n    }\n    return false;\n}\n\nregistry\n    .category(\"error_handlers\")\n    .add(\"legacyRejectPromiseHandler\", legacyRejectPromiseHandler, { sequence: 1 });\n", "/** @odoo-module **/\n\nimport { registry } from \"@web/core/registry\";\nimport { ConnectionLostError, RPCError } from \"../core/network/rpc_service\";\nimport { lostConnectionHandler, rpcErrorHandler } from \"@web/core/errors/error_handlers\";\n\nconst errorHandlerRegistry = registry.category(\"error_handlers\");\n\n/**\n * @typedef {import(\"../env\").OdooEnv} OdooEnv\n * @typedef {import(\"../core/errors/error_service\").UncaughtError} UncaughError\n */\n\n// -----------------------------------------------------------------------------\n// Legacy RPC error handling\n// -----------------------------------------------------------------------------\n\n/**\n * @param {OdooEnv} env\n * @param {Error} error\n * @param {Error} originalError\n * @returns {boolean}\n */\nfunction legacyRPCErrorHandler(env, error, originalError) {\n    if (\n        originalError &&\n        originalError.legacy &&\n        originalError.message &&\n        (originalError.message instanceof RPCError ||\n            originalError.message instanceof ConnectionLostError)\n    ) {\n        const event = originalError.event;\n        originalError = originalError.message;\n        if (event.isDefaultPrevented()) {\n            // in theory, here, event was already handled\n            error.unhandledRejectionEvent.preventDefault();\n            return true;\n        }\n        event.preventDefault();\n        if (originalError instanceof ConnectionLostError) {\n            return lostConnectionHandler(env, error, originalError);\n        }\n        return rpcErrorHandler(env, error, originalError);\n    }\n    return false;\n}\nerrorHandlerRegistry.add(\"legacyRPCErrorHandler\", legacyRPCErrorHandler, { sequence: 2 });\n", "odoo.define('web.KeyboardNavigationMixin', function (require) {\n    \"use strict\";\n    var BrowserDetection = require('web.BrowserDetection');\n    const core = require('web.core');\n\n    /**\n     * list of the key that should not be used as accesskeys. Either because we want to reserve them for a specific behavior in Odoo or\n     * because they will not work in certain browser/OS\n     */\n    var knownUnusableAccessKeys = [' ',\n        'A', // reserved for Odoo Edit\n        'B', // reserved for Odoo Previous Breadcrumb (Back)\n        'C', // reserved for Odoo Create\n        'H', // reserved for Odoo Home\n        'J', // reserved for Odoo Discard\n        'K', // reserved for Odoo Kanban view\n        'L', // reserved for Odoo List view\n        'N', // reserved for Odoo pager Next\n        'P', // reserved for Odoo pager Previous\n        'S', // reserved for Odoo Save\n        'Q', // reserved for Odoo Search\n        'E', // chrome does not support 'E' access key --> go to address bar to search google\n        'F', // chrome does not support 'F' access key --> go to menu\n        'D', // chrome does not support 'D' access key --> go to address bar\n        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' // reserved for Odoo menus\n    ];\n\n    var KeyboardNavigationMixin = {\n        events: {\n            'keydown': '_onKeyDown',\n            'keyup': '_onKeyUp',\n        },\n\n        /**\n         * @constructor\n         * @param {object} [options]\n         * @param {boolean} [options.autoAccessKeys=true]\n         *      Whether accesskeys should be created automatically for buttons\n         *      without them in the page.\n         * @param {boolean} [options.skipRenderOverlay=false]\n         *      Whether the accesskeys overlay rendering must be skipped.\n         */\n        init: function (options) {\n            this.options = Object.assign({\n                autoAccessKeys: true,\n                skipRenderOverlay: false,\n            }, options);\n            this._areAccessKeyVisible = false;\n            this.BrowserDetection = new BrowserDetection();\n        },\n        /**\n         * @override\n         */\n        start: function () {\n            const temp = this._hideAccessKeyOverlay.bind(this);\n            this._hideAccessKeyOverlay = () => temp();\n            window.addEventListener('blur', this._hideAccessKeyOverlay);\n            core.bus.on('click', null, this._hideAccessKeyOverlay);\n        },\n        /**\n         * @destructor\n         */\n        destroy: function () {\n            window.removeEventListener('blur', this._hideAccessKeyOverlay);\n            core.bus.off('click', null, this._hideAccessKeyOverlay);\n        },\n\n        //--------------------------------------------------------------------------\n        // Private\n        //--------------------------------------------------------------------------\n\n        /**\n         * @private\n         */\n        _addAccessKeyOverlays: function () {\n            if (this.options.skipRenderOverlay) {\n                return;\n            }\n            var accesskeyElements = $(document).find('[accesskey]').filter(':visible');\n            _.each(accesskeyElements, function (elem) {\n                var overlay = $(_.str.sprintf(\"<div class='o_web_accesskey_overlay'>%s</div>\", $(elem).attr('accesskey').toUpperCase()));\n\n                var $overlayParent;\n                if (elem.tagName.toUpperCase() === \"INPUT\") {\n                    // special case for the search input that has an access key\n                    // defined. We cannot set the overlay on the input itself,\n                    // only on its parent.\n                    $overlayParent = $(elem).parent();\n                } else {\n                    $overlayParent = $(elem);\n                }\n\n                if ($overlayParent.css('position') !== 'absolute') {\n                    $overlayParent.css('position', 'relative');\n                }\n                overlay.appendTo($overlayParent);\n            });\n        },\n        /**\n         * @private\n         * @return {jQuery[]}\n         */\n        _getAllUsedAccessKeys: function () {\n            var usedAccessKeys = knownUnusableAccessKeys.slice();\n            this.$el.find('[accesskey]').each(function (_, elem) {\n                usedAccessKeys.push(elem.accessKey.toUpperCase());\n            });\n            return usedAccessKeys;\n        },\n        /**\n         * hides the overlay that shows the access keys.\n         *\n         * @private\n         * @param $parent {jQueryElemen} the parent of the DOM element to which shorcuts overlay have been added\n         * @return {undefined|jQuery}\n         */\n        _hideAccessKeyOverlay: function () {\n            this._areAccessKeyVisible = false;\n            var overlays = this.$el.find('.o_web_accesskey_overlay');\n            if (overlays.length) {\n                return overlays.remove();\n            }\n        },\n        /**\n         * @private\n         */\n        _setAccessKeyOnTopNavigation: function () {\n            this.$el.find('.o_menu_sections>li>a').each(function (number, item) {\n                item.accessKey = number + 1;\n            });\n        },\n\n        //--------------------------------------------------------------------------\n        // Handlers\n        //--------------------------------------------------------------------------\n\n        /**\n         * Assign access keys to all buttons inside $el and sets an overlay to show the access key\n         * The access keys will be assigned using first the name of the button, letter by letter until we find one available,\n         * after that we will assign any available letters.\n         * Not all letters should be used as access keys, some of the should be reserved for standard odoo behavior or browser behavior\n         *\n         * @private\n         * @param keyDownEvent {jQueryKeyboardEvent} the keyboard event triggered\n         * return {undefined|false}\n         */\n        _onKeyDown: function (keyDownEvent) {\n            if ($('body.o_ui_blocked').length &&\n            (keyDownEvent.altKey || keyDownEvent.key === 'Alt') &&\n            !keyDownEvent.ctrlKey) {\n                if (keyDownEvent.preventDefault) keyDownEvent.preventDefault(); else keyDownEvent.returnValue = false;\n                if (keyDownEvent.stopPropagation) keyDownEvent.stopPropagation();\n                if (keyDownEvent.cancelBubble) keyDownEvent.cancelBubble = true;\n                return false;\n            }\n            if (!this._areAccessKeyVisible &&\n                (keyDownEvent.altKey || keyDownEvent.key === 'Alt') &&\n                !keyDownEvent.ctrlKey) {\n\n                this._areAccessKeyVisible = true;\n\n                this._setAccessKeyOnTopNavigation();\n\n                var usedAccessKey = this._getAllUsedAccessKeys();\n\n                if (this.options.autoAccessKeys) {\n                    var buttonsWithoutAccessKey = this.$el.find('button.btn:visible')\n                        .not('[accesskey]')\n                        .not('[disabled]')\n                        .not('[tabindex=\"-1\"]');\n                    _.each(buttonsWithoutAccessKey, function (elem) {\n                        var buttonString = [elem.innerText, elem.title, \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"].join('');\n                        for (var letterIndex = 0; letterIndex < buttonString.length; letterIndex++) {\n                            var candidateAccessKey = buttonString[letterIndex].toUpperCase();\n                            if (candidateAccessKey >= 'A' && candidateAccessKey <= 'Z' &&\n                                !_.includes(usedAccessKey, candidateAccessKey)) {\n                                elem.accessKey = candidateAccessKey;\n                                usedAccessKey.push(candidateAccessKey);\n                                break;\n                            }\n                        }\n                    });\n                }\n\n                var elementsWithoutAriaKeyshortcut = this.$el.find('[accesskey]').not('[aria-keyshortcuts]');\n                _.each(elementsWithoutAriaKeyshortcut, function (elem) {\n                    elem.setAttribute('aria-keyshortcuts', 'Alt+Shift+' + elem.accessKey);\n                });\n                this._addAccessKeyOverlays();\n            }\n            // on mac, there are a number of keys that are only accessible though the usage of\n            // the ALT key (like the @ sign in most keyboards)\n            // for them we do not facilitate the access keys, so they will need to be activated classically\n            // though Control + Alt + key (case sensitive), see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/accesskey\n            if (this.BrowserDetection.isOsMac())\n                return;\n\n            if (keyDownEvent.altKey && !keyDownEvent.ctrlKey && keyDownEvent.key.length === 1) { // we don't want to catch the Alt key down, only the characters A to Z and number keys\n                var elementWithAccessKey = [];\n                if (keyDownEvent.keyCode >= 65 && keyDownEvent.keyCode <= 90 || keyDownEvent.keyCode >= 97 && keyDownEvent.keyCode <= 122) {\n                    // 65 = A, 90 = Z, 97 = a, 122 = z\n                    elementWithAccessKey = document.querySelectorAll('[accesskey=\"' + String.fromCharCode(keyDownEvent.keyCode).toLowerCase() +\n                        '\"], [accesskey=\"' + String.fromCharCode(keyDownEvent.keyCode).toUpperCase() + '\"]');\n                    if (elementWithAccessKey.length) {\n                        if (this.BrowserDetection.isOsMac() ||\n                            !this.BrowserDetection.isBrowserChrome()) { // on windows and linux, chrome does not prevent the default of the accesskeys\n                            elementWithAccessKey[0].focus();\n                            elementWithAccessKey[0].click();\n                            if (keyDownEvent.preventDefault) keyDownEvent.preventDefault(); else keyDownEvent.returnValue = false;\n                            if (keyDownEvent.stopPropagation) keyDownEvent.stopPropagation();\n                            if (keyDownEvent.cancelBubble) keyDownEvent.cancelBubble = true;\n                            return false;\n                        }\n                    }\n                }\n                else {\n                    // identify if the user has tapped on the number keys above the text keys.\n                    // this is not trivial because alt is a modifier and will not input the actual number in most keyboard layouts\n                    var numberKey;\n                    if (keyDownEvent.originalEvent.code && keyDownEvent.originalEvent.code.indexOf('Digit') === 0) {\n                        //chrome & FF have the key Digit set correctly for the numbers\n                        numberKey = keyDownEvent.originalEvent.code[keyDownEvent.originalEvent.code.length - 1];\n                    } else if (keyDownEvent.originalEvent.key &&\n                        keyDownEvent.originalEvent.key.length === 1 &&\n                        keyDownEvent.originalEvent.key >= '0' &&\n                        keyDownEvent.originalEvent.key <= '9') {\n                        //edge does not use 'code' on the original event, but the 'key' is set correctly\n                        numberKey = keyDownEvent.originalEvent.key;\n                    } else if (keyDownEvent.keyCode >= 48 && keyDownEvent.keyCode <= 57) {\n                        //fallback on keyCode if both code and key are either not set or not digits\n                        numberKey = keyDownEvent.keyCode - 48;\n                    }\n\n                    if (numberKey >= '0' && numberKey <= '9') {\n                        elementWithAccessKey = document.querySelectorAll('[accesskey=\"' + numberKey + '\"]');\n                        if (elementWithAccessKey.length) {\n                            elementWithAccessKey[0].click();\n                            if (keyDownEvent.preventDefault) keyDownEvent.preventDefault(); else keyDownEvent.returnValue = false;\n                            if (keyDownEvent.stopPropagation) keyDownEvent.stopPropagation();\n                            if (keyDownEvent.cancelBubble) keyDownEvent.cancelBubble = true;\n                            return false;\n                        }\n                    }\n                }\n            }\n        },\n        /**\n         * hides the shortcut overlays when keyup event is triggered on the ALT key\n         *\n         * @private\n         * @param keyUpEvent {jQueryKeyboardEvent} the keyboard event triggered\n         * @return {undefined|false}\n         */\n        _onKeyUp: function (keyUpEvent) {\n            if ((keyUpEvent.altKey || keyUpEvent.key === 'Alt') && !keyUpEvent.ctrlKey) {\n                if (this.options.skipRenderOverlay) {\n                    return;\n                }\n                this._hideAccessKeyOverlay();\n                if (keyUpEvent.preventDefault) keyUpEvent.preventDefault(); else keyUpEvent.returnValue = false;\n                if (keyUpEvent.stopPropagation) keyUpEvent.stopPropagation();\n                if (keyUpEvent.cancelBubble) keyUpEvent.cancelBubble = true;\n                return false;\n            }\n        },\n    };\n\n    return KeyboardNavigationMixin;\n\n});\n", "odoo.define('bus.Longpolling', function (require) {\n\"use strict\";\n\nvar Bus = require('web.Bus');\nvar ServicesMixin = require('web.ServicesMixin');\n\n\n/**\n * Event Longpolling bus used to bind events on the server long polling return\n *\n * trigger:\n * - window_focus : when the window focus change (true for focused, false for blur)\n * - notification : when a notification is receive from the long polling\n *\n * @class Longpolling\n */\nvar LongpollingBus = Bus.extend(ServicesMixin, {\n    // constants\n    PARTNERS_PRESENCE_CHECK_PERIOD: 30000,  // don't check presence more than once every 30s\n    ERROR_RETRY_DELAY: 10000, // 10 seconds\n    POLL_ROUTE: '/longpolling/poll',\n\n    // properties\n    _isActive: null,\n    _lastNotificationID: 0,\n    _isOdooFocused: true,\n    _pollRetryTimeout: null,\n\n    /**\n     * @override\n     */\n    init: function (parent, params) {\n        this._super.apply(this, arguments);\n        this._id = _.uniqueId('bus');\n\n        // the _id is modified by crosstab_bus, so we can't use it to unbind the events in the destroy.\n        this._longPollingBusId = this._id;\n        this._options = {};\n        this._channels = [];\n\n        // bus presence\n        this._lastPresenceTime = new Date().getTime();\n        $(window).on(\"focus.\" + this._longPollingBusId, this._onFocusChange.bind(this, {focus: true}));\n        $(window).on(\"blur.\" + this._longPollingBusId, this._onFocusChange.bind(this, {focus: false}));\n        $(window).on(\"unload.\" + this._longPollingBusId, this._onFocusChange.bind(this, {focus: false}));\n\n        $(window).on(\"click.\" + this._longPollingBusId, this._onPresence.bind(this));\n        $(window).on(\"keydown.\" + this._longPollingBusId, this._onPresence.bind(this));\n        $(window).on(\"keyup.\" + this._longPollingBusId, this._onPresence.bind(this));\n    },\n    /**\n     * @override\n     */\n    destroy: function () {\n        this.stopPolling();\n        $(window).off(\"focus.\" + this._longPollingBusId);\n        $(window).off(\"blur.\" + this._longPollingBusId);\n        $(window).off(\"unload.\" + this._longPollingBusId);\n        $(window).off(\"click.\" + this._longPollingBusId);\n        $(window).off(\"keydown.\" + this._longPollingBusId);\n        $(window).off(\"keyup.\" + this._longPollingBusId);\n        this._super();\n    },\n    //--------------------------------------------------------------------------\n    // Public\n    //--------------------------------------------------------------------------\n    /**\n     * Register a new channel to listen on the longpoll (ignore if already\n     * listening on this channel).\n     * Aborts a pending longpoll, in order to re-start another longpoll, so\n     * that we can immediately get notifications on newly registered channel.\n     *\n     * @param {string} channel\n     */\n    addChannel: function (channel) {\n        if (this._channels.indexOf(channel) === -1) {\n            this._channels.push(channel);\n            this._restartPolling();\n        }\n    },\n    /**\n     * Unregister a channel from listening on the longpoll.\n     *\n     * Aborts a pending longpoll, in order to re-start another longpoll, so\n     * that we immediately remove ourselves from listening on notifications\n     * on this channel.\n     *\n     * @param {string} channel\n     */\n    deleteChannel: function (channel) {\n        var index = this._channels.indexOf(channel);\n        if (index !== -1) {\n            this._channels.splice(index, 1);\n            if (this._pollRpc) {\n                this._pollRpc.abort();\n            }\n        }\n    },\n    /**\n     * Tell whether odoo is focused or not\n     *\n     * @returns {boolean}\n     */\n    isOdooFocused: function () {\n        return this._isOdooFocused;\n    },\n    /**\n     * Start a long polling, i.e. it continually opens a long poll\n     * connection as long as it is not stopped (@see `stopPolling`)\n     */\n    startPolling: function () {\n        if (this._isActive === null) {\n            this._poll = this._poll.bind(this);\n        }\n        if (!this._isActive) {\n            this._isActive = true;\n            this._poll();\n        }\n    },\n    /**\n     * Stops any started long polling\n     *\n     * Aborts a pending longpoll so that we immediately remove ourselves\n     * from listening on notifications on this channel.\n     */\n    stopPolling: function () {\n        this._isActive = false;\n        this._channels = [];\n        clearTimeout(this._pollRetryTimeout);\n        if (this._pollRpc) {\n            this._pollRpc.abort();\n        }\n    },\n    /**\n     * Add or update an option on the longpoll bus.\n     * Stored options are sent to the server whenever a poll is started.\n     *\n     * @param {string} key\n     * @param {any} value\n     */\n    updateOption: function (key, value) {\n        this._options[key] = value;\n    },\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n    /**\n     * returns the last recorded presence\n     *\n     * @private\n     * @returns {integer} number of milliseconds since 1 January 1970 00:00:00\n     */\n    _getLastPresence: function () {\n        return this._lastPresenceTime;\n    },\n    /**\n     * Continually start a poll:\n     *\n     * A poll is a connection that is kept open for a relatively long period\n     * (up to 1 minute). Local bus data are sent to the server each time a poll\n     * is initiated, and the server may return some \"real-time\" notifications\n     * about registered channels.\n     *\n     * A poll ends on timeout, on abort, on receiving some notifications, or on\n     * receiving an error. Another poll usually starts afterward, except if the\n     * poll is aborted or stopped (@see stopPolling).\n     *\n     * @private\n     */\n    _poll: function () {\n        var self = this;\n        if (!this._isActive) {\n            return;\n        }\n        var now = new Date().getTime();\n        var options = _.extend({}, this._options, {\n            bus_inactivity: now - this._getLastPresence(),\n        });\n        var data = {channels: this._channels, last: this._lastNotificationID, options: options};\n        // The backend has a maximum cycle time of 50 seconds so give +10 seconds\n        this._pollRpc = this._makePoll(data);\n        this._pollRpc.then(function (result) {\n            self._pollRpc = false;\n            self._onPoll(result);\n            self._poll();\n        }).guardedCatch(function (result) {\n            self._pollRpc = false;\n            // no error popup if request is interrupted or fails for any reason\n            result.event.preventDefault();\n            if (result.message === \"XmlHttpRequestError abort\") {\n                self._poll();\n            } else {\n                // random delay to avoid massive longpolling\n                self._pollRetryTimeout = setTimeout(self._poll, self.ERROR_RETRY_DELAY + (Math.floor((Math.random()*20)+1)*1000));\n            }\n        });\n    },\n\n    /**\n     * @private\n     * @param data: object with poll parameters\n     */\n    _makePoll: function(data) {\n        return this._rpc({route: this.POLL_ROUTE, params: data}, {shadow : true, timeout: 60000});\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n    /**\n     * Handler when the focus of the window change.\n     * Trigger the 'window_focus' event.\n     *\n     * @private\n     * @param {Object} params\n     * @param {Boolean} params.focus\n     */\n    _onFocusChange: function (params) {\n        this._isOdooFocused = params.focus;\n        if (params.focus) {\n            this._lastPresenceTime = new Date().getTime();\n            this.trigger('window_focus', this._isOdooFocused);\n        }\n    },\n    /**\n     * Handler when the long polling receive the new notifications\n     * Update the last notification id received.\n     * Triggered the 'notification' event with a list [channel, message] from notifications.\n     *\n     * @private\n     * @param {Object[]} notifications, Input notifications have an id, channel, message\n     * @returns {Array[]} Output arrays have notification's channel and message\n     */\n    _onPoll: function (notifications) {\n        var self = this;\n        var notifs = _.map(notifications, function (notif) {\n            if (notif.id > self._lastNotificationID) {\n                self._lastNotificationID = notif.id;\n            }\n            return notif.message;\n        });\n        this.trigger(\"notification\", notifs);\n        return notifs;\n    },\n    /**\n     * Handler when they are an activity on the window (click, keydown, keyup)\n     * Update the last presence date.\n     *\n     * @private\n     */\n    _onPresence: function () {\n        this._lastPresenceTime = new Date().getTime();\n    },\n    /**\n     * Restart polling.\n     *\n     * @private\n     */\n    _restartPolling() {\n        if (this._pollRpc) {\n            this._pollRpc.abort();\n        } else {\n            this.startPolling();\n        }\n    },\n});\n\nreturn LongpollingBus;\n\n});\n", "odoo.define('bus.CrossTab', function (require) {\n\"use strict\";\n\nvar Longpolling = require('bus.Longpolling');\n\nvar session = require('web.session');\n\n/**\n * CrossTab\n *\n * This is an extension of the longpolling bus with browser cross-tab synchronization.\n * It uses a Master/Slaves with Leader Election architecture:\n * - a single tab handles longpolling.\n * - tabs are synchronized by means of the local storage.\n *\n * localStorage used keys are:\n * - {LOCAL_STORAGE_PREFIX}.{sanitizedOrigin}.channels : shared public channel list to listen during the poll\n * - {LOCAL_STORAGE_PREFIX}.{sanitizedOrigin}.options : shared options\n * - {LOCAL_STORAGE_PREFIX}.{sanitizedOrigin}.notification : the received notifications from the last poll\n * - {LOCAL_STORAGE_PREFIX}.{sanitizedOrigin}.tab_list : list of opened tab ids\n * - {LOCAL_STORAGE_PREFIX}.{sanitizedOrigin}.tab_master : generated id of the master tab\n *\n * trigger:\n * - window_focus : when the window is focused\n * - notification : when a notification is receive from the long polling\n * - become_master : when this tab became the master\n * - no_longer_master : when this tab is not longer the master (the user swith tab)\n */\nvar CrossTabBus = Longpolling.extend({\n    // constants\n    TAB_HEARTBEAT_PERIOD: 10000, // 10 seconds\n    MASTER_TAB_HEARTBEAT_PERIOD: 1500, // 1.5 seconds\n    HEARTBEAT_OUT_OF_DATE_PERIOD: 5000, // 5 seconds\n    HEARTBEAT_KILL_OLD_PERIOD: 15000, // 15 seconds\n    LOCAL_STORAGE_PREFIX: 'bus',\n\n    // properties\n    _isMasterTab: false,\n    _isRegistered: false,\n\n    /**\n     * @override\n     */\n    init: function () {\n        this._super.apply(this, arguments);\n        var now = new Date().getTime();\n        // used to prefix localStorage keys\n        this._sanitizedOrigin = session.origin.replace(/:\\/{0,2}/g, '_');\n        this._currentTabChannels = new Set();\n        // prevents collisions between different tabs and in tests\n        this._id = _.uniqueId(this.LOCAL_STORAGE_PREFIX) + ':' + now;\n        if (this._callLocalStorage('getItem', 'last_ts', 0) + 50000 < now) {\n            this._callLocalStorage('removeItem', 'last');\n        }\n        this._lastNotificationID = this._callLocalStorage('getItem', 'last', 0);\n        this.call('local_storage', 'onStorage', this, this._onStorage);\n    },\n    destroy: function () {\n        this._super();\n        clearTimeout(this._heartbeatTimeout);\n    },\n    //--------------------------------------------------------------------------\n    // Public\n    //--------------------------------------------------------------------------\n    /**\n     * Share the bus channels with the others tab by the local storage\n     *\n     * @override\n     */\n    addChannel: function (channel) {\n        this._currentTabChannels.add(channel);\n        this._super.apply(this, arguments);\n        this._updateChannels();\n    },\n    /**\n     * Share the bus channels with the others tab by the local storage\n     *\n     * @override\n     */\n    deleteChannel: function (channel) {\n        this._currentTabChannels.delete(channel);\n        this._super.apply(this, arguments);\n        this._updateChannels();\n    },\n    /**\n     * @return {string}\n     */\n    getTabId: function () {\n        return this._id;\n    },\n    /**\n     * Tells whether this bus is related to the master tab.\n     *\n     * @returns {boolean}\n     */\n    isMasterTab: function () {\n        return this._isMasterTab;\n    },\n    /**\n     * Use the local storage to share the long polling from the master tab.\n     *\n     * @override\n     */\n    startPolling: function () {\n        if (this._isActive === null) {\n            this._heartbeat = this._heartbeat.bind(this);\n        }\n        if (!this._isRegistered) {\n            this._isRegistered = true;\n\n            var peers = this._callLocalStorage('getItem', 'peers', {});\n            peers[this._id] = new Date().getTime();\n            this._callLocalStorage('setItem', 'peers', peers);\n\n            this._registerWindowUnload();\n\n            if (!this._callLocalStorage('getItem', 'master')) {\n                this._startElection();\n            }\n\n            this._heartbeat();\n\n            if (this._isMasterTab) {\n                this._callLocalStorage('setItem', 'options', this._options);\n            } else {\n                this._options = this._callLocalStorage('getItem', 'options', this._options);\n            }\n            this._updateChannels();\n            return;  // startPolling will be called again on tab registration\n        }\n\n        if (this._isMasterTab) {\n            this._super.apply(this, arguments);\n        }\n    },\n    /**\n     * Share the option with the local storage\n     *\n     * @override\n     */\n    updateOption: function () {\n        this._super.apply(this, arguments);\n        this._callLocalStorage('setItem', 'options', this._options);\n    },\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n    /**\n     * Call local_storage service\n     *\n     * @private\n     * @param {string} method (getItem, setItem, removeItem, on)\n     * @param {string} key\n     * @param {any} param\n     * @returns service information\n     */\n    _callLocalStorage: function (method, key, param) {\n        return this.call('local_storage', method, this._generateKey(key), param);\n    },\n    /**\n     * Generates localStorage keys prefixed by bus. (LOCAL_STORAGE_PREFIX = the name\n     * of this addon), and the sanitized origin, to prevent keys from\n     * conflicting when several bus instances (polling different origins)\n     * co-exist.\n     *\n     * @private\n     * @param {string} key\n     * @returns key prefixed with the origin\n     */\n    _generateKey: function (key) {\n        return this.LOCAL_STORAGE_PREFIX + '.' + this._sanitizedOrigin + '.' + key;\n    },\n    /**\n     * @override\n     * @returns {integer} number of milliseconds since 1 January 1970 00:00:00\n     */\n    _getLastPresence: function () {\n        return this._callLocalStorage('getItem', 'lastPresence') || this._super();\n    },\n    /**\n     * Check all the time (according to the constants) if the tab is the master tab and\n     * check if it is active. Use the local storage for this checks.\n     *\n     * @private\n     * @see _startElection method\n     */\n    _heartbeat: function () {\n        var now = new Date().getTime();\n        var heartbeatValue = parseInt(this._callLocalStorage('getItem', 'heartbeat', 0));\n        var peers = this._callLocalStorage('getItem', 'peers', {});\n\n        if ((heartbeatValue + this.HEARTBEAT_OUT_OF_DATE_PERIOD) < now) {\n            // Heartbeat is out of date. Electing new master\n            this._startElection();\n            heartbeatValue = parseInt(this._callLocalStorage('getItem', 'heartbeat', 0));\n        }\n\n        if (this._isMasterTab) {\n            //walk through all peers and kill old\n            var cleanedPeers = {};\n            for (var peerName in peers) {\n                if (peers[peerName] + this.HEARTBEAT_KILL_OLD_PERIOD > now) {\n                    cleanedPeers[peerName] = peers[peerName];\n                }\n            }\n\n            if (heartbeatValue !== this.lastHeartbeat) {\n                // someone else is also master...\n                // it should not happen, except in some race condition situation.\n                this._isMasterTab = false;\n                this.lastHeartbeat = 0;\n                peers[this._id] = now;\n                this._callLocalStorage('setItem', 'peers', peers);\n                this.stopPolling();\n                this.trigger('no_longer_master');\n            } else {\n                this.lastHeartbeat = now;\n                this._callLocalStorage('setItem', 'heartbeat', now);\n                this._callLocalStorage('setItem', 'peers', cleanedPeers);\n            }\n        } else {\n            //update own heartbeat\n            peers[this._id] = now;\n            this._callLocalStorage('setItem', 'peers', peers);\n        }\n\n        // Write lastPresence in local storage if it has been updated since last heartbeat\n        var hbPeriod = this._isMasterTab ? this.MASTER_TAB_HEARTBEAT_PERIOD : this.TAB_HEARTBEAT_PERIOD;\n        if (this._lastPresenceTime + hbPeriod > now) {\n            this._callLocalStorage('setItem', 'lastPresence', this._lastPresenceTime);\n        }\n\n        this._heartbeatTimeout = setTimeout(this._heartbeat.bind(this), hbPeriod);\n    },\n    /**\n     * @private\n     */\n    _registerWindowUnload: function () {\n        $(window).on('unload.' + this._id, this._onUnload.bind(this));\n    },\n    /**\n     * Check with the local storage if the current tab is the master tab.\n     * If this tab became the master, trigger 'become_master' event\n     *\n     * @private\n     */\n    _startElection: function () {\n        if (this._isMasterTab) {\n            return;\n        }\n        //check who's next\n        var now = new Date().getTime();\n        var peers = this._callLocalStorage('getItem', 'peers', {});\n        var heartbeatKillOld = now - this.HEARTBEAT_KILL_OLD_PERIOD;\n        var newMaster;\n        for (var peerName in peers) {\n            //check for dead peers\n            if (peers[peerName] < heartbeatKillOld) {\n                continue;\n            }\n            newMaster = peerName;\n            break;\n        }\n\n        if (newMaster === this._id) {\n            //we're next in queue. Electing as master\n            this.lastHeartbeat = now;\n            this._callLocalStorage('setItem', 'heartbeat', this.lastHeartbeat);\n            this._callLocalStorage('setItem', 'master', true);\n            this._isMasterTab = true;\n            this.startPolling();\n            this.trigger('become_master');\n\n            //removing master peer from queue\n            delete peers[newMaster];\n            this._callLocalStorage('setItem', 'peers', peers);\n        }\n    },\n    /**\n     * Update localstorage channels of with the channels of this tab.\n     *\n     * @private\n     * @return {boolean} true if the aggregated channels has changed.\n     */\n    _updateChannels: function () {\n        const currentPeerIds = new Set(Object.keys(this._callLocalStorage('getItem', 'peers') || {}));\n        const peerChannels = this._callLocalStorage('getItem', 'channels')  || {};\n        const peerChannelsBefore = JSON.stringify(peerChannels);\n        peerChannels[this._id] = Array.from(this._currentTabChannels);\n\n        // Clean outdated channels.\n        for (const channelPeerId of Object.keys(peerChannels)) {\n            if (!currentPeerIds.has(channelPeerId)) {\n                delete peerChannels[channelPeerId];\n            }\n        }\n\n        const peerChannelsAfter = JSON.stringify(peerChannels);\n        if (peerChannelsBefore !== peerChannelsAfter) {\n            this._callLocalStorage('setItem', 'channels', peerChannels);\n        }\n\n        const allChannels = new Set();\n        for (const channels of Object.values(peerChannels)) {\n            for (const channel of channels) {\n                allChannels.add(channel);\n            }\n        }\n        // Insure the current tab channels are always in the aggregated channels\n        // in case this tab is not in the currentPeerIds nor peerChannels.\n        for (const channel of this._currentTabChannels) {\n            allChannels.add(channel);\n        }\n        const allChannelsSorted = Array.from(allChannels).sort();\n        if (JSON.stringify(allChannelsSorted) === JSON.stringify(this._channels.sort())) {\n            return false;\n        } else {\n            this._channels = allChannelsSorted;\n            return true;\n        };\n    },\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n    /**\n     * @override\n     */\n    _onFocusChange: function (params) {\n        this._super.apply(this, arguments);\n        this._callLocalStorage('setItem', 'focus', params.focus);\n    },\n    /**\n     * If it's the master tab, the notifications ares broadcasted to other tabs by the\n     * local storage.\n     *\n     * @override\n     */\n    _onPoll: function (notifications) {\n        var notifs = this._super(notifications);\n        if (this._isMasterTab && notifs.length) {\n            this._callLocalStorage('setItem', 'last', this._lastNotificationID);\n            this._callLocalStorage('setItem', 'last_ts', new Date().getTime());\n            this._callLocalStorage('setItem', 'notification', notifs);\n        }\n    },\n    /**\n     * Handler when the local storage is updated\n     *\n     * @private\n     * @param {OdooEvent} event\n     * @param {string} event.key\n     * @param {string} event.newValue\n     */\n    _onStorage: function (e) {\n        var value = JSON.parse(e.newValue);\n        var key = e.key;\n\n        if (this._isRegistered && key === this._generateKey('master') && !value) {\n            //master was unloaded\n            this._startElection();\n        }\n\n        // last notification id changed\n        if (key === this._generateKey('last')) {\n            this._lastNotificationID = value || 0;\n        }\n        // notifications changed\n        else if (key === this._generateKey('notification')) {\n            if (!this._isMasterTab) {\n                this.trigger(\"notification\", value);\n            }\n        }\n        // update channels\n        else if (key === this._generateKey('channels')) {\n            if (this._updateChannels()) {\n                this._restartPolling();\n            };\n        }\n        // update options\n        else if (key === this._generateKey('options')) {\n            this._options = value;\n        }\n        // update focus\n        else if (key === this._generateKey('focus')) {\n            this._isOdooFocused = value;\n            this.trigger('window_focus', this._isOdooFocused);\n        }\n    },\n    /**\n     * Handler when unload the window\n     *\n     * @private\n     */\n    _onUnload: function () {\n        // unload peer\n        var peers = this._callLocalStorage('getItem', 'peers') || {};\n        delete peers[this._id];\n        this._callLocalStorage('setItem', 'peers', peers);\n        this._currentTabChannels.clear();\n        this._updateChannels();\n\n        // unload master\n        if (this._isMasterTab) {\n            this._callLocalStorage('removeItem', 'master');\n        }\n    },\n});\n\nreturn CrossTabBus;\n\n});\n\n", "odoo.define('bus.BusService', function (require) {\n\"use strict\";\n\nvar CrossTab = require('bus.CrossTab');\nvar core = require('web.core');\nvar ServicesMixin = require('web.ServicesMixin');\nconst session = require('web.session');\n\nvar BusService =  CrossTab.extend(ServicesMixin, {\n    dependencies : ['local_storage'],\n\n    // properties\n    _audio: null,\n\n    /**\n     * As the BusService doesn't extend AbstractService, we have to replicate\n     * here what is done in AbstractService\n     *\n     * @param {Object} env\n     */\n    init: function (env) {\n        this.env = env;\n        this._super();\n    },\n\n    /**\n     * Replicate the behavior of AbstractService:\n     *\n     * Directly calls the requested service, instead of triggering a\n     * 'call_service' event up, which wouldn't work as services have no parent.\n     *\n     * @param {OdooEvent} ev\n     */\n    _trigger_up: function (ev) {\n        if (ev.name === 'call_service') {\n            const payload = ev.data;\n            let args = payload.args || [];\n            if (payload.service === 'ajax' && payload.method === 'rpc') {\n                // ajax service uses an extra 'target' argument for rpc\n                args = args.concat(ev.target);\n            }\n            const service = this.env.services[payload.service];\n            const result = service[payload.method].apply(service, args);\n            payload.callback(result);\n        }\n    },\n    /**\n     * This method is necessary in order for this Class to be used to instantiate services\n     *\n     * @abstract\n     */\n    start: function () {},\n\n    //--------------------------------------------------------------------------\n    // Public\n    //--------------------------------------------------------------------------\n\n    /**\n     * Send a notification, and notify once per browser's tab\n     *\n     * @param {function} [callback] if given callback will be called when user clicks on notification\n     * @param {Object} options\n     * @param {string} options.message\n     * @param {string} options.title\n     * @param {string} [options.type] 'info', 'success', 'warning', 'danger' or ''\n     */\n    sendNotification(options, callback) {\n        if (window.Notification && Notification.permission === \"granted\") {\n            if (this.isMasterTab()) {\n                try {\n                    this._sendNativeNotification(options.title, options.message, callback);\n                } catch (error) {\n                    // Notification without Serviceworker in Chrome Android doesn't works anymore\n                    // So we fallback to displayNotification() in this case\n                    // https://bugs.chromium.org/p/chromium/issues/detail?id=481856\n                    if (error.message.indexOf('ServiceWorkerRegistration') > -1) {\n                        this.displayNotification(options);\n                        this._beep();\n                    } else {\n                        throw error;\n                    }\n                }\n            }\n        } else {\n            this.displayNotification(options);\n            if (this.isMasterTab()) {\n                this._beep();\n            }\n        }\n    },\n    /**\n     * Register listeners on notifications received on this bus service\n     *\n     * @param {Object} receiver\n     * @param {function} func\n     */\n    onNotification: function () {\n        this.on.apply(this, [\"notification\"].concat(Array.prototype.slice.call(arguments)));\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Lazily play the 'beep' audio on sent notification\n     *\n     * @private\n     */\n    _beep: function () {\n        if (typeof(Audio) !== \"undefined\") {\n            if (!this._audio) {\n                this._audio = new Audio();\n                var ext = this._audio.canPlayType(\"audio/ogg; codecs=vorbis\") ? \".ogg\" : \".mp3\";\n                this._audio.src = session.url(\"/mail/static/src/audio/ting\" + ext);\n            }\n            Promise.resolve(this._audio.play()).catch(_.noop);\n        }\n    },\n    /**\n     * Show a browser notification\n     *\n     * @private\n     * @param {string} title\n     * @param {string} content\n     * @param {function} [callback] if given callback will be called when user clicks on notification\n     */\n    _sendNativeNotification: function (title, content, callback) {\n        var notification = new Notification(\n            // The native Notification API works with plain text and not HTML\n            // unescaping is safe because done only at the **last** step\n            _.unescape(title),\n            {\n                body: _.unescape(content),\n                icon: \"/mail/static/src/img/odoobot_transparent.png\"\n            });\n        notification.onclick = function () {\n            window.focus();\n            if (this.cancel) {\n                this.cancel();\n            } else if (this.close) {\n                this.close();\n            }\n            if (callback) {\n                callback();\n            }\n        };\n    },\n\n});\n\ncore.serviceRegistry.add('bus_service', BusService);\n\nreturn BusService;\n\n});\n", "odoo.define('web_tour.public.TourManager', function (require) {\n'use strict';\n\nvar TourManager = require('web_tour.TourManager');\nvar lazyloader = require('web.public.lazyloader');\n\nTourManager.include({\n    /**\n     * @override\n     */\n    _waitBeforeTourStart: function () {\n        return this._super.apply(this, arguments).then(function () {\n            return lazyloader.allScriptsLoaded;\n        }).then(function () {\n            return new Promise(function (resolve) {\n                setTimeout(resolve);\n            });\n        });\n    },\n});\n});\n", "odoo.define('wysiwyg.widgets.Dialog', function (require) {\n'use strict';\n\nvar config = require('web.config');\nvar core = require('web.core');\nvar Dialog = require('web.Dialog');\n\nvar _t = core._t;\n\n/**\n * Extend Dialog class to handle save/cancel of edition components.\n */\nvar WysiwygDialog = Dialog.extend({\n    /**\n     * @constructor\n     */\n    init: function (parent, options) {\n        this.options = options || {};\n        if (config.device.isMobile) {\n            options.fullscreen = true;\n        }\n        this._super(parent, _.extend({}, {\n            buttons: [{\n                    text: this.options.save_text || _t(\"Save\"),\n                    classes: 'btn-primary',\n                    click: this.save,\n                },\n                {\n                    text: _t(\"Discard\"),\n                    close: true,\n                }\n            ]\n        }, this.options));\n\n        this.destroyAction = 'cancel';\n\n        var self = this;\n        this.opened(function () {\n            const selector = options.focusField\n                ? `input[name=${options.focusField}]` \n                : 'input:visible:first';\n            self.$(selector).focus();\n            self.$el.closest('.modal').addClass('o_web_editor_dialog');\n            self.$el.closest('.modal').on('hidden.bs.modal', self.options.onClose);\n        });\n        this.on('closed', this, function () {\n            self._toggleFullScreen();\n            this.trigger(this.destroyAction, this.final_data || null);\n        });\n    },\n    /**\n     * Only use on config.device.isMobile, it's used by mass mailing to allow the dialog opening on fullscreen\n     * @private\n     */\n    _toggleFullScreen: function() {\n        if (config.device.isMobile && !this.hasFullScreen) {\n            $('#iframe_target[isMobile=\"true\"] #web_editor-top-edit .o_fullscreen').click();\n        }\n    },\n    //--------------------------------------------------------------------------\n    // Public\n    //--------------------------------------------------------------------------\n\n    /**\n     * Called when the dialog is saved. Set the destroy action type to \"save\"\n     * and should set the final_data variable correctly before closing.\n     */\n    save: function () {\n        this.destroyAction = \"save\";\n        this.close();\n    },\n    /**\n     * @override\n     * @returns {*}\n     */\n    open: function() {\n        this.hasFullScreen = $(window.top.document.body).hasClass('o_field_widgetTextHtml_fullscreen');\n        this._toggleFullScreen();\n        return this._super.apply(this, arguments);\n    },\n});\n\nreturn WysiwygDialog;\n});\n", "odoo.define('web_editor.loader', function (require) {\n'use strict';\n\nvar ajax = require('web.ajax');\n\nlet wysiwygPromise;\n\nconst exports = {};\n\nfunction loadWysiwyg(additionnalAssets=[]) {\n    return ajax.loadLibs({assetLibs: ['web_editor.compiled_assets_wysiwyg', ...additionnalAssets]}, undefined, '/web_editor/public_render_template');\n}\nexports.loadWysiwyg = loadWysiwyg;\n\n/**\n * Load the assets and create a wysiwyg.\n *\n * @param {Widget} parent The wysiwyg parent\n * @param {object} options The wysiwyg options\n */\nexports.createWysiwyg = async (parent, options, additionnalAssets = []) => {\n    const wysiwygAlias = options.wysiwygAlias || 'web_editor.wysiwyg';\n    if (!wysiwygPromise) {\n        wysiwygPromise = new Promise(async (resolve) => {\n            await loadWysiwyg(additionnalAssets);\n            // Wait the loading of the service and his dependencies (use string to\n            // avoid parsing of require function).\n            const stringFunction = `return new Promise(resolve => {\n                odoo.define('${wysiwygAlias}.loaded', require => {\n                    ` + 'require' + `('${wysiwygAlias}');\n                    resolve();\n                });\n            });`;\n            await new Function(stringFunction)();\n            resolve();\n        });\n    }\n    await wysiwygPromise;\n    const Wysiwyg = odoo.__DEBUG__.services[wysiwygAlias];\n    return new Wysiwyg(parent, options);\n};\n\nexports.loadFromTextarea = async (parent, textarea, options) => {\n    var loading = textarea.nextElementSibling;\n    if (loading && !loading.classList.contains('o_wysiwyg_loading')) {\n        loading = null;\n    }\n    const $textarea = $(textarea);\n    const currentOptions = Object.assign({}, options);\n    currentOptions.value = currentOptions.value || $textarea.val() || '';\n    if (!currentOptions.value.trim()) {\n        currentOptions.value = '<p><br></p>';\n    }\n    const wysiwyg = await exports.createWysiwyg(parent, currentOptions);\n\n    const $wysiwygWrapper = $textarea.closest('.o_wysiwyg_textarea_wrapper');\n    const $form = $textarea.closest('form');\n\n    // hide and append the $textarea in $form so it's value will be send\n    // through the form.\n    $textarea.hide();\n    $form.append($textarea);\n    $wysiwygWrapper.html('');\n\n    await wysiwyg.appendTo($wysiwygWrapper);\n    $form.find('.note-editable').data('wysiwyg', wysiwyg);\n\n    // o_we_selected_image has not always been removed when\n    // saving a post so we need the line below to remove it if it is present.\n    $form.find('.note-editable').find('img.o_we_selected_image').removeClass('o_we_selected_image');\n    $form.on('click', 'button[type=submit]', (e) => {\n        $form.find('.note-editable').find('img.o_we_selected_image').removeClass('o_we_selected_image');\n        // float-left class messes up the post layout OPW 769721\n        $form.find('.note-editable').find('img.float-left').removeClass('float-left');\n        $textarea.html(wysiwyg.getValue());\n    });\n\n    return wysiwyg;\n};\n\nreturn exports;\n});\n", "odoo.define('auth_signup.signup', function (require) {\n'use strict';\n\nvar publicWidget = require('web.public.widget');\n\npublicWidget.registry.SignUpForm = publicWidget.Widget.extend({\n    selector: '.oe_signup_form',\n    events: {\n        'submit': '_onSubmit',\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onSubmit: function () {\n        var $btn = this.$('.oe_login_buttons > button[type=\"submit\"]');\n        $btn.attr('disabled', 'disabled');\n        $btn.prepend('<i class=\"fa fa-refresh fa-spin\"/> ');\n    },\n});\n});\n", "odoo.define('portal.portal', function (require) {\n'use strict';\n\nvar publicWidget = require('web.public.widget');\nconst Dialog = require('web.Dialog');\nconst {_t, qweb} = require('web.core');\nconst ajax = require('web.ajax');\nconst session = require('web.session');\n\npublicWidget.registry.portalDetails = publicWidget.Widget.extend({\n    selector: '.o_portal_details',\n    events: {\n        'change select[name=\"country_id\"]': '_onCountryChange',\n    },\n\n    /**\n     * @override\n     */\n    start: function () {\n        var def = this._super.apply(this, arguments);\n\n        this.$state = this.$('select[name=\"state_id\"]');\n        this.$stateOptions = this.$state.filter(':enabled').find('option:not(:first)');\n        this._adaptAddressForm();\n\n        return def;\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _adaptAddressForm: function () {\n        var $country = this.$('select[name=\"country_id\"]');\n        var countryID = ($country.val() || 0);\n        this.$stateOptions.detach();\n        var $displayedState = this.$stateOptions.filter('[data-country_id=' + countryID + ']');\n        var nb = $displayedState.appendTo(this.$state).show().length;\n        this.$state.parent().toggle(nb >= 1);\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onCountryChange: function () {\n        this._adaptAddressForm();\n    },\n});\n\npublicWidget.registry.PortalHomeCounters = publicWidget.Widget.extend({\n    selector: '.o_portal_my_home',\n\n    /**\n     * @override\n     */\n    start: function () {\n        var def = this._super.apply(this, arguments);\n        this._updateCounters();\n        return def;\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    async _updateCounters(elem) {\n        const numberRpc = 3;\n        const needed = this.$('[data-placeholder_count]')\n                                .map((i, o) => $(o).data('placeholder_count'))\n                                .toArray();\n        const counterByRpc = Math.ceil(needed.length / numberRpc);  // max counter, last can be less\n\n        const proms = [...Array(Math.min(numberRpc, needed.length)).keys()].map(async i => {\n            await this._rpc({\n                route: \"/my/counters\",\n                params: {\n                    counters: needed.slice(i * counterByRpc, (i + 1) * counterByRpc)\n                },\n            }).then(data => {\n                Object.keys(data).map(k => this.$(\"[data-placeholder_count='\" + k + \"']\").text(data[k]));\n            });\n        });\n        return Promise.all(proms);\n    },\n});\n\npublicWidget.registry.portalSearchPanel = publicWidget.Widget.extend({\n    selector: '.o_portal_search_panel',\n    events: {\n        'click .dropdown-item': '_onDropdownItemClick',\n        'submit': '_onSubmit',\n    },\n\n    /**\n     * @override\n     */\n    start: function () {\n        var def = this._super.apply(this, arguments);\n        this._adaptSearchLabel(this.$('.dropdown-item.active'));\n        return def;\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _adaptSearchLabel: function (elem) {\n        var $label = $(elem).clone();\n        $label.find('span.nolabel').remove();\n        this.$('input[name=\"search\"]').attr('placeholder', $label.text().trim());\n    },\n    /**\n     * @private\n     */\n    _search: function () {\n        var search = $.deparam(window.location.search.substring(1));\n        search['search_in'] = this.$('.dropdown-item.active').attr('href').replace('#', '');\n        search['search'] = this.$('input[name=\"search\"]').val();\n        window.location.search = $.param(search);\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onDropdownItemClick: function (ev) {\n        ev.preventDefault();\n        var $item = $(ev.currentTarget);\n        $item.closest('.dropdown-menu').find('.dropdown-item').removeClass('active');\n        $item.addClass('active');\n\n        this._adaptSearchLabel(ev.currentTarget);\n    },\n    /**\n     * @private\n     */\n    _onSubmit: function (ev) {\n        ev.preventDefault();\n        this._search();\n    },\n});\n\npublicWidget.registry.NewAPIKeyButton = publicWidget.Widget.extend({\n    selector: '.o_portal_new_api_key',\n    events: {\n        click: '_onClick'\n    },\n\n    async _onClick(e){\n        e.preventDefault();\n        // This call is done just so it asks for the password confirmation before starting displaying the\n        // dialog forms, to mimic the behavior from the backend, in which it asks for the password before\n        // displaying the wizard.\n        // The result of the call is unused. But it's required to call a method with the decorator `@check_identity`\n        // in order to use `handleCheckIdentity`.\n        await handleCheckIdentity(this.proxy('_rpc'), this._rpc({\n            model: 'res.users',\n            method: 'api_key_wizard',\n            args: [session.user_id],\n        }));\n        await ajax.loadXML('/portal/static/src/xml/portal_security.xml', qweb);\n        const self = this;\n        const d_description = new Dialog(self, {\n            title: _t('New API Key'),\n            $content: qweb.render('portal.keydescription'),\n            buttons: [{text: _t('Confirm'), classes: 'btn-primary', close: true, click: async () => {\n                var description = d_description.el.querySelector('[name=\"description\"]').value;\n                var wizard_id = await this._rpc({\n                    model: 'res.users.apikeys.description',\n                    method: 'create',\n                    args: [{name: description}],\n                });\n                var res = await handleCheckIdentity(\n                    this.proxy('_rpc'),\n                    this._rpc({\n                        model: 'res.users.apikeys.description',\n                        method: 'make_key',\n                        args: [wizard_id],\n                    })\n                );\n                const d_show = new Dialog(self, {\n                    title: _t('API Key Ready'),\n                    $content: qweb.render('portal.keyshow', {key: res.context.default_key}),\n                    buttons: [{text: _t('Close'), clases: 'btn-primary', close: true}],\n                });\n                d_show.on('closed', this, () => {\n                    window.location = window.location;\n                });\n                d_show.open();\n            }}, {text: _t('Discard'), close: true}],\n        });\n        d_description.opened(() => {\n            const input = d_description.el.querySelector('[name=\"description\"]');\n            input.focus();\n            d_description.el.addEventListener('submit', (e) => {\n                e.preventDefault();\n                d_description.$footer.find('.btn-primary').click();\n            });\n        });\n        d_description.open();\n    }\n});\n\npublicWidget.registry.RemoveAPIKeyButton = publicWidget.Widget.extend({\n    selector: '.o_portal_remove_api_key',\n    events: {\n        click: '_onClick'\n    },\n\n    async _onClick(e){\n        e.preventDefault();\n        await handleCheckIdentity(\n            this.proxy('_rpc'),\n            this._rpc({\n                model: 'res.users.apikeys',\n                method: 'remove',\n                args: [parseInt(this.target.id)]\n            })\n        );\n        window.location = window.location;\n    }\n});\n\n/**\n * Wraps an RPC call in a check for the result being an identity check action\n * descriptor. If no such result is found, just returns the wrapped promise's\n * result as-is; otherwise shows an identity check dialog and resumes the call\n * on success.\n *\n * Warning: does not in and of itself trigger an identity check, a promise which\n * never triggers and identity check internally will do nothing of use.\n *\n * @param {Function} rpc Widget#_rpc bound do the widget\n * @param {Promise} wrapped promise to check for an identity check request\n * @returns {Promise} result of the original call\n */\nfunction handleCheckIdentity(rpc, wrapped) {\n    return wrapped.then((r) => {\n        if (!_.isMatch(r, {type: 'ir.actions.act_window', res_model: 'res.users.identitycheck'})) {\n            return r;\n        }\n        const check_id = r.res_id;\n        return ajax.loadXML('/portal/static/src/xml/portal_security.xml', qweb).then(() => new Promise((resolve, reject) => {\n            const d = new Dialog(null, {\n                title: _t(\"Security Control\"),\n                $content: qweb.render('portal.identitycheck'),\n                buttons: [{\n                    text: _t(\"Confirm Password\"), classes: 'btn btn-primary',\n                    // nb: if click & close, waits for click to resolve before closing\n                    click() {\n                        const password_input = this.el.querySelector('[name=password]');\n                        if (!password_input.reportValidity()) {\n                            password_input.classList.add('is-invalid');\n                            return;\n                        }\n                        return rpc({\n                            model: 'res.users.identitycheck',\n                            method: 'write',\n                            args: [check_id, {password: password_input.value}]\n                        }).then(() => rpc({\n                            model: 'res.users.identitycheck',\n                            method: 'run_check',\n                            args: [check_id]\n                        })).then((r) => {\n                            this.close();\n                            resolve(r);\n                        }, (err) => {\n                            err.event.preventDefault(); // suppress crashmanager\n                            password_input.classList.add('is-invalid');\n                            password_input.setCustomValidity(_t(\"Check failed\"));\n                            password_input.reportValidity();\n                        });\n                    }\n                }, {\n                    text: _t('Cancel'), close: true\n                }]\n            }).on('close', null, () => {\n                // unlink wizard object?\n                reject();\n            });\n            d.opened(() => {\n                const pw = d.el.querySelector('[name=\"password\"]');\n                pw.focus();\n                pw.addEventListener('input', () => {\n                    pw.classList.remove('is-invalid');\n                    pw.setCustomValidity('');\n                });\n                d.el.addEventListener('submit', (e) => {\n                    e.preventDefault();\n                    d.$footer.find('.btn-primary').click();\n                });\n            });\n            d.open();\n        }));\n    });\n}\nreturn {\n    handleCheckIdentity,\n}\n});\n", "odoo.define('portal.chatter', function (require) {\n'use strict';\n\nvar core = require('web.core');\nconst dom = require('web.dom');\nvar publicWidget = require('web.public.widget');\nvar time = require('web.time');\nvar portalComposer = require('portal.composer');\nconst {Markup} = require('web.utils');\n\nvar qweb = core.qweb;\nvar _t = core._t;\n\n/**\n * Widget PortalChatter\n *\n * - Fetch message fron controller\n * - Display chatter: pager, total message, composer (according to access right)\n * - Provider API to filter displayed messages\n */\nvar PortalChatter = publicWidget.Widget.extend({\n    template: 'portal.Chatter',\n    xmlDependencies: ['/portal/static/src/xml/portal_chatter.xml'],\n    events: {\n        'click .o_portal_chatter_pager_btn': '_onClickPager',\n        'click .o_portal_chatter_js_is_internal': 'async _onClickUpdateIsInternal',\n    },\n\n    /**\n     * @constructor\n     */\n    init: function (parent, options) {\n        var self = this;\n        this.options = {};\n        this._super.apply(this, arguments);\n\n        this._setOptions(options);\n\n        this.set('messages', []);\n        this.set('message_count', this.options['message_count']);\n        this.set('pager', {});\n        this.set('domain', this.options['domain']);\n        this._currentPage = this.options['pager_start'];\n    },\n    /**\n     * @override\n     */\n    willStart: function () {\n        return Promise.all([\n            this._super.apply(this, arguments),\n            this._chatterInit()\n        ]);\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        // bind events\n        this.on(\"change:messages\", this, this._renderMessages);\n        this.on(\"change:message_count\", this, function () {\n            this._renderMessageCount();\n            this.set('pager', this._pager(this._currentPage));\n        });\n        this.on(\"change:pager\", this, this._renderPager);\n        this.on(\"change:domain\", this, this._onChangeDomain);\n        // set options and parameters\n        this.set('message_count', this.options['message_count']);\n        this.set('messages', this.preprocessMessages(this.result['messages']));\n        // bind bus event: this (portal.chatter) and 'portal.rating.composer' in portal_rating\n        // are separate and sibling widgets, this event is to be triggered from portal.rating.composer,\n        // hence bus event is bound to achieve usage of the event in another widget.\n        core.bus.on('reload_chatter_content', this, this._reloadChatterContent);\n\n        return Promise.all([this._super.apply(this, arguments), this._reloadComposer()]);\n    },\n\n    //--------------------------------------------------------------------------\n    // Public\n    //--------------------------------------------------------------------------\n\n    /**\n     * Fetch the messages and the message count from the server for the\n     * current page and current domain.\n     *\n     * @param {Array} domain\n     * @returns {Promise}\n     */\n    messageFetch: function (domain) {\n        var self = this;\n        return this._rpc({\n            route: '/mail/chatter_fetch',\n            params: self._messageFetchPrepareParams(),\n        }).then(function (result) {\n            self.set('messages', self.preprocessMessages(result['messages']));\n            self.set('message_count', result['message_count']);\n            return result;\n        });\n    },\n    /**\n     * Update the messages format\n     *\n     * @param {Array<Object>} messages\n     * @returns {Array}\n     */\n    preprocessMessages(messages) {\n        _.each(messages, function (m) {\n            m['author_avatar_url'] = _.str.sprintf('/web/image/%s/%s/author_avatar/50x50', 'mail.message', m.id);\n            m['published_date_str'] = _.str.sprintf(_t('Published on %s'), moment(time.str_to_datetime(m.date)).format('MMMM Do YYYY, h:mm:ss a'));\n            m['body'] = Markup(m.body);\n        });\n        return messages;\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Set options\n     *\n     * @param {Array<string>} options: new options to set\n     */\n    _setOptions: function (options) {\n        // underscorize the camelcased option keys\n        const defaultOptions = Object.assign({\n            'allow_composer': true,\n            'display_composer': false,\n            'csrf_token': odoo.csrf_token,\n            'message_count': 0,\n            'pager_step': 10,\n            'pager_scope': 5,\n            'pager_start': 1,\n            'is_user_public': true,\n            'is_user_employee': false,\n            'is_user_publisher': false,\n            'hash': false,\n            'pid': false,\n            'domain': [],\n            'two_columns': false,\n        }, this.options || {});\n\n        this.options = Object.entries(options).reduce(\n            (acc, [key, value]) => {\n                acc[_.str.underscored(key)] = value;\n                return acc;\n            },\n            defaultOptions);\n    },\n\n    /**\n     * Reloads chatter and message count after posting message\n     *\n     * @private\n     */\n    _reloadChatterContent: function (data) {\n        this.messageFetch();\n        this._reloadComposer();\n    },\n    _createComposerWidget: function () {\n        return new portalComposer.PortalComposer(this, this.options);\n    },\n    /**\n     * Destroy current composer widget and initialize and insert new widget\n     *\n     * @private\n     */\n    _reloadComposer: async function () {\n        if (this._composer) {\n            this._composer.destroy();\n        }\n        if (this.options.display_composer) {\n            this._composer = this._createComposerWidget();\n            await this._composer.appendTo(this.$('.o_portal_chatter_composer'));\n        }\n    },\n    /**\n     * @private\n     * @returns {Deferred}\n     */\n    _chatterInit: function () {\n        var self = this;\n        return this._rpc({\n            route: '/mail/chatter_init',\n            params: this._messageFetchPrepareParams()\n        }).then(function (result) {\n            self.result = result;\n            self.options = _.extend(self.options, self.result['options'] || {});\n            return result;\n        });\n    },\n    /**\n     * Change the current page by refreshing current domain\n     *\n     * @private\n     * @param {Number} page\n     * @param {Array} domain\n     */\n    _changeCurrentPage: function (page, domain) {\n        this._currentPage = page;\n        var d = domain ? domain : _.clone(this.get('domain'));\n        this.set('domain', d); // trigger fetch message\n    },\n    _messageFetchPrepareParams: function () {\n        var self = this;\n        var data = {\n            'res_model': this.options['res_model'],\n            'res_id': this.options['res_id'],\n            'limit': this.options['pager_step'],\n            'offset': (this._currentPage - 1) * this.options['pager_step'],\n            'allow_composer': this.options['allow_composer'],\n        };\n        // add token field to allow to post comment without being logged\n        if (self.options['token']) {\n            data['token'] = self.options['token'];\n        }\n        // add domain\n        if (this.get('domain')) {\n            data['domain'] = this.get('domain');\n        }\n        return data;\n    },\n    /**\n     * Generate the pager data for the given page number\n     *\n     * @private\n     * @param {Number} page\n     * @returns {Object}\n     */\n    _pager: function (page) {\n        page = page || 1;\n        var total = this.get('message_count');\n        var scope = this.options['pager_scope'];\n        var step = this.options['pager_step'];\n\n        // Compute Pager\n        var pageCount = Math.ceil(parseFloat(total) / step);\n\n        page = Math.max(1, Math.min(parseInt(page), pageCount));\n        scope -= 1;\n\n        var pmin = Math.max(page - parseInt(Math.floor(scope / 2)), 1);\n        var pmax = Math.min(pmin + scope, pageCount);\n\n        if (pmax - scope > 0) {\n            pmin = pmax - scope;\n        } else {\n            pmin = 1;\n        }\n\n        var pages = [];\n        _.each(_.range(pmin, pmax + 1), function (index) {\n            pages.push(index);\n        });\n\n        return {\n            \"page_count\": pageCount,\n            \"offset\": (page - 1) * step,\n            \"page\": page,\n            \"page_start\": pmin,\n            \"page_previous\": Math.max(pmin, page - 1),\n            \"page_next\": Math.min(pmax, page + 1),\n            \"page_end\": pmax,\n            \"pages\": pages\n        };\n    },\n    _renderMessages: function () {\n        this.$('.o_portal_chatter_messages').html(qweb.render(\"portal.chatter_messages\", {widget: this}));\n    },\n    _renderMessageCount: function () {\n        this.$('.o_message_counter').replaceWith(qweb.render(\"portal.chatter_message_count\", {widget: this}));\n    },\n    _renderPager: function () {\n        this.$('.o_portal_chatter_pager').replaceWith(qweb.render(\"portal.pager\", {widget: this}));\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    _onChangeDomain: function () {\n        var self = this;\n        this.messageFetch().then(function () {\n            var p = self._currentPage;\n            self.set('pager', self._pager(p));\n        });\n    },\n    /**\n     * @private\n     * @param {MouseEvent} event\n     */\n    _onClickPager: function (ev) {\n        ev.preventDefault();\n        var page = $(ev.currentTarget).data('page');\n        this._changeCurrentPage(page);\n    },\n\n    /**\n     * Toggle is_internal state of message. Update both node data and\n     * classes to ensure DOM is updated accordingly to RPC call result.\n     * @private\n     * @returns {Promise}\n     */\n    _onClickUpdateIsInternal: function (ev) {\n        ev.preventDefault();\n\n        var $elem = $(ev.currentTarget);\n        return this._rpc({\n            route: '/mail/update_is_internal',\n            params: {\n                message_id: $elem.data('message-id'),\n                is_internal: ! $elem.data('is-internal'),\n            },\n        }).then(function (result) {\n            $elem.data('is-internal', result);\n            if (result === true) {\n                $elem.addClass('o_portal_message_internal_on');\n                $elem.removeClass('o_portal_message_internal_off');\n            } else {\n                $elem.addClass('o_portal_message_internal_off');\n                $elem.removeClass('o_portal_message_internal_on');\n            }\n        });\n    },\n});\n\npublicWidget.registry.portalChatter = publicWidget.Widget.extend({\n    selector: '.o_portal_chatter',\n\n    /**\n     * @override\n     */\n    async start() {\n        const proms = [this._super.apply(this, arguments)];\n        const chatter = new PortalChatter(this, this.$el.data());\n        proms.push(chatter.appendTo(this.$el));\n        await Promise.all(proms);\n        // scroll to the right place after chatter loaded\n        if (window.location.hash === `#${this.el.id}`) {\n            dom.scrollTo(this.el, {duration: 0});\n        }\n    },\n});\n\nreturn {\n    PortalChatter: PortalChatter,\n};\n});\n", "odoo.define('portal.composer', function (require) {\n'use strict';\n\nvar ajax = require('web.ajax');\nvar core = require('web.core');\nvar publicWidget = require('web.public.widget');\n\nvar qweb = core.qweb;\nvar _t = core._t;\n\n/**\n * Widget PortalComposer\n *\n * Display the composer (according to access right)\n *\n */\nvar PortalComposer = publicWidget.Widget.extend({\n    template: 'portal.Composer',\n    xmlDependencies: ['/portal/static/src/xml/portal_chatter.xml'],\n    events: {\n        'change .o_portal_chatter_file_input': '_onFileInputChange',\n        'click .o_portal_chatter_attachment_btn': '_onAttachmentButtonClick',\n        'click .o_portal_chatter_attachment_delete': 'async _onAttachmentDeleteClick',\n        'click .o_portal_chatter_composer_btn': 'async _onSubmitButtonClick',\n    },\n\n    /**\n     * @constructor\n     */\n    init: function (parent, options) {\n        this._super.apply(this, arguments);\n        this.options = _.defaults(options || {}, {\n            'allow_composer': true,\n            'display_composer': false,\n            'csrf_token': odoo.csrf_token,\n            'token': false,\n            'res_model': false,\n            'res_id': false,\n        });\n        this.attachments = [];\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        var self = this;\n        this.$attachmentButton = this.$('.o_portal_chatter_attachment_btn');\n        this.$fileInput = this.$('.o_portal_chatter_file_input');\n        this.$sendButton = this.$('.o_portal_chatter_composer_btn');\n        this.$attachments = this.$('.o_portal_chatter_composer_input .o_portal_chatter_attachments');\n        this.$inputTextarea = this.$('.o_portal_chatter_composer_input textarea[name=\"message\"]');\n\n        return this._super.apply(this, arguments).then(function () {\n            if (self.options.default_attachment_ids) {\n                self.attachments = self.options.default_attachment_ids || [];\n                _.each(self.attachments, function(attachment) {\n                    attachment.state = 'done';\n                });\n                self._updateAttachments();\n            }\n            return Promise.resolve();\n        });\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onAttachmentButtonClick: function () {\n        this.$fileInput.click();\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     * @returns {Promise}\n     */\n    _onAttachmentDeleteClick: function (ev) {\n        var self = this;\n        var attachmentId = $(ev.currentTarget).closest('.o_portal_chatter_attachment').data('id');\n        var accessToken = _.find(this.attachments, {'id': attachmentId}).access_token;\n        ev.preventDefault();\n        ev.stopPropagation();\n\n        this.$sendButton.prop('disabled', true);\n\n        return this._rpc({\n            route: '/portal/attachment/remove',\n            params: {\n                'attachment_id': attachmentId,\n                'access_token': accessToken,\n            },\n        }).then(function () {\n            self.attachments = _.reject(self.attachments, {'id': attachmentId});\n            self._updateAttachments();\n            self.$sendButton.prop('disabled', false);\n        });\n    },\n    _prepareAttachmentData: function (file) {\n        return {\n            'name': file.name,\n            'file': file,\n            'res_id': this.options.res_id,\n            'res_model': this.options.res_model,\n            'access_token': this.options.token,\n        };\n    },\n    /**\n     * @private\n     * @returns {Promise}\n     */\n    _onFileInputChange: function () {\n        var self = this;\n\n        this.$sendButton.prop('disabled', true);\n\n        return Promise.all(_.map(this.$fileInput[0].files, function (file) {\n            return new Promise(function (resolve, reject) {\n                var data = self._prepareAttachmentData(file);\n                ajax.post('/portal/attachment/add', data).then(function (attachment) {\n                    attachment.state = 'pending';\n                    self.attachments.push(attachment);\n                    self._updateAttachments();\n                    resolve();\n                }).guardedCatch(function (error) {\n                    self.displayNotification({\n                        message: _.str.sprintf(_t(\"Could not save file <strong>%s</strong>\"),\n                            _.escape(file.name)),\n                        type: 'warning',\n                        sticky: true,\n                    });\n                    resolve();\n                });\n            });\n        })).then(function () {\n            // ensures any selection triggers a change, even if the same files are selected again\n            self.$fileInput[0].value = null;\n            self.$sendButton.prop('disabled', false);\n        });\n    },\n    /**\n     * prepares data to send message\n     *\n     * @private\n     */\n    _prepareMessageData: function () {\n        return Object.assign(this.options || {}, {\n            'message': this.$('textarea[name=\"message\"]').val(),\n            'attachment_ids': _.pluck(this.attachments, 'id'),\n            'attachment_tokens': _.pluck(this.attachments, 'access_token'),\n        });\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onSubmitButtonClick: function (ev) {\n        ev.preventDefault();\n        if (!this.$inputTextarea.val().trim() && !this.attachments.length) {\n            this.$inputTextarea.addClass('border-danger');\n            const error = _t('Some fields are required. Please make sure to write a message or attach a document');\n            this.$(\".o_portal_chatter_composer_error\").text(error).removeClass('d-none');\n            return Promise.reject();\n        } else {\n            return this._chatterPostMessage(ev.currentTarget.getAttribute('data-action'));\n        }\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _updateAttachments: function () {\n        this.$attachments.html(qweb.render('portal.Chatter.Attachments', {\n            attachments: this.attachments,\n            showDelete: true,\n        }));\n    },\n    /**\n     * post message using rpc call and display new message and message count\n     *\n     * @private\n     * @param {String} route\n     * @returns {Promise}\n     */\n    _chatterPostMessage: async function (route) {\n        const result = await this._rpc({\n            route: route,\n            params: this._prepareMessageData(),\n        });\n        core.bus.trigger('reload_chatter_content', result);\n        return result;\n    },\n});\n\nreturn {\n    PortalComposer: PortalComposer,\n};\n});\n", "odoo.define('portal.signature_form', function (require) {\n'use strict';\n\nvar core = require('web.core');\nvar publicWidget = require('web.public.widget');\nvar NameAndSignature = require('web.name_and_signature').NameAndSignature;\nvar qweb = core.qweb;\n\nvar _t = core._t;\n\n/**\n * This widget is a signature request form. It uses\n * @see NameAndSignature for the input fields, adds a submit\n * button, and handles the RPC to save the result.\n */\nvar SignatureForm = publicWidget.Widget.extend({\n    template: 'portal.portal_signature',\n    xmlDependencies: ['/portal/static/src/xml/portal_signature.xml'],\n    events: {\n        'click .o_portal_sign_submit': 'async _onClickSignSubmit',\n    },\n    custom_events: {\n        'signature_changed': '_onChangeSignature',\n    },\n\n    /**\n     * Overridden to allow options.\n     *\n     * @constructor\n     * @param {Widget} parent\n     * @param {Object} options\n     * @param {string} options.callUrl - make RPC to this url\n     * @param {string} [options.sendLabel='Accept & Sign'] - label of the\n     *  send button\n     * @param {Object} [options.rpcParams={}] - params for the RPC\n     * @param {Object} [options.nameAndSignatureOptions={}] - options for\n     *  @see NameAndSignature.init()\n     */\n    init: function (parent, options) {\n        this._super.apply(this, arguments);\n\n        this.csrf_token = odoo.csrf_token;\n\n        this.callUrl = options.callUrl || '';\n        this.rpcParams = options.rpcParams || {};\n        this.sendLabel = options.sendLabel || _t(\"Accept & Sign\");\n\n        this.nameAndSignature = new NameAndSignature(this,\n            options.nameAndSignatureOptions || {});\n    },\n    /**\n     * Overridden to get the DOM elements\n     * and to insert the name and signature.\n     *\n     * @override\n     */\n    start: function () {\n        var self = this;\n        this.$confirm_btn = this.$('.o_portal_sign_submit');\n        this.$controls = this.$('.o_portal_sign_controls');\n        var subWidgetStart = this.nameAndSignature.replace(this.$('.o_web_sign_name_and_signature'));\n        return Promise.all([subWidgetStart, this._super.apply(this, arguments)]).then(function () {\n            self.nameAndSignature.resetSignature();\n        });\n    },\n\n    //----------------------------------------------------------------------\n    // Public\n    //----------------------------------------------------------------------\n\n    /**\n     * Focuses the name.\n     *\n     * @see NameAndSignature.focusName();\n     */\n    focusName: function () {\n        this.nameAndSignature.focusName();\n    },\n    /**\n     * Resets the signature.\n     *\n     * @see NameAndSignature.resetSignature();\n     */\n    resetSignature: function () {\n        return this.nameAndSignature.resetSignature();\n    },\n\n    //----------------------------------------------------------------------\n    // Handlers\n    //----------------------------------------------------------------------\n\n    /**\n     * Handles click on the submit button.\n     *\n     * This will get the current name and signature and validate them.\n     * If they are valid, they are sent to the server, and the reponse is\n     * handled. If they are invalid, it will display the errors to the user.\n     *\n     * @private\n     * @param {Event} ev\n     * @returns {Deferred}\n     */\n    _onClickSignSubmit: function (ev) {\n        var self = this;\n        ev.preventDefault();\n\n        if (!this.nameAndSignature.validateSignature()) {\n            return;\n        }\n\n        var name = this.nameAndSignature.getName();\n        var signature = this.nameAndSignature.getSignatureImage()[1];\n\n        return this._rpc({\n            route: this.callUrl,\n            params: _.extend(this.rpcParams, {\n                'name': name,\n                'signature': signature,\n            }),\n        }).then(function (data) {\n            if (data.error) {\n                self.$('.o_portal_sign_error_msg').remove();\n                self.$controls.prepend(qweb.render('portal.portal_signature_error', {widget: data}));\n            } else if (data.success) {\n                var $success = qweb.render('portal.portal_signature_success', {widget: data});\n                self.$el.empty().append($success);\n            }\n            if (data.force_refresh) {\n                if (data.redirect_url) {\n                    window.location = data.redirect_url;\n                } else {\n                    window.location.reload();\n                }\n                // no resolve if we reload the page\n                return new Promise(function () { });\n            }\n        });\n    },\n    /**\n     * Toggles the submit button depending on the signature state.\n     *\n     * @private\n     */\n    _onChangeSignature: function () {\n        var isEmpty = this.nameAndSignature.isSignatureEmpty();\n        this.$confirm_btn.prop('disabled', isEmpty);\n    },\n});\n\npublicWidget.registry.SignatureForm = publicWidget.Widget.extend({\n    selector: '.o_portal_signature_form',\n\n    /**\n     * @private\n     */\n    start: function () {\n        var hasBeenReset = false;\n\n        var callUrl = this.$el.data('call-url');\n        var nameAndSignatureOptions = {\n            defaultName: this.$el.data('default-name'),\n            mode: this.$el.data('mode'),\n            displaySignatureRatio: this.$el.data('signature-ratio'),\n            signatureType: this.$el.data('signature-type'),\n            fontColor: this.$el.data('font-color')  || 'black',\n        };\n        var sendLabel = this.$el.data('send-label');\n\n        var form = new SignatureForm(this, {\n            callUrl: callUrl,\n            nameAndSignatureOptions: nameAndSignatureOptions,\n            sendLabel: sendLabel,\n        });\n\n        // Correctly set up the signature area if it is inside a modal\n        this.$el.closest('.modal').on('shown.bs.modal', function (ev) {\n            if (!hasBeenReset) {\n                // Reset it only the first time it is open to get correct\n                // size. After we want to keep its content on reopen.\n                hasBeenReset = true;\n                form.resetSignature();\n            } else {\n                form.focusName();\n            }\n        });\n\n        return Promise.all([\n            this._super.apply(this, arguments),\n            form.appendTo(this.$el)\n        ]);\n    },\n});\n\nreturn {\n    SignatureForm: SignatureForm,\n};\n});\n", "odoo.define('portal.PortalSidebar', function (require) {\n'use strict';\n\nvar core = require('web.core');\nvar publicWidget = require('web.public.widget');\nvar time = require('web.time');\nvar session = require('web.session');\n\nvar _t = core._t;\n\nvar PortalSidebar = publicWidget.Widget.extend({\n    /**\n     * @override\n     */\n    start: function () {\n        this._setDelayLabel();\n        return this._super.apply(this, arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //---------------------------------------------------------------------------\n\n    /**\n     * Set the due/delay information according to the given date\n     * like : <span class=\"o_portal_sidebar_timeago\" t-att-datetime=\"invoice.date_due\"/>\n     *\n     * @private\n     */\n    _setDelayLabel: function () {\n        var $sidebarTimeago = this.$el.find('.o_portal_sidebar_timeago');\n        _.each($sidebarTimeago, function (el) {\n            var dateTime = moment(time.auto_str_to_date($(el).attr('datetime'))),\n                today = moment().startOf('day'),\n                diff = dateTime.diff(today, 'days', true),\n                displayStr;\n\n            session.is_bound.then(function (){\n                if (diff === 0) {\n                    displayStr = _t('Due today');\n                } else if (diff > 0) {\n                    // Workaround: force uniqueness of these two translations. We use %1d because the string\n                    // with %d is already used in mail and mail's translations are not sent to the frontend.\n                    displayStr = _.str.sprintf(_t('Due in %1d days'), Math.abs(diff));\n                } else {\n                    displayStr = _.str.sprintf(_t('%1d days overdue'), Math.abs(diff));\n                }\n                $(el).text(displayStr);\n            });\n        });\n    },\n    /**\n     * @private\n     * @param {string} href\n     */\n    _printIframeContent: function (href) {\n        // due to this issue : https://bugzilla.mozilla.org/show_bug.cgi?id=911444\n        // open a new window with pdf for print in Firefox (in other system: http://printjs.crabbly.com)\n        if ($.browser.mozilla) {\n            window.open(href, '_blank');\n            return;\n        }\n        if (!this.printContent) {\n            this.printContent = $('<iframe id=\"print_iframe_content\" src=\"' + href + '\" style=\"display:none\"></iframe>');\n            this.$el.append(this.printContent);\n            this.printContent.on('load', function () {\n                $(this).get(0).contentWindow.print();\n            });\n        } else {\n            this.printContent.get(0).contentWindow.print();\n        }\n    },\n});\nreturn PortalSidebar;\n});\n", "odoo.define('account.AccountPortalSidebar', function (require) {\n'use strict';\n\nconst dom = require('web.dom');\nvar publicWidget = require('web.public.widget');\nvar PortalSidebar = require('portal.PortalSidebar');\nvar utils = require('web.utils');\n\npublicWidget.registry.AccountPortalSidebar = PortalSidebar.extend({\n    selector: '.o_portal_invoice_sidebar',\n    events: {\n        'click .o_portal_invoice_print': '_onPrintInvoice',\n    },\n\n    /**\n     * @override\n     */\n    start: function () {\n        var def = this._super.apply(this, arguments);\n\n        var $invoiceHtml = this.$el.find('iframe#invoice_html');\n        var updateIframeSize = this._updateIframeSize.bind(this, $invoiceHtml);\n\n        $(window).on('resize', updateIframeSize);\n\n        var iframeDoc = $invoiceHtml[0].contentDocument || $invoiceHtml[0].contentWindow.document;\n        if (iframeDoc.readyState === 'complete') {\n            updateIframeSize();\n        } else {\n            $invoiceHtml.on('load', updateIframeSize);\n        }\n\n        return def;\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Called when the iframe is loaded or the window is resized on customer portal.\n     * The goal is to expand the iframe height to display the full report without scrollbar.\n     *\n     * @private\n     * @param {object} $el: the iframe\n     */\n    _updateIframeSize: function ($el) {\n        var $wrapwrap = $el.contents().find('div#wrapwrap');\n        // Set it to 0 first to handle the case where scrollHeight is too big for its content.\n        $el.height(0);\n        $el.height($wrapwrap[0].scrollHeight);\n\n        // scroll to the right place after iframe resize\n        if (!utils.isValidAnchor(window.location.hash)) {\n            return;\n        }\n        var $target = $(window.location.hash);\n        if (!$target.length) {\n            return;\n        }\n        dom.scrollTo($target[0], {duration: 0});\n    },\n    /**\n     * @private\n     * @param {MouseEvent} ev\n     */\n    _onPrintInvoice: function (ev) {\n        ev.preventDefault();\n        var href = $(ev.currentTarget).attr('href');\n        this._printIframeContent(href);\n    },\n});\n});\n", "// Generated by CoffeeScript 1.7.1\n(function() {\n  var $, cardFromNumber, cardFromType, cards, defaultFormat, formatBackCardNumber, formatBackExpiry, formatCardNumber, formatExpiry, formatForwardExpiry, formatForwardSlashAndSpace, hasTextSelected, luhnCheck, reFormatCVC, reFormatCardNumber, reFormatExpiry, reFormatNumeric, replaceFullWidthChars, restrictCVC, restrictCardNumber, restrictExpiry, restrictNumeric, safeVal, setCardType,\n    __slice = [].slice,\n    __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };\n\n  $ = window.jQuery || window.Zepto || window.$;\n\n  $.payment = {};\n\n  $.payment.fn = {};\n\n  $.fn.payment = function() {\n    var args, method;\n    method = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];\n    return $.payment.fn[method].apply(this, args);\n  };\n\n  defaultFormat = /(\\d{1,4})/g;\n\n  $.payment.cards = cards = [\n    {\n      type: 'maestro',\n      patterns: [5018, 502, 503, 506, 56, 58, 639, 6220, 67],\n      format: defaultFormat,\n      length: [12, 13, 14, 15, 16, 17, 18, 19],\n      cvcLength: [3],\n      luhn: true\n    }, {\n      type: 'forbrugsforeningen',\n      patterns: [600],\n      format: defaultFormat,\n      length: [16],\n      cvcLength: [3],\n      luhn: true\n    }, {\n      type: 'dankort',\n      patterns: [5019],\n      format: defaultFormat,\n      length: [16],\n      cvcLength: [3],\n      luhn: true\n    }, {\n      type: 'visa',\n      patterns: [4],\n      format: defaultFormat,\n      length: [13, 16],\n      cvcLength: [3],\n      luhn: true\n    }, {\n      type: 'mastercard',\n      patterns: [51, 52, 53, 54, 55, 22, 23, 24, 25, 26, 27],\n      format: defaultFormat,\n      length: [16],\n      cvcLength: [3],\n      luhn: true\n    }, {\n      type: 'amex',\n      patterns: [34, 37],\n      format: /(\\d{1,4})(\\d{1,6})?(\\d{1,5})?/,\n      length: [15],\n      cvcLength: [3, 4],\n      luhn: true\n    }, {\n      type: 'dinersclub',\n      patterns: [30, 36, 38, 39],\n      format: /(\\d{1,4})(\\d{1,6})?(\\d{1,4})?/,\n      length: [14],\n      cvcLength: [3],\n      luhn: true\n    }, {\n      type: 'discover',\n      patterns: [60, 64, 65, 622],\n      format: defaultFormat,\n      length: [16],\n      cvcLength: [3],\n      luhn: true\n    }, {\n      type: 'unionpay',\n      patterns: [62, 88],\n      format: defaultFormat,\n      length: [16, 17, 18, 19],\n      cvcLength: [3],\n      luhn: false\n    }, {\n      type: 'jcb',\n      patterns: [35],\n      format: defaultFormat,\n      length: [16],\n      cvcLength: [3],\n      luhn: true\n    }\n  ];\n\n  cardFromNumber = function(num) {\n    var card, p, pattern, _i, _j, _len, _len1, _ref;\n    num = (num + '').replace(/\\D/g, '');\n    for (_i = 0, _len = cards.length; _i < _len; _i++) {\n      card = cards[_i];\n      _ref = card.patterns;\n      for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {\n        pattern = _ref[_j];\n        p = pattern + '';\n        if (num.substr(0, p.length) === p) {\n          return card;\n        }\n      }\n    }\n  };\n\n  cardFromType = function(type) {\n    var card, _i, _len;\n    for (_i = 0, _len = cards.length; _i < _len; _i++) {\n      card = cards[_i];\n      if (card.type === type) {\n        return card;\n      }\n    }\n  };\n\n  luhnCheck = function(num) {\n    var digit, digits, odd, sum, _i, _len;\n    odd = true;\n    sum = 0;\n    digits = (num + '').split('').reverse();\n    for (_i = 0, _len = digits.length; _i < _len; _i++) {\n      digit = digits[_i];\n      digit = parseInt(digit, 10);\n      if ((odd = !odd)) {\n        digit *= 2;\n      }\n      if (digit > 9) {\n        digit -= 9;\n      }\n      sum += digit;\n    }\n    return sum % 10 === 0;\n  };\n\n  hasTextSelected = function($target) {\n    var _ref;\n    if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== $target.prop('selectionEnd')) {\n      return true;\n    }\n    if ((typeof document !== \"undefined\" && document !== null ? (_ref = document.selection) != null ? _ref.createRange : void 0 : void 0) != null) {\n      if (document.selection.createRange().text) {\n        return true;\n      }\n    }\n    return false;\n  };\n\n  safeVal = function(value, $target) {\n    var currPair, cursor, digit, error, last, prevPair;\n    try {\n      cursor = $target.prop('selectionStart');\n    } catch (_error) {\n      error = _error;\n      cursor = null;\n    }\n    last = $target.val();\n    $target.val(value);\n    if (cursor !== null && $target.is(\":focus\")) {\n      if (cursor === last.length) {\n        cursor = value.length;\n      }\n      if (last !== value) {\n        prevPair = last.slice(cursor - 1, +cursor + 1 || 9e9);\n        currPair = value.slice(cursor - 1, +cursor + 1 || 9e9);\n        digit = value[cursor];\n        if (/\\d/.test(digit) && prevPair === (\"\" + digit + \" \") && currPair === (\" \" + digit)) {\n          cursor = cursor + 1;\n        }\n      }\n      $target.prop('selectionStart', cursor);\n      return $target.prop('selectionEnd', cursor);\n    }\n  };\n\n  replaceFullWidthChars = function(str) {\n    var chars, chr, fullWidth, halfWidth, idx, value, _i, _len;\n    if (str == null) {\n      str = '';\n    }\n    fullWidth = '\\uff10\\uff11\\uff12\\uff13\\uff14\\uff15\\uff16\\uff17\\uff18\\uff19';\n    halfWidth = '0123456789';\n    value = '';\n    chars = str.split('');\n    for (_i = 0, _len = chars.length; _i < _len; _i++) {\n      chr = chars[_i];\n      idx = fullWidth.indexOf(chr);\n      if (idx > -1) {\n        chr = halfWidth[idx];\n      }\n      value += chr;\n    }\n    return value;\n  };\n\n  reFormatNumeric = function(e) {\n    var $target;\n    $target = $(e.currentTarget);\n    return setTimeout(function() {\n      var value;\n      value = $target.val();\n      value = replaceFullWidthChars(value);\n      value = value.replace(/\\D/g, '');\n      return safeVal(value, $target);\n    });\n  };\n\n  reFormatCardNumber = function(e) {\n    var $target;\n    $target = $(e.currentTarget);\n    return setTimeout(function() {\n      var value;\n      value = $target.val();\n      value = replaceFullWidthChars(value);\n      value = $.payment.formatCardNumber(value);\n      return safeVal(value, $target);\n    });\n  };\n\n  formatCardNumber = function(e) {\n    var $target, card, digit, length, re, upperLength, value;\n    digit = String.fromCharCode(e.which);\n    if (!/^\\d+$/.test(digit)) {\n      return;\n    }\n    $target = $(e.currentTarget);\n    value = $target.val();\n    card = cardFromNumber(value + digit);\n    length = (value.replace(/\\D/g, '') + digit).length;\n    upperLength = 16;\n    if (card) {\n      upperLength = card.length[card.length.length - 1];\n    }\n    if (length >= upperLength) {\n      return;\n    }\n    if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== value.length) {\n      return;\n    }\n    if (card && card.type === 'amex') {\n      re = /^(\\d{4}|\\d{4}\\s\\d{6})$/;\n    } else {\n      re = /(?:^|\\s)(\\d{4})$/;\n    }\n    if (re.test(value)) {\n      e.preventDefault();\n      return setTimeout(function() {\n        return $target.val(value + ' ' + digit);\n      });\n    } else if (re.test(value + digit)) {\n      e.preventDefault();\n      return setTimeout(function() {\n        return $target.val(value + digit + ' ');\n      });\n    }\n  };\n\n  formatBackCardNumber = function(e) {\n    var $target, value;\n    $target = $(e.currentTarget);\n    value = $target.val();\n    if (e.which !== 8) {\n      return;\n    }\n    if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== value.length) {\n      return;\n    }\n    if (/\\d\\s$/.test(value)) {\n      e.preventDefault();\n      return setTimeout(function() {\n        return $target.val(value.replace(/\\d\\s$/, ''));\n      });\n    } else if (/\\s\\d?$/.test(value)) {\n      e.preventDefault();\n      return setTimeout(function() {\n        return $target.val(value.replace(/\\d$/, ''));\n      });\n    }\n  };\n\n  reFormatExpiry = function(e) {\n    var $target;\n    $target = $(e.currentTarget);\n    return setTimeout(function() {\n      var value;\n      value = $target.val();\n      value = replaceFullWidthChars(value);\n      value = $.payment.formatExpiry(value);\n      return safeVal(value, $target);\n    });\n  };\n\n  formatExpiry = function(e) {\n    var $target, digit, val;\n    digit = String.fromCharCode(e.which);\n    if (!/^\\d+$/.test(digit)) {\n      return;\n    }\n    $target = $(e.currentTarget);\n    val = $target.val() + digit;\n    if (/^\\d$/.test(val) && (val !== '0' && val !== '1')) {\n      e.preventDefault();\n      return setTimeout(function() {\n        return $target.val(\"0\" + val + \" / \");\n      });\n    } else if (/^\\d\\d$/.test(val)) {\n      e.preventDefault();\n      return setTimeout(function() {\n        var m1, m2;\n        m1 = parseInt(val[0], 10);\n        m2 = parseInt(val[1], 10);\n        if (m2 > 2 && m1 !== 0) {\n          return $target.val(\"0\" + m1 + \" / \" + m2);\n        } else {\n          return $target.val(\"\" + val + \" / \");\n        }\n      });\n    }\n  };\n\n  formatForwardExpiry = function(e) {\n    var $target, digit, val;\n    digit = String.fromCharCode(e.which);\n    if (!/^\\d+$/.test(digit)) {\n      return;\n    }\n    $target = $(e.currentTarget);\n    val = $target.val();\n    if (/^\\d\\d$/.test(val)) {\n      return $target.val(\"\" + val + \" / \");\n    }\n  };\n\n  formatForwardSlashAndSpace = function(e) {\n    var $target, val, which;\n    which = String.fromCharCode(e.which);\n    if (!(which === '/' || which === ' ')) {\n      return;\n    }\n    $target = $(e.currentTarget);\n    val = $target.val();\n    if (/^\\d$/.test(val) && val !== '0') {\n      return $target.val(\"0\" + val + \" / \");\n    }\n  };\n\n  formatBackExpiry = function(e) {\n    var $target, value;\n    $target = $(e.currentTarget);\n    value = $target.val();\n    if (e.which !== 8) {\n      return;\n    }\n    if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== value.length) {\n      return;\n    }\n    if (/\\d\\s\\/\\s$/.test(value)) {\n      e.preventDefault();\n      return setTimeout(function() {\n        return $target.val(value.replace(/\\d\\s\\/\\s$/, ''));\n      });\n    }\n  };\n\n  reFormatCVC = function(e) {\n    var $target;\n    $target = $(e.currentTarget);\n    return setTimeout(function() {\n      var value;\n      value = $target.val();\n      value = replaceFullWidthChars(value);\n      value = value.replace(/\\D/g, '').slice(0, 4);\n      return safeVal(value, $target);\n    });\n  };\n\n  restrictNumeric = function(e) {\n    var input;\n    if (e.metaKey || e.ctrlKey) {\n      return true;\n    }\n    if (e.which === 32) {\n      return false;\n    }\n    if (e.which === 0) {\n      return true;\n    }\n    if (e.which < 33) {\n      return true;\n    }\n    input = String.fromCharCode(e.which);\n    return !!/[\\d\\s]/.test(input);\n  };\n\n  restrictCardNumber = function(e) {\n    var $target, card, digit, value;\n    $target = $(e.currentTarget);\n    digit = String.fromCharCode(e.which);\n    if (!/^\\d+$/.test(digit)) {\n      return;\n    }\n    if (hasTextSelected($target)) {\n      return;\n    }\n    value = ($target.val() + digit).replace(/\\D/g, '');\n    card = cardFromNumber(value);\n    if (card) {\n      return value.length <= card.length[card.length.length - 1];\n    } else {\n      return value.length <= 16;\n    }\n  };\n\n  restrictExpiry = function(e) {\n    var $target, digit, value;\n    $target = $(e.currentTarget);\n    digit = String.fromCharCode(e.which);\n    if (!/^\\d+$/.test(digit)) {\n      return;\n    }\n    if (hasTextSelected($target)) {\n      return;\n    }\n    value = $target.val() + digit;\n    value = value.replace(/\\D/g, '');\n    if (value.length > 6) {\n      return false;\n    }\n  };\n\n  restrictCVC = function(e) {\n    var $target, digit, val;\n    $target = $(e.currentTarget);\n    digit = String.fromCharCode(e.which);\n    if (!/^\\d+$/.test(digit)) {\n      return;\n    }\n    if (hasTextSelected($target)) {\n      return;\n    }\n    val = $target.val() + digit;\n    return val.length <= 4;\n  };\n\n  setCardType = function(e) {\n    var $target, allTypes, card, cardType, val;\n    $target = $(e.currentTarget);\n    val = $target.val();\n    cardType = $.payment.cardType(val) || 'unknown';\n    if (!$target.hasClass(cardType)) {\n      allTypes = (function() {\n        var _i, _len, _results;\n        _results = [];\n        for (_i = 0, _len = cards.length; _i < _len; _i++) {\n          card = cards[_i];\n          _results.push(card.type);\n        }\n        return _results;\n      })();\n      $target.removeClass('unknown');\n      $target.removeClass(allTypes.join(' '));\n      $target.addClass(cardType);\n      $target.toggleClass('identified', cardType !== 'unknown');\n      return $target.trigger('payment.cardType', cardType);\n    }\n  };\n\n  $.payment.fn.formatCardCVC = function() {\n    this.on('keypress', restrictNumeric);\n    this.on('keypress', restrictCVC);\n    this.on('paste', reFormatCVC);\n    this.on('change', reFormatCVC);\n    this.on('input', reFormatCVC);\n    return this;\n  };\n\n  $.payment.fn.formatCardExpiry = function() {\n    this.on('keypress', restrictNumeric);\n    this.on('keypress', restrictExpiry);\n    this.on('keypress', formatExpiry);\n    this.on('keypress', formatForwardSlashAndSpace);\n    this.on('keypress', formatForwardExpiry);\n    this.on('keydown', formatBackExpiry);\n    this.on('change', reFormatExpiry);\n    this.on('input', reFormatExpiry);\n    return this;\n  };\n\n  $.payment.fn.formatCardNumber = function() {\n    this.on('keypress', restrictNumeric);\n    this.on('keypress', restrictCardNumber);\n    this.on('keypress', formatCardNumber);\n    this.on('keydown', formatBackCardNumber);\n    this.on('keyup', setCardType);\n    this.on('paste', reFormatCardNumber);\n    this.on('change', reFormatCardNumber);\n    this.on('input', reFormatCardNumber);\n    this.on('input', setCardType);\n    return this;\n  };\n\n  $.payment.fn.restrictNumeric = function() {\n    this.on('keypress', restrictNumeric);\n    this.on('paste', reFormatNumeric);\n    this.on('change', reFormatNumeric);\n    this.on('input', reFormatNumeric);\n    return this;\n  };\n\n  $.payment.fn.cardExpiryVal = function() {\n    return $.payment.cardExpiryVal($(this).val());\n  };\n\n  $.payment.cardExpiryVal = function(value) {\n    var month, prefix, year, _ref;\n    _ref = value.split(/[\\s\\/]+/, 2), month = _ref[0], year = _ref[1];\n    if ((year != null ? year.length : void 0) === 2 && /^\\d+$/.test(year)) {\n      prefix = (new Date).getFullYear();\n      prefix = prefix.toString().slice(0, 2);\n      year = prefix + year;\n    }\n    month = parseInt(month, 10);\n    year = parseInt(year, 10);\n    return {\n      month: month,\n      year: year\n    };\n  };\n\n  $.payment.validateCardNumber = function(num) {\n    var card, _ref;\n    num = (num + '').replace(/\\s+|-/g, '');\n    if (!/^\\d+$/.test(num)) {\n      return false;\n    }\n    card = cardFromNumber(num);\n    if (!card) {\n      return false;\n    }\n    return (_ref = num.length, __indexOf.call(card.length, _ref) >= 0) && (card.luhn === false || luhnCheck(num));\n  };\n\n  $.payment.validateCardExpiry = function(month, year) {\n    var currentTime, expiry, _ref;\n    if (typeof month === 'object' && 'month' in month) {\n      _ref = month, month = _ref.month, year = _ref.year;\n    }\n    if (!(month && year)) {\n      return false;\n    }\n    month = $.trim(month);\n    year = $.trim(year);\n    if (!/^\\d+$/.test(month)) {\n      return false;\n    }\n    if (!/^\\d+$/.test(year)) {\n      return false;\n    }\n    if (!((1 <= month && month <= 12))) {\n      return false;\n    }\n    if (year.length === 2) {\n      if (year < 70) {\n        year = \"20\" + year;\n      } else {\n        year = \"19\" + year;\n      }\n    }\n    if (year.length !== 4) {\n      return false;\n    }\n    expiry = new Date(year, month);\n    currentTime = new Date;\n    expiry.setMonth(expiry.getMonth() - 1);\n    expiry.setMonth(expiry.getMonth() + 1, 1);\n    return expiry > currentTime;\n  };\n\n  $.payment.validateCardCVC = function(cvc, type) {\n    var card, _ref;\n    cvc = $.trim(cvc);\n    if (!/^\\d+$/.test(cvc)) {\n      return false;\n    }\n    card = cardFromType(type);\n    if (card != null) {\n      return _ref = cvc.length, __indexOf.call(card.cvcLength, _ref) >= 0;\n    } else {\n      return cvc.length >= 3 && cvc.length <= 4;\n    }\n  };\n\n  $.payment.cardType = function(num) {\n    var _ref;\n    if (!num) {\n      return null;\n    }\n    return ((_ref = cardFromNumber(num)) != null ? _ref.type : void 0) || null;\n  };\n\n  $.payment.formatCardNumber = function(num) {\n    var card, groups, upperLength, _ref;\n    num = num.replace(/\\D/g, '');\n    card = cardFromNumber(num);\n    if (!card) {\n      return num;\n    }\n    upperLength = card.length[card.length.length - 1];\n    num = num.slice(0, upperLength);\n    if (card.format.global) {\n      return (_ref = num.match(card.format)) != null ? _ref.join(' ') : void 0;\n    } else {\n      groups = card.format.exec(num);\n      if (groups == null) {\n        return;\n      }\n      groups.shift();\n      groups = $.grep(groups, function(n) {\n        return n;\n      });\n      return groups.join(' ');\n    }\n  };\n\n  $.payment.formatExpiry = function(expiry) {\n    var mon, parts, sep, year;\n    parts = expiry.match(/^\\D*(\\d{1,2})(\\D+)?(\\d{1,4})?/);\n    if (!parts) {\n      return '';\n    }\n    mon = parts[1] || '';\n    sep = parts[2] || '';\n    year = parts[3] || '';\n    if (year.length > 0) {\n      sep = ' / ';\n    } else if (sep === ' /') {\n      mon = mon.substring(0, 1);\n      sep = '';\n    } else if (mon.length === 2 || sep.length > 0) {\n      sep = ' / ';\n    } else if (mon.length === 1 && (mon !== '0' && mon !== '1')) {\n      mon = \"0\" + mon;\n      sep = ' / ';\n    }\n    return mon + sep + year;\n  };\n\n}).call(this);\n", "odoo.define('payment.checkout_form', require => {\n    'use strict';\n\n    const publicWidget = require('web.public.widget');\n\n    const paymentFormMixin = require('payment.payment_form_mixin');\n\n    publicWidget.registry.PaymentCheckoutForm = publicWidget.Widget.extend(paymentFormMixin, {\n        selector: 'form[name=\"o_payment_checkout\"]',\n        events: Object.assign({}, publicWidget.Widget.prototype.events, {\n                'click div[name=\"o_payment_option_card\"]': '_onClickPaymentOption',\n                'click a[name=\"o_payment_icon_more\"]': '_onClickMorePaymentIcons',\n                'click a[name=\"o_payment_icon_less\"]': '_onClickLessPaymentIcons',\n                'click button[name=\"o_payment_submit_button\"]': '_onClickPay',\n                'submit': '_onSubmit',\n            }),\n\n        /**\n         * @constructor\n         */\n        init: function () {\n            const preventDoubleClick = handlerMethod => {\n                return _.debounce(handlerMethod, 500, true);\n            };\n            this._super(...arguments);\n            // Prevent double-clicks and browser glitches on all inputs\n            this._onClickLessPaymentIcons = preventDoubleClick(this._onClickLessPaymentIcons);\n            this._onClickMorePaymentIcons = preventDoubleClick(this._onClickMorePaymentIcons);\n            this._onClickPay = preventDoubleClick(this._onClickPay);\n            this._onClickPaymentOption = preventDoubleClick(this._onClickPaymentOption);\n            this._onSubmit = preventDoubleClick(this._onSubmit);\n        },\n\n        //--------------------------------------------------------------------------\n        // Handlers\n        //--------------------------------------------------------------------------\n\n        /**\n         * Handle a direct payment, a payment with redirection, or a payment by token.\n         *\n         * Called when clicking on the 'Pay' button or when submitting the form.\n         *\n         * @private\n         * @param {Event} ev\n         * @return {undefined}\n         */\n        _onClickPay: async function (ev) {\n            ev.stopPropagation();\n            ev.preventDefault();\n\n            // Check that the user has selected a payment option\n            const $checkedRadios = this.$('input[name=\"o_payment_radio\"]:checked');\n            if (!this._ensureRadioIsChecked($checkedRadios)) {\n                return;\n            }\n            const checkedRadio = $checkedRadios[0];\n\n            // Extract contextual values from the radio button\n            const provider = this._getProviderFromRadio(checkedRadio);\n            const paymentOptionId = this._getPaymentOptionIdFromRadio(checkedRadio);\n            const flow = this._getPaymentFlowFromRadio(checkedRadio);\n\n            // Update the tx context with the value of the \"Save my payment details\" checkbox\n            if (flow !== 'token') {\n                const $tokenizeCheckbox = this.$(\n                    `#o_payment_acquirer_inline_form_${paymentOptionId}` // Only match acq. radios\n                ).find('input[name=\"o_payment_save_as_token\"]');\n                this.txContext.tokenizationRequested = $tokenizeCheckbox.length === 1\n                    && $tokenizeCheckbox[0].checked;\n            } else {\n                this.txContext.tokenizationRequested = false;\n            }\n\n            // Make the payment\n            this._hideError(); // Don't keep the error displayed if the user is going through 3DS2\n            this._disableButton(true); // Disable until it is needed again\n            $('body').block({\n                message: false,\n                overlayCSS: {backgroundColor: \"#000\", opacity: 0, zIndex: 1050},\n            });\n            this._processPayment(provider, paymentOptionId, flow);\n        },\n\n        /**\n         * Delegate the handling of the payment request to `_onClickPay`.\n         *\n         * Called when submitting the form (e.g. through the Return key).\n         *\n         * @private\n         * @param {Event} ev\n         * @return {undefined}\n         */\n        _onSubmit: function (ev) {\n            ev.stopPropagation();\n            ev.preventDefault();\n\n            this._onClickPay(ev);\n        },\n\n    });\n    return publicWidget.registry.PaymentCheckoutForm;\n});\n", "odoo.define('payment.manage_form', require => {\n    'use strict';\n\n    const core = require('web.core');\n    const publicWidget = require('web.public.widget');\n    const Dialog = require('web.Dialog');\n\n    const paymentFormMixin = require('payment.payment_form_mixin');\n\n    const _t = core._t;\n\n    publicWidget.registry.PaymentManageForm = publicWidget.Widget.extend(paymentFormMixin, {\n        selector: 'form[name=\"o_payment_manage\"]',\n        events: Object.assign({}, publicWidget.Widget.prototype.events, {\n            'click div[name=\"o_payment_option_card\"]': '_onClickPaymentOption',\n            'click a[name=\"o_payment_icon_more\"]': '_onClickMorePaymentIcons',\n            'click a[name=\"o_payment_icon_less\"]': '_onClickLessPaymentIcons',\n            'click button[name=\"o_payment_submit_button\"]': '_onClickSaveToken',\n            'click button[name=\"o_payment_delete_token\"]': '_onClickDeleteToken',\n            'submit': '_onSubmit',\n        }),\n\n        /**\n         * @constructor\n         */\n        init: function () {\n            const preventDoubleClick = handlerMethod => {\n                return _.debounce(handlerMethod, 500, true);\n            };\n            this._super(...arguments);\n            // Prevent double-clicks and browser glitches on all inputs\n            this._onClickDeleteToken = preventDoubleClick(this._onClickDeleteToken);\n            this._onClickLessPaymentIcons = preventDoubleClick(this._onClickLessPaymentIcons);\n            this._onClickMorePaymentIcons = preventDoubleClick(this._onClickMorePaymentIcons);\n            this._onClickPaymentOption = preventDoubleClick(this._onClickPaymentOption);\n            this._onClickSaveToken = preventDoubleClick(this._onClickSaveToken);\n            this._onSubmit = preventDoubleClick(this._onSubmit);\n        },\n\n        //--------------------------------------------------------------------------\n        // Private\n        //--------------------------------------------------------------------------\n\n        /**\n         * Assign the token to a record.\n         *\n         * @private\n         * @param {number} tokenId - The id of the token to assign\n         * @return {undefined}\n         */\n        _assignToken: function (tokenId) {\n            // Call the assign route to assign the token to a record\n            this._rpc({\n                route: this.txContext.assignTokenRoute,\n                params: {\n                    'token_id': tokenId,\n                    'csrf_token': core.csrf_token,\n                }\n            }).then(() => {\n                window.location = this.txContext.landingRoute;\n            }).guardedCatch(error => {\n                error.event.preventDefault();\n                this._displayError(\n                    _t(\"Server Error\"),\n                    _t(\"We are not able to save your payment method.\"),\n                    error.message.data.message\n                );\n            });\n        },\n\n        /**\n         * Search for documents linked to the token and ask the user for confirmation.\n         *\n         * If any such document is found, a confirmation dialog is shown.\n         *\n         * @private\n         * @param {number} tokenId - The id of the token to delete\n         * @return {undefined}\n         */\n        _deleteToken: function (tokenId) {\n            const execute = () => {\n                this._rpc({\n                    route: '/payment/archive_token',\n                    params: {\n                        'token_id': tokenId,\n                    },\n                }).then(() => {\n                    const $tokenCard = this.$(\n                        `input[name=\"o_payment_radio\"][data-payment-option-id=\"${tokenId}\"]` +\n                        `[data-payment-option-type=\"token\"]`\n                    ).closest('div[name=\"o_payment_option_card\"]');\n                    $tokenCard.siblings(`#o_payment_token_inline_form_${tokenId}`).remove();\n                    $tokenCard.remove();\n                    this._disableButton(false);\n                }).guardedCatch(error => {\n                    error.event.preventDefault();\n                    this._displayError(\n                        _t(\"Server Error\"),\n                        _t(\"We are not able to delete your payment method.\"),\n                        error.message.data.message\n                    );\n                });\n            };\n\n            // Fetch documents linked to the token\n            this._rpc({\n                model: 'payment.token',\n                method: 'get_linked_records_info',\n                args: [tokenId],\n            }).then(result => {\n                const $dialogContentMessage = $(\n                    '<span>', {text: _t(\"Are you sure you want to delete this payment method?\")}\n                );\n                if (result.length > 0) { // There are documents linked to the token, list them\n                    $dialogContentMessage.append($('<br>'));\n                    $dialogContentMessage.append($(\n                        '<span>', {text: _t(\"It is currently linked to the following documents:\")}\n                    ));\n                    const $documentInfoList = $('<ul>');\n                    result.forEach(documentInfo => {\n                        $documentInfoList.append($('<li>').append($(\n                            '<a>', {\n                                href: documentInfo.url,\n                                target: '_blank',\n                                title: documentInfo.description,\n                                text: documentInfo.name\n                            }\n                        )));\n                    });\n                    $dialogContentMessage.append($documentInfoList);\n                }\n                new Dialog(this, {\n                    title: _t(\"Warning!\"),\n                    size: 'medium',\n                    $content: $('<div>').append($dialogContentMessage),\n                    buttons: [\n                        {\n                            text: _t(\"Confirm Deletion\"), classes: 'btn-primary', close: true,\n                            click: execute\n                        },\n                        {\n                            text: _t(\"Cancel\"), close: true\n                        },\n                    ],\n                }).open();\n            }).guardedCatch(error => {\n                this._displayError(\n                    _t(\"Server Error\"),\n                    _t(\"We are not able to delete your payment method.\"),\n                    error.message.data.message\n                );\n            });\n        },\n\n        //--------------------------------------------------------------------------\n        // Handlers\n        //--------------------------------------------------------------------------\n\n        /**\n         * Find the radio button linked to the click 'Delete' button and trigger the token deletion.\n         *\n         * Let `_onClickPaymentOption` select the radio button and display the inline form.\n         *\n         * Called when clicking on the 'Delete' button of a token.\n         *\n         * @private\n         * @param {Event} ev\n         * @return {undefined}\n         */\n        _onClickDeleteToken: function (ev) {\n            ev.preventDefault();\n\n            // Extract contextual values from the delete button\n            const linkedRadio = $(ev.currentTarget).siblings().find('input[name=\"o_payment_radio\"]')[0];\n            const tokenId = this._getPaymentOptionIdFromRadio(linkedRadio);\n\n            // Delete the token\n            this._deleteToken(tokenId);\n        },\n\n        /**\n         * Handle the creation of a new token or the assignation of a token to a record.\n         *\n         * Called when clicking on the 'Save Payment Method' button of when submitting the form.\n         *\n         * @private\n         * @param {Event} ev\n         * @return {undefined}\n         */\n        _onClickSaveToken: async function (ev) {\n            ev.stopPropagation();\n            ev.preventDefault();\n\n            // Check that the user has selected a payment option\n            const $checkedRadios = this.$('input[name=\"o_payment_radio\"]:checked');\n            if (!this._ensureRadioIsChecked($checkedRadios)) {\n                return;\n            }\n            const checkedRadio = $checkedRadios[0];\n\n            // Extract contextual values from the radio button\n            const provider = this._getProviderFromRadio(checkedRadio);\n            const paymentOptionId = this._getPaymentOptionIdFromRadio(checkedRadio);\n            const flow = this._getPaymentFlowFromRadio(checkedRadio);\n\n            // Save the payment method\n            this._hideError(); // Don't keep the error displayed if the user is going through 3DS2\n            this._disableButton(true); // Disable until it is needed again\n            $('body').block({\n                message: false,\n                overlayCSS: {backgroundColor: \"#000\", opacity: 0, zIndex: 1050},\n            });\n            if (flow !== 'token') { // Creation of a new token\n                this.txContext.tokenizationRequested = true;\n                this.txContext.isValidation = true;\n                this._processPayment(provider, paymentOptionId, flow);\n            } else if (this.txContext.allowTokenSelection) { // Assignation of a token to a record\n                this._assignToken(paymentOptionId);\n            }\n        },\n\n        /**\n         * Delegate the handling of the token to `_onClickSaveToken`.\n         *\n         * Called when submitting the form (e.g. through the Return key).\n         *\n         * @private\n         * @param {Event} ev\n         * @return {undefined}\n         */\n        _onSubmit: function (ev) {\n            ev.stopPropagation();\n            ev.preventDefault();\n\n            this._onClickSaveToken(ev);\n        },\n\n    });\n    return publicWidget.registry.PaymentManageForm;\n});\n", "odoo.define('payment.payment_form_mixin', require => {\n    'use strict';\n\n    const core = require('web.core');\n    const Dialog = require('web.Dialog');\n\n    const _t = core._t;\n\n    return {\n\n        /**\n         * @override\n         */\n        start: async function () {\n            this.txContext = {}; // Synchronously initialize txContext before any await.\n            Object.assign(this.txContext, this.$el.data());\n            await this._super(...arguments);\n            window.addEventListener('pageshow', function (event) {\n                if (event.persisted) {\n                    window.location.reload();\n                }\n            });\n            this.$('[data-toggle=\"tooltip\"]').tooltip();\n            const $checkedRadios = this.$('input[name=\"o_payment_radio\"]:checked');\n            if ($checkedRadios.length === 1) {\n                const checkedRadio = $checkedRadios[0];\n                this._displayInlineForm(checkedRadio);\n                this._enableButton();\n            } else {\n                this._setPaymentFlow(); // Initialize the payment flow to let acquirers overwrite it\n            }\n        },\n\n        //--------------------------------------------------------------------------\n        // Private\n        //--------------------------------------------------------------------------\n\n        /**\n         * Disable the submit button.\n         *\n         * The icons are updated to either show that an action is processing or that the button is\n         * not ready, depending on the value of `showLoadingAnimation`.\n         *\n         * @private\n         * @param {boolean} showLoadingAnimation - Whether a spinning loader should be shown\n         * @return {undefined}\n         */\n        _disableButton: (showLoadingAnimation = true) => {\n            const $submitButton = this.$('button[name=\"o_payment_submit_button\"]');\n            const iconClass = $submitButton.data('icon-class');\n            $submitButton.attr('disabled', true);\n            if (showLoadingAnimation) {\n                $submitButton.find('i').removeClass(iconClass);\n                $submitButton.prepend(\n                    '<span class=\"o_loader\"><i class=\"fa fa-refresh fa-spin\"></i>&nbsp;</span>'\n                );\n            }\n        },\n\n        /**\n         * Display an error in the payment form.\n         *\n         * If no payment option is selected, the error is displayed in a dialog. If exactly one\n         * payment option is selected, the error is displayed in the inline form of that payment\n         * option and the view is focused on the error.\n         *\n         * @private\n         * @param {string} title - The title of the error\n         * @param {string} description - The description of the error\n         * @param {string} error - The raw error message\n         * @return {(Dialog|undefined)} A dialog showing the error if no payment option is selected,\n         *                              undefined otherwise.\n         */\n        _displayError: function (title, description = '', error = '') {\n            const $checkedRadios = this.$('input[name=\"o_payment_radio\"]:checked');\n            if ($checkedRadios.length !== 1) { // Cannot find selected payment option, show dialog\n                return new Dialog(null, {\n                    title: _.str.sprintf(_t(\"Error: %s\"), title),\n                    size: 'medium',\n                    $content: `<p>${_.str.escapeHTML(description) || ''}</p>`,\n                    buttons: [{text: _t(\"Ok\"), close: true}]\n                }).open();\n            } else { // Show error in inline form\n                this._hideError(); // Remove any previous error\n\n                // Build the html for the error\n                let errorHtml = `<div class=\"alert alert-danger mb4\" name=\"o_payment_error\">\n                                 <b>${_.str.escapeHTML(title)}</b>`;\n                if (description !== '') {\n                    errorHtml += `</br>${_.str.escapeHTML(description)}`;\n                }\n                if (error !== '') {\n                    errorHtml += `</br>${_.str.escapeHTML(error)}`;\n                }\n                errorHtml += '</div>';\n\n                // Append error to inline form and center the page on the error\n                const checkedRadio = $checkedRadios[0];\n                const paymentOptionId = this._getPaymentOptionIdFromRadio(checkedRadio);\n                const formType = $(checkedRadio).data('payment-option-type');\n                const $inlineForm = this.$(`#o_payment_${formType}_inline_form_${paymentOptionId}`);\n                $inlineForm.removeClass('d-none'); // Show the inline form even if it was empty\n                $inlineForm.append(errorHtml).find('div[name=\"o_payment_error\"]')[0]\n                    .scrollIntoView({behavior: 'smooth', block: 'center'});\n            }\n            this._enableButton(); // Enable button back after it was disabled before processing\n            $('body').unblock(); // The page is blocked at this point, unblock it\n        },\n\n        /**\n         * Display the inline form of the selected payment option and hide others.\n         *\n         * @private\n         * @param {HTMLInputElement} radio - The radio button linked to the payment option\n         * @return {undefined}\n         */\n        _displayInlineForm: function (radio) {\n            this._hideInlineForms(); // Collapse previously opened inline forms\n            this._hideError(); // The error is only relevant until it is hidden with its inline form\n            this._setPaymentFlow(); // Reset the payment flow to let acquirers overwrite it\n\n            // Extract contextual values from the radio button\n            const provider = this._getProviderFromRadio(radio);\n            const paymentOptionId = this._getPaymentOptionIdFromRadio(radio);\n            const flow = this._getPaymentFlowFromRadio(radio);\n\n            // Prepare the inline form of the selected payment option and display it if not empty\n            this._prepareInlineForm(provider, paymentOptionId, flow);\n            const formType = $(radio).data('payment-option-type');\n            const $inlineForm = this.$(`#o_payment_${formType}_inline_form_${paymentOptionId}`);\n            if ($inlineForm.children().length > 0) {\n                $inlineForm.removeClass('d-none');\n            }\n        },\n\n        /**\n         * Check if the submit button can be enabled and do it if so.\n         *\n         * The icons are updated to show that the button is ready.\n         *\n         * @private\n         * @return {boolean} Whether the button was enabled.\n         */\n        _enableButton: function () {\n            if (this._isButtonReady()) {\n                const $submitButton = this.$('button[name=\"o_payment_submit_button\"]');\n                const iconClass = $submitButton.data('icon-class');\n                $submitButton.attr('disabled', false);\n                $submitButton.find('i').addClass(iconClass);\n                $submitButton.find('span.o_loader').remove();\n                return true;\n            }\n            return false;\n        },\n\n        /**\n         * Verify that exactly one radio button is checked and display an error otherwise.\n         *\n         * @private\n         * @param {jQuery} $checkedRadios - The currently check radio buttons\n         *\n         * @return {boolean} Whether exactly one radio button among the provided radios is checked\n         */\n        _ensureRadioIsChecked: function ($checkedRadios) {\n            if ($checkedRadios.length === 0) {\n                this._displayError(\n                    _t(\"No payment option selected\"),\n                    _t(\"Please select a payment option.\")\n                );\n                return false;\n            } else if ($checkedRadios.length > 1) {\n                this._displayError(\n                    _t(\"Multiple payment options selected\"),\n                    _t(\"Please select only one payment option.\")\n                );\n                return false;\n            }\n            return true;\n        },\n\n        /**\n         * Determine and return the online payment flow of the selected payment option.\n         *\n         * As some acquirers implement both the direct payment and the payment with redirection, the\n         * flow cannot be inferred from the radio button only. The radio button only indicates\n         * whether the payment option is a token. If not, the transaction context is looked up to\n         * determine whether the flow is 'direct' or 'redirect'.\n         *\n         * @private\n         * @param {HTMLInputElement} radio - The radio button linked to the payment option\n         * @return {string} The flow of the selected payment option. redirect, direct or token.\n         */\n        _getPaymentFlowFromRadio: function (radio) {\n            if (\n                $(radio).data('payment-option-type') === 'token'\n                || this.txContext.flow === 'token'\n            ) {\n                return 'token';\n            } else if (this.txContext.flow === 'redirect') {\n                return 'redirect';\n            } else {\n                return 'direct';\n            }\n        },\n\n        /**\n         * Determine and return the id of the selected payment option.\n         *\n         * @private\n         * @param {HTMLInputElement} radio - The radio button linked to the payment option\n         * @return {number} The acquirer id or the token id or of the payment option linked to the\n         *                  radio button.\n         */\n        _getPaymentOptionIdFromRadio: radio => $(radio).data('payment-option-id'),\n\n        /**\n         * Determine and return the provider of the selected payment option.\n         *\n         * @private\n         * @param {HTMLInputElement} radio - The radio button linked to the payment option\n         * @return {number} The provider of the payment option linked to the radio button.\n         */\n        _getProviderFromRadio: radio => $(radio).data('provider'),\n\n        /**\n         * Remove the error in the acquirer form.\n         *\n         * @private\n         * @return {jQuery} The removed error\n         */\n        _hideError: () => this.$('div[name=\"o_payment_error\"]').remove(),\n\n        /**\n         * Collapse all inline forms.\n         *\n         * @private\n         * @return {undefined}.\n         */\n        _hideInlineForms: () => this.$('[name=\"o_payment_inline_form\"]').addClass('d-none'),\n\n        /**\n         * Hide the \"Save my payment details\" label and checkbox, and the submit button.\n         *\n         * The inputs should typically be hidden when the customer has to perform additional actions\n         * in the inline form. All inputs are automatically shown again when the customer clicks on\n         * another inline form.\n         *\n         * @private\n         * @return {undefined}\n         */\n        _hideInputs: function () {\n            const $submitButton = this.$('button[name=\"o_payment_submit_button\"]');\n            const $tokenizeCheckboxes = this.$('input[name=\"o_payment_save_as_token\"]');\n            $submitButton.addClass('d-none');\n            $tokenizeCheckboxes.closest('label').addClass('d-none');\n        },\n\n        /**\n         * Verify that the submit button is ready to be enabled.\n         *\n         * For a module to support a custom behavior for the submit button, it must override this\n         * method and only return true if the result of this method is true and if nothing prevents\n         * enabling the submit button for that custom behavior.\n         *\n         * @private\n         *\n         * @return {boolean} Whether the submit button can be enabled\n         */\n        _isButtonReady: function () {\n            const $checkedRadios = this.$('input[name=\"o_payment_radio\"]:checked');\n            if ($checkedRadios.length === 1) {\n                const checkedRadio = $checkedRadios[0];\n                const flow = this._getPaymentFlowFromRadio(checkedRadio);\n                return flow !== 'token' || this.txContext.allowTokenSelection;\n            } else {\n                return false;\n            }\n        },\n\n        /**\n         * Prepare the params to send to the transaction route.\n         *\n         * For an acquirer to overwrite generic params or to add acquirer-specific ones, it must\n         * override this method and return the extended transaction route params.\n         *\n         * @private\n         * @param {string} provider - The provider of the selected payment option's acquirer\n         * @param {number} paymentOptionId - The id of the selected payment option\n         * @param {string} flow - The online payment flow of the selected payment option\n         * @return {object} The transaction route params\n         */\n        _prepareTransactionRouteParams: function (provider, paymentOptionId, flow) {\n            return {\n                'payment_option_id': paymentOptionId,\n                'reference_prefix': this.txContext.referencePrefix !== undefined\n                    ? this.txContext.referencePrefix.toString() : null,\n                'amount': this.txContext.amount !== undefined\n                    ? parseFloat(this.txContext.amount) : null,\n                'currency_id': this.txContext.currencyId\n                    ? parseInt(this.txContext.currencyId) : null,\n                'partner_id': parseInt(this.txContext.partnerId),\n                'invoice_id': this.txContext.invoiceId\n                    ? parseInt(this.txContext.invoiceId) : null,\n                'flow': flow,\n                'tokenization_requested': this.txContext.tokenizationRequested,\n                'landing_route': this.txContext.landingRoute,\n                'is_validation': this.txContext.isValidation,\n                'access_token': this.txContext.accessToken\n                    ? this.txContext.accessToken : undefined,\n                'csrf_token': core.csrf_token,\n            };\n        },\n\n        /**\n         * Prepare the acquirer-specific inline form of the selected payment option.\n         *\n         * For an acquirer to manage an inline form, it must override this method. When the override\n         * is called, it must lookup the parameters to decide whether it is necessary to prepare its\n         * inline form. Otherwise, the call must be sent back to the parent method.\n         *\n         * @private\n         * @param {string} provider - The provider of the selected payment option's acquirer\n         * @param {number} paymentOptionId - The id of the selected payment option\n         * @param {string} flow - The online payment flow of the selected payment option\n         * @return {Promise}\n         */\n        _prepareInlineForm: (provider, paymentOptionId, flow) => Promise.resolve(),\n\n        /**\n         * Process the payment.\n         *\n         * For an acquirer to do pre-processing work on the transaction processing flow, or to\n         * define its entire own flow that requires re-scheduling the RPC to the transaction route,\n         * it must override this method.\n         * If only post-processing work is needed, an override of `_processRedirectPayment`,\n         * `_processDirectPayment` or `_processTokenPayment` might be more appropriate.\n         *\n         * @private\n         * @param {string} provider - The provider of the payment option's acquirer\n         * @param {number} paymentOptionId - The id of the payment option handling the transaction\n         * @param {string} flow - The online payment flow of the transaction\n         * @return {Promise}\n         */\n        _processPayment: function (provider, paymentOptionId, flow) {\n            // Call the transaction route to create a tx and retrieve the processing values\n            return this._rpc({\n                route: this.txContext.transactionRoute,\n                params: this._prepareTransactionRouteParams(provider, paymentOptionId, flow),\n            }).then(processingValues => {\n                if (flow === 'redirect') {\n                    return this._processRedirectPayment(\n                        provider, paymentOptionId, processingValues\n                    );\n                } else if (flow === 'direct') {\n                    return this._processDirectPayment(provider, paymentOptionId, processingValues);\n                } else if (flow === 'token') {\n                    return this._processTokenPayment(provider, paymentOptionId, processingValues);\n                }\n            }).guardedCatch(error => {\n                error.event.preventDefault();\n                this._displayError(\n                    _t(\"Server Error\"),\n                    _t(\"We are not able to process your payment.\"),\n                    error.message.data.message\n                );\n            });\n        },\n\n        /**\n         * Execute the acquirer-specific implementation of the direct payment flow.\n         *\n         * For an acquirer to redefine the processing of the direct payment flow, it must override\n         * this method.\n         *\n         * @private\n         * @param {string} provider - The provider of the acquirer\n         * @param {number} acquirerId - The id of the acquirer handling the transaction\n         * @param {object} processingValues - The processing values of the transaction\n         * @return {Promise}\n         */\n        _processDirectPayment: (provider, acquirerId, processingValues) => Promise.resolve(),\n\n        /**\n         * Redirect the customer by submitting the redirect form included in the processing values.\n         *\n         * For an acquirer to redefine the processing of the payment with redirection flow, it must\n         * override this method.\n         *\n         * @private\n         * @param {string} provider - The provider of the acquirer\n         * @param {number} acquirerId - The id of the acquirer handling the transaction\n         * @param {object} processingValues - The processing values of the transaction\n         * @return {undefined}\n         */\n        _processRedirectPayment: (provider, acquirerId, processingValues) => {\n            // Append the redirect form to the body\n            const $redirectForm = $(processingValues.redirect_form_html).attr(\n                'id', 'o_payment_redirect_form'\n            );\n            $(document.getElementsByTagName('body')[0]).append($redirectForm);\n\n            // Submit the form\n            $redirectForm.submit();\n        },\n\n        /**\n         * Redirect the customer to the status route.\n         *\n         * For an acquirer to redefine the processing of the payment by token flow, it must override\n         * this method.\n         *\n         * @private\n         * @param {string} provider - The provider of the token's acquirer\n         * @param {number} tokenId - The id of the token handling the transaction\n         * @param {object} processingValues - The processing values of the transaction\n         * @return {undefined}\n         */\n        _processTokenPayment: (provider, tokenId, processingValues) => {\n            // The flow is already completed as payments by tokens are immediately processed\n            window.location = '/payment/status';\n        },\n\n        /**\n         * Set the online payment flow for the selected payment option.\n         *\n         * For an acquirer to manage direct payments, it must call this method from within its\n         * override of `_prepareInlineForm` to declare its payment flow for the selected payment\n         * option.\n         *\n         * @private\n         * @param {string} flow - The flow for the selected payment option. Either 'redirect',\n         *                        'direct' or 'token'\n         * @return {undefined}\n         */\n        _setPaymentFlow: function (flow = 'redirect') {\n            if (flow !== 'redirect' && flow !== 'direct' && flow !== 'token') {\n                console.warn(\n                    `payment_form_mixin: method '_setPaymentFlow' was called with invalid flow: \n                    ${flow}. Falling back to 'redirect'.`\n                );\n                this.txContext.flow = 'redirect';\n            } else {\n                this.txContext.flow = flow;\n            }\n        },\n\n        /**\n         * Show the \"Save my payment details\" label and checkbox, and the submit button.\n         *\n         * @private\n         * @return {undefined}.\n         */\n        _showInputs: function () {\n            const $submitButton = this.$('button[name=\"o_payment_submit_button\"]');\n            const $tokenizeCheckboxes = this.$('input[name=\"o_payment_save_as_token\"]');\n            $submitButton.removeClass('d-none');\n            $tokenizeCheckboxes.closest('label').removeClass('d-none');\n        },\n\n        //--------------------------------------------------------------------------\n        // Handlers\n        //--------------------------------------------------------------------------\n\n        /**\n         * Hide all extra payment icons of the acquirer linked to the clicked button.\n         *\n         * Called when clicking on the \"show less\" button.\n         *\n         * @private\n         * @param {Event} ev\n         * @return {undefined}\n         */\n        _onClickLessPaymentIcons: ev => {\n            ev.preventDefault();\n            ev.stopPropagation();\n            // Hide the extra payment icons, and the \"show less\" button\n            const $itemList = $(ev.currentTarget).parents('ul');\n            const maxIconNumber = $itemList.data('max-icons');\n            $itemList.children('li').slice(maxIconNumber).addClass('d-none');\n            // Show the \"show more\" button\n            $itemList.find('a[name=\"o_payment_icon_more\"]').parents('li').removeClass('d-none');\n        },\n\n        /**\n         * Display all the payment icons of the acquirer linked to the clicked button.\n         *\n         * Called when clicking on the \"show more\" button.\n         *\n         * @private\n         * @param {Event} ev\n         * @return {undefined}\n         */\n        _onClickMorePaymentIcons: ev => {\n            ev.preventDefault();\n            ev.stopPropagation();\n            // Display all the payment icons, and the \"show less\" button\n            $(ev.currentTarget).parents('ul').children('li').removeClass('d-none');\n            // Hide the \"show more\" button\n            $(ev.currentTarget).parents('li').addClass('d-none');\n        },\n\n        /**\n         * Mark the clicked card radio button as checked and open the inline form, if any.\n         *\n         * Called when clicking on the card of a payment option.\n         *\n         * @private\n         * @param {Event} ev\n         * @return {undefined}\n         */\n        _onClickPaymentOption: function (ev) {\n            // Uncheck all radio buttons\n            this.$('input[name=\"o_payment_radio\"]').prop('checked', false);\n            // Check radio button linked to selected payment option\n            const checkedRadio = $(ev.currentTarget).find('input[name=\"o_payment_radio\"]')[0];\n            $(checkedRadio).prop('checked', true);\n\n            // Show the inputs in case they had been hidden\n            this._showInputs();\n\n            // Disable the submit button while building the content\n            this._disableButton(false);\n\n            // Unfold and prepare the inline form of selected payment option\n            this._displayInlineForm(checkedRadio);\n\n            // Re-enable the submit button\n            this._enableButton();\n        },\n\n    };\n\n});\n", "odoo.define('payment.post_processing', function (require) {\n    'use strict';\n\n    var publicWidget = require('web.public.widget');\n    var core = require('web.core');\n    const {Markup} = require('web.utils');\n\n    var _t = core._t;\n\n    $.blockUI.defaults.css.border = '0';\n    $.blockUI.defaults.css[\"background-color\"] = '';\n    $.blockUI.defaults.overlayCSS[\"opacity\"] = '0.9';\n\n    publicWidget.registry.PaymentPostProcessing = publicWidget.Widget.extend({\n        selector: 'div[name=\"o_payment_status\"]',\n        xmlDependencies: ['/payment/static/src/xml/payment_post_processing.xml'],\n\n        _pollCount: 0,\n\n        start: function() {\n            this.displayLoading();\n            this.poll();\n            return this._super.apply(this, arguments);\n        },\n        /* Methods */\n        startPolling: function () {\n            var timeout = 3000;\n            //\n            if(this._pollCount >= 10 && this._pollCount < 20) {\n                timeout = 10000;\n            }\n            else if(this._pollCount >= 20) {\n                timeout = 30000;\n            }\n            //\n            setTimeout(this.poll.bind(this), timeout);\n            this._pollCount ++;\n        },\n        poll: function () {\n            var self = this;\n            this._rpc({\n                route: '/payment/status/poll',\n                params: {\n                    'csrf_token': core.csrf_token,\n                }\n            }).then(function(data) {\n                if(data.success === true) {\n                    self.processPolledData(data.display_values_list);\n                }\n                else {\n                    switch(data.error) {\n                    case \"tx_process_retry\":\n                        break;\n                    case \"no_tx_found\":\n                        self.displayContent(\"payment.no_tx_found\", {});\n                        break;\n                    default: // if an exception is raised\n                        self.displayContent(\"payment.exception\", {exception_msg: data.error});\n                        break;\n                    }\n                }\n                self.startPolling();\n\n            }).guardedCatch(function() {\n                self.displayContent(\"payment.rpc_error\", {});\n                self.startPolling();\n            });\n        },\n        processPolledData: function (display_values_list) {\n            var render_values = {\n                'tx_draft': [],\n                'tx_pending': [],\n                'tx_authorized': [],\n                'tx_done': [],\n                'tx_cancel': [],\n                'tx_error': [],\n            };\n\n            if (display_values_list.length > 0) {\n                // In almost every cases there will be a single transaction to display. If there are\n                // more than one transaction, the last one will most likely be the one that was\n                // confirmed. We use this one to redirect the user to the final page.\n                window.location = display_values_list[0].landing_route;\n                return;\n            }\n\n            // group the transaction according to their state\n            display_values_list.forEach(function (display_values) {\n                var key = 'tx_' + display_values.state;\n                if(key in render_values) {\n                    if (display_values[\"display_message\"]) {\n                        display_values.display_message = Markup(display_values.display_message)\n                    }\n                    render_values[key].push(display_values);\n                }\n            });\n\n            function countTxInState(states) {\n                var nbTx = 0;\n                for (var prop in render_values) {\n                    if (states.indexOf(prop) > -1 && render_values.hasOwnProperty(prop)) {\n                        nbTx += render_values[prop].length;\n                    }\n                }\n                return nbTx;\n            }\n                       \n            /*\n            * When the server sends the list of monitored transactions, it tries to post-process \n            * all the successful ones. If it succeeds or if the post-process has already been made, \n            * the transaction is removed from the list of monitored transactions and won't be \n            * included in the next response. We assume that successful and post-process \n            * transactions should always prevail on others, regardless of their number or state.\n            */\n            if (render_values['tx_done'].length === 1 &&\n                render_values['tx_done'][0].is_post_processed) {\n                    window.location = render_values['tx_done'][0].landing_route;\n                    return;\n            }\n            // If there are multiple transactions monitored, display them all to the customer. If\n            // there is only one transaction monitored, redirect directly the customer to the\n            // landing route.\n            if(countTxInState(['tx_done', 'tx_error', 'tx_pending', 'tx_authorized']) === 1) {\n                // We don't want to redirect customers to the landing page when they have a pending\n                // transaction. The successful transactions are dealt with before.\n                var tx = render_values['tx_authorized'][0] || render_values['tx_error'][0];\n                if (tx) {\n                    window.location = tx.landing_route;\n                    return;\n                }\n            }\n\n            this.displayContent(\"payment.display_tx_list\", render_values);\n        },\n        displayContent: function (xmlid, render_values) {\n            var html = core.qweb.render(xmlid, render_values);\n            $.unblockUI();\n            this.$el.find('div[name=\"o_payment_status_content\"]').html(html);\n        },\n        displayLoading: function () {\n            var msg = _t(\"We are processing your payment, please wait ...\");\n            $.blockUI({\n                'message': '<h2 class=\"text-white\"><img src=\"/web/static/img/spin.png\" class=\"fa-pulse\"/>' +\n                    '    <br />' + msg +\n                    '</h2>'\n            });\n        },\n    });\n});\n", "odoo.define('sale.SalePortalSidebar', function (require) {\n'use strict';\n\nvar publicWidget = require('web.public.widget');\nvar PortalSidebar = require('portal.PortalSidebar');\n\npublicWidget.registry.SalePortalSidebar = PortalSidebar.extend({\n    selector: '.o_portal_sale_sidebar',\n\n    /**\n     * @constructor\n     */\n    init: function (parent, options) {\n        this._super.apply(this, arguments);\n        this.authorizedTextTag = ['em', 'b', 'i', 'u'];\n        this.spyWatched = $('body[data-target=\".navspy\"]');\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        var def = this._super.apply(this, arguments);\n        var $spyWatcheElement = this.$el.find('[data-id=\"portal_sidebar\"]');\n        this._setElementId($spyWatcheElement);\n        // Nav Menu ScrollSpy\n        this._generateMenu();\n        // After singature, automatically open the popup for payment\n        if ($.bbq.getState('allow_payment') === 'yes' && this.$('#o_sale_portal_paynow').length) {\n            this.$('#o_sale_portal_paynow').trigger('click');\n            $.bbq.removeState('allow_payment');\n        }\n        return def;\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //---------------------------------------------------------------------------\n\n    /**\n     * create an unique id and added as a attribute of spyWatched element\n     *\n     * @private\n     * @param {string} prefix\n     * @param {Object} $el\n     *\n     */\n    _setElementId: function (prefix, $el) {\n        var id = _.uniqueId(prefix);\n        this.spyWatched.find($el).attr('id', id);\n        return id;\n    },\n    /**\n     * generate the new spy menu\n     *\n     * @private\n     *\n     */\n    _generateMenu: function () {\n        var self = this,\n            lastLI = false,\n            lastUL = null,\n            $bsSidenav = this.$el.find('.bs-sidenav');\n\n        $(\"#quote_content [id^=quote_header_], #quote_content [id^=quote_]\", this.spyWatched).attr(\"id\", \"\");\n        _.each(this.spyWatched.find(\"#quote_content h2, #quote_content h3\"), function (el) {\n            var id, text;\n            switch (el.tagName.toLowerCase()) {\n                case \"h2\":\n                    id = self._setElementId('quote_header_', el);\n                    text = self._extractText($(el));\n                    if (!text) {\n                        break;\n                    }\n                    lastLI = $(\"<li class='nav-item'>\").append($('<a class=\"nav-link\" style=\"max-width: 200px;\" href=\"#' + id + '\"/>').text(text)).appendTo($bsSidenav);\n                    lastUL = false;\n                    break;\n                case \"h3\":\n                    id = self._setElementId('quote_', el);\n                    text = self._extractText($(el));\n                    if (!text) {\n                        break;\n                    }\n                    if (lastLI) {\n                        if (!lastUL) {\n                            lastUL = $(\"<ul class='nav flex-column'>\").appendTo(lastLI);\n                        }\n                        $(\"<li class='nav-item'>\").append($('<a class=\"nav-link\" style=\"max-width: 200px;\" href=\"#' + id + '\"/>').text(text)).appendTo(lastUL);\n                    }\n                    break;\n            }\n            el.setAttribute('data-anchor', true);\n        });\n        this.trigger_up('widgets_start_request', {$target: $bsSidenav});\n    },\n    /**\n     * extract text of menu title for sidebar\n     *\n     * @private\n     * @param {Object} $node\n     *\n     */\n    _extractText: function ($node) {\n        var self = this;\n        var rawText = [];\n        _.each($node.contents(), function (el) {\n            var current = $(el);\n            if ($.trim(current.text())) {\n                var tagName = current.prop(\"tagName\");\n                if (_.isUndefined(tagName) || (!_.isUndefined(tagName) && _.contains(self.authorizedTextTag, tagName.toLowerCase()))) {\n                    rawText.push($.trim(current.text()));\n                }\n            }\n        });\n        return rawText.join(' ');\n    },\n});\n});\n", "odoo.define('sale.payment_form', require => {\n    'use strict';\n\n    const checkoutForm = require('payment.checkout_form');\n    const manageForm = require('payment.manage_form');\n\n    const salePaymentMixin = {\n\n        //--------------------------------------------------------------------------\n        // Private\n        //--------------------------------------------------------------------------\n\n        /**\n         * Add `sale_order_id` to the transaction route params if it is provided.\n         *\n         * @override method from payment.payment_form_mixin\n         * @private\n         * @param {string} provider - The provider of the selected payment option's acquirer\n         * @param {number} paymentOptionId - The id of the selected payment option\n         * @param {string} flow - The online payment flow of the selected payment option\n         * @return {object} The extended transaction route params\n         */\n        _prepareTransactionRouteParams: function (provider, paymentOptionId, flow) {\n            const transactionRouteParams = this._super(...arguments);\n            return {\n                ...transactionRouteParams,\n                'sale_order_id': this.txContext.saleOrderId\n                    ? parseInt(this.txContext.saleOrderId) : undefined,\n            };\n        },\n\n    };\n\n    checkoutForm.include(salePaymentMixin);\n    manageForm.include(salePaymentMixin);\n\n});\n", "odoo.define('sale_management.sale_management', function (require) {\n'use strict';\n\nvar publicWidget = require('web.public.widget');\n\npublicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({\n    selector: '.o_portal_sale_sidebar',\n    events: {\n        'click a.js_update_line_json': '_onClick',\n        'click a.js_add_optional_products': '_onClickOptionalProduct',\n        'change .js_quantity': '_onChangeQuantity'\n    },\n    /**\n     * @override\n     */\n    async start() {\n        await this._super(...arguments);\n        this.orderDetail = this.$el.find('table#sales_order_table').data();\n    },\n    /**\n     * Process the change in line quantity\n     *\n     * @private\n     * @param {Event} ev\n     */\n    _onChangeQuantity(ev) {\n        ev.preventDefault();\n        let self = this,\n            $target = $(ev.currentTarget),\n            quantity = parseInt($target.val());\n\n        this._callUpdateLineRoute(self.orderDetail.orderId, {\n            'line_id': $target.data('lineId'),\n            'input_quantity': quantity >= 0 ? quantity : false,\n            'access_token': self.orderDetail.token\n        }).then((data) => {\n            window.location.reload();\n        });\n    },\n    /**\n     * Reacts to the click on the -/+ buttons\n     *\n     * @param {Event} ev\n     */\n    _onClick(ev) {\n        ev.preventDefault();\n        let self = this,\n            $target = $(ev.currentTarget);\n        this._callUpdateLineRoute(self.orderDetail.orderId, {\n            'line_id': $target.data('lineId'),\n            'remove': $target.data('remove'),\n            'unlink': $target.data('unlink'),\n            'access_token': self.orderDetail.token\n        }).then((data) => {\n            window.location.reload();\n        });\n    },\n    /**\n     * trigger when optional product added to order from portal.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    _onClickOptionalProduct(ev) {\n        ev.preventDefault();\n        let self = this,\n            $target = $(ev.currentTarget);\n        // to avoid double click on link with href.\n        $target.css('pointer-events', 'none');\n\n        this._rpc({\n            route: \"/my/orders/\" + self.orderDetail.orderId + \"/add_option/\" + $target.data('optionId'),\n            params: {access_token: self.orderDetail.token}\n        }).then((data) => {\n            window.location.reload();\n        });\n    },\n    /**\n     * Calls the route to get updated values of the line and order\n     * when the quantity of a product has changed\n     *\n     * @private\n     * @param {integer} order_id\n     * @param {Object} params\n     * @return {Deferred}\n     */\n    _callUpdateLineRoute(order_id, params) {\n        return this._rpc({\n            route: \"/my/orders/\" + order_id + \"/update_line_dict\",\n            params: params,\n        });\n    },\n});\n});\n", "odoo.define('google_recaptcha.ReCaptchaV3', function (require) {\n\"use strict\";\n\nconst ajax = require('web.ajax');\nconst Class = require('web.Class');\nconst core = require('web.core');\nconst { session } = require('@web/session');\n\nconst _t = core._t;\n\nconst ReCaptcha = Class.extend({\n    /**\n     * @override\n     */\n    init: function () {\n        this._publicKey = session.recaptcha_public_key;\n    },\n    /**\n     * Loads the recaptcha libraries.\n     *\n     * @returns {Promise|boolean} promise if libs are loading else false if the reCaptcha key is empty.\n     */\n    loadLibs: function () {\n        if (this._publicKey) {\n            this._recaptchaReady = ajax.loadJS(`https://www.recaptcha.net/recaptcha/api.js?render=${encodeURIComponent(this._publicKey)}`)\n                .then(() => new Promise(resolve => window.grecaptcha.ready(() => resolve())));\n            return this._recaptchaReady.then(() => !!document.querySelector('.grecaptcha-badge'));\n        }\n        return false;\n    },\n    /**\n     * Returns an object with the token if reCaptcha call succeeds\n     * If no key is set an object with a message is returned\n     * If an error occurred an object with the error message is returned\n     *\n     * @param {string} action\n     * @returns {Promise|Object}\n     */\n    getToken: async function (action) {\n        if (!this._publicKey) {\n            return {\n                message: _t(\"No recaptcha site key set.\"),\n            };\n        }\n        await this._recaptchaReady;\n        try {\n            return {\n                token: await window.grecaptcha.execute(this._publicKey, {action: action})\n            };\n        } catch (e) {\n            return {\n                error: _t(\"The recaptcha site key is invalid.\"),\n            };\n        }\n    },\n});\n\nreturn {\n    ReCaptcha: ReCaptcha,\n};\n});\n", "odoo.define('website.utils', function (require) {\n'use strict';\n\nvar ajax = require('web.ajax');\nvar core = require('web.core');\n\nconst { qweb, _t } = core;\n\n/**\n * Allows to load anchors from a page.\n *\n * @param {string} url\n * @returns {Deferred<string[]>}\n */\nfunction loadAnchors(url) {\n    return new Promise(function (resolve, reject) {\n        if (url === window.location.pathname || url[0] === '#') {\n            resolve(document.body.outerHTML);\n        } else if (url.length && !url.startsWith(\"http\")) {\n            $.get(window.location.origin + url).then(resolve, reject);\n        } else { // avoid useless query\n            resolve();\n        }\n    }).then(function (response) {\n        const anchors = _.map($(response).find('[id][data-anchor=true]'), function (el) {\n            return '#' + el.id;\n        });\n        // Always suggest the top and the bottom of the page as internal link\n        // anchor even if the header and the footer are not in the DOM. Indeed,\n        // the \"scrollTo\" function handles the scroll towards those elements\n        // even when they are not in the DOM.\n        if (!anchors.includes('#top')) {\n            anchors.unshift('#top');\n        }\n        if (!anchors.includes('#bottom')) {\n            anchors.push('#bottom');\n        }\n        return anchors;\n    }).catch(error => {\n        console.debug(error);\n        return [];\n    });\n}\n\n/**\n * Allows the given input to propose existing website URLs.\n *\n * @param {ServicesMixin|Widget} self - an element capable to trigger an RPC\n * @param {jQuery} $input\n */\nfunction autocompleteWithPages(self, $input, options) {\n    $.widget(\"website.urlcomplete\", $.ui.autocomplete, {\n        options: options || {},\n        _create: function () {\n            this._super();\n            this.widget().menu(\"option\", \"items\", \"> :not(.ui-autocomplete-category)\");\n        },\n        _renderMenu: function (ul, items) {\n            const self = this;\n            items.forEach(item => {\n                if (item.separator) {\n                    self._renderSeparator(ul, item);\n                }\n                else {\n                    self._renderItem(ul, item);\n                }\n            });\n        },\n        _renderSeparator: function (ul, item) {\n            return $(\"<li class='ui-autocomplete-category font-weight-bold text-capitalize p-2'>\")\n                   .append(`<div>${item.separator}</div>`)\n                   .appendTo(ul);\n        },\n        _renderItem: function (ul, item) {\n            return $(\"<li>\")\n                   .data('ui-autocomplete-item', item)\n                   .append(`<div>${item.label}</div>`)\n                   .appendTo(ul);\n        },\n    });\n    $input.urlcomplete({\n        source: function (request, response) {\n            if (request.term[0] === '#') {\n                loadAnchors(request.term).then(function (anchors) {\n                    response(anchors);\n                });\n            } else if (request.term.startsWith('http') || request.term.length === 0) {\n                // avoid useless call to /website/get_suggested_links\n                response();\n            } else {\n                return self._rpc({\n                    route: '/website/get_suggested_links',\n                    params: {\n                        needle: request.term,\n                        limit: 15,\n                    }\n                }).then(function (res) {\n                    let choices = res.matching_pages;\n                    res.others.forEach(other => {\n                        if (other.values.length) {\n                            choices = choices.concat(\n                                [{separator: other.title}],\n                                other.values,\n                            );\n                        }\n                    });\n                    response(choices);\n                });\n            }\n        },\n        select: function (ev, ui) {\n            // choose url in dropdown with arrow change ev.target.value without trigger_up\n            // so cannot check here if value has been updated\n            ev.target.value = ui.item.value;\n            self.trigger_up('website_url_chosen');\n            ev.preventDefault();\n        },\n    });\n}\n\n/**\n * @param {jQuery} $element\n * @param {jQuery} [$excluded]\n */\nfunction onceAllImagesLoaded($element, $excluded) {\n    var defs = _.map($element.find('img').addBack('img'), function (img) {\n        if (img.complete || $excluded && ($excluded.is(img) || $excluded.has(img).length)) {\n            return; // Already loaded\n        }\n        var def = new Promise(function (resolve, reject) {\n            $(img).one('load', function () {\n                resolve();\n            });\n        });\n        return def;\n    });\n    return Promise.all(defs);\n}\n\n/**\n * @deprecated\n * @todo create Dialog.prompt instead of this\n */\nfunction prompt(options, _qweb) {\n    /**\n     * A bootstrapped version of prompt() albeit asynchronous\n     * This was built to quickly prompt the user with a single field.\n     * For anything more complex, please use editor.Dialog class\n     *\n     * Usage Ex:\n     *\n     * website.prompt(\"What... is your quest ?\").then(function (answer) {\n     *     arthur.reply(answer || \"To seek the Holy Grail.\");\n     * });\n     *\n     * website.prompt({\n     *     select: \"Please choose your destiny\",\n     *     init: function () {\n     *         return [ [0, \"Sub-Zero\"], [1, \"Robo-Ky\"] ];\n     *     }\n     * }).then(function (answer) {\n     *     mame_station.loadCharacter(answer);\n     * });\n     *\n     * @param {Object|String} options A set of options used to configure the prompt or the text field name if string\n     * @param {String} [options.window_title=''] title of the prompt modal\n     * @param {String} [options.input] tell the modal to use an input text field, the given value will be the field title\n     * @param {String} [options.textarea] tell the modal to use a textarea field, the given value will be the field title\n     * @param {String} [options.select] tell the modal to use a select box, the given value will be the field title\n     * @param {Object} [options.default=''] default value of the field\n     * @param {Function} [options.init] optional function that takes the `field` (enhanced with a fillWith() method) and the `dialog` as parameters [can return a promise]\n     */\n    if (typeof options === 'string') {\n        options = {\n            text: options\n        };\n    }\n    var xmlDef;\n    if (_.isUndefined(_qweb)) {\n        _qweb = 'website.prompt';\n        xmlDef = ajax.loadXML('/website/static/src/xml/website.xml', core.qweb);\n    }\n    options = _.extend({\n        window_title: '',\n        field_name: '',\n        'default': '', // dict notation for IE<9\n        init: function () {},\n        btn_primary_title: _t('Create'),\n        btn_secondary_title: _t('Cancel'),\n    }, options || {});\n\n    var type = _.intersection(Object.keys(options), ['input', 'textarea', 'select']);\n    type = type.length ? type[0] : 'input';\n    options.field_type = type;\n    options.field_name = options.field_name || options[type];\n\n    var def = new Promise(function (resolve, reject) {\n        Promise.resolve(xmlDef).then(function () {\n            var dialog = $(qweb.render(_qweb, options)).appendTo('body');\n            options.$dialog = dialog;\n            var field = dialog.find(options.field_type).first();\n            field.val(options['default']); // dict notation for IE<9\n            field.fillWith = function (data) {\n                if (field.is('select')) {\n                    var select = field[0];\n                    data.forEach(function (item) {\n                        select.options[select.options.length] = new window.Option(item[1], item[0]);\n                    });\n                } else {\n                    field.val(data);\n                }\n            };\n            var init = options.init(field, dialog);\n            Promise.resolve(init).then(function (fill) {\n                if (fill) {\n                    field.fillWith(fill);\n                }\n                dialog.modal('show');\n                field.focus();\n                dialog.on('click', '.btn-primary', function () {\n                    var backdrop = $('.modal-backdrop');\n                    resolve({ val: field.val(), field: field, dialog: dialog });\n                    dialog.modal('hide').remove();\n                        backdrop.remove();\n                });\n            });\n            dialog.on('hidden.bs.modal', function () {\n                    var backdrop = $('.modal-backdrop');\n                reject();\n                dialog.remove();\n                    backdrop.remove();\n            });\n            if (field.is('input[type=\"text\"], select')) {\n                field.keypress(function (e) {\n                    if (e.which === 13) {\n                        e.preventDefault();\n                        dialog.find('.btn-primary').trigger('click');\n                    }\n                });\n            }\n        });\n    });\n\n    return def;\n}\n\nfunction websiteDomain(self) {\n    var websiteID;\n    self.trigger_up('context_get', {\n        callback: function (ctx) {\n            websiteID = ctx['website_id'];\n        },\n    });\n    return ['|', ['website_id', '=', false], ['website_id', '=', websiteID]];\n}\n\nfunction sendRequest(route, params) {\n    function _addInput(form, name, value) {\n        let param = document.createElement('input');\n        param.setAttribute('type', 'hidden');\n        param.setAttribute('name', name);\n        param.setAttribute('value', value);\n        form.appendChild(param);\n    }\n\n    let form = document.createElement('form');\n    form.setAttribute('action', route);\n    form.setAttribute('method', params.method || 'POST');\n\n    if (core.csrf_token) {\n        _addInput(form, 'csrf_token', core.csrf_token);\n    }\n\n    for (const key in params) {\n        const value = params[key];\n        if (Array.isArray(value) && value.length) {\n            for (const val of value) {\n                _addInput(form, key, val);\n            }\n        } else {\n            _addInput(form, key, value);\n        }\n    }\n\n    document.body.appendChild(form);\n    form.submit();\n}\n\n/**\n * Converts a base64 SVG into a base64 PNG.\n *\n * @param {string|HTMLImageElement} src - an URL to a SVG or a *loaded* image\n *      with such an URL. This allows the call to potentially be a bit more\n *      efficient in that second case.\n * @returns {Promise<string>} a base64 PNG (as result of a Promise)\n */\nasync function svgToPNG(src) {\n    function checkImg(imgEl) {\n        // Firefox does not support drawing SVG to canvas unless it has width\n        // and height attributes set on the root <svg>.\n        return (imgEl.naturalHeight !== 0);\n    }\n    function toPNGViaCanvas(imgEl) {\n        const canvas = document.createElement('canvas');\n        canvas.width = imgEl.width;\n        canvas.height = imgEl.height;\n        canvas.getContext('2d').drawImage(imgEl, 0, 0);\n        return canvas.toDataURL('image/png');\n    }\n\n    // In case we receive a loaded image and that this image is not problematic,\n    // we can convert it to PNG directly.\n    if (src instanceof HTMLImageElement) {\n        const loadedImgEl = src;\n        if (checkImg(loadedImgEl)) {\n            return toPNGViaCanvas(loadedImgEl);\n        }\n        src = loadedImgEl.src;\n    }\n\n    // At this point, we either did not receive a loaded image or the received\n    // loaded image is problematic => we have to do some asynchronous code.\n    return new Promise(resolve => {\n        const imgEl = new Image();\n        imgEl.onload = () => {\n            if (checkImg(imgEl)) {\n                resolve(imgEl);\n                return;\n            }\n\n            // Set arbitrary height on image and attach it to the DOM to force\n            // width computation.\n            imgEl.height = 1000;\n            imgEl.style.opacity = 0;\n            document.body.appendChild(imgEl);\n\n            const request = new XMLHttpRequest();\n            request.open('GET', imgEl.src, true);\n            request.onload = () => {\n                // Convert the data URI to a SVG element\n                const parser = new DOMParser();\n                const result = parser.parseFromString(request.responseText, 'text/xml');\n                const svgEl = result.getElementsByTagName(\"svg\")[0];\n\n                // Add the attributes Firefox needs and remove the image from\n                // the DOM.\n                svgEl.setAttribute('width', imgEl.width);\n                svgEl.setAttribute('height', imgEl.height);\n                imgEl.remove();\n\n                // Convert the SVG element to a data URI\n                const svg64 = btoa(new XMLSerializer().serializeToString(svgEl));\n                const finalImg = new Image();\n                finalImg.onload = () => {\n                    resolve(finalImg);\n                };\n                finalImg.src = `data:image/svg+xml;base64,${svg64}`;\n            };\n            request.send();\n        };\n        imgEl.src = src;\n    }).then(loadedImgEl => toPNGViaCanvas(loadedImgEl));\n}\n\n/**\n * Generates a Google Maps URL based on the given parameter.\n *\n * @param {DOMStringMap} dataset\n * @returns {string} a Google Maps URL\n */\nfunction generateGMapLink(dataset) {\n    return 'https://maps.google.com/maps?q=' + encodeURIComponent(dataset.mapAddress)\n            + '&t=' + encodeURIComponent(dataset.mapType)\n            + '&z=' + encodeURIComponent(dataset.mapZoom)\n            + '&ie=UTF8&iwloc=&output=embed';\n}\n\nreturn {\n    loadAnchors: loadAnchors,\n    autocompleteWithPages: autocompleteWithPages,\n    onceAllImagesLoaded: onceAllImagesLoaded,\n    prompt: prompt,\n    sendRequest: sendRequest,\n    websiteDomain: websiteDomain,\n    svgToPNG: svgToPNG,\n    generateGMapLink: generateGMapLink,\n};\n});\n", "/** @odoo-module alias=website.root */\n\nimport ajax from 'web.ajax';\nimport { _t } from 'web.core';\nimport KeyboardNavigationMixin from 'web.KeyboardNavigationMixin';\nimport {Markup} from 'web.utils';\nimport session from 'web.session';\nimport publicRootData from 'web.public.root';\nimport \"web.zoomodoo\";\nimport { FullscreenIndication } from '@website/js/widgets/fullscreen_indication';\n\nexport const WebsiteRoot = publicRootData.PublicRoot.extend(KeyboardNavigationMixin, {\n    // TODO remove KeyboardNavigationMixin in master\n    events: _.extend({}, KeyboardNavigationMixin.events, publicRootData.PublicRoot.prototype.events || {}, {\n        'click .js_change_lang': '_onLangChangeClick',\n        'click .js_publish_management .js_publish_btn': '_onPublishBtnClick',\n        'click .js_multi_website_switch': '_onWebsiteSwitch',\n        'shown.bs.modal': '_onModalShown',\n    }),\n    custom_events: _.extend({}, publicRootData.PublicRoot.prototype.custom_events || {}, {\n        'gmap_api_request': '_onGMapAPIRequest',\n        'gmap_api_key_request': '_onGMapAPIKeyRequest',\n        'ready_to_clean_for_save': '_onWidgetsStopRequest',\n        'seo_object_request': '_onSeoObjectRequest',\n        'will_remove_snippet': '_onWidgetsStopRequest',\n    }),\n\n    /**\n     * @override\n     */\n    init() {\n        this.isFullscreen = false;\n        KeyboardNavigationMixin.init.call(this, {\n            autoAccessKeys: false,\n            skipRenderOverlay: true,\n        });\n\n        // Special case for Safari browser: padding on wrapwrap is added by the\n        // layout option (boxed, etc), but it also receives a border on top of\n        // it to simulate an addition of padding. That padding is added with\n        // the \"sidebar\" header template to combine both options/effects.\n        // Sadly, the border hack is not working on safari, the menu is somehow\n        // broken and its content is not visible.\n        // This class will be used in scss to instead add the border size to the\n        // padding directly on Safari when \"sidebar\" menu is enabled.\n        if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent) && document.querySelector('#wrapwrap')) {\n            document.querySelector('#wrapwrap').classList.add('o_safari_browser');\n        }\n\n        return this._super(...arguments);\n    },\n    /**\n     * @override\n     */\n    willStart: async function () {\n        this.fullscreenIndication = new FullscreenIndication(this);\n        return Promise.all([\n            this._super(...arguments),\n            this.fullscreenIndication.appendTo(document.body),\n        ]);\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        KeyboardNavigationMixin.start.call(this);\n        // Compatibility lang change ?\n        if (!this.$('.js_change_lang').length) {\n            var $links = this.$('.js_language_selector a:not([data-oe-id])');\n            var m = $(_.min($links, function (l) {\n                return $(l).attr('href').length;\n            })).attr('href');\n            $links.each(function () {\n                var $link = $(this);\n                var t = $link.attr('href');\n                var l = (t === m) ? \"default\" : t.split('/')[1];\n                $link.data('lang', l).addClass('js_change_lang');\n            });\n        }\n\n        // Enable magnify on zommable img\n        this.$('.zoomable img[data-zoom]').zoomOdoo();\n\n        return this._super.apply(this, arguments);\n    },\n    /**\n     * @override\n     */\n    destroy() {\n        KeyboardNavigationMixin.destroy.call(this);\n        return this._super(...arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @override\n     */\n    _getContext: function (context) {\n        var html = document.documentElement;\n        return _.extend({\n            'website_id': html.getAttribute('data-website-id') | 0,\n        }, this._super.apply(this, arguments));\n    },\n    /**\n     * @override\n     */\n    _getExtraContext: function (context) {\n        var html = document.documentElement;\n        return _.extend({\n            'editable': !!(html.dataset.editable || $('[data-oe-model]').length), // temporary hack, this should be done in python\n            'translatable': !!html.dataset.translatable,\n            'edit_translations': !!html.dataset.edit_translations,\n        }, this._super.apply(this, arguments));\n    },\n    /**\n     * @private\n     * @param {boolean} [refetch=false]\n     */\n    async _getGMapAPIKey(refetch) {\n        if (refetch || !this._gmapAPIKeyProm) {\n            this._gmapAPIKeyProm = new Promise(async resolve => {\n                const data = await this._rpc({\n                    route: '/website/google_maps_api_key',\n                });\n                resolve(JSON.parse(data).google_maps_api_key || '');\n            });\n        }\n        return this._gmapAPIKeyProm;\n    },\n    /**\n     * @override\n     */\n    _getPublicWidgetsRegistry: function (options) {\n        var registry = this._super.apply(this, arguments);\n        if (options.editableMode) {\n            return _.pick(registry, function (PublicWidget) {\n                return !PublicWidget.prototype.disabledInEditableMode;\n            });\n        }\n        return registry;\n    },\n    /**\n     * @private\n     * @param {boolean} [editableMode=false]\n     * @param {boolean} [refetch=false]\n     */\n    async _loadGMapAPI(editableMode, refetch) {\n        // Note: only need refetch to reload a configured key and load the\n        // library. If the library was loaded with a correct key and that the\n        // key changes meanwhile... it will not work but we can agree the user\n        // can bother to reload the page at that moment.\n        if (refetch || !this._gmapAPILoading) {\n            this._gmapAPILoading = new Promise(async resolve => {\n                const key = await this._getGMapAPIKey(refetch);\n\n                window.odoo_gmap_api_post_load = (async function odoo_gmap_api_post_load() {\n                    await this._startWidgets(undefined, {editableMode: editableMode});\n                    resolve(key);\n                }).bind(this);\n\n                if (!key) {\n                    if (!editableMode && session.is_admin) {\n                        const message = _t(\"Cannot load google map.\");\n                        const urlTitle = _t(\"Check your configuration.\");\n                        this.displayNotification({\n                            type: 'warning',\n                            sticky: true,\n                            message:\n                                Markup`<div>\n                                    <span>${message}</span><br/>\n                                    <a href=\"/web#action=website.action_website_configuration\">${urlTitle}</a>\n                                </div>`,\n                        });\n                    }\n                    resolve(false);\n                    this._gmapAPILoading = false;\n                    return;\n                }\n                await ajax.loadJS(`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=places&callback=odoo_gmap_api_post_load&key=${encodeURIComponent(key)}`);\n            });\n        }\n        return this._gmapAPILoading;\n    },\n    /**\n     * Toggles the fullscreen mode.\n     *\n     * @private\n     * @param {boolean} state toggle fullscreen on/off (true/false)\n     */\n    _toggleFullscreen(state) {\n        this.isFullscreen = state;\n        if (this.isFullscreen) {\n            this.fullscreenIndication.show();\n        } else {\n            this.fullscreenIndication.hide();\n        }\n        document.body.classList.add('o_fullscreen_transition');\n        document.body.classList.toggle('o_fullscreen', this.isFullscreen);\n        document.body.style.overflowX = 'hidden';\n        let resizing = true;\n        window.requestAnimationFrame(function resizeFunction() {\n            window.dispatchEvent(new Event('resize'));\n            if (resizing) {\n                window.requestAnimationFrame(resizeFunction);\n            }\n        });\n        let stopResizing;\n        const onTransitionEnd = ev => {\n            if (ev.target === document.body && ev.propertyName === 'padding-top') {\n                stopResizing();\n            }\n        };\n        stopResizing = () => {\n            resizing = false;\n            document.body.style.overflowX = '';\n            document.body.removeEventListener('transitionend', onTransitionEnd);\n            document.body.classList.remove('o_fullscreen_transition');\n        };\n        document.body.addEventListener('transitionend', onTransitionEnd);\n        // Safeguard in case the transitionend event doesn't trigger for whatever reason.\n        window.setTimeout(() => stopResizing(), 500);\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @override\n     */\n    _onWidgetsStartRequest: function (ev) {\n        ev.data.options = _.clone(ev.data.options || {});\n        ev.data.options.editableMode = ev.data.editableMode;\n        this._super.apply(this, arguments);\n    },\n    /**\n     * @todo review\n     * @private\n     */\n    _onLangChangeClick: function (ev) {\n        ev.preventDefault();\n\n        var $target = $(ev.currentTarget);\n        // retrieve the hash before the redirect\n        var redirect = {\n            lang: encodeURIComponent($target.data('url_code')),\n            url: encodeURIComponent($target.attr('href').replace(/[&?]edit_translations[^&?]+/, '')),\n            hash: encodeURIComponent(window.location.hash)\n        };\n        window.location.href = _.str.sprintf(\"/website/lang/%(lang)s?r=%(url)s%(hash)s\", redirect);\n    },\n    /**\n     * @private\n     * @param {OdooEvent} ev\n     */\n    async _onGMapAPIRequest(ev) {\n        ev.stopPropagation();\n        const apiKey = await this._loadGMapAPI(ev.data.editableMode, ev.data.refetch);\n        ev.data.onSuccess(apiKey);\n    },\n    /**\n     * @private\n     * @param {OdooEvent} ev\n     */\n    async _onGMapAPIKeyRequest(ev) {\n        ev.stopPropagation();\n        const apiKey = await this._getGMapAPIKey(ev.data.refetch);\n        ev.data.onSuccess(apiKey);\n    },\n    /**\n    /**\n     * Checks information about the page SEO object.\n     *\n     * @private\n     * @param {OdooEvent} ev\n     */\n    _onSeoObjectRequest: function (ev) {\n        var res = this._unslugHtmlDataObject('seo-object');\n        ev.data.callback(res);\n    },\n    /**\n     * Returns a model/id object constructed from html data attribute.\n     *\n     * @private\n     * @param {string} dataAttr\n     * @returns {Object} an object with 2 keys: model and id, or null\n     * if not found\n     */\n    _unslugHtmlDataObject: function (dataAttr) {\n        var repr = $('html').data(dataAttr);\n        var match = repr && repr.match(/(.+)\\((\\d+),(.*)\\)/);\n        if (!match) {\n            return null;\n        }\n        return {\n            model: match[1],\n            id: match[2] | 0,\n        };\n    },\n    /**\n     * @todo review\n     * @private\n     */\n    _onPublishBtnClick: function (ev) {\n        ev.preventDefault();\n        if (document.body.classList.contains('editor_enable')) {\n            return;\n        }\n\n        var self = this;\n        var $data = $(ev.currentTarget).parents(\".js_publish_management:first\");\n        this._rpc({\n            route: $data.data('controller') || '/website/publish',\n            params: {\n                id: +$data.data('id'),\n                object: $data.data('object'),\n            },\n        })\n        .then(function (result) {\n            $data.toggleClass(\"css_published\", result).toggleClass(\"css_unpublished\", !result);\n            $data.find('input').prop(\"checked\", result);\n            $data.parents(\"[data-publish]\").attr(\"data-publish\", +result ? 'on' : 'off');\n        });\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onWebsiteSwitch: function (ev) {\n        var websiteId = ev.currentTarget.getAttribute('website-id');\n        var websiteDomain = ev.currentTarget.getAttribute('domain');\n        let url = `/website/force/${encodeURIComponent(websiteId)}`;\n        if (websiteDomain && window.location.hostname !== websiteDomain) {\n            url = websiteDomain + url;\n        }\n        const path = window.location.pathname + window.location.search + window.location.hash;\n        window.location.href = $.param.querystring(url, {'path': path});\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onModalShown: function (ev) {\n        $(ev.target).addClass('modal_shown');\n    },\n    /**\n     * @override\n     */\n    _onKeyDown(ev) {\n        if (!session.user_id) {\n            return;\n        }\n        // If document.body doesn't contain the element, it was probably removed as a consequence of pressing Esc.\n        // we don't want to toggle fullscreen as the removal (eg, closing a modal) is the intended action.\n        if (ev.keyCode !== $.ui.keyCode.ESCAPE || !document.body.contains(ev.target) || ev.target.closest('.modal')) {\n            return KeyboardNavigationMixin._onKeyDown.apply(this, arguments);\n        }\n        this._toggleFullscreen(!this.isFullscreen);\n    },\n});\n\nexport default {\n    WebsiteRoot: WebsiteRoot,\n};\n", "/** @odoo-module **/\n\nimport Dialog from 'web.Dialog';\n\nDialog.include({\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @override\n     */\n    _isBlocking(index, el) {\n        if (el.parentElement && el.parentElement.id === 'website_cookies_bar'\n                && !el.classList.contains('o_cookies_popup')) {\n            return false;\n        }\n        return this._super(...arguments);\n    },\n});\n", "/** @odoo-module **/\n\nimport Widget from 'web.Widget';\n\nexport const FullscreenIndication = Widget.extend({\n    xmlDependencies: ['/website/static/src/xml/website.xml'],\n    template: 'website.fullscreen_indication', \n\n    init: function () {\n        this.visible = false;\n        this.displayTime = 2000;\n        this._super(...arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Public\n    //--------------------------------------------------------------------------\n\n    /**\n     * Displays the fullscreen indication modal\n     */\n    show: function () {\n        clearTimeout(this.autofade);\n        this.visible = true;\n        this.el.classList.add('o_transition', 'o_visible');\n        this.autofade = setTimeout(() => {\n            this.hide(true);\n        }, this.displayTime);\n    },\n    /**\n     * Hides the fullscreen indication modal with optionnal transition\n     * \n     * @param {boolean} [withTransition=false]\n     */\n    hide: function (withTransition = false) {\n        this.el.classList.toggle('o_transition', withTransition);\n        this.el.classList.remove('o_visible');\n        this.visible = false;\n    },\n});\n", "odoo.define('website.content.compatibility', function (require) {\n'use strict';\n\n/**\n * Tweaks the website rendering so that the old browsers correctly render the\n * content too.\n */\n\nrequire('web.dom_ready');\n\n// Check the browser and its version and add the info as an attribute of the\n// HTML element so that css selectors can match it\nvar browser = _.findKey($.browser, function (v) { return v === true; });\nif ($.browser.mozilla && +$.browser.version.replace(/^([0-9]+\\.[0-9]+).*/, '\\$1') < 20) {\n    browser = 'msie';\n}\nbrowser += (',' + $.browser.version);\nvar mobileRegex = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i;\nif (mobileRegex.test(window.navigator.userAgent.toLowerCase())) {\n    browser += ',mobile';\n}\ndocument.documentElement.setAttribute('data-browser', browser);\n\n// Check if flex is supported and add the info as an attribute of the HTML\n// element so that css selectors can match it (only if not supported)\nvar htmlStyle = document.documentElement.style;\nvar isFlexSupported = (('flexWrap' in htmlStyle)\n                    || ('WebkitFlexWrap' in htmlStyle)\n                    || ('msFlexWrap' in htmlStyle));\nif (!isFlexSupported) {\n    document.documentElement.setAttribute('data-no-flex', '');\n}\n\nreturn {\n    browser: browser,\n    isFlexSupported: isFlexSupported,\n};\n});\n", "odoo.define('website.content.menu', function (require) {\n'use strict';\n\nconst config = require('web.config');\nvar dom = require('web.dom');\nvar publicWidget = require('web.public.widget');\nvar animations = require('website.content.snippets.animation');\nconst extraMenuUpdateCallbacks = [];\nconst weUtils = require('web_editor.utils');\n\nconst BaseAnimatedHeader = animations.Animation.extend({\n    disabledInEditableMode: false,\n    effects: [{\n        startEvents: 'scroll',\n        update: '_updateHeaderOnScroll',\n    }, {\n        startEvents: 'resize',\n        update: '_updateHeaderOnResize',\n    }],\n\n    /**\n     * @constructor\n     */\n    init: function () {\n        this._super(...arguments);\n        this.fixedHeader = false;\n        this.scrolledPoint = 0;\n        this.hasScrolled = false;\n        this.closeOpenedMenus = false;\n        this.scrollHeightTooShort = false;\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        // Used to prevent the editor's unbreakable protection from restoring\n        // the menu's auto-hide updates in edit mode.\n        this.el.addEventListener(\"autoMoreMenu.willAdapt\", () => this.options.wysiwyg\n            && this.options.wysiwyg.odooEditor.unbreakableStepUnactive());\n        this.$main = this.$el.next('main');\n        this.isOverlayHeader = !!this.$el.closest('.o_header_overlay, .o_header_overlay_theme').length;\n        this.$dropdowns = this.$el.find('.dropdown, .dropdown-menu');\n        this.$navbarCollapses = this.$el.find('.navbar-collapse');\n\n        // While scrolling through navbar menus on medium devices, body should not be scrolled with it\n        this.$navbarCollapses.on('show.bs.collapse.BaseAnimatedHeader', function () {\n            if (config.device.size_class <= config.device.SIZES.SM) {\n                $(document.body).addClass('overflow-hidden');\n            }\n        }).on('hide.bs.collapse.BaseAnimatedHeader', function () {\n            $(document.body).removeClass('overflow-hidden');\n        });\n\n        // We can rely on transitionend which is well supported but not on\n        // transitionstart, so we listen to a custom odoo event.\n        this._transitionCount = 0;\n        this.$el.on('odoo-transitionstart.BaseAnimatedHeader', () => {\n            this.el.classList.add('o_transitioning');\n            this._adaptToHeaderChangeLoop(1);\n        });\n        this.$el.on('transitionend.BaseAnimatedHeader', () => this._adaptToHeaderChangeLoop(-1));\n\n        return this._super(...arguments);\n    },\n    /**\n     * @override\n     */\n    destroy: function () {\n        this._toggleFixedHeader(false);\n        this.$el.removeClass('o_header_affixed o_header_is_scrolled o_header_no_transition o_transitioning');\n        this.$navbarCollapses.off('.BaseAnimatedHeader');\n        this.$el.off('.BaseAnimatedHeader');\n        this._super(...arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _adaptFixedHeaderPosition() {\n        dom.compensateScrollbar(this.el, this.fixedHeader, false, 'right');\n    },\n    /**\n     * @private\n     */\n    _adaptToHeaderChange: function () {\n        this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerUnactive();\n        this._updateMainPaddingTop();\n        // Take menu into account when `dom.scrollTo()` is used whenever it is\n        // visible - be it floating, fully displayed or partially hidden.\n        this.el.classList.toggle('o_top_fixed_element', this._isShown());\n\n        for (const callback of extraMenuUpdateCallbacks) {\n            callback();\n        }\n        this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerActive();\n    },\n    /**\n     * @private\n     * @param {integer} [addCount=0]\n     */\n    _adaptToHeaderChangeLoop: function (addCount = 0) {\n        this._adaptToHeaderChange();\n\n        this._transitionCount += addCount;\n        this._transitionCount = Math.max(0, this._transitionCount);\n\n        // As long as we detected a transition start without its related\n        // transition end, keep updating the main padding top.\n        if (this._transitionCount > 0) {\n            window.requestAnimationFrame(() => this._adaptToHeaderChangeLoop());\n\n            // The normal case would be to have the transitionend event to be\n            // fired but we cannot rely on it, so we use a timeout as fallback.\n            if (addCount !== 0) {\n                clearTimeout(this._changeLoopTimer);\n                this._changeLoopTimer = setTimeout(() => {\n                    this._adaptToHeaderChangeLoop(-this._transitionCount);\n                }, 500);\n            }\n        } else {\n            // When we detected all transitionend events, we need to stop the\n            // setTimeout fallback.\n            clearTimeout(this._changeLoopTimer);\n            this.el.classList.remove('o_transitioning');\n        }\n    },\n    /**\n     * @private\n     */\n    _computeTopGap() {\n        return 0;\n    },\n    /**\n     * @private\n     */\n    _isShown() {\n        return true;\n    },\n    /**\n     * @private\n     * @param {boolean} [useFixed=true]\n     */\n    _toggleFixedHeader: function (useFixed = true) {\n        this.fixedHeader = useFixed;\n        this._adaptToHeaderChange();\n        this.el.classList.toggle('o_header_affixed', useFixed);\n        this._adaptFixedHeaderPosition();\n    },\n    /**\n     * @private\n     */\n    _updateMainPaddingTop: function () {\n        this.headerHeight = this.$el.outerHeight();\n        this.topGap = this._computeTopGap();\n\n        if (this.isOverlayHeader) {\n            return;\n        }\n        this.$main.css('padding-top', this.fixedHeader ? this.headerHeight : '');\n    },\n    /**\n     * Checks if the size of the header will decrease by adding the\n     * 'o_header_is_scrolled' class. If so, we do not add this class if the\n     * remaining scroll height is not enough to stay above 'this.scrolledPoint'\n     * after the transition, otherwise it causes the scroll position to move up\n     * again below 'this.scrolledPoint' and trigger an infinite loop.\n     *\n     * @todo header effects should be improved in the future to not ever change\n     * the page scroll-height during their animation. The code would probably be\n     * simpler but also prevent having weird scroll \"jumps\" during animations\n     * (= depending on the logo height after/before scroll, a scroll step (one\n     * mousewheel event for example) can be bigger than other ones).\n     *\n     * @private\n     * @returns {boolean}\n     */\n    _scrollHeightTooShort() {\n        const scrollEl = $().getScrollingElement()[0];\n        const remainingScroll = (scrollEl.scrollHeight - scrollEl.clientHeight) - this.scrolledPoint;\n        const clonedHeader = this.el.cloneNode(true);\n        scrollEl.append(clonedHeader);\n        clonedHeader.classList.add('o_header_is_scrolled', 'o_header_affixed', 'o_header_no_transition');\n        const endHeaderHeight = clonedHeader.offsetHeight;\n        clonedHeader.remove();\n        const heightDiff = this.headerHeight - endHeaderHeight;\n        return heightDiff > 0 ? remainingScroll <= heightDiff : false;\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Called when the window is scrolled\n     *\n     * @private\n     * @param {integer} scroll\n     */\n    _updateHeaderOnScroll: function (scroll) {\n        // Disable css transition if refresh with scrollTop > 0\n        if (!this.hasScrolled) {\n            this.hasScrolled = true;\n            if (scroll > 0) {\n                this.$el.addClass('o_header_no_transition');\n            }\n        } else {\n            this.$el.removeClass('o_header_no_transition');\n            this.closeOpenedMenus = true;\n        }\n\n        // Indicates the page is scrolled, the logo size is changed.\n        const headerIsScrolled = (scroll > this.scrolledPoint);\n        if (this.headerIsScrolled !== headerIsScrolled) {\n            this.scrollHeightTooShort = headerIsScrolled && this._scrollHeightTooShort();\n            if (!this.scrollHeightTooShort) {\n                this.el.classList.toggle('o_header_is_scrolled', headerIsScrolled);\n                this.$el.trigger('odoo-transitionstart');\n                this.headerIsScrolled = headerIsScrolled;\n            }\n        }\n\n        if (this.closeOpenedMenus) {\n            this.$dropdowns.removeClass('show');\n            this.$navbarCollapses.removeClass('show').attr('aria-expanded', false);\n        }\n    },\n    /**\n     * Called when the window is resized\n     *\n     * @private\n     */\n    _updateHeaderOnResize: function () {\n        this._adaptFixedHeaderPosition();\n        if (document.body.classList.contains('overflow-hidden')\n                && config.device.size_class > config.device.SIZES.SM) {\n            document.body.classList.remove('overflow-hidden');\n            this.$el.find('.navbar-collapse').removeClass('show');\n        }\n    },\n});\n\npublicWidget.registry.StandardAffixedHeader = BaseAnimatedHeader.extend({\n    selector: 'header.o_header_standard:not(.o_header_sidebar)',\n\n    /**\n     * @constructor\n     */\n    init: function () {\n        this._super(...arguments);\n        this.fixedHeaderShow = false;\n        this.scrolledPoint = 300;\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        this.headerHeight = this.$el.outerHeight();\n        return this._super.apply(this, arguments);\n    },\n    /**\n     * @override\n     */\n    destroy() {\n        this.$el.css('transform', '');\n        this._super(...arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @override\n     */\n    _isShown() {\n        return !this.fixedHeader || this.fixedHeaderShow;\n    },\n    /**\n     * Called when the window is scrolled\n     *\n     * @override\n     * @param {integer} scroll\n     */\n    _updateHeaderOnScroll: function (scroll) {\n        this._super(...arguments);\n\n        const mainPosScrolled = (scroll > this.headerHeight + this.topGap);\n        const reachPosScrolled = (scroll > this.scrolledPoint + this.topGap) && !this.scrollHeightTooShort;\n        const fixedUpdate = (this.fixedHeader !== mainPosScrolled);\n        const showUpdate = (this.fixedHeaderShow !== reachPosScrolled);\n\n        if (fixedUpdate || showUpdate) {\n            this.$el.css('transform',\n                reachPosScrolled\n                ? `translate(0, -${this.topGap}px)`\n                : mainPosScrolled\n                ? 'translate(0, -100%)'\n                : '');\n            void this.$el[0].offsetWidth; // Force a paint refresh\n        }\n\n        this.fixedHeaderShow = reachPosScrolled;\n\n        if (fixedUpdate) {\n            this._toggleFixedHeader(mainPosScrolled);\n        } else if (showUpdate) {\n            this._adaptToHeaderChange();\n        }\n    },\n});\n\npublicWidget.registry.FixedHeader = BaseAnimatedHeader.extend({\n    selector: 'header.o_header_fixed:not(.o_header_sidebar)',\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @override\n     */\n    _updateHeaderOnScroll: function (scroll) {\n        this._super(...arguments);\n        // Need to be 'unfixed' when the window is not scrolled so that the\n        // transparent menu option still works.\n        if (scroll > (this.scrolledPoint + this.topGap)) {\n            if (!this.$el.hasClass('o_header_affixed')) {\n                this.$el.css('transform', `translate(0, -${this.topGap}px)`);\n                void this.$el[0].offsetWidth; // Force a paint refresh\n                this._toggleFixedHeader(true);\n            }\n        } else {\n            this._toggleFixedHeader(false);\n            void this.$el[0].offsetWidth; // Force a paint refresh\n            this.$el.css('transform', '');\n        }\n    },\n});\n\nconst BaseDisappearingHeader = publicWidget.registry.FixedHeader.extend({\n    /**\n     * @override\n     */\n    init: function () {\n        this._super(...arguments);\n        this.scrollingDownwards = true;\n        this.hiddenHeader = false;\n        this.position = 0;\n        this.atTop = true;\n        this.checkPoint = 0;\n        this.scrollOffsetLimit = 200;\n    },\n    /**\n     * @override\n     */\n    destroy: function () {\n        this._showHeader();\n        this._super.apply(this, arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _hideHeader: function () {\n        this.$el.trigger('odoo-transitionstart');\n    },\n    /**\n     * @override\n     */\n    _isShown() {\n        return !this.fixedHeader || !this.hiddenHeader;\n    },\n    /**\n     * @private\n     */\n    _showHeader: function () {\n        this.$el.trigger('odoo-transitionstart');\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @override\n     */\n    _updateHeaderOnScroll: function (scroll) {\n        this._super(...arguments);\n\n        const scrollingDownwards = (scroll > this.position);\n        const atTop = (scroll <= 0);\n        if (scrollingDownwards !== this.scrollingDownwards) {\n            this.checkPoint = scroll;\n        }\n\n        this.scrollingDownwards = scrollingDownwards;\n        this.position = scroll;\n        this.atTop = atTop;\n\n        if (scrollingDownwards) {\n            if (!this.hiddenHeader && scroll - this.checkPoint > (this.scrollOffsetLimit + this.topGap)) {\n                this.hiddenHeader = true;\n                this._hideHeader();\n            }\n        } else {\n            if (this.hiddenHeader && scroll - this.checkPoint < -(this.scrollOffsetLimit + this.topGap) / 2) {\n                this.hiddenHeader = false;\n                this._showHeader();\n            }\n        }\n\n        if (atTop && !this.atTop) {\n            // Force reshowing the invisible-on-scroll sections when reaching\n            // the top again\n            this._showHeader();\n        }\n    },\n});\n\npublicWidget.registry.DisappearingHeader = BaseDisappearingHeader.extend({\n    selector: 'header.o_header_disappears:not(.o_header_sidebar)',\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @override\n     */\n    _hideHeader: function () {\n        this._super(...arguments);\n        this.$el.css('transform', 'translate(0, -100%)');\n    },\n    /**\n     * @override\n     */\n    _showHeader: function () {\n        this._super(...arguments);\n        this.$el.css('transform', this.atTop ? '' : `translate(0, -${this.topGap}px)`);\n    },\n});\n\npublicWidget.registry.FadeOutHeader = BaseDisappearingHeader.extend({\n    selector: 'header.o_header_fade_out:not(.o_header_sidebar)',\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @override\n     */\n    _hideHeader: function () {\n        this._super(...arguments);\n        this.$el.stop(false, true).fadeOut();\n    },\n    /**\n     * @override\n     */\n    _showHeader: function () {\n        this._super(...arguments);\n        this.$el.css('transform', this.atTop ? '' : `translate(0, -${this.topGap}px)`);\n        this.$el.stop(false, true).fadeIn();\n    },\n});\n\n/**\n * Note: this works well with the affixMenu... by chance (menuDirection is\n * called after alphabetically).\n *\n * @todo check bootstrap v4: maybe handled automatically now ?\n */\npublicWidget.registry.menuDirection = publicWidget.Widget.extend({\n    selector: 'header .navbar .nav',\n    disabledInEditableMode: false,\n    events: {\n        'show.bs.dropdown': '_onDropdownShow',\n    },\n\n    /**\n     * @override\n     */\n    start: function () {\n        const isRTL = !!this.el.closest('#wrapwrap.o_rtl');\n        this.right = isRTL ? 'left' : 'right';\n        this.left = isRTL ? 'right' : 'left';\n        this.defaultAlignment = this.$el.is('.ml-auto, .ml-auto ~ *') ? this.right : this.left;\n        return this._super.apply(this, arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {string} alignment - either 'left' or 'right'\n     * @param {integer} liOffset\n     * @param {integer} liWidth\n     * @param {integer} menuWidth\n     * @param {integer} pageWidth\n     * @returns {boolean}\n     */\n    _checkOpening: function (alignment, liOffset, liWidth, menuWidth, pageWidth) {\n        if (alignment === this.left) {\n            // Check if ok to open the dropdown to the right (no window overflow)\n            return (liOffset + menuWidth <= pageWidth);\n        } else {\n            // Check if ok to open the dropdown to the left (no window overflow)\n            return (liOffset + liWidth - menuWidth >= 0);\n        }\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onDropdownShow: function (ev) {\n        var $li = $(ev.target);\n        var $menu = $li.children('.dropdown-menu');\n        var liOffset = $li.offset().left;\n        var liWidth = $li.outerWidth();\n        var menuWidth = $menu.outerWidth();\n        var pageWidth = $('#wrapwrap').outerWidth();\n\n        $menu.removeClass('dropdown-menu-left dropdown-menu-right');\n\n        var alignment = this.defaultAlignment;\n        if ($li.nextAll(':visible').length === 0) {\n            // The dropdown is the last menu item, open to the left side\n            // (right side with rtl languages).\n            alignment = this.right;\n        }\n\n        // If can't open in the current direction because it would overflow the\n        // page, change the direction. But if the other direction would do the\n        // same, change back the direction.\n        for (var i = 0; i < 2; i++) {\n            if (!this._checkOpening(alignment, liOffset, liWidth, menuWidth, pageWidth)) {\n                alignment = (alignment === this.left ? this.right : this.left);\n            }\n        }\n\n        $menu.addClass('dropdown-menu-' + alignment);\n    },\n});\n\npublicWidget.registry.hoverableDropdown = animations.Animation.extend({\n    selector: 'header.o_hoverable_dropdown',\n    disabledInEditableMode: false,\n    effects: [{\n        startEvents: 'resize',\n        update: '_dropdownHover',\n    }],\n    events: {\n        'mouseenter .dropdown': '_onMouseEnter',\n        'mouseleave .dropdown': '_onMouseLeave',\n    },\n\n    /**\n     * @override\n     */\n    start: function () {\n        this.$dropdownMenus = this.$el.find('.dropdown-menu');\n        this.$dropdownToggles = this.$el.find('.dropdown-toggle');\n        this._dropdownHover();\n        return this._super.apply(this, arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _dropdownHover: function () {\n        if (config.device.size_class > config.device.SIZES.SM) {\n            this.$dropdownMenus.css('margin-top', '0');\n            this.$dropdownMenus.css('top', 'unset');\n        } else {\n            this.$dropdownMenus.css('margin-top', '');\n            this.$dropdownMenus.css('top', '');\n        }\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onMouseEnter: function (ev) {\n        // The user must click on the dropdown if he is on mobile (no way to\n        // hover) or if the dropdown is the extra menu ('+').\n        if (config.device.size_class <= config.device.SIZES.SM ||\n            ev.currentTarget.classList.contains('o_extra_menu_items')) {\n            return;\n        }\n        $(ev.currentTarget.querySelector('.dropdown-toggle')).dropdown('show');\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onMouseLeave: function (ev) {\n        if (config.device.size_class <= config.device.SIZES.SM ||\n            ev.currentTarget.classList.contains('o_extra_menu_items')) {\n            return;\n        }\n        $(ev.currentTarget.querySelector('.dropdown-toggle')).dropdown('hide');\n    },\n});\n\npublicWidget.registry.HeaderMainCollapse = publicWidget.Widget.extend({\n    selector: 'header#top',\n    disabledInEditableMode: false,\n    events: {\n        'show.bs.collapse #top_menu_collapse': '_onCollapseShow',\n        'hidden.bs.collapse #top_menu_collapse': '_onCollapseHidden',\n    },\n\n    /**\n     * @override\n     */\n    start() {\n        // This is a fix in stable to move the language switcher in the navbar\n        // when the \"off-canvas\" mobile menu is enabled. Without this the\n        // language switcher is inaccessible in the \"off-canvas\" mobile menu.\n        // TODO: Remove this in master and make the adaptations only in the XML\n        // templates.\n        this.languageSelectorMustBeMoved = false;\n        // If mobile menu is \"off-canvas\".\n        if (this.$target[0].querySelector('.o_offcanvas_menu_toggler')) {\n            this.navbarEl = this.$target[0].querySelector('#top_menu');\n            this.languageSelectorEl = this.$target[0].querySelectorAll('#top_menu_collapse .js_language_selector');\n            // As there are 2 language selectors in 'template_header_vertical',\n            // we have to do this to be sure to move the last of the 2 (the one\n            // displayed on mobile).\n            this.languageSelectorEl = this.languageSelectorEl[this.languageSelectorEl.length - 1];\n            this.languageSelectorMustBeMoved = this.languageSelectorEl\n                && !this.navbarEl.contains(this.languageSelectorEl);\n            // Specific case with the \"hamburger full\" header template where\n            // there is the same bug with the call to action.\n            if (this.$target[0].querySelector('.o_header_hamburger_full_toggler')) {\n                const callToActionEl = this.$target[0].querySelector('.oe_structure_solo');\n                if (callToActionEl) {\n                    this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerUnactive();\n                    callToActionEl.classList.add('nav-item');\n                    this.navbarEl.append(callToActionEl);\n                    this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerActive();\n                }\n            }\n            // Specific case with the \"boxed\" header template where the \"call to\n            // action\" button is inaccessible in the \"off-canvas\" mobile menu.\n            this.offcanvasAndBoxedHeader = false;\n            if (weUtils.getCSSVariableValue('header-template').includes('boxed')) {\n                this.boxedHeaderCallToActionEl = this.$target[0].querySelector('#top_menu_collapse .oe_structure_solo');\n                this.offcanvasAndBoxedHeader = !!this.boxedHeaderCallToActionEl;\n            }\n        }\n        return this._super(...arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onCollapseShow() {\n        this.el.classList.add('o_top_menu_collapse_shown');\n        if (this.languageSelectorMustBeMoved) {\n            this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerUnactive();\n            this.languageSelectorEl.classList.add('nav-item');\n            this.navbarEl.append(this.languageSelectorEl);\n            this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerActive();\n        }\n        if (this.offcanvasAndBoxedHeader) {\n            this.boxedHeaderCallToActionEl.classList.add('nav-item');\n            this.navbarEl.append(this.boxedHeaderCallToActionEl);\n        }\n    },\n    /**\n     * @private\n     */\n    _onCollapseHidden() {\n        this.el.classList.remove('o_top_menu_collapse_shown');\n        if (this.languageSelectorMustBeMoved) {\n            this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerUnactive();\n            this.languageSelectorEl.classList.remove('nav-item');\n            this.navbarEl.after(this.languageSelectorEl);\n            this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerActive();\n        }\n        if (this.offcanvasAndBoxedHeader) {\n            this.boxedHeaderCallToActionEl.classList.remove('nav-item');\n            this.navbarEl.after(this.boxedHeaderCallToActionEl);\n        }\n    },\n});\n\nreturn {\n    extraMenuUpdateCallbacks: extraMenuUpdateCallbacks,\n};\n});\n", "odoo.define('website.content.snippets.animation', function (require) {\n'use strict';\n\n/**\n * Provides a way to start JS code for snippets' initialization and animations.\n */\n\nconst ajax = require('web.ajax');\nvar Class = require('web.Class');\nvar config = require('web.config');\nvar core = require('web.core');\nconst dom = require('web.dom');\nvar mixins = require('web.mixins');\nvar publicWidget = require('web.public.widget');\nconst wUtils = require('website.utils');\n\nvar qweb = core.qweb;\n\n// Initialize fallbacks for the use of requestAnimationFrame,\n// cancelAnimationFrame and performance.now()\nwindow.requestAnimationFrame = window.requestAnimationFrame\n    || window.webkitRequestAnimationFrame\n    || window.mozRequestAnimationFrame\n    || window.msRequestAnimationFrame\n    || window.oRequestAnimationFrame;\nwindow.cancelAnimationFrame = window.cancelAnimationFrame\n    || window.webkitCancelAnimationFrame\n    || window.mozCancelAnimationFrame\n    || window.msCancelAnimationFrame\n    || window.oCancelAnimationFrame;\nif (!window.performance || !window.performance.now) {\n    window.performance = {\n        now: function () {\n            return Date.now();\n        }\n    };\n}\n\n/**\n * Add the notion of edit mode to public widgets.\n */\npublicWidget.Widget.include({\n    /**\n     * Indicates if the widget should not be instantiated in edit. The default\n     * is true, indeed most (all?) defined widgets only want to initialize\n     * events and states which should not be active in edit mode (this is\n     * especially true for non-website widgets).\n     *\n     * @type {boolean}\n     */\n    disabledInEditableMode: true,\n    /**\n     * Acts as @see Widget.events except that the events are only binded if the\n     * Widget instance is instanciated in edit mode. The property is not\n     * considered if @see disabledInEditableMode is false.\n     */\n    edit_events: null,\n    /**\n     * Acts as @see Widget.events except that the events are only binded if the\n     * Widget instance is instanciated in readonly mode. The property only\n     * makes sense if @see disabledInEditableMode is false, you should simply\n     * use @see Widget.events otherwise.\n     */\n    read_events: null,\n\n    /**\n     * Initializes the events that will need to be binded according to the\n     * given mode.\n     *\n     * @constructor\n     * @param {Object} parent\n     * @param {Object} [options]\n     * @param {boolean} [options.editableMode=false]\n     *        true if the page is in edition mode\n     */\n    init: function (parent, options) {\n        this._super.apply(this, arguments);\n\n        this.editableMode = this.options.editableMode || false;\n        var extraEvents = this.editableMode ? this.edit_events : this.read_events;\n        if (extraEvents) {\n            this.events = _.extend({}, this.events || {}, extraEvents);\n        }\n    },\n});\n\n/**\n * In charge of handling one animation loop using the requestAnimationFrame\n * feature. This is used by the `Animation` class below and should not be called\n * directly by an end developer.\n *\n * This uses a simple API: it can be started, stopped, played and paused.\n */\nvar AnimationEffect = Class.extend(mixins.ParentedMixin, {\n    /**\n     * @constructor\n     * @param {Object} parent\n     * @param {function} updateCallback - the animation update callback\n     * @param {string} [startEvents=scroll]\n     *        space separated list of events which starts the animation loop\n     * @param {jQuery|DOMElement} [$startTarget=window]\n     *        the element(s) on which the startEvents are listened\n     * @param {Object} [options]\n     * @param {function} [options.getStateCallback]\n     *        a function which returns a value which represents the state of the\n     *        animation, i.e. for two same value, no refreshing of the animation\n     *        is needed. Can be used for optimization. If the $startTarget is\n     *        the window element, this defaults to returning the current\n     *        scoll offset of the window or the size of the window for the\n     *        scroll and resize events respectively.\n     * @param {string} [options.endEvents]\n     *        space separated list of events which pause the animation loop. If\n     *        not given, the animation is stopped after a while (if no\n     *        startEvents is received again)\n     * @param {jQuery|DOMElement} [options.$endTarget=$startTarget]\n     *        the element(s) on which the endEvents are listened\n     * @param {boolean} [options.enableInModal]\n     *        when it is true, it means that the 'scroll' event must be\n     *        triggered when scrolling a modal.\n     */\n    init: function (parent, updateCallback, startEvents, $startTarget, options) {\n        mixins.ParentedMixin.init.call(this);\n        this.setParent(parent);\n\n        options = options || {};\n        this._minFrameTime = 1000 / (options.maxFPS || 100);\n\n        // Initialize the animation startEvents, startTarget, endEvents, endTarget and callbacks\n        this._updateCallback = updateCallback;\n        this.startEvents = startEvents || 'scroll';\n        const modalEl = options.enableInModal ? parent.target.closest('.modal') : null;\n        const mainScrollingElement = modalEl ? modalEl : $().getScrollingElement()[0];\n        const mainScrollingTarget = mainScrollingElement === document.documentElement ? window : mainScrollingElement;\n        this.$startTarget = $($startTarget ? $startTarget : this.startEvents === 'scroll' ? mainScrollingTarget : window);\n        if (options.getStateCallback) {\n            this._getStateCallback = options.getStateCallback;\n        } else if (this.startEvents === 'scroll' && this.$startTarget[0] === mainScrollingTarget) {\n            const $scrollable = this.$startTarget;\n            this._getStateCallback = function () {\n                return $scrollable.scrollTop();\n            };\n        } else if (this.startEvents === 'resize' && this.$startTarget[0] === window) {\n            this._getStateCallback = function () {\n                return {\n                    width: window.innerWidth,\n                    height: window.innerHeight,\n                };\n            };\n        } else {\n            this._getStateCallback = function () {\n                return undefined;\n            };\n        }\n        this.endEvents = options.endEvents || false;\n        this.$endTarget = options.$endTarget ? $(options.$endTarget) : this.$startTarget;\n\n        this._updateCallback = this._updateCallback.bind(parent);\n        this._getStateCallback = this._getStateCallback.bind(parent);\n\n        // Add a namespace to events using the generated uid\n        this._uid = '_animationEffect' + _.uniqueId();\n        this.startEvents = _processEvents(this.startEvents, this._uid);\n        if (this.endEvents) {\n            this.endEvents = _processEvents(this.endEvents, this._uid);\n        }\n\n        function _processEvents(events, namespace) {\n            events = events.split(' ');\n            return _.each(events, function (e, index) {\n                events[index] += ('.' + namespace);\n            }).join(' ');\n        }\n    },\n    /**\n     * @override\n     */\n    destroy: function () {\n        mixins.ParentedMixin.destroy.call(this);\n        this.stop();\n    },\n\n    //--------------------------------------------------------------------------\n    // Public\n    //--------------------------------------------------------------------------\n\n    /**\n     * Initializes when the animation must be played and paused and initializes\n     * the animation first frame.\n     */\n    start: function () {\n        // Initialize the animation first frame\n        this._paused = false;\n        this._rafID = window.requestAnimationFrame((function (t) {\n            this._update(t);\n            this._paused = true;\n        }).bind(this));\n\n        // Initialize the animation play/pause events\n        if (this.endEvents) {\n            /**\n             * If there are endEvents, the animation should begin playing when\n             * the startEvents are triggered on the $startTarget and pause when\n             * the endEvents are triggered on the $endTarget.\n             */\n            this.$startTarget.on(this.startEvents, (function (e) {\n                if (this._paused) {\n                    _.defer(this.play.bind(this, e));\n                }\n            }).bind(this));\n            this.$endTarget.on(this.endEvents, (function () {\n                if (!this._paused) {\n                    _.defer(this.pause.bind(this));\n                }\n            }).bind(this));\n        } else {\n            /**\n             * Else, if there is no endEvents, the animation should begin playing\n             * when the startEvents are *continuously* triggered on the\n             * $startTarget or fully played once. To achieve this, the animation\n             * begins playing and is scheduled to pause after 2 seconds. If the\n             * startEvents are triggered during that time, this is not paused\n             * for another 2 seconds. This allows to describe an \"effect\"\n             * animation (which lasts less than 2 seconds) or an animation which\n             * must be playing *during* an event (scroll, mousemove, resize,\n             * repeated clicks, ...).\n             */\n            var pauseTimer = null;\n            this.$startTarget.on(this.startEvents, _.throttle((function (e) {\n                this.play(e);\n\n                clearTimeout(pauseTimer);\n                pauseTimer = _.delay((function () {\n                    this.pause();\n                    pauseTimer = null;\n                }).bind(this), 2000);\n            }).bind(this), 250, {trailing: false}));\n        }\n    },\n    /**\n     * Pauses the animation and destroys the attached events which trigger the\n     * animation to be played or paused.\n     */\n    stop: function () {\n        this.$startTarget.off(this.startEvents);\n        if (this.endEvents) {\n            this.$endTarget.off(this.endEvents);\n        }\n        this.pause();\n    },\n    /**\n     * Forces the requestAnimationFrame loop to start.\n     *\n     * @param {Event} e - the event which triggered the animation to play\n     */\n    play: function (e) {\n        this._newEvent = e;\n        if (!this._paused) {\n            return;\n        }\n        this._paused = false;\n        this._rafID = window.requestAnimationFrame(this._update.bind(this));\n        this._lastUpdateTimestamp = undefined;\n    },\n    /**\n     * Forces the requestAnimationFrame loop to stop.\n     */\n    pause: function () {\n        if (this._paused) {\n            return;\n        }\n        this._paused = true;\n        window.cancelAnimationFrame(this._rafID);\n        this._lastUpdateTimestamp = undefined;\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Callback which is repeatedly called by the requestAnimationFrame loop.\n     * It controls the max fps at which the animation is running and initializes\n     * the values that the update callback needs to describe the animation\n     * (state, elapsedTime, triggered event).\n     *\n     * @private\n     * @param {DOMHighResTimeStamp} timestamp\n     */\n    _update: function (timestamp) {\n        if (this._paused) {\n            return;\n        }\n        this._rafID = window.requestAnimationFrame(this._update.bind(this));\n\n        // Check the elapsed time since the last update callback call.\n        // Consider it 0 if there is no info of last timestamp and leave this\n        // _update call if it was called too soon (would overflow the set max FPS).\n        var elapsedTime = 0;\n        if (this._lastUpdateTimestamp) {\n            elapsedTime = timestamp - this._lastUpdateTimestamp;\n            if (elapsedTime < this._minFrameTime) {\n                return;\n            }\n        }\n\n        // Check the new animation state thanks to the get state callback and\n        // store its new value. If the state is the same as the previous one,\n        // leave this _update call, except if there is an event which triggered\n        // the \"play\" method again.\n        var animationState = this._getStateCallback(elapsedTime, this._newEvent);\n        if (!this._newEvent\n         && animationState !== undefined\n         && _.isEqual(animationState, this._animationLastState)) {\n            return;\n        }\n        this._animationLastState = animationState;\n\n        // Call the update callback with frame parameters\n        this._updateCallback(this._animationLastState, elapsedTime, this._newEvent);\n        this._lastUpdateTimestamp = timestamp; // Save the timestamp at which the update callback was really called\n        this._newEvent = undefined; // Forget the event which triggered the last \"play\" call\n    },\n});\n\n/**\n * Also register AnimationEffect automatically (@see effects, _prepareEffects).\n */\nvar Animation = publicWidget.Widget.extend({\n    /**\n     * The max FPS at which all the automatic animation effects will be\n     * running by default.\n     */\n    maxFPS: 100,\n    /**\n     * @see this._prepareEffects\n     *\n     * @type {Object[]}\n     * @type {string} startEvents\n     *       The names of the events which trigger the effect to begin playing.\n     * @type {string} [startTarget]\n     *       A selector to find the target where to listen for the start events\n     *       (if no selector, the window target will be used). If the whole\n     *       $target of the animation should be used, use the 'selector' string.\n     * @type {string} [endEvents]\n     *       The name of the events which trigger the end of the effect (if none\n     *       is defined, the animation will stop after a while\n     *       @see AnimationEffect.start).\n     * @type {string} [endTarget]\n     *       A selector to find the target where to listen for the end events\n     *       (if no selector, the startTarget will be used). If the whole\n     *       $target of the animation should be used, use the 'selector' string.\n     * @type {string} update\n     *       A string which refers to a method which will be used as the update\n     *       callback for the effect. It receives 3 arguments: the animation\n     *       state, the elapsedTime since last update and the event which\n     *       triggered the animation (undefined if just a new update call\n     *       without trigger).\n     * @type {string} [getState]\n     *       The animation state is undefined by default, the scroll offset for\n     *       the particular {startEvents: 'scroll'} effect and an object with\n     *       width and height for the particular {startEvents: 'resize'} effect.\n     *       There is the possibility to define the getState callback of the\n     *       animation effect with this key. This allows to improve performance\n     *       even further in some cases.\n     */\n    effects: [],\n\n    /**\n     * Initializes the animation. The method should not be called directly as\n     * called automatically on animation instantiation and on restart.\n     *\n     * Also, prepares animation's effects and start them if any.\n     *\n     * @override\n     */\n    start: function () {\n        this._prepareEffects();\n        _.each(this._animationEffects, function (effect) {\n            effect.start();\n        });\n        return this._super.apply(this, arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Registers `AnimationEffect` instances.\n     *\n     * This can be done by extending this method and calling the @see _addEffect\n     * method in it or, better, by filling the @see effects property.\n     *\n     * @private\n     */\n    _prepareEffects: function () {\n        this._animationEffects = [];\n\n        var self = this;\n        _.each(this.effects, function (desc) {\n            self._addEffect(self[desc.update], desc.startEvents, _findTarget(desc.startTarget), {\n                getStateCallback: desc.getState && self[desc.getState],\n                endEvents: desc.endEvents || undefined,\n                $endTarget: _findTarget(desc.endTarget),\n                maxFPS: self.maxFPS,\n                enableInModal: desc.enableInModal || undefined,\n            });\n\n            // Return the DOM element matching the selector in the form\n            // described above.\n            function _findTarget(selector) {\n                if (selector) {\n                    if (selector === 'selector') {\n                        return self.$target;\n                    }\n                    return self.$(selector);\n                }\n                return undefined;\n            }\n        });\n    },\n    /**\n     * Registers a new `AnimationEffect` according to given parameters.\n     *\n     * @private\n     * @see AnimationEffect.init\n     */\n    _addEffect: function (updateCallback, startEvents, $startTarget, options) {\n        this._animationEffects.push(\n            new AnimationEffect(this, updateCallback, startEvents, $startTarget, options)\n        );\n    },\n});\n\n//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n\nvar registry = publicWidget.registry;\n\nregistry.slider = publicWidget.Widget.extend({\n    selector: '.carousel',\n    disabledInEditableMode: false,\n    edit_events: {\n        'content_changed': '_onContentChanged',\n    },\n\n    /**\n     * @override\n     */\n    start: function () {\n        this.$('img').on('load.slider', () => this._computeHeights());\n        this._computeHeights();\n        // Initialize carousel and pause if in edit mode.\n        this.$target.carousel(this.editableMode ? 'pause' : undefined);\n        $(window).on('resize.slider', _.debounce(() => this._computeHeights(), 250));\n        if (this.editableMode) {\n            // Prevent carousel slide to be an history step.\n            this.$target.on('slide.bs.carousel', () => {\n                this.options.wysiwyg.odooEditor.observerUnactive();\n            });\n            this.$target.on('slid.bs.carousel', () => {\n                this.options.wysiwyg.odooEditor.observerActive();\n            });\n        }\n        return this._super.apply(this, arguments);\n    },\n    /**\n     * @override\n     */\n    destroy: function () {\n        this._super.apply(this, arguments);\n        this.$('img').off('.slider');\n        this.$target.carousel('pause');\n        this.$target.removeData('bs.carousel');\n        _.each(this.$('.carousel-item'), function (el) {\n            $(el).css('min-height', '');\n        });\n        $(window).off('.slider');\n        this.$target.off('.carousel');\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _computeHeights: function () {\n        var maxHeight = 0;\n        var $items = this.$('.carousel-item');\n        $items.css('min-height', '');\n        _.each($items, el => {\n            var $item = $(el);\n            var isActive = $item.hasClass('active');\n            this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerUnactive('_computeHeights');\n            $item.addClass('active');\n            this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerActive('_computeHeights');\n            var height = $item.outerHeight();\n            if (height > maxHeight) {\n                maxHeight = height;\n            }\n            this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerUnactive('_computeHeights');\n            $item.toggleClass('active', isActive);\n            this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerActive('_computeHeights');\n        });\n        $items.css('min-height', maxHeight);\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onContentChanged: function (ev) {\n        this._computeHeights();\n    },\n});\n\nregistry.Parallax = Animation.extend({\n    selector: '.parallax',\n    disabledInEditableMode: false,\n    effects: [{\n        startEvents: 'scroll',\n        update: '_onWindowScroll',\n        enableInModal: true,\n    }],\n\n    /**\n     * @override\n     */\n    start: function () {\n        this._rebuild();\n        $(window).on('resize.animation_parallax', _.debounce(this._rebuild.bind(this), 500));\n        this.modalEl = this.$target[0].closest('.modal');\n        if (this.modalEl) {\n            $(this.modalEl).on('shown.bs.modal.animation_parallax', () => {\n                this._rebuild();\n                this.modalEl.dispatchEvent(new Event('scroll'));\n            });\n        }\n        return this._super.apply(this, arguments);\n    },\n    /**\n     * @override\n     */\n    destroy: function () {\n        this._super.apply(this, arguments);\n        $(window).off('.animation_parallax');\n        if (this.modalEl) {\n            $(this.modalEl).off('.animation_parallax');\n        }\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Prepares the background element which will scroll at a different speed\n     * according to the viewport dimensions and other snippet parameters.\n     *\n     * @private\n     */\n    _rebuild: function () {\n        // Add/find bg DOM element to hold the parallax bg (support old v10.0 parallax)\n        this.$bg = this.$('> .s_parallax_bg');\n\n        // Get parallax speed\n        this.speed = parseFloat(this.$target.attr('data-scroll-background-ratio') || 0);\n\n        // Reset offset if parallax effect will not be performed and leave\n        var noParallaxSpeed = (this.speed === 0 || this.speed === 1);\n        if (noParallaxSpeed) {\n            this.$bg.css({\n                transform: '',\n                top: '',\n                bottom: '',\n            });\n            return;\n        }\n\n        // Initialize parallax data according to snippet and viewport dimensions\n        this.viewport = document.body.clientHeight - $('#wrapwrap').position().top;\n        this.visibleArea = [this.$target.offset().top];\n        this.visibleArea.push(this.visibleArea[0] + this.$target.innerHeight() + this.viewport);\n        this.ratio = this.speed * (this.viewport / 10);\n\n        // Provide a \"safe-area\" to limit parallax\n        const absoluteRatio = Math.abs(this.ratio);\n        this.$bg.css({\n            top: -absoluteRatio,\n            bottom: -absoluteRatio,\n        });\n    },\n\n    //--------------------------------------------------------------------------\n    // Effects\n    //--------------------------------------------------------------------------\n\n    /**\n     * Describes how to update the snippet when the window scrolls.\n     *\n     * @private\n     * @param {integer} scrollOffset\n     */\n    _onWindowScroll: function (scrollOffset) {\n        // Speed == 0 is no effect and speed == 1 is handled by CSS only\n        if (this.speed === 0 || this.speed === 1) {\n            return;\n        }\n\n        // Perform translation if the element is visible only\n        var vpEndOffset = scrollOffset + this.viewport;\n        if (vpEndOffset >= this.visibleArea[0]\n         && vpEndOffset <= this.visibleArea[1]) {\n            this.$bg.css('transform', 'translateY(' + _getNormalizedPosition.call(this, vpEndOffset) + 'px)');\n        }\n\n        function _getNormalizedPosition(pos) {\n            // Normalize scroll in a 1 to 0 range\n            var r = (pos - this.visibleArea[1]) / (this.visibleArea[0] - this.visibleArea[1]);\n            // Normalize accordingly to current options\n            return Math.round(this.ratio * (2 * r - 1));\n        }\n    },\n});\n\nconst MobileYoutubeAutoplayMixin = {\n    /**\n     * Takes care of any necessary setup for autoplaying video. In practice,\n     * this method will load the youtube iframe API for mobile environments\n     * because mobile environments don't support the youtube autoplay param\n     * passed in the url.\n     *\n     * @private\n     * @param {string} src - The source url of the video\n     */\n    _setupAutoplay: function (src) {\n        let promise = Promise.resolve();\n\n        this.isYoutubeVideo = src.indexOf('youtube') >= 0;\n        this.isMobileEnv = config.device.size_class <= config.device.SIZES.LG && config.device.touch;\n\n        if (this.isYoutubeVideo && this.isMobileEnv && !window.YT) {\n            const oldOnYoutubeIframeAPIReady = window.onYouTubeIframeAPIReady;\n            promise = new Promise(resolve => {\n                window.onYouTubeIframeAPIReady = () => {\n                    if (oldOnYoutubeIframeAPIReady) {\n                        oldOnYoutubeIframeAPIReady();\n                    }\n                    return resolve();\n                };\n            });\n            ajax.loadJS('https://www.youtube.com/iframe_api');\n        }\n\n        return promise;\n    },\n    /**\n     * @private\n     * @param {DOMElement} iframeEl - the iframe containing the video player\n     */\n    _triggerAutoplay: function (iframeEl) {\n        // YouTube does not allow to auto-play video in mobile devices, so we\n        // have to play the video manually.\n        if (this.isMobileEnv && this.isYoutubeVideo) {\n            new window.YT.Player(iframeEl, {\n                events: {\n                    onReady: ev => ev.target.playVideo(),\n                }\n            });\n        }\n    },\n};\n\nregistry.mediaVideo = publicWidget.Widget.extend(MobileYoutubeAutoplayMixin, {\n    selector: '.media_iframe_video',\n\n    /**\n     * @override\n     */\n    start: function () {\n        // TODO: this code should be refactored to make more sense and be better\n        // integrated with Odoo (this refactoring should be done in master).\n\n        const proms = [this._super.apply(this, arguments)];\n        let iframeEl = this.$target[0].querySelector(':scope > iframe');\n\n        // The following code is only there to ensure compatibility with\n        // videos added before bug fixes or new Odoo versions where the\n        // <iframe/> element is properly saved.\n        if (!iframeEl) {\n            iframeEl = this._generateIframe();\n        }\n\n        if (!iframeEl) {\n            // Something went wrong: no iframe is present in the DOM and the\n            // widget was unable to create one on the fly.\n            return Promise.all(proms);\n        }\n\n        proms.push(this._setupAutoplay(iframeEl.getAttribute('src')));\n        return Promise.all(proms).then(() => {\n            this._triggerAutoplay(iframeEl);\n        });\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _generateIframe: function () {\n        // Bug fix / compatibility: empty the <div/> element as all information\n        // to rebuild the iframe should have been saved on the <div/> element\n        this.$target.empty();\n\n        // Add extra content for size / edition\n        this.$target.append(\n            '<div class=\"css_editable_mode_display\">&nbsp;</div>' +\n            '<div class=\"media_iframe_video_size\">&nbsp;</div>'\n        );\n\n        // Rebuild the iframe. Depending on version / compatibility / instance,\n        // the src is saved in the 'data-src' attribute or the\n        // 'data-oe-expression' one (the latter is used as a workaround in 10.0\n        // system but should obviously be reviewed in master).\n        var src = _.escape(this.$target.data('oe-expression') || this.$target.data('src'));\n        // Validate the src to only accept supported domains we can trust\n        var m = src.match(/^(?:https?:)?\\/\\/([^/?#]+)/);\n        if (!m) {\n            // Unsupported protocol or wrong URL format, don't inject iframe\n            return;\n        }\n        var domain = m[1].replace(/^www\\./, '');\n        var supportedDomains = ['youtu.be', 'youtube.com', 'youtube-nocookie.com', 'instagram.com', 'vine.co', 'player.vimeo.com', 'vimeo.com', 'dailymotion.com', 'player.youku.com', 'youku.com'];\n        if (!_.contains(supportedDomains, domain)) {\n            // Unsupported domain, don't inject iframe\n            return;\n        }\n        const iframeEl = $('<iframe/>', {\n            src: src,\n            frameborder: '0',\n            allowfullscreen: 'allowfullscreen',\n        })[0];\n        this.$target.append(iframeEl);\n        return iframeEl;\n    },\n});\n\nregistry.backgroundVideo = publicWidget.Widget.extend(MobileYoutubeAutoplayMixin, {\n    selector: '.o_background_video',\n    xmlDependencies: ['/website/static/src/xml/website.background.video.xml'],\n    disabledInEditableMode: false,\n\n    /**\n     * @override\n     */\n    start: function () {\n        var proms = [this._super(...arguments)];\n\n        this.videoSrc = this.el.dataset.bgVideoSrc;\n        this.iframeID = _.uniqueId('o_bg_video_iframe_');\n        proms.push(this._setupAutoplay(this.videoSrc));\n        if (this.isYoutubeVideo && this.isMobileEnv && !this.videoSrc.includes('enablejsapi=1')) {\n            // Compatibility: when choosing an autoplay youtube video via the\n            // media manager, the API was not automatically enabled before but\n            // only enabled here in the case of background videos.\n            // TODO migrate those old cases so this code can be removed?\n            this.videoSrc += '&enablejsapi=1';\n        }\n\n        var throttledUpdate = _.throttle(() => this._adjustIframe(), 50);\n\n        var $dropdownMenu = this.$el.closest('.dropdown-menu');\n        if ($dropdownMenu.length) {\n            this.$dropdownParent = $dropdownMenu.parent();\n            this.$dropdownParent.on('shown.bs.dropdown.backgroundVideo', throttledUpdate);\n        }\n\n        $(window).on('resize.' + this.iframeID, throttledUpdate);\n\n        const $modal = this.$target.closest('.modal');\n        if ($modal.length) {\n            $modal.on('show.bs.modal', () => {\n                const videoContainerEl = this.$target[0].querySelector('.o_bg_video_container');\n                videoContainerEl.classList.add('d-none');\n            });\n            $modal.on('shown.bs.modal', () => {\n                this._adjustIframe();\n                const videoContainerEl = this.$target[0].querySelector('.o_bg_video_container');\n                videoContainerEl.classList.remove('d-none');\n            });\n        }\n        return Promise.all(proms).then(() => this._appendBgVideo());\n    },\n    /**\n     * @override\n     */\n    destroy: function () {\n        this._super.apply(this, arguments);\n\n        if (this.$dropdownParent) {\n            this.$dropdownParent.off('.backgroundVideo');\n        }\n\n        $(window).off('resize.' + this.iframeID);\n\n        if (this.$bgVideoContainer) {\n            this.$bgVideoContainer.remove();\n        }\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Adjusts iframe sizes and position so that it fills the container and so\n     * that it is centered in it.\n     *\n     * @private\n     */\n    _adjustIframe: function () {\n        if (!this.$iframe) {\n            return;\n        }\n\n        this.$iframe.removeClass('show');\n\n        // Adjust the iframe\n        var wrapperWidth = this.$target.innerWidth();\n        var wrapperHeight = this.$target.innerHeight();\n        var relativeRatio = (wrapperWidth / wrapperHeight) / (16 / 9);\n        var style = {};\n        if (relativeRatio >= 1.0) {\n            style['width'] = '100%';\n            style['height'] = (relativeRatio * 100) + '%';\n            style['left'] = '0';\n            style['top'] = (-(relativeRatio - 1.0) / 2 * 100) + '%';\n        } else {\n            style['width'] = ((1 / relativeRatio) * 100) + '%';\n            style['height'] = '100%';\n            style['left'] = (-((1 / relativeRatio) - 1.0) / 2 * 100) + '%';\n            style['top'] = '0';\n        }\n        this.$iframe.css(style);\n\n        void this.$iframe[0].offsetWidth; // Force style addition\n        this.$iframe.addClass('show');\n    },\n    /**\n     * Append background video related elements to the target.\n     *\n     * @private\n     */\n    _appendBgVideo: function () {\n        var $oldContainer = this.$bgVideoContainer || this.$('> .o_bg_video_container');\n        this.$bgVideoContainer = $(qweb.render('website.background.video', {\n            videoSrc: this.videoSrc,\n            iframeID: this.iframeID,\n        }));\n        this.$iframe = this.$bgVideoContainer.find('.o_bg_video_iframe');\n        this.$iframe.one('load', () => {\n            this.$bgVideoContainer.find('.o_bg_video_loading').remove();\n        });\n        this.$bgVideoContainer.prependTo(this.$target);\n        $oldContainer.remove();\n\n        this._adjustIframe();\n        this._triggerAutoplay(this.$iframe[0]);\n    },\n});\n\nregistry.socialShare = publicWidget.Widget.extend({\n    selector: '.oe_social_share',\n    xmlDependencies: ['/website/static/src/xml/website.share.xml'],\n    events: {\n        'mouseenter': '_onMouseEnter',\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _bindSocialEvent: function () {\n        this.$('.oe_social_facebook').click($.proxy(this._renderSocial, this, 'facebook'));\n        this.$('.oe_social_twitter').click($.proxy(this._renderSocial, this, 'twitter'));\n        this.$('.oe_social_linkedin').click($.proxy(this._renderSocial, this, 'linkedin'));\n    },\n    /**\n     * @private\n     */\n    _render: function () {\n        this.$el.popover({\n            content: qweb.render('website.social_hover', {medias: this.socialList}),\n            placement: 'bottom',\n            container: this.$el,\n            html: true,\n            trigger: 'manual',\n            animation: false,\n        }).popover(\"show\");\n\n        this.$el.off('mouseleave.socialShare').on('mouseleave.socialShare', function () {\n            var self = this;\n            setTimeout(function () {\n                if (!$(\".popover:hover\").length) {\n                    $(self).popover('dispose');\n                }\n            }, 200);\n        });\n    },\n    /**\n     * @private\n     */\n    _renderSocial: function (social) {\n        var url = this.$el.data('urlshare') || document.URL.split(/[?#]/)[0];\n        url = encodeURIComponent(url);\n        var title = document.title.split(\" | \")[0];  // get the page title without the company name\n        var hashtags = ' #' + document.title.split(\" | \")[1].replace(' ', '') + ' ' + this.hashtags;  // company name without spaces (for hashtag)\n        var socialNetworks = {\n            'facebook': 'https://www.facebook.com/sharer/sharer.php?u=' + url,\n            'twitter': 'https://twitter.com/intent/tweet?original_referer=' + url + '&text=' + encodeURIComponent(title + hashtags + ' - ') + url,\n            'linkedin': 'https://www.linkedin.com/sharing/share-offsite/?url=' + url,\n        };\n        if (!_.contains(_.keys(socialNetworks), social)) {\n            return;\n        }\n        var wHeight = 500;\n        var wWidth = 500;\n        window.open(socialNetworks[social], '', 'menubar=no, toolbar=no, resizable=yes, scrollbar=yes, height=' + wHeight + ',width=' + wWidth);\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Called when the user hovers the animation element -> open the social\n     * links popover.\n     *\n     * @private\n     */\n    _onMouseEnter: function () {\n        var social = this.$el.data('social');\n        this.socialList = social ? social.split(',') : ['facebook', 'twitter', 'linkedin'];\n        this.hashtags = this.$el.data('hashtags') || '';\n\n        this._render();\n        this._bindSocialEvent();\n    },\n});\n\nregistry.anchorSlide = publicWidget.Widget.extend({\n    selector: 'a[href^=\"/\"][href*=\"#\"], a[href^=\"#\"]',\n    events: {\n        'click': '_onAnimateClick',\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {jQuery} $el the element to scroll to.\n     * @param {string} [scrollValue='true'] scroll value\n     * @returns {Promise}\n     */\n    async _scrollTo($el, scrollValue = 'true') {\n        return dom.scrollTo($el[0], {\n            duration: scrollValue === 'true' ? 500 : 0,\n            extraOffset: this._computeExtraOffset(),\n        });\n    },\n    /**\n     * @private\n     */\n    _computeExtraOffset() {\n        return 0;\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onAnimateClick: function (ev) {\n        if (this.$target[0].pathname !== window.location.pathname) {\n            return;\n        }\n        var hash = this.$target[0].hash;\n        if (hash === '#top' || hash === '#bottom') {\n            // If the anchor targets #top or #bottom, directly call the\n            // \"scrollTo\" function. The reason is that the header or the footer\n            // could have been removed from the DOM. By receiving a string as\n            // parameter, the \"scrollTo\" function handles the scroll to the top\n            // or to the bottom of the document even if the header or the\n            // footer is removed from the DOM.\n            dom.scrollTo(hash, {\n                duration: 500,\n                extraOffset: this._computeExtraOffset(),\n            });\n            return;\n        }\n        if (!hash.length) {\n            return;\n        }\n        // Escape special characters to make the jQuery selector to work.\n        hash = '#' + $.escapeSelector(hash.substring(1));\n        var $anchor = $(hash);\n        const scrollValue = $anchor.attr('data-anchor');\n        if (!$anchor.length || !scrollValue) {\n            return;\n        }\n        ev.preventDefault();\n        this._scrollTo($anchor, scrollValue);\n    },\n});\n\nregistry.FullScreenHeight = publicWidget.Widget.extend({\n    selector: '.o_full_screen_height',\n    disabledInEditableMode: false,\n\n    /**\n     * @override\n     */\n    start() {\n        this.inModal = !!this.el.closest('.modal');\n\n        // TODO maybe review the way the public widgets work for non-visible-at-\n        // load snippets -> probably better to not do anything for them and\n        // start the widgets only once they become visible..?\n        if (this.$el.is(':not(:visible)') || this.$el.outerHeight() > this._computeIdealHeight()) {\n            // Only initialize if taller than the ideal height as some extra css\n            // rules may alter the full-screen-height class behavior in some\n            // cases (blog...).\n            this._adaptSize();\n            $(window).on('resize.FullScreenHeight', _.debounce(() => this._adaptSize(), 250));\n        }\n        return this._super(...arguments);\n    },\n    /**\n     * @override\n     */\n    destroy() {\n        this._super(...arguments);\n        $(window).off('.FullScreenHeight');\n        this.el.style.setProperty('min-height', '');\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _adaptSize() {\n        const height = this._computeIdealHeight();\n        this.el.style.setProperty('min-height', `${height}px`, 'important');\n    },\n    /**\n     * @private\n     */\n    _computeIdealHeight() {\n        const windowHeight = $(window).outerHeight();\n        if (this.inModal) {\n            return (windowHeight - $('#wrapwrap').position().top);\n        }\n\n        // Doing it that way allows to considerer fixed headers, hidden headers,\n        // connected users, ...\n        const firstContentEl = $('#wrapwrap > main > :first-child')[0]; // first child to consider the padding-top of main\n        const mainTopPos = firstContentEl.getBoundingClientRect().top + dom.closestScrollable(firstContentEl.parentNode).scrollTop;\n        return (windowHeight - mainTopPos);\n    },\n});\n\nregistry.ScrollButton = registry.anchorSlide.extend({\n    selector: '.o_scroll_button',\n\n    /**\n     * @override\n     */\n    _onAnimateClick: function (ev) {\n        ev.preventDefault();\n        // Scroll to the next visible element after the current one.\n        const currentSectionEl = this.el.closest('section');\n        let nextEl = currentSectionEl.nextElementSibling;\n        while (nextEl) {\n            if ($(nextEl).is(':visible')) {\n                this._scrollTo($(nextEl));\n                return;\n            }\n            nextEl = nextEl.nextElementSibling;\n        }\n    },\n});\n\nregistry.FooterSlideout = publicWidget.Widget.extend({\n    selector: '#wrapwrap:has(.o_footer_slideout)',\n    disabledInEditableMode: false,\n\n    /**\n     * @override\n     */\n    async start() {\n        const $main = this.$('> main');\n        const slideoutEffect = $main.outerHeight() >= $(window).outerHeight();\n        this.el.classList.toggle('o_footer_effect_enable', slideoutEffect);\n\n        // Add a pixel div over the footer, after in the DOM, so that the\n        // height of the footer is understood by Firefox sticky implementation\n        // (which it seems to not understand because of the combination of 3\n        // items: the footer is the last :visible element in the #wrapwrap, the\n        // #wrapwrap uses flex layout and the #wrapwrap is the element with a\n        // scrollbar).\n        // TODO check if the hack is still needed by future browsers.\n        this.__pixelEl = document.createElement('div');\n        this.__pixelEl.style.width = `1px`;\n        this.__pixelEl.style.height = `1px`;\n        this.__pixelEl.style.marginTop = `-1px`;\n        // On safari, add a background attachment fixed to fix the glitches that\n        // appear when scrolling the page with a footer slide out.\n        if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {\n            this.__pixelEl.style.backgroundColor = \"transparent\";\n            this.__pixelEl.style.backgroundAttachment = \"fixed\";\n            this.__pixelEl.style.backgroundImage = \"url(/website/static/src/img/website_logo.svg)\";\n        }\n        this.el.appendChild(this.__pixelEl);\n\n        return this._super(...arguments);\n    },\n    /**\n     * @override\n     */\n    destroy() {\n        this._super(...arguments);\n        this.el.classList.remove('o_footer_effect_enable');\n        this.__pixelEl.remove();\n    },\n});\n\nregistry.TopMenuCollapse = publicWidget.Widget.extend({\n    selector: \"header #top_menu_collapse\",\n\n    /**\n     * @override\n     */\n    async start() {\n        this.throttledResize = _.throttle(() => this._onResize(), 25);\n        window.addEventListener(\"resize\", this.throttledResize);\n        return this._super(...arguments);\n    },\n    /**\n     * @override\n     */\n    destroy() {\n        this._super(...arguments);\n        window.removeEventListener(\"resize\", this.throttledResize);\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onResize() {\n        if (this.el.classList.contains(\"show\")) {\n            const togglerEl = this.el.closest(\"nav\").querySelector(\".navbar-toggler\");\n            if (getComputedStyle(togglerEl).display === \"none\") {\n                this.$el.collapse(\"hide\");\n            }\n        }\n    },\n});\n\nregistry.HeaderHamburgerFull = publicWidget.Widget.extend({\n    selector: 'header:has(.o_header_hamburger_full_toggler):not(:has(.o_offcanvas_menu_toggler))',\n    events: {\n        'click .o_header_hamburger_full_toggler': '_onToggleClick',\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onToggleClick() {\n        document.body.classList.add('overflow-hidden');\n        setTimeout(() => $(window).trigger('scroll'), 100);\n    },\n});\n\nregistry.BottomFixedElement = publicWidget.Widget.extend({\n    selector: '#wrapwrap',\n\n    /**\n     * @override\n     */\n    async start() {\n        this.$scrollingElement = $().getScrollingElement();\n        this.__hideBottomFixedElements = _.debounce(() => this._hideBottomFixedElements(), 500);\n        this.$scrollingElement.on('scroll.bottom_fixed_element', this.__hideBottomFixedElements);\n        $(window).on('resize.bottom_fixed_element', this.__hideBottomFixedElements);\n        return this._super(...arguments);\n    },\n    /**\n     * @override\n     */\n    destroy() {\n        this._super(...arguments);\n        this.$scrollingElement.off('.bottom_fixed_element');\n        $(window).off('.bottom_fixed_element');\n        $('.o_bottom_fixed_element').removeClass('o_bottom_fixed_element_hidden');\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Hides the elements that are fixed at the bottom of the screen if the\n     * scroll reaches the bottom of the page and if the elements hide a button.\n     *\n     * @private\n     */\n    _hideBottomFixedElements() {\n        // Note: check in the whole DOM instead of #wrapwrap as unfortunately\n        // some things are still put outside of the #wrapwrap (like the livechat\n        // button which is the main reason of this code).\n        const $bottomFixedElements = $('.o_bottom_fixed_element');\n        if (!$bottomFixedElements.length) {\n            return;\n        }\n\n        // The bottom fixed elements are always hidden when a modal is open\n        // thanks to the CSS that is based on the 'modal-open' class added to\n        // the body. However, when the modal does not have a backdrop (e.g.\n        // cookies bar), this 'modal-open' class is not added. That's why we\n        // handle it here. Note that the popup widget code triggers a 'scroll'\n        // event when the modal is hidden to make the bottom fixed elements\n        // reappear.\n        if (this.el.querySelector('.s_popup_no_backdrop.show')) {\n            for (const el of $bottomFixedElements) {\n                el.classList.add('o_bottom_fixed_element_hidden');\n            }\n            return;\n        }\n\n        $bottomFixedElements.removeClass('o_bottom_fixed_element_hidden');\n        if ((this.$scrollingElement[0].offsetHeight + this.$scrollingElement[0].scrollTop) >= (this.$scrollingElement[0].scrollHeight - 2)) {\n            const buttonEls = [...this.$('.btn:visible')];\n            for (const el of $bottomFixedElements) {\n                if (buttonEls.some(button => dom.areColliding(button, el))) {\n                    el.classList.add('o_bottom_fixed_element_hidden');\n                }\n            }\n        }\n    },\n});\n\nregistry.WebsiteAnimate = publicWidget.Widget.extend({\n    selector: '#wrapwrap',\n    disabledInEditableMode: false,\n\n    offsetRatio: 0.3, // Dynamic offset ratio: 0.3 = (element's height/3)\n    offsetMin: 10, // Minimum offset for small elements (in pixels)\n\n    /**\n     * @override\n     */\n    start() {\n        this.lastScroll = 0;\n        this.$scrollingElement = $().getScrollingElement(this.ownerDocument);\n        this.$animatedElements = this.$('.o_animate');\n\n        // Fix for \"transform: none\" not overriding keyframe transforms on\n        // some iPhone using Safari. Note that all animated elements are checked\n        // (not only one) as the bug is not systematic and may depend on some\n        // other conditions (for example: an animated image in a block which is\n        // hidden on mobile would not have the issue).\n        const couldOverflowBecauseOfSafariBug = [...this.$animatedElements].some(el => {\n            return window.getComputedStyle(el).transform !== 'none';\n        });\n        this.forceOverflowXHidden = false;\n        if (couldOverflowBecauseOfSafariBug) {\n            this._toggleOverflowXHidden(true);\n            // Now prevent any call to _toggleOverflowXHidden to have an effect\n            this.forceOverflowXHidden = true;\n        }\n\n        // By default, elements are hidden by the css of o_animate.\n        // Render elements and trigger the animation then pause it in state 0.\n        _.each(this.$animatedElements, el => {\n            if (el.closest('.dropdown')) {\n                el.classList.add('o_animate_in_dropdown');\n                return;\n            }\n            this._resetAnimation($(el));\n        });\n        // Then we render all the elements, the ones which are invisible\n        // in state 0 (like fade_in for example) will stay invisible.\n        this.$animatedElements.css(\"visibility\", \"visible\");\n\n        // We use addEventListener instead of jQuery because we need 'capture'.\n        // Setting capture to true allows to take advantage of event bubbling\n        // for events that otherwise don\u2019t support it. (e.g. useful when\n        // scrolling a modal)\n        this.__onScrollWebsiteAnimate = _.throttle(this._onScrollWebsiteAnimate.bind(this), 200);\n        this.$scrollingElement[0].addEventListener('scroll', this.__onScrollWebsiteAnimate, {capture: true});\n\n        $(window).on('resize.o_animate, shown.bs.modal.o_animate, slid.bs.carousel.o_animate, shown.bs.tab.o_animate, shown.bs.collapse.o_animate', () => {\n            this.windowsHeight = $(window).height();\n            this._scrollWebsiteAnimate(this.$scrollingElement[0]);\n        }).trigger(\"resize\");\n\n        return this._super(...arguments);\n    },\n    /**\n     * @override\n     */\n    destroy() {\n        this._super(...arguments);\n        this.$target.find('.o_animate')\n            .removeClass('o_animating o_animated o_animate_preview o_animate_in_dropdown')\n            .css({\n                'animation-name': '',\n                'animation-play-state': '',\n                'visibility': '',\n            });\n        $(window).off('.o_animate');\n        this.$scrollingElement[0].removeEventListener('scroll', this.__onScrollWebsiteAnimate, {capture: true});\n        this.$scrollingElement[0].classList.remove('o_wanim_overflow_x_hidden');\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Starts animation and/or update element's state.\n     *\n     * @private\n     * @param {jQuery} $el\n     */\n    _startAnimation($el) {\n        // Forces the browser to redraw using setTimeout.\n        setTimeout(() => {\n            this._toggleOverflowXHidden(true);\n            $el\n            .css({\"animation-play-state\": \"running\"})\n            .addClass(\"o_animating\")\n            .one('webkitAnimationEnd oanimationend msAnimationEnd animationend', () => {\n                $el.addClass(\"o_animated\").removeClass(\"o_animating\");\n                this._toggleOverflowXHidden(false);\n                $(window).trigger(\"resize\");\n            });\n        });\n    },\n    /**\n     * @private\n     * @param {jQuery} $el\n     */\n    _resetAnimation($el) {\n        const animationName = $el.css(\"animation-name\");\n        $el.css({\"animation-name\": \"dummy-none\", \"animation-play-state\": \"\"})\n           .removeClass(\"o_animated o_animating\");\n\n        this._toggleOverflowXHidden(false);\n        // trigger a DOM reflow\n        void $el[0].offsetWidth;\n        $el.css({'animation-name': animationName , 'animation-play-state': 'paused'});\n    },\n    /**\n     * Shows/hides the horizontal scrollbar (on the #wrapwrap).\n     *\n     * @private\n     * @param {Boolean} add\n     */\n    _toggleOverflowXHidden(add) {\n        if (this.forceOverflowXHidden) {\n            return;\n        }\n        if (add) {\n            this.$scrollingElement[0].classList.add('o_wanim_overflow_x_hidden');\n        } else if (!this.$scrollingElement.find('.o_animating').length) {\n            this.$scrollingElement[0].classList.remove('o_wanim_overflow_x_hidden');\n        }\n    },\n    /**\n     * Gets element top offset by not taking CSS transforms into calculations.\n     *\n     * @private\n     * @param {Element} el\n     */\n    _getElementOffsetTop(el) {\n        // Loop through the DOM tree and add its parent's offset to get page offset.\n        var top = 0;\n        do {\n            top += el.offsetTop || 0;\n            el = el.offsetParent;\n        } while (el);\n        return top;\n    },\n    /**\n     * @private\n     * @param {Element} el\n     */\n    _scrollWebsiteAnimate(el) {\n        const scroll = $(el).scrollTop();\n        // Handle reverse scrolling\n        const direction = (scroll < this.lastScroll) ? -1 : 1;\n        this.lastScroll = scroll;\n\n        _.each(this.$target.find('.o_animate:not(.o_animate_in_dropdown)'), el => {\n            const $el = $(el);\n            const elHeight = $el.height();\n            const elOffset = direction * Math.max((elHeight * this.offsetRatio), this.offsetMin);\n            const state = $el.css(\"animation-play-state\");\n\n            // We need to offset for the change in position from some animation.\n            // So we get the top value by not taking CSS transforms into calculations.\n            // Cookies bar might be opened and considered as a modal but it is\n            // not really one (eg 'discrete' layout), and should not be used as\n            // scrollTop value.\n            const scrollTop = document.body.classList.contains(\"modal-open\") ?\n                this.$target.find('.modal:visible').scrollTop() :\n                this.$scrollingElement.scrollTop();\n            const elTop = this._getElementOffsetTop($el[0]) - scrollTop;\n\n            const visible = this.windowsHeight > (elTop + elOffset) && 0 < (elTop + elHeight - elOffset);\n            if (visible && state === \"paused\") {\n                $el.addClass(\"o_visible\");\n                this._startAnimation($el);\n            } else if (!visible && $el.hasClass(\"o_animate_both_scroll\") && state === \"running\") {\n                $el.removeClass(\"o_visible\");\n                this._resetAnimation($el);\n            }\n        });\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onScrollWebsiteAnimate(ev) {\n        this._scrollWebsiteAnimate(ev.currentTarget);\n    },\n});\n\n/**\n * The websites, by default, use image lazy loading via the loading=\"lazy\"\n * attribute on <img> elements. However, this does not work great on all\n * browsers. This widget fixes the behaviors with as less code as possible.\n */\nregistry.ImagesLazyLoading = publicWidget.Widget.extend({\n    selector: '#wrapwrap',\n\n    /**\n     * @override\n     */\n    start() {\n        // For each image on the page, force a 1px min-height so that Chrome\n        // understands the image exists on different zoom sizes of the browser.\n        // Indeed, without this, on a 90% zoom, some images were never loaded.\n        // Once the image has been loaded, the 1px min-height is removed.\n        // Note: another possible solution without JS would be this CSS rule:\n        // ```\n        // [loading=\"lazy\"] {\n        //     min-height: 1px;\n        // }\n        // ```\n        // This would solve the problem the same way with a CSS rule with a\n        // very small priority (any class setting a min-height would still have\n        // priority). However, the min-height would always be forced even once\n        // the image is loaded, which could mess with some layouts relying on\n        // the image intrinsic min-height.\n        const imgEls = this.$target[0].querySelectorAll('img[loading=\"lazy\"]');\n        for (const imgEl of imgEls) {\n            // Write initial min-height on the dataset, so that it can also\n            // be properly restored on widget destroy.\n            imgEl.dataset.lazyLoadingInitialMinHeight = imgEl.style.minHeight;\n            imgEl.style.minHeight = '1px';\n            wUtils.onceAllImagesLoaded($(imgEl)).then(() => {\n                if (this.isDestroyed()) {\n                    return;\n                }\n                this._restoreImage(imgEl);\n            });\n        }\n        return this._super(...arguments);\n    },\n    /**\n     * @override\n     */\n    destroy() {\n        this._super(...arguments);\n        const imgEls = this.$target[0].querySelectorAll('img[data-lazy-loading-initial-min-height]');\n        for (const imgEl of imgEls) {\n            this._restoreImage(imgEl);\n        }\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {HTMLImageElement} imgEl\n     */\n    _restoreImage(imgEl) {\n        imgEl.style.minHeight = imgEl.dataset.lazyLoadingInitialMinHeight;\n        delete imgEl.dataset.lazyLoadingInitialMinHeight;\n    },\n});\n\n/**\n * @todo while this solution mitigates the issue, it is not fixing it entirely\n * but mainly, we should find a better solution than a JS solution as soon as\n * one is available and ideally without having to make ugly patches to the SVGs.\n *\n * Due to a bug on Chrome when using browser zoom, there is sometimes a gap\n * between sections with shapes. This gap is due to a rounding issue when\n * positioning the SVG background images. This code reduces the rounding error\n * by ensuring that shape elements always have a width value as close to an\n * integer as possible.\n *\n * Note: a gap also appears between some shapes without zoom. This is likely\n * due to error in the shapes themselves. Many things were done to try and fix\n * this, but the remaining errors will likely be fixed with a review of the\n * shapes in future Odoo versions.\n *\n * /!\\\n * If a better solution for stable comes up, this widget behavior may be\n * disabled, avoid depending on it if possible.\n * /!\\\n */\nregistry.ZoomedBackgroundShape = publicWidget.Widget.extend({\n    selector: '.o_we_shape',\n    disabledInEditableMode: false,\n\n    /**\n     * @override\n     */\n    start() {\n        this._onBackgroundShapeResize();\n        this.throttledShapeResize = _.throttle(() => this._onBackgroundShapeResize(), 25);\n        window.addEventListener('resize', this.throttledShapeResize);\n        return this._super(...arguments);\n    },\n    /**\n     * @override\n     */\n    destroy() {\n        this._updateShapePosition();\n        window.removeEventListener('resize', this.throttledShapeResize);\n        this._super(...arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Updates the left and right offset of the shape.\n     *\n     * @private\n     * @param {string} offset\n     */\n    _updateShapePosition(offset = '') {\n        this.el.style.left = offset;\n        this.el.style.right = offset;\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onBackgroundShapeResize() {\n        this._updateShapePosition();\n        // Get the decimal part of the shape element width.\n        let decimalPart = this.el.getBoundingClientRect().width % 1;\n        // Round to two decimal places.\n        decimalPart = Math.round((decimalPart + Number.EPSILON) * 100) / 100;\n        // If there is a decimal part. (e.g. Chrome + browser zoom enabled)\n        if (decimalPart > 0) {\n            // Compensate for the gap by giving an integer width value to the\n            // shape by changing its \"right\" and \"left\" positions.\n            let offset = (decimalPart < 0.5 ? decimalPart : decimalPart - 1) / 2;\n            // This never causes the horizontal scrollbar to appear because it\n            // only appears if the overflow to the right exceeds 0.333px.\n            this._updateShapePosition(offset + 'px');\n        }\n    },\n});\n\nreturn {\n    Widget: publicWidget.Widget,\n    Animation: Animation,\n    registry: registry,\n\n    Class: Animation, // Deprecated\n};\n});\n", "/** @odoo-module alias=website.navbar */\n\nimport core from 'web.core';\nimport dom from 'web.dom';\nimport publicWidget from 'web.public.widget';\nimport concurrency from 'web.concurrency';\nimport Widget from 'web.Widget';\nimport { initAutoMoreMenu } from '@web/legacy/js/core/menu';\nimport { registry } from \"@web/core/registry\";\n\nvar WebsiteNavbar = publicWidget.RootWidget.extend({\n    xmlDependencies: ['/website/static/src/xml/website.xml'],\n    events: _.extend({}, publicWidget.RootWidget.prototype.events || {}, {\n        'click [data-action]': '_onActionMenuClick',\n        'mouseover > ul > li.dropdown:not(.show)': '_onMenuHovered',\n        'click .o_mobile_menu_toggle': '_onMobileMenuToggleClick',\n        'mouseenter #oe_applications:not(:has(.dropdown-item))': '_onOeApplicationsHovered',\n        'show.bs.dropdown #oe_applications:not(:has(.dropdown-item))': '_onOeApplicationsShow',\n    }),\n    custom_events: _.extend({}, publicWidget.RootWidget.prototype.custom_events || {}, {\n        'action_demand': '_onActionDemand',\n        'edit_mode': '_onEditMode',\n        'readonly_mode': '_onReadonlyMode',\n        'ready_to_save': '_onSave',\n    }),\n\n    /**\n     * @constructor\n     */\n    init: function () {\n        this._super.apply(this, arguments);\n        var self = this;\n        var initPromise = new Promise(function (resolve) {\n            self.resolveInit = resolve;\n        });\n        this._widgetDefs = [initPromise];\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        var self = this;\n        initAutoMoreMenu(this.el.querySelector('.o_menu_sections'), {\n            maxWidth: function () {\n                // The navbar contains different elements in community and\n                // enterprise, so we check for both of them here only\n                return self.$el.width()\n                    - (self.$('.o_menu_systray').outerWidth(true) || 0)\n                    - (self.$('#oe_applications').outerWidth(true) || 0)\n                    - (self.$('.o_menu_toggle').outerWidth(true) || 0)\n                    - (self.$('.o_menu_brand').outerWidth(true) || 0);\n            },\n        });\n        return this._super.apply(this, arguments).then(function () {\n            self.resolveInit();\n        });\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @override\n     */\n    _attachComponent: function () {\n        var def = this._super.apply(this, arguments);\n        this._widgetDefs.push(def);\n        return def;\n    },\n    /**\n     * As the WebsiteNavbar instance is designed to be unique, the associated\n     * registry has been instantiated outside of the class and is simply\n     * returned here.\n     *\n     * @override\n     */\n    _getRegistry: function () {\n        return registry.category(\"website_navbar_widgets\");\n    },\n    /**\n     * Searches for the automatic widget {@see RootWidget} which can handle that\n     * action.\n     *\n     * @private\n     * @param {string} actionName\n     * @param {Array} params\n     * @returns {Promise}\n     */\n    _handleAction: function (actionName, params, _i) {\n        var self = this;\n        return this._whenReadyForActions().then(function () {\n            var defs = [];\n            _.each(self._widgets, function (w) {\n                if (!w.handleAction) {\n                    return;\n                }\n\n                var def = w.handleAction(actionName, params);\n                if (def !== null) {\n                    defs.push(def);\n                }\n            });\n            if (!defs.length) {\n                // Handle the case where all action-capable components are not\n                // instantiated yet (rare) -> retry some times to eventually abort\n                if (_i > 50) {\n                    console.warn(_.str.sprintf(\"Action '%s' was not able to be handled.\", actionName));\n                    return Promise.reject();\n                }\n                return concurrency.delay(100).then(function () {\n                    return self._handleAction(actionName, params, (_i || 0) + 1);\n                });\n            }\n            return Promise.all(defs).then(function (values) {\n                if (values.length === 1) {\n                    return values[0];\n                }\n                return values;\n            });\n        });\n    },\n    /**\n     * @private\n     * @returns {Promise}\n     */\n    async _loadAppMenus() {\n        if (!this._loadAppMenusProm) {\n            this._loadAppMenusProm = this._rpc({\n                model: 'ir.ui.menu',\n                method: 'load_menus_root',\n                args: [],\n                kwargs: {\n                    context: {force_action: true},\n                },\n            });\n            const result = await this._loadAppMenusProm;\n            const menus = core.qweb.render('website.oe_applications_menu', {\n                'menu_data': result,\n            });\n            this.$('#oe_applications .dropdown-menu').html(menus);\n        }\n        return this._loadAppMenusProm;\n    },\n    /**\n     * @private\n     */\n    _whenReadyForActions: function () {\n        return Promise.all(this._widgetDefs);\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Called when the backend applications menu is hovered -> fetch the\n     * available menus and insert it in DOM.\n     *\n     * @private\n     */\n    _onOeApplicationsHovered: function () {\n        this._loadAppMenus();\n    },\n    /**\n     * Called when the backend applications menu is opening -> fetch the\n     * available menus and insert it in DOM. Needed on top of hovering as the\n     * dropdown could be opened via keyboard (or the user could just already\n     * be over the dropdown when the JS is fully loaded).\n     *\n     * @private\n     */\n    _onOeApplicationsShow: function () {\n        this._loadAppMenus();\n    },\n    /**\n     * Called when an action menu is clicked -> searches for the automatic\n     * widget {@see RootWidget} which can handle that action.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    _onActionMenuClick: function (ev) {\n        const restore = dom.addButtonLoadingEffect(ev.currentTarget);\n        this._handleAction($(ev.currentTarget).data('action')).then(restore).guardedCatch(restore);\n    },\n    /**\n     * Called when an action is asked to be executed from a child widget ->\n     * searches for the automatic widget {@see RootWidget} which can handle\n     * that action.\n     */\n    _onActionDemand: function (ev) {\n        var def = this._handleAction(ev.data.actionName, ev.data.params);\n        if (ev.data.onSuccess) {\n            def.then(ev.data.onSuccess);\n        }\n        if (ev.data.onFailure) {\n            def.guardedCatch(ev.data.onFailure);\n        }\n    },\n    /**\n     * Called in response to edit mode activation -> hides the navbar.\n     *\n     * @private\n     */\n    _onEditMode: function () {\n        this.$el.addClass('editing_mode');\n        this.do_hide();\n    },\n    /**\n     * Called when a submenu is hovered -> automatically opens it if another\n     * menu was already opened.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    _onMenuHovered: function (ev) {\n        var $opened = this.$('> ul > li.dropdown.show');\n        if ($opened.length) {\n            $opened.find('.dropdown-toggle').dropdown('toggle');\n            $(ev.currentTarget).find('.dropdown-toggle').dropdown('toggle');\n        }\n    },\n    /**\n     * Called when the mobile menu toggle button is click -> modifies the DOM\n     * to open the mobile menu.\n     *\n     * @private\n     */\n    _onMobileMenuToggleClick: function () {\n        this.$el.parent().toggleClass('o_mobile_menu_opened');\n    },\n    /**\n     * Called in response to edit mode activation -> hides the navbar.\n     *\n     * @private\n     */\n    _onReadonlyMode: function () {\n        this.$el.removeClass('editing_mode');\n        this.do_show();\n    },\n    /**\n     * Called in response to edit mode saving -> checks if action-capable\n     * children have something to save.\n     *\n     * @private\n     * @param {OdooEvent} ev\n     */\n    _onSave: function (ev) {\n        ev.data.defs.push(this._handleAction('on_save'));\n    },\n});\n\nvar WebsiteNavbarActionWidget = Widget.extend({\n    /**\n     * 'Action name' -> 'Handler name' object\n     *\n     * Any [data-action=\"x\"] element inside the website navbar will\n     * automatically trigger an action \"x\". This action can then be handled by\n     * any `WebsiteNavbarActionWidget` instance if the action name \"x\" is\n     * registered in this `actions` object.\n     */\n    actions: {},\n\n    //--------------------------------------------------------------------------\n    // Public\n    //--------------------------------------------------------------------------\n\n    /**\n     * Checks if the widget can execute an action whose name is given, with the\n     * given parameters. If it is the case, execute that action.\n     *\n     * @param {string} actionName\n     * @param {Array} params\n     * @returns {Promise|null} action's promise or null if no action was found\n     */\n    handleAction: function (actionName, params) {\n        var action = this[this.actions[actionName]];\n        if (action) {\n            return Promise.resolve(action.apply(this, params || []));\n        }\n        return null;\n    },\n});\n\nregistry.category(\"public_root_widgets\").add(\"WebsiteNavbar\", {\n    Widget: WebsiteNavbar,\n    selector: '#oe_main_menu_navbar',\n});\n\nexport default {\n    WebsiteNavbar: WebsiteNavbar,\n    WebsiteNavbarActionWidget: WebsiteNavbarActionWidget,\n};\n", "//\n// This file is meant to allow to switch the type of an input #password\n// from password to text on mousedown on an input group.\n// On mouse down, we see the password in clear text\n// On mouse up, we hide it again.\n//\nodoo.define('website.show_password', function (require) {\n'use strict';\n\nvar publicWidget = require('web.public.widget');\n\npublicWidget.registry.ShowPassword = publicWidget.Widget.extend({\n    selector: '#showPass',\n    events: {\n        'mousedown': '_onShowText',\n        'touchstart': '_onShowText',\n    },\n\n    /**\n     * @override\n     */\n    destroy: function () {\n        this._super(...arguments);\n        $('body').off(\".ShowPassword\");\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onShowPassword: function () {\n        this.$el.closest('.input-group').find('#password').attr('type', 'password');\n    },\n    /**\n     * @private\n     */\n    _onShowText: function () {\n        $('body').one('mouseup.ShowPassword touchend.ShowPassword', this._onShowPassword.bind(this));\n        this.$el.closest('.input-group').find('#password').attr('type', 'text');\n    },\n});\n\nreturn publicWidget.registry.ShowPassword;\n\n});\n", "odoo.define('website.post_link', function (require) {\n'use strict';\n\nconst publicWidget = require('web.public.widget');\nconst wUtils = require('website.utils');\n\npublicWidget.registry.postLink = publicWidget.Widget.extend({\n    selector: '.post_link',\n    events: {\n        'click': '_onClickPost',\n    },\n\n    /**\n     * @override\n     */\n    start() {\n        // Allows the link to be interacted with only when Javascript is loaded.\n        this.el.classList.add('o_post_link_js_loaded');\n        return this._super(...arguments);\n    },\n    /**\n     * @override\n     */\n    destroy() {\n        this._super(...arguments);\n        this.el.classList.remove('o_post_link_js_loaded');\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    _onClickPost: function (ev) {\n        ev.preventDefault();\n        const url = this.el.dataset.post || this.el.href;\n        let data = {};\n        for (let [key, value] of Object.entries(this.el.dataset)) {\n            if (key.startsWith('post_')) {\n                data[key.slice(5)] = value;\n            }\n        }\n        wUtils.sendRequest(url, data);\n    },\n});\n\n});\n", "//\n// This file is meant to regroup your javascript code. You can either copy/past\n// any code that should be executed on each page loading or write your own\n// taking advantage of the Odoo framework to create new behaviors or modify\n// existing ones. For example, doing this will greet any visitor with a 'Hello,\n// world !' message in a popup:\n//\n/*\nodoo.define('website.user_custom_code', function (require) {\n'use strict';\n\nvar Dialog = require('web.Dialog');\nvar publicWidget = require('web.public.widget');\n\npublicWidget.registry.HelloWorldPopup = publicWidget.Widget.extend({\n    selector: '#wrapwrap',\n\n    start: function () {\n        Dialog.alert(this, \"Hello, world!\");\n        return this._super.apply(this, arguments);\n    },\n})\n});\n*/\n", "/** @odoo-module **/\n\nimport publicWidget from 'web.public.widget';\nimport {generateGMapLink} from 'website.utils';\n\npublicWidget.registry.Map = publicWidget.Widget.extend({\n    selector: '.s_map',\n\n    /**\n     * @override\n     */\n    start() {\n        if (!this.el.querySelector('.s_map_embedded')) {\n            // The iframe is not found inside the snippet. This is probably due\n            // the sanitization of a field during the save, like in a product\n            // description field.\n            // In such cases, reconstruct the iframe.\n            const dataset = this.el.dataset;\n            if (dataset.mapAddress) {\n                const iframeEl = document.createElement('iframe');\n                iframeEl.classList.add('s_map_embedded', 'o_not_editable');\n                iframeEl.setAttribute('width', '100%');\n                iframeEl.setAttribute('height', '100%');\n                iframeEl.setAttribute('frameborder', '0');\n                iframeEl.setAttribute('scrolling', 'no');\n                iframeEl.setAttribute('marginheight', '0');\n                iframeEl.setAttribute('marginwidth', '0');\n                iframeEl.setAttribute('src', generateGMapLink(dataset));\n                this.el.querySelector('.s_map_color_filter').before(iframeEl);\n            }\n        }\n        return this._super(...arguments);\n    },\n});\n\nexport default publicWidget.registry.Map;\n", "$(function () {\n    $('input.o-purchase-datetimepicker').datetimepicker();\n    $('input.o-purchase-datetimepicker').on(\"hide.datetimepicker\", function () {\n        $(this).parents('form').submit();\n    });\n})\n", "odoo.define('purchase.PurchasePortalSidebar', function (require) {\n'use strict';\n\nvar publicWidget = require('web.public.widget');\nvar PortalSidebar = require('portal.PortalSidebar');\n\npublicWidget.registry.PurchasePortalSidebar = PortalSidebar.extend({\n    selector: '.o_portal_purchase_sidebar',\n\n    /**\n     * @constructor\n     */\n    init: function (parent, options) {\n        this._super.apply(this, arguments);\n        this.authorizedTextTag = ['em', 'b', 'i', 'u'];\n        this.spyWatched = $('body[data-target=\".navspy\"]');\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        var def = this._super.apply(this, arguments);\n        var $spyWatcheElement = this.$el.find('[data-id=\"portal_sidebar\"]');\n        this._setElementId($spyWatcheElement);\n        // Nav Menu ScrollSpy\n        this._generateMenu();\n        return def;\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //---------------------------------------------------------------------------\n\n    /**\n     * create an unique id and added as a attribute of spyWatched element\n     *\n     * @private\n     * @param {string} prefix\n     * @param {Object} $el\n     *\n     */\n    _setElementId: function (prefix, $el) {\n        var id = _.uniqueId(prefix);\n        this.spyWatched.find($el).attr('id', id);\n        return id;\n    },\n    /**\n     * generate the new spy menu\n     *\n     * @private\n     *\n     */\n    _generateMenu: function () {\n        var self = this,\n            lastLI = false,\n            lastUL = null,\n            $bsSidenav = this.$el.find('.bs-sidenav');\n\n        $(\"#quote_content [id^=quote_header_], #quote_content [id^=quote_]\", this.spyWatched).attr(\"id\", \"\");\n        _.each(this.spyWatched.find(\"#quote_content h2, #quote_content h3\"), function (el) {\n            var id, text;\n            switch (el.tagName.toLowerCase()) {\n                case \"h2\":\n                    id = self._setElementId('quote_header_', el);\n                    text = self._extractText($(el));\n                    if (!text) {\n                        break;\n                    }\n                    lastLI = $(\"<li class='nav-item'>\").append($('<a class=\"nav-link\" style=\"max-width: 200px;\" href=\"#' + id + '\"/>').text(text)).appendTo($bsSidenav);\n                    lastUL = false;\n                    break;\n                case \"h3\":\n                    id = self._setElementId('quote_', el);\n                    text = self._extractText($(el));\n                    if (!text) {\n                        break;\n                    }\n                    if (lastLI) {\n                        if (!lastUL) {\n                            lastUL = $(\"<ul class='nav flex-column'>\").appendTo(lastLI);\n                        }\n                        $(\"<li class='nav-item'>\").append($('<a class=\"nav-link\" style=\"max-width: 200px;\" href=\"#' + id + '\"/>').text(text)).appendTo(lastUL);\n                    }\n                    break;\n            }\n            el.setAttribute('data-anchor', true);\n        });\n        this.trigger_up('widgets_start_request', {$target: $bsSidenav});\n    },\n    /**\n     * extract text of menu title for sidebar\n     *\n     * @private\n     * @param {Object} $node\n     *\n     */\n    _extractText: function ($node) {\n        var self = this;\n        var rawText = [];\n        _.each($node.contents(), function (el) {\n            var current = $(el);\n            if ($.trim(current.text())) {\n                var tagName = current.prop(\"tagName\");\n                if (_.isUndefined(tagName) || (!_.isUndefined(tagName) && _.contains(self.authorizedTextTag, tagName.toLowerCase()))) {\n                    rawText.push($.trim(current.text()));\n                }\n            }\n        });\n        return rawText.join(' ');\n    },\n});\n});\n", "/** @odoo-module **/\n\nimport time from 'web.time';\nimport publicWidget from 'web.public.widget';\n\npublicWidget.registry.ProjectRatingImage = publicWidget.Widget.extend({\n    selector: '.o_portal_project_rating .o_rating_image',\n\n    /**\n     * @override\n     */\n    start: function () {\n        this.$el.popover({\n            placement: 'bottom',\n            trigger: 'hover',\n            html: true,\n            content: function () {\n                var $elem = $(this);\n                var id = $elem.data('id');\n                var ratingDate = $elem.data('rating-date');\n                var baseDate = time.auto_str_to_date(ratingDate);\n                var duration = moment(baseDate).fromNow();\n                var $rating = $('#rating_' + id);\n                $rating.find('.rating_timeduration').text(duration);\n                return $rating.html();\n            },\n        });\n        return this._super.apply(this, arguments);\n    },\n});\n", "/** @odoo-module **/\n\nimport publicWidget from 'web.public.widget';\n\npublicWidget.registry.WebsitePaymentDonation = publicWidget.Widget.extend({\n    selector: '.o_donation_payment_form',\n    events: {\n        'focus .o_amount_input': '_onFocusAmountInput',\n        'change #donation_comment_checkbox': '_onChangeDonationComment'\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onFocusAmountInput(ev) {\n        this.$target.find('#other_amount').prop(\"checked\", true);\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onChangeDonationComment(ev) {\n        const $donationComment = this.$target.find('#donation_comment');\n        const checked = $(ev.currentTarget).is(':checked');\n        $donationComment.toggleClass('d-none', !checked);\n        if (!checked) {\n            $donationComment.val('');\n        }\n    },\n});\n", "/** @odoo-module **/\n\nimport core from 'web.core';\nimport {_t} from 'web.core';\nimport checkoutForm from 'payment.checkout_form';\n\ncheckoutForm.include({\n    events: _.extend({}, checkoutForm.prototype.events || {}, {\n        'change .o_wpayment_fee_impact': '_onFeeParameterChange',\n    }),\n\n    /**\n     * @override\n     */\n    start: function () {\n        core.bus.on('update_shipping_cost', this, this._updateShippingCost);\n        return this._super.apply(this, arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Perform some validations for donations before performing payment \n     *\n     * @override method from payment.payment_form_mixin\n     * @private\n     * @param {string} provider - The provider of the payment option's acquirer\n     * @param {number} paymentOptionId - The id of the payment option handling the transaction\n     * @param {string} flow - The online payment flow of the transaction\n     * @return {Promise}\n     */\n    _processPayment: function (provider, paymentOptionId, flow) {\n        if ($('.o_donation_payment_form').length) {\n            const errorFields = {};\n            if (!this.$('input[name=\"email\"]')[0].checkValidity()) {\n                errorFields['email'] = _t(\"Email is invalid\");\n            }\n            const mandatoryFields = {\n                'name': _t('Name'),\n                'email': _t('Email'),\n                'country_id': _t('Country'),\n            };\n            for (const id in mandatoryFields) {\n                const $field = this.$('input[name=\"' + id + '\"],select[name=\"' + id + '\"]');\n                $field.removeClass('is-invalid').popover('dispose');\n                if (!$field.val().trim()) {\n                    errorFields[id] = _.str.sprintf(_t(\"Field '%s' is mandatory\"), mandatoryFields[id]);\n                }\n            }\n            if (Object.keys(errorFields).length) {\n                for (const id in errorFields) {\n                    const $field = this.$('input[name=\"' + id + '\"],select[name=\"' + id + '\"]');\n                    $field.addClass('is-invalid');\n                    $field.popover({content: errorFields[id], trigger: 'hover', container: 'body', placement: 'top'});\n                    $field.data(\"bs.popover\").config.content = errorFields[id];\n                }\n                this._displayError(\n                    _t(\"Validation Error\"),\n                    _t(\"Some information is missing to process your payment.\")\n                );\n                return Promise.resolve();\n            }\n        }\n        return this._super(...arguments);\n    },\n    /**\n     * Add params used by the donation snippet to the transaction route params.\n     *\n     * @override method from payment.payment_form_mixin\n     * @private\n     * @param {string} provider - The provider of the selected payment option's acquirer\n     * @param {number} paymentOptionId - The id of the selected payment option\n     * @param {string} flow - The online payment flow of the selected payment option\n     * @return {object} The extended transaction route params\n     */\n    _prepareTransactionRouteParams: function (provider, paymentOptionId, flow) {\n        const transactionRouteParams = this._super(...arguments);\n        return $('.o_donation_payment_form').length ? {\n            ...transactionRouteParams,\n            'partner_details': {\n                'name': this.$('input[name=\"name\"]').val(),\n                'email': this.$('input[name=\"email\"]').val(),\n                'country_id': this.$('select[name=\"country_id\"]').val(),\n            },\n            'donation_comment': this.$('#donation_comment').val(),\n            'donation_recipient_email': this.$('input[name=\"donation_recipient_email\"]').val(),\n        } : transactionRouteParams;\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Update the total amount to be paid.\n     *\n     * Called upon change of shipping method\n     *\n     * @private\n     * @param {float} amount\n     */\n     _updateShippingCost: function (amount) {\n        this.txContext.amount = amount;\n     },\n    /**\n     * Update the fees associated to each acquirer.\n     *\n     * Called upon change of any parameter that might impact the fees (marked with\n     * .o_wpayment_fee_impact).\n     *\n     * @private\n     * @param {Event} ev\n     * @return {undefined}\n     */\n    _onFeeParameterChange: function (ev) {\n        const targetId = ev.target.id;\n        if (targetId.indexOf(\"amount\") >= 0) {\n            this.txContext.amount = ev.target.value;\n        }\n        const acquirerIds = [];\n        for (const card of this.$('.o_payment_option_card:has(.o_payment_fee)')) {\n            const radio = $(card).find('input[name=\"o_payment_radio\"]');\n            if (radio.data(\"paymentOptionType\") === 'acquirer') {\n                acquirerIds.push(radio.data(\"paymentOptionId\"));\n            }\n        }\n        const countryId = this.$('select[name=\"country_id\"]').val();\n        if (acquirerIds && this.txContext.amount) {\n            this._rpc({\n                route: '/donation/get_acquirer_fees',\n                params: {\n                    'acquirer_ids': acquirerIds,\n                    'amount': this.txContext.amount !== undefined\n                        ? parseFloat(this.txContext.amount) : null,\n                    'currency_id': this.txContext.currencyId\n                        ? parseInt(this.txContext.currencyId) : null,\n                    'country_id': countryId,\n                },\n            }).then(feesPerAcquirer => {\n                for (const card of this.$('.o_payment_option_card:has(.o_payment_fee)')) {\n                    const radio = $(card).find('input[name=\"o_payment_radio\"]');\n                    if (radio.data(\"paymentOptionType\") === 'acquirer') {\n                        const acquirerId = radio.data(\"paymentOptionId\");\n                        const chunk = $(card).find('.o_payment_fee .oe_currency_value')[0];\n                        chunk.innerText = (feesPerAcquirer[acquirerId] || 0).toFixed(2);\n                    }\n                }\n            }).guardedCatch(error => {\n                error.event.preventDefault();\n                this._displayError(\n                    _t(\"Server Error\"),\n                    _t(\"We could not obtain payment fees.\"),\n                    error.message.data.message\n                );\n            });\n        }\n    },\n});\n", "odoo.define('website_mail.follow', function (require) {\n'use strict';\n\nvar publicWidget = require('web.public.widget');\n\npublicWidget.registry.follow = publicWidget.Widget.extend({\n    selector: '#wrapwrap:has(.js_follow)',\n    disabledInEditableMode: false,\n\n    /**\n     * @override\n     */\n    start: function () {\n        var self = this;\n        this.isUser = false;\n        var $jsFollowEls = this.$el.find('.js_follow');\n\n        var always = function (data) {\n            self.isUser = data[0].is_user;\n            const $jsFollowToEnable = $jsFollowEls.filter(function () {\n                const model = this.dataset.object;\n                return model in data[1] && data[1][model].includes(parseInt(this.dataset.id));\n            });\n            self._toggleSubscription(true, data[0].email, $jsFollowToEnable);\n            self._toggleSubscription(false, data[0].email, $jsFollowEls.not($jsFollowToEnable));\n            $jsFollowEls.removeClass('d-none');\n        };\n\n        const records = {};\n        for (const el of $jsFollowEls) {\n            const model = el.dataset.object;\n            if (!(model in records)) {\n                records[model] = [];\n            }\n            records[model].push(parseInt(el.dataset.id));\n        }\n\n        this._rpc({\n            route: '/website_mail/is_follower',\n            params: {\n                records: records,\n            },\n        }).then(always).guardedCatch(always);\n\n        // not if editable mode to allow designer to edit\n        if (!this.editableMode) {\n            $('.js_follow > .input-group-append.d-none').removeClass('d-none');\n            this.$target.find('.js_follow_btn, .js_unfollow_btn').on('click', function (event) {\n                event.preventDefault();\n                self._onClick(event);\n            });\n        }\n        return this._super.apply(this, arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Toggles subscription state for every given records.\n     *\n     * @private\n     * @param {boolean} follow\n     * @param {string} email\n     * @param {jQuery} $jsFollowEls\n     */\n    _toggleSubscription: function (follow, email, $jsFollowEls) {\n        if (follow) {\n            this._updateSubscriptionDOM(follow, email, $jsFollowEls);\n        } else {\n            for (const el of $jsFollowEls) {\n                const follow = !email && el.getAttribute('data-unsubscribe');\n                this._updateSubscriptionDOM(follow, email, $(el));\n            }\n        }\n    },\n    /**\n     * Updates subscription DOM for every given records.\n     * This should not be called directly, use `_toggleSubscription`.\n     *\n     * @private\n     * @param {boolean} follow\n     * @param {string} email\n     * @param {jQuery} $jsFollowEls\n     */\n    _updateSubscriptionDOM: function (follow, email, $jsFollowEls) {\n        $jsFollowEls.find('input.js_follow_email')\n            .val(email || \"\")\n            .attr(\"disabled\", email && (follow || this.isUser) ? \"disabled\" : false);\n        $jsFollowEls.attr(\"data-follow\", follow ? 'on' : 'off');\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onClick: function (ev) {\n        var self = this;\n        var $jsFollow = $(ev.currentTarget).closest('.js_follow');\n        var $email = $jsFollow.find(\".js_follow_email\");\n\n        if ($email.length && !$email.val().match(/.+@.+/)) {\n            $jsFollow.addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');\n            return false;\n        }\n        $jsFollow.removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');\n\n        var email = $email.length ? $email.val() : false;\n        if (email || this.isUser) {\n            this._rpc({\n                route: '/website_mail/follow',\n                params: {\n                    'id': +$jsFollow.data('id'),\n                    'object': $jsFollow.data('object'),\n                    'message_is_follower': $jsFollow.attr(\"data-follow\") || \"off\",\n                    'email': email,\n                },\n            }).then(function (follow) {\n                self._toggleSubscription(follow, email, $jsFollow);\n            });\n        }\n    },\n});\n});\n", "odoo.define('rating.portal.chatter', function (require) {\n'use strict';\n\nvar core = require('web.core');\nvar portalChatter = require('portal.chatter');\nvar utils = require('web.utils');\nvar time = require('web.time');\n\nvar _t = core._t;\nvar PortalChatter = portalChatter.PortalChatter;\nvar qweb = core.qweb;\n\n/**\n * PortalChatter\n *\n * Extends Frontend Chatter to handle rating\n */\nPortalChatter.include({\n    events: _.extend({}, PortalChatter.prototype.events, {\n        // star based control\n        'click .o_website_rating_select': '_onClickStarDomain',\n        'click .o_website_rating_select_text': '_onClickStarDomainReset',\n        // publisher comments\n        'click .o_wrating_js_publisher_comment_btn': '_onClickPublisherComment',\n        'click .o_wrating_js_publisher_comment_edit': '_onClickPublisherComment',\n        'click .o_wrating_js_publisher_comment_delete': '_onClickPublisherCommentDelete',\n        'click .o_wrating_js_publisher_comment_submit': '_onClickPublisherCommentSubmit',\n        'click .o_wrating_js_publisher_comment_cancel': '_onClickPublisherCommentCancel',\n    }),\n    xmlDependencies: (PortalChatter.prototype.xmlDependencies || [])\n        .concat([\n            '/portal_rating/static/src/xml/portal_tools.xml',\n            '/portal_rating/static/src/xml/portal_chatter.xml'\n        ]),\n\n    /**\n     * @constructor\n     */\n    init: function (parent, options) {\n        this._super.apply(this, arguments);\n        // options\n        if (!_.contains(this.options, 'display_rating')) {\n            this.options = _.defaults(this.options, {\n                'display_rating': false,\n                'rating_default_value': 0.0,\n            });\n        }\n        // rating card\n        this.set('rating_card_values', {});\n        this.set('rating_value', false);\n        this.on(\"change:rating_value\", this, this._onChangeRatingDomain);\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        this._super.apply(this, arguments);\n        this.on(\"change:rating_card_values\", this, this._renderRatingCard);\n    },\n\n    //--------------------------------------------------------------------------\n    // Public\n    //--------------------------------------------------------------------------\n\n    /**\n     * Update the messages format\n     *\n     * @param {Array<Object>} messages\n     * @returns {Array}\n     */\n    preprocessMessages: function (messages) {\n        var self = this;\n        messages = this._super.apply(this, arguments);\n        if (this.options['display_rating']) {\n            _.each(messages, function (m, i) {\n                m.rating_value = self.roundToHalf(m['rating_value']);\n                m.rating = self._preprocessCommentData(m.rating, i);\n            });\n        }\n        // save messages in the widget to process correctly the publisher comment templates\n        this.messages = messages;\n        return messages;\n    },\n    /**\n     * Round the given value with a precision of 0.5.\n     *\n     * Examples:\n     * - 1.2 --> 1.0\n     * - 1.7 --> 1.5\n     * - 1.9 --> 2.0\n     *\n     * @param {Number} value\n     * @returns Number\n     **/\n    roundToHalf: function (value) {\n        var converted = parseFloat(value); // Make sure we have a number\n        var decimal = (converted - parseInt(converted, 10));\n        decimal = Math.round(decimal * 10);\n        if (decimal === 5) {\n            return (parseInt(converted, 10) + 0.5);\n        }\n        if ((decimal < 3) || (decimal > 7)) {\n            return Math.round(converted);\n        } else {\n            return (parseInt(converted, 10) + 0.5);\n        }\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @override\n     * @returns {Promise}\n     */\n    _chatterInit: async function () {\n        const result = await this._super(...arguments);\n        this._updateRatingCardValues(result);\n        return result;\n    },\n    /**\n     * @override\n     * @param {Array} domain\n     * @returns {Promise}\n     */\n    messageFetch: async function (domain) {\n        const result = await this._super(...arguments);\n        this._updateRatingCardValues(result);\n        return result;\n    },\n    /**\n     * Calculates and Updates rating values i.e. average, percentage\n     *\n     * @private\n     */\n    _updateRatingCardValues: function (result) {\n        if (!result['rating_stats']) {\n            return;\n        }\n        const ratingData = {\n            'avg': Math.round(result['rating_stats']['avg'] * 100) / 100,\n            'percent': [],\n        };\n        _.each(_.keys(result['rating_stats']['percent']).reverse(), function (rating) {\n            ratingData['percent'].push({\n                'num': rating,\n                'percent': utils.round_precision(result['rating_stats']['percent'][rating], 0.01),\n            });\n        });\n        this.set('rating_card_values', ratingData);\n    },\n    /**\n     * @override\n     */\n    _messageFetchPrepareParams: function () {\n        var params = this._super.apply(this, arguments);\n        if (this.options['display_rating']) {\n            params['rating_include'] = true;\n        }\n        return params;\n    },\n\n    /**\n     * render rating card\n     *\n     * @private\n     */\n    _renderRatingCard: function () {\n        this.$('.o_website_rating_card_container').replaceWith(qweb.render(\"portal_rating.rating_card\", {widget: this}));\n    },\n    /**\n     * Default rating data for publisher comment qweb template\n     * @private\n     * @param {Integer} messageIndex \n     */\n    _newPublisherCommentData: function (messageIndex) {\n        return {\n            mes_index: messageIndex,\n            publisher_id: this.options.partner_id,\n            publisher_avatar: _.str.sprintf('/web/image/res.partner/%s/avatar_128/50x50', this.options.partner_id),\n            publisher_name: _t(\"Write your comment\"),\n            publisher_datetime: '',\n            publisher_comment: '',\n        };\n    }, \n\n     /**\n     * preprocess the rating data comming from /website/rating/comment or the chatter_init\n     * Can be also use to have new rating data for a new publisher comment\n     * @param {JSON} rawRating \n     * @returns {JSON} the process rating data\n     */\n    _preprocessCommentData: function (rawRating, messageIndex) {\n        var ratingData = {\n            id: rawRating.id,\n            mes_index: messageIndex,\n            publisher_datetime: rawRating.publisher_datetime ? moment(time.str_to_datetime(rawRating.publisher_datetime)).format('MMMM Do YYYY, h:mm:ss a') : \"\",\n            publisher_comment: rawRating.publisher_comment ? rawRating.publisher_comment : '',\n        };\n\n        // split array (id, display_name) of publisher_id into publisher_id and publisher_name\n        if (rawRating.publisher_id && rawRating.publisher_id.length >= 2) {\n            ratingData.publisher_id = rawRating.publisher_id[0];\n            ratingData.publisher_name = rawRating.publisher_id[1];\n            ratingData.publisher_avatar = _.str.sprintf('/web/image/res.partner/%s/avatar_128/50x50', ratingData.publisher_id);\n        }\n        var commentData = _.extend(this._newPublisherCommentData(messageIndex), ratingData);\n        return commentData;\n    },\n\n    /** ---------------\n     * Selection of elements for the publisher comment feature\n     * Only available from a source in a publisher_comment or publisher_comment_form template\n     */\n\n    _getCommentContainer: function ($source) {\n        return $source.parents(\".o_wrating_publisher_container\").first().find(\".o_wrating_publisher_comment\").first();\n    },\n\n    _getCommentButton: function ($source) {\n        return $source.parents(\".o_wrating_publisher_container\").first().find(\".o_wrating_js_publisher_comment_btn\").first();\n    },\n\n    _getCommentTextarea: function ($source) {\n        return $source.parents(\".o_wrating_publisher_container\").first().find(\".o_portal_rating_comment_input\").first();\n    },\n\n    _focusTextComment: function ($source) {\n        this._getCommentTextarea($source).focus();\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {MouseEvent} ev\n     */\n    _onClickStarDomain: function (ev) {\n        var $tr = this.$(ev.currentTarget);\n        var num = $tr.data('star');\n        if ($tr.css('opacity') === '1') {\n            this.set('rating_value', num);\n            this.$('.o_website_rating_select').css({\n                'opacity': 0.5,\n            });\n            this.$('.o_website_rating_select_text[data-star=\"' + num + '\"]').css({\n                'visibility': 'visible',\n                'opacity': 1,\n            });\n            this.$('.o_website_rating_select[data-star=\"' + num + '\"]').css({\n                'opacity': 1,\n            });\n        }\n    },\n    /**\n     * @private\n     * @param {MouseEvent} ev\n     */\n    _onClickStarDomainReset: function (ev) {\n        ev.stopPropagation();\n        ev.preventDefault();\n        this.set('rating_value', false);\n        this.$('.o_website_rating_select_text').css('visibility', 'hidden');\n        this.$('.o_website_rating_select').css({\n            'opacity': 1,\n        });\n    },\n\n    /**\n     * @private\n     * @param {MouseEvent} ev\n     */\n    _onClickPublisherComment: function (ev) {\n        var $source = this.$(ev.currentTarget);\n        // If the form is already present => like cancel remove the form\n        if (this._getCommentTextarea($source).length === 1) {\n            this._getCommentContainer($source).empty();\n            return;\n        }\n        var messageIndex = $source.data(\"mes_index\");\n        var data = {is_publisher: this.options['is_user_publisher']}; \n        data.rating = this._newPublisherCommentData(messageIndex);\n        \n        var oldRating = this.messages[messageIndex].rating;\n        data.rating.publisher_comment = oldRating.publisher_comment ? oldRating.publisher_comment : '';\n        this._getCommentContainer($source).html($(qweb.render(\"portal_rating.chatter_rating_publisher_form\", data)));\n        this._focusTextComment($source);\n    },\n\n    /**\n     * @private\n     * @param {MouseEvent} ev\n     */\n    _onClickPublisherCommentDelete: function (ev) {\n        var self = this;\n        var $source = this.$(ev.currentTarget);\n\n        var messageIndex = $source.data(\"mes_index\");\n        var ratingId = this.messages[messageIndex].rating.id;\n\n        this._rpc({\n            route: '/website/rating/comment',\n            params: {\n                \"rating_id\": ratingId,\n                \"publisher_comment\": '' // Empty publisher comment means no comment\n            }\n        }).then(function (res) {\n            self.messages[messageIndex].rating = self._preprocessCommentData(res, messageIndex);\n            self._getCommentButton($source).removeClass(\"d-none\");\n            self._getCommentContainer($source).empty();\n        });\n    },  \n\n     /**\n     * @private\n     * @param {MouseEvent} ev\n     */\n    _onClickPublisherCommentSubmit: function (ev) {\n        var self = this;\n        var $source = this.$(ev.currentTarget);\n\n        var messageIndex = $source.data(\"mes_index\");\n        var comment = this._getCommentTextarea($source).val();\n        var ratingId = this.messages[messageIndex].rating.id;\n\n        this._rpc({\n            route: '/website/rating/comment',\n            params: {\n                \"rating_id\": ratingId,\n                \"publisher_comment\": comment\n            }\n        }).then(function (res) {\n\n            // Modify the related message\n            self.messages[messageIndex].rating = self._preprocessCommentData(res, messageIndex);\n            if (self.messages[messageIndex].rating.publisher_comment !== '') {\n                // Remove the button comment if exist and render the comment\n                self._getCommentButton($source).addClass('d-none');\n                self._getCommentContainer($source).html($(qweb.render(\"portal_rating.chatter_rating_publisher_comment\", { \n                    rating: self.messages[messageIndex].rating,\n                    is_publisher: self.options.is_user_publisher\n                })));\n            } else {\n                // Empty string or false considers as no comment\n                self._getCommentButton($source).removeClass(\"d-none\");\n                self._getCommentContainer($source).empty();       \n            }\n        });\n    },\n\n     /**\n     * @private\n     * @param {MouseEvent} ev\n     */\n    _onClickPublisherCommentCancel: function (ev) {\n        var $source = this.$(ev.currentTarget);\n        var messageIndex = $source.data(\"mes_index\");\n\n        var comment = this.messages[messageIndex].rating.publisher_comment;\n        if (comment) {\n            var data = {\n                rating: this.messages[messageIndex].rating,\n                is_publisher: this.options.is_user_publisher\n            };\n            this._getCommentContainer($source).html($(qweb.render(\"portal_rating.chatter_rating_publisher_comment\", data)));\n        } else {\n            this._getCommentContainer($source).empty();\n        }\n    },\n\n    /**\n     * @private\n     */\n    _onChangeRatingDomain: function () {\n        var domain = [];\n        if (this.get('rating_value')) {\n            domain = [['rating_value', '=', this.get('rating_value')]];\n        }\n        this._changeCurrentPage(1, domain);\n    },\n});\n});\n", "odoo.define('rating.portal.composer', function (require) {\n'use strict';\n\nvar core = require('web.core');\nvar portalComposer = require('portal.composer');\n\nvar _t = core._t;\n\nvar PortalComposer = portalComposer.PortalComposer;\n\n/**\n * PortalComposer\n *\n * Extends Portal Composer to handle rating submission\n */\nPortalComposer.include({\n    events: _.extend({}, PortalComposer.prototype.events, {\n        'click .stars i': '_onClickStar',\n        'mouseleave .stars': '_onMouseleaveStarBlock',\n        'mousemove .stars i': '_onMoveStar',\n        'mouseleave .stars i': '_onMoveLeaveStar',\n    }),\n\n    /**\n     * @constructor\n     */\n    init: function (parent, options) {\n        this._super.apply(this, arguments);\n\n        // apply ratio to default rating value\n        if (options.default_rating_value) {\n            options.default_rating_value = parseFloat(options.default_rating_value);\n        }\n\n        // default options\n        this.options = _.defaults(this.options, {\n            'default_message': false,\n            'default_message_id': false,\n            'default_rating_value': 0.0,\n            'force_submit_url': false,\n        });\n        // star input widget\n        this.labels = {\n            '0': \"\",\n            '1': _t(\"I hate it\"),\n            '2': _t(\"I don't like it\"),\n            '3': _t(\"It's okay\"),\n            '4': _t(\"I like it\"),\n            '5': _t(\"I love it\"),\n        };\n        this.user_click = false; // user has click or not\n        this.set(\"star_value\", this.options.default_rating_value);\n        this.on(\"change:star_value\", this, this._onChangeStarValue);\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        var self = this;\n        return this._super.apply(this, arguments).then(function () {\n            // rating stars\n            self.$input = self.$('input[name=\"rating_value\"]');\n            self.$star_list = self.$('.stars').find('i');\n\n            // set the default value to trigger the display of star widget and update the hidden input value.\n            self.set(\"star_value\", self.options.default_rating_value); \n            self.$input.val(self.options.default_rating_value);\n        });\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @override\n     * @private\n     */\n    _prepareMessageData: function () {\n        return Object.assign(this._super(...arguments) || {}, {\n            'message_id': this.options.default_message_id,\n            'rating_value': this.$input.val()\n        });\n    },\n    /**\n     * @private\n     */\n    _onChangeStarValue: function () {\n        var val = this.get(\"star_value\");\n        var index = Math.floor(val);\n        var decimal = val - index;\n        // reset the stars\n        this.$star_list.removeClass('fa-star fa-star-half-o').addClass('fa-star-o');\n\n        this.$('.stars').find(\"i:lt(\" + index + \")\").removeClass('fa-star-o fa-star-half-o').addClass('fa-star');\n        if (decimal) {\n            this.$('.stars').find(\"i:eq(\" + index + \")\").removeClass('fa-star-o fa-star fa-star-half-o').addClass('fa-star-half-o');\n        }\n        this.$('.rate_text .badge').text(this.labels[index]);\n    },\n    /**\n     * @private\n     */\n    _onClickStar: function (ev) {\n        var index = this.$('.stars i').index(ev.currentTarget);\n        this.set(\"star_value\", index + 1);\n        this.user_click = true;\n        this.$input.val(this.get(\"star_value\"));\n    },\n    /**\n     * @private\n     */\n    _onMouseleaveStarBlock: function () {\n        this.$('.rate_text').hide();\n    },\n    /**\n     * @private\n     * @param {MouseEvent} ev\n     */\n    _onMoveStar: function (ev) {\n        var index = this.$('.stars i').index(ev.currentTarget);\n        this.$('.rate_text').show();\n        this.set(\"star_value\", index + 1);\n    },\n    /**\n     * @private\n     */\n    _onMoveLeaveStar: function () {\n        if (!this.user_click) {\n            this.set(\"star_value\", parseInt(this.$input.val()));\n        }\n        this.user_click = false;\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @override\n     * @private\n     */\n    _onSubmitButtonClick: function (ev) {\n        return this._super(...arguments).then((result) => {\n            const $modal = this.$el.closest('#ratingpopupcomposer');\n            $modal.on('hidden.bs.modal', () => {\n              this.trigger_up('reload_rating_popup_composer', result);\n            });\n            $modal.modal('hide');\n        });\n    },\n});\n});\n", "odoo.define('portal.rating.composer', function (require) {\n'use strict';\n\nconst publicWidget = require('web.public.widget');\nconst session = require('web.session');\nconst portalComposer = require('portal.composer');\nconst {_t, qweb} = require('web.core');\n\nconst PortalComposer = portalComposer.PortalComposer;\n\n/**\n * RatingPopupComposer\n *\n * Display the rating average with a static star widget, and open\n * a popup with the portal composer when clicking on it.\n **/\nconst RatingPopupComposer = publicWidget.Widget.extend({\n    selector: '.o_rating_popup_composer',\n    custom_events: {\n        reload_rating_popup_composer: '_onReloadRatingPopupComposer',\n    },\n    xmlDependencies: [\n        '/portal/static/src/xml/portal_chatter.xml',\n        '/portal_rating/static/src/xml/portal_chatter.xml',\n        '/portal_rating/static/src/xml/portal_tools.xml',\n        '/portal_rating/static/src/xml/portal_rating_composer.xml',\n    ],\n\n    willStart: function (parent) {\n        const def = this._super.apply(this, arguments);\n\n        const options = this.$el.data();\n        this.rating_avg = Math.round(options['rating_avg'] * 100) / 100 || 0.0;\n        this.rating_count = options['rating_count'] || 0.0;\n\n        this.options = _.defaults({}, options, {\n            'token': false,\n            'res_model': false,\n            'res_id': false,\n            'pid': 0,\n            'display_rating': true,\n            'csrf_token': odoo.csrf_token,\n            'user_id': session.user_id,\n        });\n\n        return def;\n    },\n\n    /**\n     * @override\n     */\n    start: function () {\n        return Promise.all([\n            this._super.apply(this, arguments),\n            this._reloadRatingPopupComposer(),\n        ]);\n    },\n\n    /**\n     * Destroy existing ratingPopup and insert new ratingPopup widget\n     *\n     * @private\n     * @param {Object} data\n     */\n    _reloadRatingPopupComposer: function () {\n        if (this.options.hide_rating_avg) {\n            this.$('.o_rating_popup_composer_stars').empty();\n        } else {\n            const ratingAverage = qweb.render(\n                'portal_rating.rating_stars_static', {\n                inline_mode: true,\n                widget: this,\n                val: this.rating_avg,\n            });\n            this.$('.o_rating_popup_composer_stars').empty().html(ratingAverage);\n        }\n\n        // Append the modal\n        const modal = qweb.render(\n            'portal_rating.PopupComposer', {\n            inline_mode: true,\n            widget: this,\n            val: this.rating_avg,\n        });\n        this.$('.o_rating_popup_composer_modal').html(modal);\n\n        if (this._composer) {\n            this._composer.destroy();\n        }\n\n        // Instantiate the \"Portal Composer\" widget and insert it into the modal\n        this._composer = new PortalComposer(this, this.options);\n        return this._composer.appendTo(this.$('.o_rating_popup_composer_modal .o_portal_chatter_composer')).then(() => {\n            // Change the text of the button\n            this.$('.o_rating_popup_composer_text').text(\n                this.options.default_message_id ?\n                _t('Modify your review') : _t('Add a review')\n            );\n        });\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {OdooEvent} event\n     */\n    _onReloadRatingPopupComposer: function (event) {\n        const data = event.data;\n\n        // Refresh the internal state of the widget\n        this.rating_avg = data.rating_avg;\n        this.rating_count = data.rating_count;\n        this.rating_value = data.rating_value;\n\n        // Clean the dictionary\n        delete data.rating_avg;\n        delete data.rating_count;\n        delete data.rating_value;\n\n        this.options = _.extend(this.options, data);\n\n        this._reloadRatingPopupComposer();\n    }\n});\n\npublicWidget.registry.RatingPopupComposer = RatingPopupComposer;\n\nreturn RatingPopupComposer;\n\n});\n", "odoo.define('sale.VariantMixin', function (require) {\n'use strict';\n\nvar concurrency = require('web.concurrency');\nvar core = require('web.core');\nvar utils = require('web.utils');\nvar ajax = require('web.ajax');\nvar session = require('web.session');\nvar _t = core._t;\n\nvar VariantMixin = {\n    events: {\n        'change .css_attribute_color input': '_onChangeColorAttribute',\n        'change .main_product:not(.in_cart) input.js_quantity': 'onChangeAddQuantity',\n        'change [data-attribute_exclusions]': 'onChangeVariant'\n    },\n\n    //--------------------------------------------------------------------------\n    // Public\n    //--------------------------------------------------------------------------\n\n    /**\n     * When a variant is changed, this will check:\n     * - If the selected combination is available or not\n     * - The extra price if applicable\n     * - The display name of the product (\"Customizable desk (White, Steel)\")\n     * - The new total price\n     * - The need of adding a \"custom value\" input\n     *   If the custom value is the only available value\n     *   (defined by its data 'is_single_and_custom'),\n     *   the custom value will have it's own input & label\n     *\n     * 'change' events triggered by the user entered custom values are ignored since they\n     * are not relevant\n     *\n     * @param {MouseEvent} ev\n     */\n    onChangeVariant: function (ev) {\n        var $parent = $(ev.target).closest('.js_product');\n        if (!$parent.data('uniqueId')) {\n            $parent.data('uniqueId', _.uniqueId());\n        }\n        this._throttledGetCombinationInfo($parent.data('uniqueId'))(ev);\n    },\n    /**\n     * @see onChangeVariant\n     *\n     * @private\n     * @param {Event} ev\n     * @returns {Deferred}\n     */\n    _getCombinationInfo: function (ev) {\n        if ($(ev.target).hasClass('variant_custom_value')) {\n            return Promise.resolve();\n        }\n\n        const $parent = $(ev.target).closest('.js_product');\n        const combination = this.getSelectedVariantValues($parent);\n        let parentCombination;\n\n        if ($parent.hasClass('main_product')) {\n            parentCombination = $parent.find('ul[data-attribute_exclusions]').data('attribute_exclusions').parent_combination;\n            const $optProducts = $parent.parent().find(`[data-parent-unique-id='${$parent.data('uniqueId')}']`);\n\n            for (const optionalProduct of $optProducts) {\n                const $currentOptionalProduct = $(optionalProduct);\n                const childCombination = this.getSelectedVariantValues($currentOptionalProduct);\n                const productTemplateId = parseInt($currentOptionalProduct.find('.product_template_id').val());\n                ajax.jsonRpc(this._getUri('/sale/get_combination_info'), 'call', {\n                    'product_template_id': productTemplateId,\n                    'product_id': this._getProductId($currentOptionalProduct),\n                    'combination': childCombination,\n                    'add_qty': parseInt($currentOptionalProduct.find('input[name=\"add_qty\"]').val()),\n                    'pricelist_id': this.pricelistId || false,\n                    'parent_combination': combination,\n                    'context': session.user_context,\n                }).then((combinationData) => {\n                    this._onChangeCombination(ev, $currentOptionalProduct, combinationData);\n                    this._checkExclusions($currentOptionalProduct, childCombination, combinationData.parent_exclusions);\n                });\n            }\n        } else {\n            parentCombination = this.getSelectedVariantValues(\n                $parent.parent().find('.js_product.in_cart.main_product')\n            );\n        }\n\n        return ajax.jsonRpc(this._getUri('/sale/get_combination_info'), 'call', {\n            'product_template_id': parseInt($parent.find('.product_template_id').val()),\n            'product_id': this._getProductId($parent),\n            'combination': combination,\n            'add_qty': parseInt($parent.find('input[name=\"add_qty\"]').val()),\n            'pricelist_id': this.pricelistId || false,\n            'parent_combination': parentCombination,\n            'context': session.user_context,\n        }).then((combinationData) => {\n            this._onChangeCombination(ev, $parent, combinationData);\n            this._checkExclusions($parent, combination, combinationData.parent_exclusions);\n        });\n    },\n\n    /**\n     * Will add the \"custom value\" input for this attribute value if\n     * the attribute value is configured as \"custom\" (see product_attribute_value.is_custom)\n     *\n     * @private\n     * @param {MouseEvent} ev\n     */\n    handleCustomValues: function ($target) {\n        var $variantContainer;\n        var $customInput = false;\n        if ($target.is('input[type=radio]') && $target.is(':checked')) {\n            $variantContainer = $target.closest('ul').closest('li');\n            $customInput = $target;\n        } else if ($target.is('select')) {\n            $variantContainer = $target.closest('li');\n            $customInput = $target\n                .find('option[value=\"' + $target.val() + '\"]');\n        }\n\n        if ($variantContainer) {\n            if ($customInput && $customInput.data('is_custom') === 'True') {\n                var attributeValueId = $customInput.data('value_id');\n                var attributeValueName = $customInput.data('value_name');\n\n                if ($variantContainer.find('.variant_custom_value').length === 0\n                        || $variantContainer\n                              .find('.variant_custom_value')\n                              .data('custom_product_template_attribute_value_id') !== parseInt(attributeValueId)) {\n                    $variantContainer.find('.variant_custom_value').remove();\n\n                    const previousCustomValue = $customInput.attr(\"previous_custom_value\");\n                    var $input = $('<input>', {\n                        type: 'text',\n                        'data-custom_product_template_attribute_value_id': attributeValueId,\n                        'data-attribute_value_name': attributeValueName,\n                        class: 'variant_custom_value form-control mt-2'\n                    });\n\n                    $input.attr('placeholder', attributeValueName);\n                    $input.addClass('custom_value_radio');\n                    $variantContainer.append($input);\n                    if (previousCustomValue) {\n                        $input.val(previousCustomValue);\n                    }\n                }\n            } else {\n                $variantContainer.find('.variant_custom_value').remove();\n            }\n        }\n    },\n\n    /**\n     * Hack to add and remove from cart with json\n     *\n     * @param {MouseEvent} ev\n     */\n    onClickAddCartJSON: function (ev) {\n        ev.preventDefault();\n        var $link = $(ev.currentTarget);\n        var $input = $link.closest('.input-group').find(\"input\");\n        var min = parseFloat($input.data(\"min\") || 0);\n        var max = parseFloat($input.data(\"max\") || Infinity);\n        var previousQty = parseFloat($input.val() || 0, 10);\n        var quantity = ($link.has(\".fa-minus\").length ? -1 : 1) + previousQty;\n        var newQty = quantity > min ? (quantity < max ? quantity : max) : min;\n\n        if (newQty !== previousQty) {\n            $input.val(newQty).trigger('change');\n        }\n        return false;\n    },\n\n    /**\n     * When the quantity is changed, we need to query the new price of the product.\n     * Based on the price list, the price might change when quantity exceeds X\n     *\n     * @param {MouseEvent} ev\n     */\n    onChangeAddQuantity: function (ev) {\n        var $parent;\n\n        if ($(ev.currentTarget).closest('.oe_advanced_configurator_modal').length > 0){\n            $parent = $(ev.currentTarget).closest('.oe_advanced_configurator_modal');\n        } else if ($(ev.currentTarget).closest('form').length > 0){\n            $parent = $(ev.currentTarget).closest('form');\n        }  else {\n            $parent = $(ev.currentTarget).closest('.o_product_configurator');\n        }\n\n        this.triggerVariantChange($parent);\n    },\n\n    /**\n     * Triggers the price computation and other variant specific changes\n     *\n     * @param {$.Element} $container\n     */\n    triggerVariantChange: function ($container) {\n        var self = this;\n        $container.find('ul[data-attribute_exclusions]').trigger('change');\n        $container.find('input.js_variant_change:checked, select.js_variant_change').each(function () {\n            self.handleCustomValues($(this));\n        });\n    },\n\n    /**\n     * Will look for user custom attribute values\n     * in the provided container\n     *\n     * @param {$.Element} $container\n     * @returns {Array} array of custom values with the following format\n     *   {integer} custom_product_template_attribute_value_id\n     *   {string} attribute_value_name\n     *   {string} custom_value\n     */\n    getCustomVariantValues: function ($container) {\n        var variantCustomValues = [];\n        $container.find('.variant_custom_value').each(function (){\n            var $variantCustomValueInput = $(this);\n            if ($variantCustomValueInput.length !== 0){\n                variantCustomValues.push({\n                    'custom_product_template_attribute_value_id': $variantCustomValueInput.data('custom_product_template_attribute_value_id'),\n                    'attribute_value_name': $variantCustomValueInput.data('attribute_value_name'),\n                    'custom_value': $variantCustomValueInput.val(),\n                });\n            }\n        });\n\n        return variantCustomValues;\n    },\n\n    /**\n     * Will look for attribute values that do not create product variant\n     * (see product_attribute.create_variant \"dynamic\")\n     *\n     * @param {$.Element} $container\n     * @returns {Array} array of attribute values with the following format\n     *   {integer} custom_product_template_attribute_value_id\n     *   {string} attribute_value_name\n     *   {integer} value\n     *   {string} attribute_name\n     *   {boolean} is_custom\n     */\n    getNoVariantAttributeValues: function ($container) {\n        var noVariantAttributeValues = [];\n        var variantsValuesSelectors = [\n            'input.no_variant.js_variant_change:checked',\n            'select.no_variant.js_variant_change'\n        ];\n\n        $container.find(variantsValuesSelectors.join(',')).each(function (){\n            var $variantValueInput = $(this);\n            var singleNoCustom = $variantValueInput.data('is_single') && !$variantValueInput.data('is_custom');\n\n            if ($variantValueInput.is('select')){\n                $variantValueInput = $variantValueInput.find('option[value=' + $variantValueInput.val() + ']');\n            }\n\n            if ($variantValueInput.length !== 0 && !singleNoCustom){\n                noVariantAttributeValues.push({\n                    'custom_product_template_attribute_value_id': $variantValueInput.data('value_id'),\n                    'attribute_value_name': $variantValueInput.data('value_name'),\n                    'value': $variantValueInput.val(),\n                    'attribute_name': $variantValueInput.data('attribute_name'),\n                    'is_custom': $variantValueInput.data('is_custom')\n                });\n            }\n        });\n\n        return noVariantAttributeValues;\n    },\n\n    /**\n     * Will return the list of selected product.template.attribute.value ids\n     * For the modal, the \"main product\"'s attribute values are stored in the\n     * \"unchanged_value_ids\" data\n     *\n     * @param {$.Element} $container the container to look into\n     */\n    getSelectedVariantValues: function ($container) {\n        var values = [];\n        var unchangedValues = $container\n            .find('div.oe_unchanged_value_ids')\n            .data('unchanged_value_ids') || [];\n\n        var variantsValuesSelectors = [\n            'input.js_variant_change:checked',\n            'select.js_variant_change'\n        ];\n        _.each($container.find(variantsValuesSelectors.join(', ')), function (el) {\n            values.push(+$(el).val());\n        });\n\n        return values.concat(unchangedValues);\n    },\n\n    /**\n     * Will return a promise:\n     *\n     * - If the product already exists, immediately resolves it with the product_id\n     * - If the product does not exist yet (\"dynamic\" variant creation), this method will\n     *   create the product first and then resolve the promise with the created product's id\n     *\n     * @param {$.Element} $container the container to look into\n     * @param {integer} productId the product id\n     * @param {integer} productTemplateId the corresponding product template id\n     * @param {boolean} useAjax wether the rpc call should be done using ajax.jsonRpc or using _rpc\n     * @returns {Promise} the promise that will be resolved with a {integer} productId\n     */\n    selectOrCreateProduct: function ($container, productId, productTemplateId, useAjax) {\n        var self = this;\n        productId = parseInt(productId);\n        productTemplateId = parseInt(productTemplateId);\n        var productReady = Promise.resolve();\n        if (productId) {\n            productReady = Promise.resolve(productId);\n        } else {\n            var params = {\n                product_template_id: productTemplateId,\n                product_template_attribute_value_ids:\n                    JSON.stringify(self.getSelectedVariantValues($container)),\n            };\n\n            var route = '/sale/create_product_variant';\n            if (useAjax) {\n                productReady = ajax.jsonRpc(route, 'call', params);\n            } else {\n                productReady = this._rpc({route: route, params: params});\n            }\n        }\n\n        return productReady;\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Will disable attribute value's inputs based on combination exclusions\n     * and will disable the \"add\" button if the selected combination\n     * is not available\n     *\n     * This will check both the exclusions within the product itself and\n     * the exclusions coming from the parent product (meaning that this product\n     * is an option of the parent product)\n     *\n     * It will also check that the selected combination does not exactly\n     * match a manually archived product\n     *\n     * @private\n     * @param {$.Element} $parent the parent container to apply exclusions\n     * @param {Array} combination the selected combination of product attribute values\n     * @param {Array} parentExclusions the exclusions induced by the variant selection of the parent product\n     * For example chair cannot have steel legs if the parent Desk doesn't have steel legs\n     */\n    _checkExclusions: function ($parent, combination, parentExclusions) {\n        var self = this;\n        var combinationData = $parent\n            .find('ul[data-attribute_exclusions]')\n            .data('attribute_exclusions');\n\n        if (parentExclusions && combinationData.parent_exclusions) {\n            combinationData.parent_exclusions = parentExclusions;\n        }\n        $parent\n            .find('option, input, label, .o_variant_pills')\n            .removeClass('css_not_available')\n            .attr('title', function () { return $(this).data('value_name') || ''; })\n            .data('excluded-by', '');\n\n        // exclusion rules: array of ptav\n        // for each of them, contains array with the other ptav they exclude\n        if (combinationData.exclusions) {\n            // browse all the currently selected attributes\n            _.each(combination, function (current_ptav) {\n                if (combinationData.exclusions.hasOwnProperty(current_ptav)) {\n                    // for each exclusion of the current attribute:\n                    _.each(combinationData.exclusions[current_ptav], function (excluded_ptav) {\n                        // disable the excluded input (even when not already selected)\n                        // to give a visual feedback before click\n                        self._disableInput(\n                            $parent,\n                            excluded_ptav,\n                            current_ptav,\n                            combinationData.mapped_attribute_names\n                        );\n                    });\n                }\n            });\n        }\n\n        // parent exclusions (tell which attributes are excluded from parent)\n        _.each(combinationData.parent_exclusions, function (exclusions, excluded_by){\n            // check that the selected combination is in the parent exclusions\n            _.each(exclusions, function (ptav) {\n\n                // disable the excluded input (even when not already selected)\n                // to give a visual feedback before click\n                self._disableInput(\n                    $parent,\n                    ptav,\n                    excluded_by,\n                    combinationData.mapped_attribute_names,\n                    combinationData.parent_product_name\n                );\n            });\n        });\n    },\n    /**\n     * Extracted to a method to be extendable by other modules\n     *\n     * @param {$.Element} $parent\n     */\n    _getProductId: function ($parent) {\n        return parseInt($parent.find('.product_id').val());\n    },\n    /**\n     * Will disable the input/option that refers to the passed attributeValueId.\n     * This is used for showing the user that some combinations are not available.\n     *\n     * It will also display a message explaining why the input is not selectable.\n     * Based on the \"excludedBy\" and the \"productName\" params.\n     * e.g: Not available with Color: Black\n     *\n     * @private\n     * @param {$.Element} $parent\n     * @param {integer} attributeValueId\n     * @param {integer} excludedBy The attribute value that excludes this input\n     * @param {Object} attributeNames A dict containing all the names of the attribute values\n     *   to show a human readable message explaining why the input is disabled.\n     * @param {string} [productName] The parent product. If provided, it will be appended before\n     *   the name of the attribute value that excludes this input\n     *   e.g: Not available with Customizable Desk (Color: Black)\n     */\n    _disableInput: function ($parent, attributeValueId, excludedBy, attributeNames, productName) {\n        var $input = $parent\n            .find('option[value=' + attributeValueId + '], input[value=' + attributeValueId + ']');\n        $input.addClass('css_not_available');\n        $input.closest('label').addClass('css_not_available');\n        $input.closest('.o_variant_pills').addClass('css_not_available');\n\n        if (excludedBy && attributeNames) {\n            var $target = $input.is('option') ? $input : $input.closest('label').add($input);\n            var excludedByData = [];\n            if ($target.data('excluded-by')) {\n                excludedByData = JSON.parse($target.data('excluded-by'));\n            }\n\n            var excludedByName = attributeNames[excludedBy];\n            if (productName) {\n                excludedByName = productName + ' (' + excludedByName + ')';\n            }\n            excludedByData.push(excludedByName);\n\n            $target.attr('title', _.str.sprintf(_t('Not available with %s'), excludedByData.join(', ')));\n            $target.data('excluded-by', JSON.stringify(excludedByData));\n        }\n    },\n    /**\n     * @see onChangeVariant\n     *\n     * @private\n     * @param {MouseEvent} ev\n     * @param {$.Element} $parent\n     * @param {Array} combination\n     */\n    _onChangeCombination: function (ev, $parent, combination) {\n        var self = this;\n        var $price = $parent.find(\".oe_price:first .oe_currency_value\");\n        var $default_price = $parent.find(\".oe_default_price:first .oe_currency_value\");\n        var $optional_price = $parent.find(\".oe_optional:first .oe_currency_value\");\n        $price.text(self._priceToStr(combination.price));\n        $default_price.text(self._priceToStr(combination.list_price));\n\n        var isCombinationPossible = true;\n        if (!_.isUndefined(combination.is_combination_possible)) {\n            isCombinationPossible = combination.is_combination_possible;\n        }\n        this._toggleDisable($parent, isCombinationPossible);\n\n        if (combination.has_discounted_price) {\n            $default_price\n                .closest('.oe_website_sale')\n                .addClass(\"discount\");\n            $optional_price\n                .closest('.oe_optional')\n                .removeClass('d-none')\n                .css('text-decoration', 'line-through');\n            $default_price.parent().removeClass('d-none');\n        } else {\n            $default_price\n                .closest('.oe_website_sale')\n                .removeClass(\"discount\");\n            $optional_price.closest('.oe_optional').addClass('d-none');\n            $default_price.parent().addClass('d-none');\n        }\n\n        var rootComponentSelectors = [\n            'tr.js_product',\n            '.oe_website_sale',\n            '.o_product_configurator'\n        ];\n\n        // update images only when changing product\n        // or when either ids are 'false', meaning dynamic products.\n        // Dynamic products don't have images BUT they may have invalid\n        // combinations that need to disable the image.\n        if (!combination.product_id ||\n            !this.last_product_id ||\n            combination.product_id !== this.last_product_id) {\n            this.last_product_id = combination.product_id;\n            self._updateProductImage(\n                $parent.closest(rootComponentSelectors.join(', ')),\n                combination.display_image,\n                combination.product_id,\n                combination.product_template_id,\n                combination.carousel,\n                isCombinationPossible\n            );\n        }\n\n        $parent\n            .find('.product_id')\n            .first()\n            .val(combination.product_id || 0)\n            .trigger('change');\n\n        $parent\n            .find('.product_display_name')\n            .first()\n            .text(combination.display_name);\n\n        $parent\n            .find('.js_raw_price')\n            .first()\n            .text(combination.price)\n            .trigger('change');\n\n        this.handleCustomValues($(ev.target));\n    },\n\n    /**\n     * returns the formatted price\n     *\n     * @private\n     * @param {float} price\n     */\n    _priceToStr: function (price) {\n        var l10n = _t.database.parameters;\n        var precision = 2;\n\n        if ($('.decimal_precision').length) {\n            precision = parseInt($('.decimal_precision').last().data('precision'));\n        }\n        var formatted = _.str.sprintf('%.' + precision + 'f', price).split('.');\n        formatted[0] = utils.insert_thousand_seps(formatted[0]);\n        return formatted.join(l10n.decimal_point);\n    },\n    /**\n     * Returns a throttled `_getCombinationInfo` with a leading and a trailing\n     * call, which is memoized per `uniqueId`, and for which previous results\n     * are dropped.\n     *\n     * The uniqueId is needed because on the configurator modal there might be\n     * multiple elements triggering the rpc at the same time, and we need each\n     * individual product rpc to be executed, but only once per individual\n     * product.\n     *\n     * The leading execution is to keep good reactivity on the first call, for\n     * a better user experience. The trailing is because ultimately only the\n     * information about the last selected combination is useful. All\n     * intermediary rpc can be ignored and are therefore best not done at all.\n     *\n     * The DropMisordered is to make sure slower rpc are ignored if the result\n     * of a newer rpc has already been received.\n     *\n     * @private\n     * @param {string} uniqueId\n     * @returns {function}\n     */\n    _throttledGetCombinationInfo: _.memoize(function (uniqueId) {\n        var dropMisordered = new concurrency.DropMisordered();\n        var _getCombinationInfo = _.throttle(this._getCombinationInfo.bind(this), 500);\n        return function (ev, params) {\n            return dropMisordered.add(_getCombinationInfo(ev, params));\n        };\n    }),\n    /**\n     * Toggles the disabled class depending on the $parent element\n     * and the possibility of the current combination.\n     *\n     * @private\n     * @param {$.Element} $parent\n     * @param {boolean} isCombinationPossible\n     */\n    _toggleDisable: function ($parent, isCombinationPossible) {\n        $parent.toggleClass('css_not_available', !isCombinationPossible);\n        if ($parent.hasClass('in_cart')) {\n            const primaryButton = $parent.parents('.modal-content').find('.modal-footer .btn-primary');\n            primaryButton.prop('disabled', !isCombinationPossible);\n            primaryButton.toggleClass('disabled', !isCombinationPossible);\n        }\n    },\n    /**\n     * Updates the product image.\n     * This will use the productId if available or will fallback to the productTemplateId.\n     *\n     * @private\n     * @param {$.Element} $productContainer\n     * @param {boolean} displayImage will hide the image if true. It will use the 'invisible' class\n     *   instead of d-none to prevent layout change\n     * @param {integer} product_id\n     * @param {integer} productTemplateId\n     */\n    _updateProductImage: function ($productContainer, displayImage, productId, productTemplateId) {\n        var model = productId ? 'product.product' : 'product.template';\n        var modelId = productId || productTemplateId;\n        var imageUrl = '/web/image/{0}/{1}/' + (this._productImageField ? this._productImageField : 'image_1024');\n        var imageSrc = imageUrl\n            .replace(\"{0}\", model)\n            .replace(\"{1}\", modelId);\n\n        var imagesSelectors = [\n            'span[data-oe-model^=\"product.\"][data-oe-type=\"image\"] img:first',\n            'img.product_detail_img',\n            'span.variant_image img',\n            'img.variant_image',\n        ];\n\n        var $img = $productContainer.find(imagesSelectors.join(', '));\n\n        if (displayImage) {\n            $img.removeClass('invisible').attr('src', imageSrc);\n        } else {\n            $img.addClass('invisible');\n        }\n    },\n\n    /**\n     * Highlight selected color\n     *\n     * @private\n     * @param {MouseEvent} ev\n     */\n    _onChangeColorAttribute: function (ev) {\n        var $parent = $(ev.target).closest('.js_product');\n        $parent.find('.css_attribute_color')\n            .removeClass(\"active\")\n            .filter(':has(input:checked)')\n            .addClass(\"active\");\n    },\n\n    /**\n     * Extension point for website_sale\n     *\n     * @private\n     * @param {string} uri The uri to adapt\n     */\n    _getUri: function (uri) {\n        return uri;\n    }\n};\n\nreturn VariantMixin;\n\n});\n", "odoo.define('website_sale.VariantMixin', function (require) {\n'use strict';\n\nvar VariantMixin = require('sale.VariantMixin');\n\n/**\n * Website behavior is slightly different from backend so we append\n * \"_website\" to URLs to lead to a different route\n *\n * @private\n * @param {string} uri The uri to adapt\n */\nVariantMixin._getUri = function (uri) {\n    if (this.isWebsite) {\n        return uri + '_website';\n    } else {\n        return uri;\n    }\n};\nconst originalOnChangeCombination = VariantMixin._onChangeCombination;\nVariantMixin._onChangeCombination = function (ev, $parent, combination) {\n    const $pricePerUom = $parent.find(\".o_base_unit_price:first .oe_currency_value\");\n    if ($pricePerUom) {\n        if (combination.is_combination_possible !== false && combination.base_unit_price != 0) {\n            $pricePerUom.parents(\".o_base_unit_price_wrapper\").removeClass(\"d-none\");\n            $pricePerUom.text(this._priceToStr(combination.base_unit_price));\n            $parent.find(\".oe_custom_base_unit:first\").text(combination.base_unit_name);\n        } else {\n            $pricePerUom.parents(\".o_base_unit_price_wrapper\").addClass(\"d-none\");\n        }\n    }\n\n    // Triggers a new JS event with the correct payload, which is then handled\n    // by the google analytics tracking code.\n    // Indeed, every time another variant is selected, a new view_item event\n    // needs to be tracked by google analytics.\n    if ('product_tracking_info' in combination) {\n        const $product = $('#product_detail');\n        $product.data('product-tracking-info', combination['product_tracking_info']);\n        $product.trigger('view_item_event', combination['product_tracking_info']);\n    }\n\n    originalOnChangeCombination.apply(this, [ev, $parent, combination]);\n};\n\nconst originalToggleDisable = VariantMixin._toggleDisable;\n/**\n * Toggles the disabled class depending on the $parent element\n * and the possibility of the current combination. This override\n * allows us to disable the secondary button in the website\n * sale product configuration modal.\n *\n * @private\n * @param {$.Element} $parent\n * @param {boolean} isCombinationPossible\n */\nVariantMixin._toggleDisable = function ($parent, isCombinationPossible) {\n    if ($parent.hasClass('in_cart')) {\n        const secondaryButton = $parent.parents('.modal-content').find('.modal-footer .btn-secondary');\n        secondaryButton.prop('disabled', !isCombinationPossible);\n        secondaryButton.toggleClass('disabled', !isCombinationPossible);\n    }\n    originalToggleDisable.apply(this, [$parent, isCombinationPossible]);\n};\n\nreturn VariantMixin;\n\n});\n", "odoo.define('website_sale.cart', function (require) {\n'use strict';\n\nvar publicWidget = require('web.public.widget');\nvar core = require('web.core');\nvar _t = core._t;\n\nvar timeout;\n\npublicWidget.registry.websiteSaleCartLink = publicWidget.Widget.extend({\n    // TODO in master: remove the second selector.\n    selector: '#top a[href$=\"/shop/cart\"]:not(.js_change_lang), #top_menu a[href$=\"/shop/cart\"]:not(.js_change_lang)',\n    events: {\n        'mouseenter': '_onMouseEnter',\n        'mouseleave': '_onMouseLeave',\n        'click': '_onClick',\n    },\n\n    /**\n     * @constructor\n     */\n    init: function () {\n        this._super.apply(this, arguments);\n        this._popoverRPC = null;\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        this.$el.popover({\n            trigger: 'manual',\n            animation: true,\n            html: true,\n            title: function () {\n                return _t(\"My Cart\");\n            },\n            container: 'body',\n            placement: 'auto',\n            template: '<div class=\"popover mycart-popover\" role=\"tooltip\"><div class=\"arrow\"></div><h3 class=\"popover-header\"></h3><div class=\"popover-body\"></div></div>'\n        });\n        return this._super.apply(this, arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onMouseEnter: function (ev) {\n        var self = this;\n        clearTimeout(timeout);\n        $(this.selector).not(ev.currentTarget).popover('hide');\n        timeout = setTimeout(function () {\n            if (!self.$el.is(':hover') || $('.mycart-popover:visible').length) {\n                return;\n            }\n            self._popoverRPC = $.get(\"/shop/cart\", {\n                type: 'popover',\n            }).then(function (data) {\n                self.$el.data(\"bs.popover\").config.content = data;\n                self.$el.popover(\"show\");\n                $('.popover').on('mouseleave', function () {\n                    self.$el.trigger('mouseleave');\n                });\n            });\n        }, 300);\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onMouseLeave: function (ev) {\n        var self = this;\n        setTimeout(function () {\n            if ($('.popover:hover').length) {\n                return;\n            }\n            if (!self.$el.is(':hover')) {\n               self.$el.popover('hide');\n            }\n        }, 1000);\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onClick: function (ev) {\n        // When clicking on the cart link, prevent any popover to show up (by\n        // clearing the related setTimeout) and, if a popover rpc is ongoing,\n        // wait for it to be completed before going to the link's href. Indeed,\n        // going to that page may perform the same computation the popover rpc\n        // is already doing.\n        clearTimeout(timeout);\n        if (this._popoverRPC && this._popoverRPC.state() === 'pending') {\n            ev.preventDefault();\n            var href = ev.currentTarget.href;\n            this._popoverRPC.then(function () {\n                window.location.href = href;\n            });\n        }\n    },\n});\n});\n\nodoo.define('website_sale.website_sale_category', function (require) {\n'use strict';\n\nvar publicWidget = require('web.public.widget');\n\npublicWidget.registry.websiteSaleCategory = publicWidget.Widget.extend({\n    selector: '#o_shop_collapse_category',\n    events: {\n        'click .fa-chevron-right': '_onOpenClick',\n        'click .fa-chevron-down': '_onCloseClick',\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onOpenClick: function (ev) {\n        var $fa = $(ev.currentTarget);\n        $fa.parent().siblings().find('.fa-chevron-down:first').click();\n        $fa.parents('li').find('ul:first').show('normal');\n        $fa.toggleClass('fa-chevron-down fa-chevron-right');\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onCloseClick: function (ev) {\n        var $fa = $(ev.currentTarget);\n        $fa.parent().find('ul:first').hide('normal');\n        $fa.toggleClass('fa-chevron-down fa-chevron-right');\n    },\n});\n});\n\nodoo.define('website_sale.website_sale', function (require) {\n'use strict';\n\nvar core = require('web.core');\nvar config = require('web.config');\nvar publicWidget = require('web.public.widget');\nvar VariantMixin = require('website_sale.VariantMixin');\nvar wSaleUtils = require('website_sale.utils');\nconst cartHandlerMixin = wSaleUtils.cartHandlerMixin;\nrequire(\"web.zoomodoo\");\nconst {extraMenuUpdateCallbacks} = require('website.content.menu');\nconst dom = require('web.dom');\n\npublicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, cartHandlerMixin, {\n    selector: '.oe_website_sale',\n    events: _.extend({}, VariantMixin.events || {}, {\n        'change form .js_product:first input[name=\"add_qty\"]': '_onChangeAddQuantity',\n        'mouseup .js_publish': '_onMouseupPublish',\n        'touchend .js_publish': '_onMouseupPublish',\n        'change .oe_cart input.js_quantity[data-product-id]': '_onChangeCartQuantity',\n        'click .oe_cart a.js_add_suggested_products': '_onClickSuggestedProduct',\n        'click a.js_add_cart_json': '_onClickAddCartJSON',\n        'click .a-submit': '_onClickSubmit',\n        'change form.js_attributes input, form.js_attributes select': '_onChangeAttribute',\n        'mouseup form.js_add_cart_json label': '_onMouseupAddCartLabel',\n        'touchend form.js_add_cart_json label': '_onMouseupAddCartLabel',\n        'click .show_coupon': '_onClickShowCoupon',\n        'submit .o_wsale_products_searchbar_form': '_onSubmitSaleSearch',\n        'change select[name=\"country_id\"]': '_onChangeCountry',\n        'change #shipping_use_same': '_onChangeShippingUseSame',\n        'click .toggle_summary': '_onToggleSummary',\n        'click #add_to_cart, .o_we_buy_now, #products_grid .o_wsale_product_btn .a-submit': 'async _onClickAdd',\n        'click input.js_product_change': 'onChangeVariant',\n        'change .js_main_product [data-attribute_exclusions]': 'onChangeVariant',\n        'change oe_advanced_configurator_modal [data-attribute_exclusions]': 'onChangeVariant',\n        'click .o_product_page_reviews_link': '_onClickReviewsLink',\n    }),\n\n    /**\n     * @constructor\n     */\n    init: function () {\n        this._super.apply(this, arguments);\n\n        this._changeCartQuantity = _.debounce(this._changeCartQuantity.bind(this), 500);\n        this._changeCountry = _.debounce(this._changeCountry.bind(this), 500);\n\n        this.isWebsite = true;\n\n        delete this.events['change .main_product:not(.in_cart) input.js_quantity'];\n        delete this.events['change [data-attribute_exclusions]'];\n    },\n    /**\n     * @override\n     */\n    start() {\n        const def = this._super(...arguments);\n\n        this._applyHashFromSearch();\n\n        _.each(this.$('div.js_product'), function (product) {\n            $('input.js_product_change', product).first().trigger('change');\n        });\n\n        // This has to be triggered to compute the \"out of stock\" feature and the hash variant changes\n        this.triggerVariantChange(this.$el);\n\n        this.$('select[name=\"country_id\"]').change();\n\n        core.bus.on('resize', this, function () {\n            if (config.device.size_class === config.device.SIZES.XL) {\n                $('.toggle_summary_div').addClass('d-none d-xl-block');\n            }\n        });\n\n        this._startZoom();\n\n        window.addEventListener('hashchange', () => {\n            this._applyHash();\n            this.triggerVariantChange(this.$el);\n        });\n\n        this.getRedirectOption();\n        return def;\n    },\n    /**\n     * The selector is different when using list view of variants.\n     *\n     * @override\n     */\n    getSelectedVariantValues: function ($container) {\n        var combination = $container.find('input.js_product_change:checked')\n            .data('combination');\n\n        if (combination) {\n            return combination;\n        }\n        return VariantMixin.getSelectedVariantValues.apply(this, arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    _applyHash: function () {\n        var hash = window.location.hash.substring(1);\n        if (hash) {\n            var params = $.deparam(hash);\n            if (params['attr']) {\n                var attributeIds = params['attr'].split(',');\n                var $inputs = this.$('input.js_variant_change, select.js_variant_change option');\n                _.each(attributeIds, function (id) {\n                    var $toSelect = $inputs.filter('[data-value_id=\"' + id + '\"]');\n                    if ($toSelect.is('input[type=\"radio\"]')) {\n                        $toSelect.prop('checked', true);\n                    } else if ($toSelect.is('option')) {\n                        $toSelect.prop('selected', true);\n                    }\n                });\n                this._changeAttribute(['.css_attribute_color', '.o_variant_pills']);\n            }\n        }\n    },\n\n    /**\n     * Sets the url hash from the selected product options.\n     *\n     * @private\n     */\n    _setUrlHash: function ($parent) {\n        var $attributes = $parent.find('input.js_variant_change:checked, select.js_variant_change option:selected');\n        var attributeIds = _.map($attributes, function (elem) {\n            return $(elem).data('value_id');\n        });\n        history.replaceState(undefined, undefined, '#attr=' + attributeIds.join(','));\n    },\n    /**\n     * Set the checked values active.\n     *\n     * @private\n     * @param {Array} valueSelectors Selectors\n     */\n    _changeAttribute: function (valueSelectors) {\n        _.each(valueSelectors, function (selector) {\n            $(selector).removeClass(\"active\")\n                       .filter(':has(input:checked)')\n                       .addClass(\"active\");\n        });\n    },\n    /**\n     * @private\n     */\n    _changeCartQuantity: function ($input, value, $dom_optional, line_id, productIDs) {\n        _.each($dom_optional, function (elem) {\n            $(elem).find('.js_quantity').text(value);\n            productIDs.push($(elem).find('span[data-product-id]').data('product-id'));\n        });\n        $input.data('update_change', true);\n\n        this._rpc({\n            route: \"/shop/cart/update_json\",\n            params: {\n                line_id: line_id,\n                product_id: parseInt($input.data('product-id'), 10),\n                set_qty: value\n            },\n        }).then(function (data) {\n            $input.data('update_change', false);\n            var check_value = parseInt($input.val() || 0, 10);\n            if (isNaN(check_value)) {\n                check_value = 1;\n            }\n            if (value !== check_value) {\n                $input.trigger('change');\n                return;\n            }\n            if (!data.cart_quantity) {\n                return window.location = '/shop/cart';\n            }\n            $input.val(data.quantity);\n            $('.js_quantity[data-line-id='+line_id+']').val(data.quantity).text(data.quantity);\n\n            wSaleUtils.updateCartNavBar(data);\n            wSaleUtils.showWarning(data.warning);\n        });\n    },\n    /**\n     * @private\n     */\n    _changeCountry: function () {\n        if (!$(\"#country_id\").val()) {\n            return;\n        }\n        this._rpc({\n            route: \"/shop/country_infos/\" + $(\"#country_id\").val(),\n            params: {\n                mode: $(\"#country_id\").attr('mode'),\n            },\n        }).then(function (data) {\n            // placeholder phone_code\n            $(\"input[name='phone']\").attr('placeholder', data.phone_code !== 0 ? '+'+ data.phone_code : '');\n\n            // populate states and display\n            var selectStates = $(\"select[name='state_id']\");\n            // dont reload state at first loading (done in qweb)\n            if (selectStates.data('init')===0 || selectStates.find('option').length===1) {\n                if (data.states.length || data.state_required) {\n                    selectStates.html('');\n                    _.each(data.states, function (x) {\n                        var opt = $('<option>').text(x[1])\n                            .attr('value', x[0])\n                            .attr('data-code', x[2]);\n                        selectStates.append(opt);\n                    });\n                    selectStates.parent('div').show();\n                } else {\n                    selectStates.val('').parent('div').hide();\n                }\n                selectStates.data('init', 0);\n            } else {\n                selectStates.data('init', 0);\n            }\n\n            // manage fields order / visibility\n            if (data.fields) {\n                if ($.inArray('zip', data.fields) > $.inArray('city', data.fields)){\n                    $(\".div_zip\").before($(\".div_city\"));\n                } else {\n                    $(\".div_zip\").after($(\".div_city\"));\n                }\n                var all_fields = [\"street\", \"zip\", \"city\", \"country_name\"]; // \"state_code\"];\n                _.each(all_fields, function (field) {\n                    $(\".checkout_autoformat .div_\" + field.split('_')[0]).toggle($.inArray(field, data.fields)>=0);\n                });\n            }\n\n            if ($(\"label[for='zip']\").length) {\n                $(\"label[for='zip']\").toggleClass('label-optional', !data.zip_required);\n                $(\"label[for='zip']\").get(0).toggleAttribute('required', !!data.zip_required);\n            }\n            if ($(\"label[for='zip']\").length) {\n                $(\"label[for='state_id']\").toggleClass('label-optional', !data.state_required);\n                $(\"label[for='state_id']\").get(0).toggleAttribute('required', !!data.state_required);\n            }\n        });\n    },\n    /**\n     * This is overridden to handle the \"List View of Variants\" of the web shop.\n     * That feature allows directly selecting the variant from a list instead of selecting the\n     * attribute values.\n     *\n     * Since the layout is completely different, we need to fetch the product_id directly\n     * from the selected variant.\n     *\n     * @override\n     */\n    _getProductId: function ($parent) {\n        if ($parent.find('input.js_product_change').length !== 0) {\n            return parseInt($parent.find('input.js_product_change:checked').val());\n        }\n        else {\n            return VariantMixin._getProductId.apply(this, arguments);\n        }\n    },\n    /**\n     * @private\n     */\n    _startZoom: function () {\n        // Do not activate image zoom for mobile devices, since it might prevent users from scrolling the page\n        if (!config.device.isMobile) {\n            var autoZoom = $('.ecom-zoomable').data('ecom-zoom-auto') || false,\n            attach = '#o-carousel-product';\n            _.each($('.ecom-zoomable img[data-zoom]'), function (el) {\n                onImageLoaded(el, function () {\n                    var $img = $(el);\n                    $img.zoomOdoo({event: autoZoom ? 'mouseenter' : 'click', attach: attach});\n                    $img.attr('data-zoom', 1);\n                });\n            });\n        }\n\n        function onImageLoaded(img, callback) {\n            // On Chrome the load event already happened at this point so we\n            // have to rely on complete. On Firefox it seems that the event is\n            // always triggered after this so we can rely on it.\n            //\n            // However on the \"complete\" case we still want to keep listening to\n            // the event because if the image is changed later (eg. product\n            // configurator) a new load event will be triggered (both browsers).\n            $(img).on('load', function () {\n                callback();\n            });\n            if (img.complete) {\n                callback();\n            }\n        }\n    },\n    /**\n     * On website, we display a carousel instead of only one image\n     *\n     * @override\n     * @private\n     */\n    _updateProductImage: function ($productContainer, displayImage, productId, productTemplateId, newCarousel, isCombinationPossible) {\n        var $carousel = $productContainer.find('#o-carousel-product');\n        // When using the web editor, don't reload this or the images won't\n        // be able to be edited depending on if this is done loading before\n        // or after the editor is ready.\n        if (window.location.search.indexOf('enable_editor') === -1) {\n            var $newCarousel = $(newCarousel);\n            $carousel.after($newCarousel);\n            $carousel.remove();\n            $carousel = $newCarousel;\n            $carousel.carousel(0);\n            this._startZoom();\n            // fix issue with carousel height\n            this.trigger_up('widgets_start_request', {$target: $carousel});\n        }\n        $carousel.toggleClass('css_not_available', !isCombinationPossible);\n    },\n    /**\n     * @private\n     * @param {MouseEvent} ev\n     */\n    _onClickAdd: function (ev) {\n        ev.preventDefault();\n        var def = () => {\n            this.getCartHandlerOptions(ev);\n            return this._handleAdd($(ev.currentTarget).closest('form'));\n        };\n        if ($('.js_add_cart_variants').children().length) {\n            return this._getCombinationInfo(ev).then(() => {\n                return !$(ev.target).closest('.js_product').hasClass(\"css_not_available\") ? def() : Promise.resolve();\n            });\n        }\n        return def();\n    },\n    /**\n     * Initializes the optional products modal\n     * and add handlers to the modal events (confirm, back, ...)\n     *\n     * @private\n     * @param {$.Element} $form the related webshop form\n     */\n    _handleAdd: function ($form) {\n        var self = this;\n        this.$form = $form;\n\n        var productSelector = [\n            'input[type=\"hidden\"][name=\"product_id\"]',\n            'input[type=\"radio\"][name=\"product_id\"]:checked'\n        ];\n\n        var productReady = this.selectOrCreateProduct(\n            $form,\n            parseInt($form.find(productSelector.join(', ')).first().val(), 10),\n            $form.find('.product_template_id').val(),\n            false\n        );\n\n        return productReady.then(function (productId) {\n            $form.find(productSelector.join(', ')).val(productId);\n\n            self.rootProduct = {\n                product_id: productId,\n                quantity: parseFloat($form.find('input[name=\"add_qty\"]').val() || 1),\n                product_custom_attribute_values: self.getCustomVariantValues($form.find('.js_product')),\n                variant_values: self.getSelectedVariantValues($form.find('.js_product')),\n                no_variant_attribute_values: self.getNoVariantAttributeValues($form.find('.js_product'))\n            };\n\n            return self._onProductReady();\n        });\n    },\n\n    _onProductReady: function () {\n        return this._submitForm();\n    },\n\n    /**\n     * Add custom variant values and attribute values that do not generate variants\n     * in the params to submit form if 'stay on page' option is disabled, or call\n     * '_addToCartInPage' otherwise.\n     *\n     * @private\n     * @returns {Promise}\n     */\n    _submitForm: function () {\n        const params = this.rootProduct;\n\n        const $product = $('#product_detail');\n        const productTrackingInfo = $product.data('product-tracking-info');\n        if (productTrackingInfo) {\n            productTrackingInfo.quantity = params.quantity;\n            $product.trigger('add_to_cart_event', [productTrackingInfo]);\n        }\n\n        params.add_qty = params.quantity;\n        params.product_custom_attribute_values = JSON.stringify(params.product_custom_attribute_values);\n        params.no_variant_attribute_values = JSON.stringify(params.no_variant_attribute_values);\n        return this.addToCart(params);\n    },\n    /**\n     * @private\n     * @param {MouseEvent} ev\n     */\n    _onClickAddCartJSON: function (ev){\n        this.onClickAddCartJSON(ev);\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onChangeAddQuantity: function (ev) {\n        this.onChangeAddQuantity(ev);\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onMouseupPublish: function (ev) {\n        $(ev.currentTarget).parents('.thumbnail').toggleClass('disabled');\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onChangeCartQuantity: function (ev) {\n        var $input = $(ev.currentTarget);\n        if ($input.data('update_change')) {\n            return;\n        }\n        var value = parseInt($input.val() || 0, 10);\n        if (isNaN(value)) {\n            value = 1;\n        }\n        var $dom = $input.closest('tr');\n        // var default_price = parseFloat($dom.find('.text-danger > span.oe_currency_value').text());\n        var $dom_optional = $dom.nextUntil(':not(.optional_product.info)');\n        var line_id = parseInt($input.data('line-id'), 10);\n        var productIDs = [parseInt($input.data('product-id'), 10)];\n        this._changeCartQuantity($input, value, $dom_optional, line_id, productIDs);\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onClickSuggestedProduct: function (ev) {\n        $(ev.currentTarget).prev('input').val(1).trigger('change');\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onClickSubmit: function (ev, forceSubmit) {\n        if ($(ev.currentTarget).is('#add_to_cart, #products_grid .a-submit') && !forceSubmit) {\n            return;\n        }\n        var $aSubmit = $(ev.currentTarget);\n        if (!ev.isDefaultPrevented() && !$aSubmit.is(\".disabled\")) {\n            ev.preventDefault();\n            $aSubmit.closest('form').submit();\n        }\n        if ($aSubmit.hasClass('a-submit-disable')){\n            $aSubmit.addClass(\"disabled\");\n        }\n        if ($aSubmit.hasClass('a-submit-loading')){\n            var loading = '<span class=\"fa fa-cog fa-spin\"/>';\n            var fa_span = $aSubmit.find('span[class*=\"fa\"]');\n            if (fa_span.length){\n                fa_span.replaceWith(loading);\n            } else {\n                $aSubmit.append(loading);\n            }\n        }\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onChangeAttribute: function (ev) {\n        if (!ev.isDefaultPrevented()) {\n            ev.preventDefault();\n            $(ev.currentTarget).closest(\"form\").submit();\n        }\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onMouseupAddCartLabel: function (ev) { // change price when they are variants\n        var $label = $(ev.currentTarget);\n        var $price = $label.parents(\"form:first\").find(\".oe_price .oe_currency_value\");\n        if (!$price.data(\"price\")) {\n            $price.data(\"price\", parseFloat($price.text()));\n        }\n        var value = $price.data(\"price\") + parseFloat($label.find(\".badge span\").text() || 0);\n\n        var dec = value % 1;\n        $price.html(value + (dec < 0.01 ? \".00\" : (dec < 1 ? \"0\" : \"\") ));\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onClickShowCoupon: function (ev) {\n        $(ev.currentTarget).hide();\n        $('.coupon_form').removeClass('d-none');\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onSubmitSaleSearch: function (ev) {\n        if (!this.$('.dropdown_sorty_by').length) {\n            return;\n        }\n        var $this = $(ev.currentTarget);\n        if (!ev.isDefaultPrevented() && !$this.is(\".disabled\")) {\n            ev.preventDefault();\n            var oldurl = $this.attr('action');\n            oldurl += (oldurl.indexOf(\"?\")===-1) ? \"?\" : \"\";\n            if ($this.find('[name=noFuzzy]').val() === \"true\") {\n                oldurl += '&noFuzzy=true';\n            }\n            var search = $this.find('input.search-query');\n            window.location = oldurl + '&' + search.attr('name') + '=' + encodeURIComponent(search.val());\n        }\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onChangeCountry: function (ev) {\n        if (!this.$('.checkout_autoformat').length) {\n            return;\n        }\n        this._changeCountry();\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onChangeShippingUseSame: function (ev) {\n        $('.ship_to_other').toggle(!$(ev.currentTarget).prop('checked'));\n    },\n    /**\n     * Toggles the add to cart button depending on the possibility of the\n     * current combination.\n     *\n     * @override\n     */\n    _toggleDisable: function ($parent, isCombinationPossible) {\n        VariantMixin._toggleDisable.apply(this, arguments);\n        $parent.find(\"#add_to_cart\").toggleClass('disabled', !isCombinationPossible);\n        $parent.find(\".o_we_buy_now\").toggleClass('disabled', !isCombinationPossible);\n    },\n    /**\n     * Write the properties of the form elements in the DOM to prevent the\n     * current selection from being lost when activating the web editor.\n     *\n     * @override\n     */\n    onChangeVariant: function (ev) {\n        var $component = $(ev.currentTarget).closest('.js_product');\n        $component.find('input').each(function () {\n            var $el = $(this);\n            $el.attr('checked', $el.is(':checked'));\n        });\n        $component.find('select option').each(function () {\n            var $el = $(this);\n            $el.attr('selected', $el.is(':selected'));\n        });\n\n        this._setUrlHash($component);\n\n        return VariantMixin.onChangeVariant.apply(this, arguments);\n    },\n    /**\n     * @private\n     */\n    _onToggleSummary: function () {\n        $('.toggle_summary_div').toggleClass('d-none');\n        $('.toggle_summary_div').removeClass('d-xl-block');\n    },\n    /**\n     * @private\n     */\n    _applyHashFromSearch() {\n        const params = $.deparam(window.location.search.slice(1));\n        if (params.attrib) {\n            const dataValueIds = [];\n            for (const attrib of [].concat(params.attrib)) {\n                const attribSplit = attrib.split('-');\n                const attribValueSelector = `.js_variant_change[name=\"ptal-${attribSplit[0]}\"][value=\"${attribSplit[1]}\"]`;\n                const attribValue = this.el.querySelector(attribValueSelector);\n                if (attribValue !== null) {\n                    dataValueIds.push(attribValue.dataset.value_id);\n                }\n            }\n            if (dataValueIds.length) {\n                history.replaceState(undefined, undefined, `#attr=${dataValueIds.join(',')}`);\n            }\n        }\n        this._applyHash();\n    },\n    /**\n     * @private\n     */\n    _onClickReviewsLink: function () {\n        $('#o_product_page_reviews_content').collapse('show');\n    },\n});\n\npublicWidget.registry.WebsiteSaleLayout = publicWidget.Widget.extend({\n    selector: '.oe_website_sale',\n    disabledInEditableMode: false,\n    events: {\n        'change .o_wsale_apply_layout': '_onApplyShopLayoutChange',\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onApplyShopLayoutChange: function (ev) {\n        const wysiwyg = this.options.wysiwyg;\n        if (wysiwyg) {\n            wysiwyg.odooEditor.observerUnactive('_onApplyShopLayoutChange');\n        }\n        var switchToList = $(ev.currentTarget).find('.o_wsale_apply_list input').is(':checked');\n        if (!this.editableMode) {\n            this._rpc({\n                route: '/shop/save_shop_layout_mode',\n                params: {\n                    'layout_mode': switchToList ? 'list' : 'grid',\n                },\n            });\n        }\n        var $grid = this.$('#products_grid');\n        // Disable transition on all list elements, then switch to the new\n        // layout then reenable all transitions after having forced a redraw\n        // TODO should probably be improved to allow disabling transitions\n        // altogether with a class/option.\n        $grid.find('*').css('transition', 'none');\n        $grid.toggleClass('o_wsale_layout_list', switchToList);\n        void $grid[0].offsetWidth;\n        $grid.find('*').css('transition', '');\n        if (wysiwyg) {\n            wysiwyg.odooEditor.observerActive('_onApplyShopLayoutChange');\n        }\n    },\n});\n\npublicWidget.registry.websiteSaleCart = publicWidget.Widget.extend({\n    selector: '.oe_website_sale .oe_cart',\n    events: {\n        'click .js_change_shipping': '_onClickChangeShipping',\n        'click .js_edit_address': '_onClickEditAddress',\n        'click .js_delete_product': '_onClickDeleteProduct',\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onClickChangeShipping: function (ev) {\n        var $old = $('.all_shipping').find('.card.border.border-primary');\n        $old.find('.btn-ship').toggle();\n        $old.addClass('js_change_shipping');\n        $old.removeClass('border border-primary');\n\n        var $new = $(ev.currentTarget).parent('div.one_kanban').find('.card');\n        $new.find('.btn-ship').toggle();\n        $new.removeClass('js_change_shipping');\n        $new.addClass('border border-primary');\n\n        var $form = $(ev.currentTarget).parent('div.one_kanban').find('form.d-none');\n        $.post($form.attr('action'), $form.serialize()+'&xhr=1');\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onClickEditAddress: function (ev) {\n        ev.preventDefault();\n        $(ev.currentTarget).closest('div.one_kanban').find('form.d-none').attr('action', '/shop/address').submit();\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onClickDeleteProduct: function (ev) {\n        ev.preventDefault();\n        $(ev.currentTarget).closest('tr').find('.js_quantity').val(0).trigger('change');\n    },\n});\n\npublicWidget.registry.websiteSaleCarouselProduct = publicWidget.Widget.extend({\n    selector: '#o-carousel-product',\n    disabledInEditableMode: false,\n    events: {\n        'wheel .o_carousel_product_indicators': '_onMouseWheel',\n    },\n\n    /**\n     * @override\n     */\n    async start() {\n        await this._super(...arguments);\n        this._updateCarouselPosition();\n        extraMenuUpdateCallbacks.push(this._updateCarouselPosition.bind(this));\n        if (this.$target.find('.carousel-indicators').length > 0) {\n            this.$target.on('slide.bs.carousel.carousel_product_slider', this._onSlideCarouselProduct.bind(this));\n            $(window).on('resize.carousel_product_slider', _.throttle(this._onSlideCarouselProduct.bind(this), 150));\n            this._updateJustifyContent();\n        }\n    },\n    /**\n     * @override\n     */\n    destroy() {\n        this.$target.css('top', '');\n        this.$target.off('.carousel_product_slider');\n        this._super(...arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _updateCarouselPosition() {\n        this.$target.css('top', dom.scrollFixedOffset() + 5);\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Center the selected indicator to scroll the indicators list when it\n     * overflows.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    _onSlideCarouselProduct: function (ev) {\n        const isReversed = this.$target.css('flex-direction') === \"column-reverse\";\n        const isLeftIndicators = this.$target.hasClass('o_carousel_product_left_indicators');\n        const $indicatorsDiv = isLeftIndicators ? this.$target.find('.o_carousel_product_indicators') : this.$target.find('.carousel-indicators');\n        let indicatorIndex = $(ev.relatedTarget).index();\n        indicatorIndex = indicatorIndex > -1 ? indicatorIndex : this.$target.find('li.active').index();\n        const $indicator = $indicatorsDiv.find('[data-slide-to=' + indicatorIndex + ']');\n        const indicatorsDivSize = isLeftIndicators && !isReversed ? $indicatorsDiv.outerHeight() : $indicatorsDiv.outerWidth();\n        const indicatorSize = isLeftIndicators && !isReversed ? $indicator.outerHeight() : $indicator.outerWidth();\n        const indicatorPosition = isLeftIndicators && !isReversed ? $indicator.position().top : $indicator.position().left;\n        const scrollSize = isLeftIndicators && !isReversed ? $indicatorsDiv[0].scrollHeight : $indicatorsDiv[0].scrollWidth;\n        let indicatorsPositionDiff = (indicatorPosition + (indicatorSize/2)) - (indicatorsDivSize/2);\n        indicatorsPositionDiff = Math.min(indicatorsPositionDiff, scrollSize - indicatorsDivSize);\n        this._updateJustifyContent();\n        const indicatorsPositionX = isLeftIndicators && !isReversed ? '0' : '-' + indicatorsPositionDiff;\n        const indicatorsPositionY = isLeftIndicators && !isReversed ? '-' + indicatorsPositionDiff : '0';\n        const translate3D = indicatorsPositionDiff > 0 ? \"translate3d(\" + indicatorsPositionX + \"px,\" + indicatorsPositionY + \"px,0)\" : '';\n        $indicatorsDiv.css(\"transform\", translate3D);\n    },\n    /**\n     * @private\n     */\n     _updateJustifyContent: function () {\n        const $indicatorsDiv = this.$target.find('.carousel-indicators');\n        $indicatorsDiv.css('justify-content', 'start');\n        if (config.device.size_class <= config.device.SIZES.MD) {\n            if (($indicatorsDiv.children().last().position().left + this.$target.find('li').outerWidth()) < $indicatorsDiv.outerWidth()) {\n                $indicatorsDiv.css('justify-content', 'center');\n            }\n        }\n    },\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onMouseWheel: function (ev) {\n        ev.preventDefault();\n        if (ev.originalEvent.deltaY > 0) {\n            this.$target.carousel('next');\n        } else {\n            this.$target.carousel('prev');\n        }\n    },\n});\n\npublicWidget.registry.websiteSaleProductPageReviews = publicWidget.Widget.extend({\n    selector: '#o_product_page_reviews',\n    disabledInEditableMode: false,\n\n    /**\n     * @override\n     */\n    async start() {\n        await this._super(...arguments);\n        this._updateChatterComposerPosition();\n        extraMenuUpdateCallbacks.push(this._updateChatterComposerPosition.bind(this));\n    },\n    /**\n     * @override\n     */\n    destroy() {\n        this.$target.find('.o_portal_chatter_composer').css('top', '');\n        this._super(...arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _updateChatterComposerPosition() {\n        this.$target.find('.o_portal_chatter_composer').css('top', dom.scrollFixedOffset() + 20);\n    },\n});\n\nreturn {\n    WebsiteSale: publicWidget.registry.WebsiteSale,\n    WebsiteSaleLayout: publicWidget.registry.WebsiteSaleLayout,\n    websiteSaleCart: publicWidget.registry.websiteSaleCart,\n    WebsiteSaleCarouselProduct: publicWidget.registry.websiteSaleCarouselProduct,\n    WebsiteSaleProductPageReviews: publicWidget.registry.websiteSaleProductPageReviews,\n};\n\n});\n\nodoo.define('website_sale.price_range_option', function (require) {\n'use strict';\n\nconst publicWidget = require('web.public.widget');\n\npublicWidget.registry.multirangePriceSelector = publicWidget.Widget.extend({\n    selector: '#o_wsale_price_range_option',\n    events: {\n        'newRangeValue input[type=\"range\"]': '_onPriceRangeSelected',\n    },\n\n    //----------------------------------------------------------------------\n    // Handlers\n    //----------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {Event} ev\n     */\n    _onPriceRangeSelected(ev) {\n        const range = ev.currentTarget;\n        const searchParams = new URLSearchParams(window.location.search);\n        searchParams.delete(\"min_price\");\n        searchParams.delete(\"max_price\");\n        if (parseFloat(range.min) !== range.valueLow) {\n            searchParams.set(\"min_price\", range.valueLow);\n        }\n        if (parseFloat(range.max) !== range.valueHigh) {\n            searchParams.set(\"max_price\", range.valueHigh);\n        }\n        window.location.search = searchParams.toString();\n    },\n});\n});\n", "odoo.define('website_sale.utils', function (require) {\n'use strict';\n\nconst wUtils = require('website.utils');\n\nconst cartHandlerMixin = {\n    getRedirectOption() {\n        const html = document.documentElement;\n        this.stayOnPageOption = html.dataset.add2cartRedirect !== '0';\n    },\n    getCartHandlerOptions(ev) {\n        this.isBuyNow = ev.currentTarget.classList.contains('o_we_buy_now');\n        const targetSelector = ev.currentTarget.dataset.animationSelector || 'img';\n        this.$itemImgContainer = this.$(ev.currentTarget).closest(`:has(${targetSelector})`);\n    },\n    /**\n     * Used to add product depending on stayOnPageOption value.\n     */\n    addToCart(params) {\n        if (this.isBuyNow) {\n            params.express = true;\n        } else if (this.stayOnPageOption) {\n            return this._addToCartInPage(params);\n        }\n        return wUtils.sendRequest('/shop/cart/update', params);\n    },\n    /**\n     * @private\n     */\n    _addToCartInPage(params) {\n        params.force_create = true;\n        return this._rpc({\n            route: \"/shop/cart/update_json\",\n            params: params,\n        }).then(async data => {\n            if (data.cart_quantity && (data.cart_quantity !== parseInt($(\".my_cart_quantity\").text()))) {\n                await animateClone($('header .o_wsale_my_cart').first(), this.$itemImgContainer, 25, 40);\n                updateCartNavBar(data);\n            }\n        });\n    },\n};\n\nfunction animateClone($cart, $elem, offsetTop, offsetLeft) {\n    if (!$cart.length) {\n        return Promise.resolve();\n    }\n    $cart.find('.o_animate_blink').addClass('o_red_highlight o_shadow_animation').delay(500).queue(function () {\n        $(this).removeClass(\"o_shadow_animation\").dequeue();\n    }).delay(2000).queue(function () {\n        $(this).removeClass(\"o_red_highlight\").dequeue();\n    });\n    return new Promise(function (resolve, reject) {\n        var $imgtodrag = $elem.find('img').eq(0);\n        if ($imgtodrag.length) {\n            var $imgclone = $imgtodrag.clone()\n                .offset({\n                    top: $imgtodrag.offset().top,\n                    left: $imgtodrag.offset().left\n                })\n                .removeClass()\n                .addClass('o_website_sale_animate')\n                .appendTo(document.body)\n                .css({\n                    // Keep the same size on cloned img.\n                    width: $imgtodrag.width(),\n                    height: $imgtodrag.height(),\n                })\n                .animate({\n                    top: $cart.offset().top + offsetTop,\n                    left: $cart.offset().left + offsetLeft,\n                    width: 75,\n                    height: 75,\n                }, 1000, 'easeInOutExpo');\n\n            $imgclone.animate({\n                width: 0,\n                height: 0,\n            }, function () {\n                resolve();\n                $(this).detach();\n            });\n        } else {\n            resolve();\n        }\n    });\n}\n\n/**\n * Updates both navbar cart\n * @param {Object} data\n */\nfunction updateCartNavBar(data) {\n    $(\".my_cart_quantity\")\n        .parents('li.o_wsale_my_cart').removeClass('d-none').end()\n        .addClass('o_mycart_zoom_animation').delay(300)\n        .queue(function () {\n            $(this)\n                .toggleClass('fa fa-warning', !data.cart_quantity)\n                .attr('title', data.warning)\n                .text(data.cart_quantity || '')\n                .removeClass('o_mycart_zoom_animation')\n                .dequeue();\n        });\n\n    $(\".js_cart_lines\").first().before(data['website_sale.cart_lines']).end().remove();\n    $(\".js_cart_summary\").first().before(data['website_sale.short_cart_summary']).end().remove();\n}\n\n/**\n * Displays `message` in an alert box at the top of the page if it's a\n * non-empty string.\n *\n * @param {string | null} message\n */\nfunction showWarning(message) {\n    if (!message) {\n        return;\n    }\n    var $page = $('.oe_website_sale');\n    var cart_alert = $page.children('#data_warning');\n    if (!cart_alert.length) {\n        cart_alert = $(\n            '<div class=\"alert alert-danger alert-dismissible\" role=\"alert\" id=\"data_warning\">' +\n                '<button type=\"button\" class=\"close\" data-dismiss=\"alert\">&times;</button> ' +\n                '<span></span>' +\n            '</div>').prependTo($page);\n    }\n    cart_alert.children('span:last-child').text(message);\n}\n\nreturn {\n    animateClone: animateClone,\n    updateCartNavBar: updateCartNavBar,\n    cartHandlerMixin: cartHandlerMixin,\n    showWarning: showWarning,\n};\n});\n", "odoo.define('website_sale.payment', require => {\n    'use strict';\n\n    const checkoutForm = require('payment.checkout_form');\n    const publicWidget = require('web.public.widget');\n\n    const websiteSalePaymentMixin = {\n\n        /**\n         * @override\n         */\n        init: function () {\n            this._onClickTCCheckbox = _.debounce(this._onClickTCCheckbox, 100, true);\n            this._super(...arguments);\n        },\n\n        /**\n         * @override\n         */\n        start: function () {\n            this.$checkbox = this.$('#checkbox_tc');\n            this.$submitButton = this.$('button[name=\"o_payment_submit_button\"]');\n            this._adaptConfirmButton();\n            return this._super(...arguments);\n        },\n\n        //--------------------------------------------------------------------------\n        // Private\n        //--------------------------------------------------------------------------\n\n        /**\n         * Update the data on the submit button with the status of the Terms and Conditions input.\n         *\n         * @private\n         * @return {undefined}\n         */\n        _adaptConfirmButton: function () {\n            if (this.$checkbox.length > 0) {\n                const disabledReasons = this.$submitButton.data('disabled_reasons') || {};\n                disabledReasons.tc = !this.$checkbox.prop('checked');\n                this.$submitButton.data('disabled_reasons', disabledReasons);\n            }\n        },\n\n    };\n\n    checkoutForm.include(Object.assign({}, websiteSalePaymentMixin, {\n        events: Object.assign({}, checkoutForm.prototype.events, {\n            'change #checkbox_tc': '_onClickTCCheckbox',\n        }),\n\n        //----------------------------------------------------------------------\n        // Private\n        //----------------------------------------------------------------------\n\n        /**\n         * Verify that the Terms and Condition checkbox is checked.\n         *\n         * @override method from payment.payment_form_mixin\n         * @private\n         * @return {boolean} Whether the submit button can be enabled\n         */\n        _isButtonReady: function () {\n            const disabledReasonFound = _.contains(\n                this.$submitButton.data('disabled_reasons'), true\n            );\n            return !disabledReasonFound && this._super();\n        },\n\n        //--------------------------------------------------------------------------\n        // Handlers\n        //--------------------------------------------------------------------------\n\n        /**\n         * Enable the submit button if it all conditions are met.\n         *\n         * @private\n         * @return {undefined}\n         */\n        _onClickTCCheckbox: function () {\n            this._adaptConfirmButton();\n\n            if (!this._enableButton()) {\n                this._disableButton(false);\n            }\n        },\n\n    }));\n\n    publicWidget.registry.WebsiteSalePayment = publicWidget.Widget.extend(\n        Object.assign({}, websiteSalePaymentMixin, {\n            selector: 'div[name=\"o_website_sale_free_cart\"]',\n            events: {\n                'change #checkbox_tc': '_onClickTCCheckbox',\n            },\n\n            /**\n             * @override\n             */\n            start: function () {\n                this.$checkbox = this.$('#checkbox_tc');\n                this.$submitButton = this.$('button[name=\"o_payment_submit_button\"]');\n                this._onClickTCCheckbox();\n                return this._super(...arguments);\n            },\n\n            //--------------------------------------------------------------------------\n            // Handlers\n            //--------------------------------------------------------------------------\n\n            /**\n             * Enable the submit button if it all conditions are met.\n             *\n             * @private\n             * @return {undefined}\n             */\n            _onClickTCCheckbox: function () {\n                this._adaptConfirmButton();\n\n                const disabledReasonFound = _.contains(\n                    this.$submitButton.data('disabled_reasons'), true\n                );\n                this.$submitButton.prop('disabled', disabledReasonFound);\n            },\n        }));\n});\n", "odoo.define('website_sale.validate', function (require) {\n'use strict';\n\nvar publicWidget = require('web.public.widget');\nvar core = require('web.core');\nvar _t = core._t;\n\npublicWidget.registry.websiteSaleValidate = publicWidget.Widget.extend({\n    selector: 'div.oe_website_sale_tx_status[data-order-id]',\n\n    /**\n     * @override\n     */\n    start: function () {\n        var def = this._super.apply(this, arguments);\n        this._poll_nbr = 0;\n        this._paymentTransationPollStatus();\n        return def;\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _paymentTransationPollStatus: function () {\n        var self = this;\n        this._rpc({\n            route: '/shop/payment/get_status/' + parseInt(this.$el.data('order-id')),\n        }).then(function (result) {\n            self._poll_nbr += 1;\n            if (result.recall) {\n                if (self._poll_nbr < 20) {\n                    setTimeout(function () {\n                        self._paymentTransationPollStatus();\n                    }, Math.ceil(self._poll_nbr / 3) * 1000);\n                } else {\n                    var $message = $(result.message);\n                    var $warning =  $(\"<i class='fa fa-warning' style='margin-right:10px;'>\");\n                    $warning.attr(\"title\", _t(\"We are waiting for confirmation from the bank or the payment provider\"));\n                    $message.find('span:first').prepend($warning);\n                    result.message = $message.html();\n                }\n            }\n            self.$el.html(result.message);\n        });\n    },\n});\n});\n", "odoo.define('website_sale.recently_viewed', function (require) {\n\nvar publicWidget = require('web.public.widget');\nvar utils = require('web.utils');\n\npublicWidget.registry.productsRecentlyViewedUpdate = publicWidget.Widget.extend({\n    selector: '#product_detail',\n    events: {\n        'change input.product_id[name=\"product_id\"]': '_onProductChange',\n    },\n    debounceValue: 8000,\n\n    /**\n     * @constructor\n     */\n    init: function () {\n        this._super.apply(this, arguments);\n        this._onProductChange = _.debounce(this._onProductChange, this.debounceValue);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Debounced method that wait some time before marking the product as viewed.\n     * @private\n     * @param {HTMLInputElement} $input\n     */\n    _updateProductView: function ($input) {\n        var productId = parseInt($input.val());\n        var cookieName = 'seen_product_id_' + productId;\n        if (! parseInt(this.el.dataset.viewTrack, 10)) {\n            return; // Is not tracked\n        }\n        if (utils.get_cookie(cookieName)) {\n            return; // Already tracked in the last 30min\n        }\n        if ($(this.el).find('.js_product.css_not_available').length) {\n            return; // Variant not possible\n        }\n        this._rpc({\n            route: '/shop/products/recently_viewed_update',\n            params: {\n                product_id: productId,\n            }\n        }).then(function (res) {\n            if (res && res.visitor_uuid) {\n                utils.set_cookie('visitor_uuid', res.visitor_uuid);\n            }\n            utils.set_cookie(cookieName, productId, 30 * 60);\n        });\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Call debounced method when product change to reset timer.\n     * @private\n     * @param {Event} ev\n     */\n    _onProductChange: function (ev) {\n        this._updateProductView($(ev.currentTarget));\n    },\n});\n});\n", "odoo.define('website_sale.tracking', function (require) {\n\nvar publicWidget = require('web.public.widget');\n\npublicWidget.registry.websiteSaleTracking = publicWidget.Widget.extend({\n    selector: '.oe_website_sale',\n    events: {\n        'click form[action=\"/shop/cart/update\"] a.a-submit': '_onAddProductIntoCart',\n        'click a[href=\"/shop/checkout\"]': '_onCheckoutStart',\n        'click div.oe_cart a[href^=\"/web?redirect\"][href$=\"/shop/checkout\"]': '_onCustomerSignin',\n        'click form[action=\"/shop/confirm_order\"] a.a-submit': '_onOrder',\n        'click form[target=\"_self\"] button[type=submit]': '_onOrderPayment',\n        'view_item_event': '_onViewItem',\n        'add_to_cart_event': '_onAddToCart',\n    },\n\n    /**\n     * @override\n     */\n    start: function () {\n        var self = this;\n\n        // ...\n        const $confirmation = this.$('div.oe_website_sale_tx_status');\n        if ($confirmation.length) {\n            const orderID = $confirmation.data('order-id');\n            const json = $confirmation.data('order-tracking-info');\n            this._vpv('/stats/ecom/order_confirmed/' + orderID);\n            self._trackGA('event', 'purchase', json);\n        }\n\n        return this._super.apply(this, arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _trackGA: function () {\n        const websiteGA = window.gtag || function () {};\n        websiteGA.apply(this, arguments);\n    },\n    /**\n     * @private\n     */\n    _vpv: function (page) { //virtual page view\n        this._trackGA('event', 'page_view', {\n            'page_path': page,\n        });\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onViewItem(event, productTrackingInfo) {\n        const trackingInfo = {\n            'currency': productTrackingInfo['currency'],\n            'value': productTrackingInfo['price'],\n            'items': [productTrackingInfo],\n        };\n        this._trackGA('event', 'view_item', trackingInfo);\n    },\n\n    /**\n     * @private\n     */\n    _onAddToCart(event, ...productsTrackingInfo) {\n        const trackingInfo = {\n            'currency': productsTrackingInfo[0]['currency'],\n            'value': productsTrackingInfo.reduce((acc, val) => acc + val['price'] * val['quantity'], 0),\n            'items': productsTrackingInfo,\n        };\n        this._trackGA('event', 'add_to_cart', trackingInfo);\n    },\n\n    /**\n     * @private\n     */\n    _onAddProductIntoCart: function () {\n        var productID = this.$('input[name=\"product_id\"]').attr('value');\n        this._vpv('/stats/ecom/product_add_to_cart/' + productID);\n    },\n    /**\n     * @private\n     */\n    _onCheckoutStart: function () {\n        this._vpv('/stats/ecom/customer_checkout');\n    },\n    /**\n     * @private\n     */\n    _onCustomerSignin: function () {\n        this._vpv('/stats/ecom/customer_signin');\n    },\n    /**\n     * @private\n     */\n    _onOrder: function () {\n        if ($('#top_menu [href=\"/web/login\"]').length) {\n            this._vpv('/stats/ecom/customer_signup');\n        }\n        this._vpv('/stats/ecom/order_checkout');\n    },\n    /**\n     * @private\n     */\n    _onOrderPayment: function () {\n        var method = $('#payment_method input[name=acquirer]:checked').nextAll('span:first').text();\n        this._vpv('/stats/ecom/order_payment/' + method);\n    },\n});\n\nreturn publicWidget.registry.websiteSaleTracking;\n\n});\n", "/**\n * This code has been more that widely inspired by the multirange library\n * which can be found on https://github.com/LeaVerou/multirange.\n *\n * The license file can be found in the same folder as this file.\n */\n\nodoo.define('website_sale.multirange', function () {\n'use strict';\n\n/**\n * The multirange library will display the two values as one range input with\n * two cursors linked by a background. This is to be used with Bootstrap\n * custom-range inputs.\n *\n * There is 2 number inputs on the right and left of the multirange to\n * display and allow quick and precise value modifications. They are\n * initialized with the same value provided to the input (min, max, step).\n *\n * There is 2 events that are added to the input:\n * - oldRangeValue: Triggered when the user clicks on a cursor or on focus\n *                  of the right or left number input.\n * - newRangeValue: Triggered when the user release a cursor or on focus\n *                  out of the right or left number input.\n *\n * The options available for the multirange are :\n * - On range input or as multirange method options:\n *     - min: minimal value of the range. Default: 0.\n *     - max: maximal value of the range. Default: 100.\n *     - step: precision of the range. Default: 1.\n *     - currency: symbol preceding the displayed values. Default: Empty.\n *     - currencyPosition: currency before/after value. Default: \"after\".\n *     - value: the current value of the range. Default: \"0,100\".\n *\n * - As multirange method options only:\n *     - displayCounterInput: if we display the value. Default: true.\n *\n * Initialization of a multiple range input can be done in two ways:\n *\n * Having the inputs with the options as properties and the multiple\n * property set will let the library initialize it just after DOM loaded.\n *\n * <input type=\"range\" multiple=\"multiple\" class=\"custom-range\n * range-with-input\" min=2 max=10 step=0.5 data-currency=\"\u20ac\"\n * data-currency-position=\"before\" value=\"4,8\"/>\n *\n * Providing a HTMLElement and an Object with the desired options.\n *\n * <input id=\"multi\" type=\"range\" class=\"custom-range\"/>\n *\n * multirange(document.querySelector('#multi'), {\n *     min: 2,\n *     max: 10,\n *     step: 0.5,\n *     currency: \"\u20ac\",\n *     currencyPosition: \"before\",\n *     value: \"4,8\"\n *     rangeWithInput: true,\n * });\n */\n\nconst HTMLInputElement = window.HTMLInputElement;\nconst descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, \"value\");\n\nclass Multirange {\n    constructor(input, options = {}) {\n        const self = this;\n\n        /* Set default and optionnal values */\n        this.input = input;\n        this.rangeWithInput = options.rangeWithInput === true || this.input.classList.contains('range-with-input');\n        const value = options.value || this.input.getAttribute(\"value\");\n        const values = value === null ? [] : value.split(\",\");\n        this.input.min = this.min = options.min || this.input.min || 0;\n        this.input.max = this.max = options.max || this.input.max || 100;\n        this.input.step = this.step = options.step || this.input.step || 1;\n        this.currency = options.currency || this.input.dataset.currency || '';\n        this.currencyPosition = options.currencyPosition || this.input.dataset.currencyPosition || 'after';\n\n        /* Wrap the input and add its ghost */\n        this.rangeDiv = document.createElement(\"div\");\n        this.rangeDiv.classList.add(\"multirange-wrapper\");\n        this.input.parentNode.insertBefore(this.rangeDiv, this.input.nextSibling);\n        this.rangeDiv.appendChild(this.input);\n        this.ghost = this.input.cloneNode();\n        this.rangeDiv.appendChild(this.ghost);\n\n        this.input.classList.add(\"multirange\", \"original\");\n        this.ghost.classList.add(\"multirange\", \"ghost\");\n        this.input.value = values[0] || this.min;\n        this.ghost.value = values[1] || this.max;\n\n        this.inputTipLocator = document.createElement(\"div\");\n        this.inputTipLocator.classList.add(\"tip-locator\");\n        this.ghostTipLocator = document.createElement(\"div\");\n        this.ghostTipLocator.classList.add(\"tip-locator\");\n        this.rangeDiv.insertBefore(this.ghostTipLocator, this.input.nextSibling);\n        this.rangeDiv.insertBefore(this.inputTipLocator, this.ghost.nextSibling);\n        this.leftCounter = document.createElement(\"span\");\n        this.leftCounter.classList.add(\"multirange-min\");\n        this.rightCounter = document.createElement(\"span\");\n        this.rightCounter.classList.add(\"multirange-max\");\n        this.tipLocatorOptions = {\n            container: this.rangeDiv,\n            html: true,\n        };\n        $(this.inputTipLocator).popover(Object.assign(\n            this.tipLocatorOptions,\n            {\n                placement: 'top',\n                content: this.leftCounter\n            })\n        );\n        $(this.ghostTipLocator).popover(Object.assign(\n            this.tipLocatorOptions,\n            {\n                placement: 'bottom',\n                content: this.rightCounter\n            })\n        );\n\n        $(this.inputTipLocator).add($(this.ghostTipLocator)).popover('show');\n        /* Add the counterInput */\n        if (this.rangeWithInput) {\n            this.leftInput = document.createElement(\"input\");\n            this.leftInput.type = \"number\";\n            this.leftInput.style.display = \"none\";\n            this.leftInput.min = this.min;\n            this.leftInput.max = this.max;\n            this.leftInput.step = this.step;\n            this.rightInput = this.leftInput.cloneNode();\n\n            this.leftInput.classList.add(\"multirange-min\");\n            this.rightInput.classList.add(\"multirange-max\");\n\n            this.leftCounter.parentNode.appendChild(this.leftInput);\n            this.rightCounter.parentNode.appendChild(this.rightInput);\n        }\n\n        /* Define new properties on range input to link it with ghost, especially for Safari compatibility*/\n        Object.defineProperty(this.input, \"originalValue\", descriptor.get ? descriptor : {\n            get: function () {\n                return this.value;\n            },\n            set: function (v) {\n                this.value = v;\n            }\n        });\n\n        Object.defineProperties(this.input, {\n            valueLow: {\n                get: function () {\n                    return Math.min(this.originalValue, self.ghost.value);\n                },\n                set: function (v) {\n                    this.originalValue = v;\n                },\n                enumerable: true\n            },\n            valueHigh: {\n                get: function () {\n                    return Math.max(this.originalValue, self.ghost.value);\n                },\n                set: function (v) {\n                    self.ghost.value = v;\n                },\n                enumerable: true\n            }\n        });\n\n        if (descriptor.get) {\n            Object.defineProperty(this.input, \"value\", {\n                get: function () {\n                    return this.valueLow + \",\" + this.valueHigh;\n                },\n                set: function (v) {\n                    const values = v.split(\",\");\n                    this.valueLow = values[0];\n                    this.valueHigh = values[1];\n                    this.update();\n                },\n                enumerable: true\n            });\n        }\n\n        if (typeof this.input.oninput === \"function\") {\n            this.ghost.oninput = this.input.oninput.bind(this.input);\n        }\n\n        this.input.addEventListener(\"input\", this.update.bind(this));\n        this.ghost.addEventListener(\"input\", this.update.bind(this));\n\n        this.input.addEventListener(\"touchstart\", this.saveOldValues.bind(this));\n        this.ghost.addEventListener(\"touchstart\", this.saveOldValues.bind(this));\n        this.input.addEventListener(\"mousedown\", this.saveOldValues.bind(this));\n        this.ghost.addEventListener(\"mousedown\", this.saveOldValues.bind(this));\n\n        this.input.addEventListener(\"touchend\", this.dispatchNewValueEvent.bind(this));\n        this.ghost.addEventListener(\"touchend\", this.dispatchNewValueEvent.bind(this));\n        this.input.addEventListener(\"mouseup\", this.dispatchNewValueEvent.bind(this));\n        this.ghost.addEventListener(\"mouseup\", this.dispatchNewValueEvent.bind(this));\n\n        if (this.rangeWithInput) {\n            this.leftCounter.addEventListener(\"click\", this.counterInputSwitch.bind(this));\n            this.rightCounter.addEventListener(\"click\", this.counterInputSwitch.bind(this));\n\n            this.leftInput.addEventListener(\"blur\", this.counterInputSwitch.bind(this));\n            this.rightInput.addEventListener(\"blur\", this.counterInputSwitch.bind(this));\n\n            this.leftInput.addEventListener(\"keypress\", this.elementBlurOnEnter.bind(this));\n            this.rightInput.addEventListener(\"keypress\", this.elementBlurOnEnter.bind(this));\n\n            this.leftInput.addEventListener(\"focus\", this.selectAllFocus.bind(this));\n            this.rightInput.addEventListener(\"focus\", this.selectAllFocus.bind(this));\n        }\n        this.update();\n        $(this.rangeDiv).addClass('visible');\n\n    }\n\n    update() {\n        const low = 100 * (this.input.valueLow - this.min) / (this.max - this.min);\n        const high = 100 * (this.input.valueHigh - this.min) / (this.max - this.min);\n        const tipOffsetLow = 8 - (low * 0.15);\n        const tipOffsetHigh = 8 - (high * 0.15);\n        this.rangeDiv.style.setProperty(\"--low\", low + '%');\n        this.rangeDiv.style.setProperty(\"--high\", high + '%');\n        $(this.inputTipLocator).css({\n            'left': `calc(${low}% + (${tipOffsetLow}px))`,\n            'top': '3px'\n        });\n        $(this.ghostTipLocator).css({\n            'left': `calc(${high}% + (${tipOffsetHigh}px))`,\n            'top': '18px'\n        });\n        $(this.inputTipLocator).add($(this.ghostTipLocator)).popover('update');\n        this.counterInputUpdate();\n    }\n\n    counterInputUpdate() {\n        if (this.rangeWithInput) {\n            this.leftCounter.innerText = this.formatNumber(this.input.valueLow);\n            this.rightCounter.innerText = this.formatNumber(this.input.valueHigh);\n            this.leftInput.value = this.input.valueLow;\n            this.rightInput.value = this.input.valueHigh;\n        }\n    }\n\n    counterInputSwitch(ev) {\n        let counter = this.rightCounter;\n        let input = this.rightInput;\n        if (ev.currentTarget.classList.contains('multirange-min')) {\n            counter = this.leftCounter;\n            input = this.leftInput;\n        }\n        if (counter.style.display === \"none\") {\n            this.input.valueLow = this.leftInput.value;\n            this.input.valueHigh = this.rightInput.value;\n            this.dispatchNewValueEvent();\n            this.update();\n            counter.style.display = \"\";\n            input.style.display = \"none\";\n        } else {\n            counter.style.display = \"none\";\n            input.style.display = \"\";\n            this.saveOldValues();\n            // Hack because firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1057858\n            window.setTimeout(function () {\n                input.focus();\n            }, 1);\n        }\n    }\n\n    elementBlurOnEnter(ev) {\n        if (ev.key === \"Enter\") {\n            ev.currentTarget.blur();\n        }\n    }\n\n    selectAllFocus(ev) {\n        ev.currentTarget.select();\n    }\n\n    dispatchNewValueEvent() {\n        if (this._previousMaxPrice !== this.input.valueHigh || this._previousMinPrice !== this.input.valueLow) {\n            this.input.dispatchEvent(new CustomEvent(\"newRangeValue\", {\n                bubbles: true,\n            }));\n        }\n    }\n\n    saveOldValues() {\n        this._previousMinPrice = this.input.valueLow;\n        this._previousMaxPrice = this.input.valueHigh;\n    }\n\n    formatNumber(number) {\n        const locale = document.querySelector(\"html\").getAttribute(\"lang\");\n        let formatedNumber = number.toLocaleString(locale, {\n            minimumFractionDigits: 2,\n            maximumFractionDigits: 2,\n        });\n        if (this.currency.length) {\n            if (this.currencyPosition === 'after') {\n                formatedNumber = formatedNumber + ' ' + this.currency;\n            } else {\n                formatedNumber = this.currency + ' ' + formatedNumber;\n            }\n        }\n        return formatedNumber;\n    }\n}\n\nfunction multirange(input, options) {\n    if (input.classList.contains('multirange')) {\n        return;\n    }\n    new Multirange(input, options);\n}\n\nreturn {\n    Multirange: Multirange,\n    init: multirange,\n};\n});\n\nodoo.define('website_sale.multirange.instance', function (require) {\n'use strict';\n\nconst publicWidget = require('web.public.widget');\nconst multirange = require('website_sale.multirange');\n\npublicWidget.registry.WebsiteMultirangeInputs = publicWidget.Widget.extend({\n    selector: 'input[type=range][multiple]:not(.multirange)',\n\n    /**\n     * @override\n     */\n    start() {\n        return this._super.apply(this, arguments).then(() => {\n            multirange.init(this.el);\n        });\n    },\n});\n});\n", "/** @odoo-module **/\n\nimport * as publicWidget from 'web.public.widget'\n\npublicWidget.registry.ProductCategoriesLinks = publicWidget.Widget.extend({\n    selector: '.products_categories',\n    events: {\n        'click [data-link-href]': '_openLink',\n    },\n\n    _openLink: function (ev) {\n        window.location.href = ev.currentTarget.getAttribute('data-link-href');\n    },\n});\n", "odoo.define('auth_totp_portal.button', function (require) {\n'use strict';\n\nconst {_t} = require('web.core');\nconst publicWidget = require('web.public.widget');\nconst Dialog = require('web.Dialog');\nconst {handleCheckIdentity} = require('portal.portal');\n\n/**\n * Replaces specific <field> elements by normal HTML, strip out the rest entirely\n */\nfunction fromField(f, record) {\n    switch (f.getAttribute('name')) {\n    case 'qrcode':\n        const qrcode = document.createElement('img');\n        qrcode.setAttribute('class', 'img img-fluid');\n        qrcode.setAttribute('src', 'data:image/png;base64,' + record['qrcode']);\n        return qrcode;\n    case 'url':\n        const url = document.createElement('a');\n        url.setAttribute('href', record['url']);\n        url.textContent = f.getAttribute('text') || record['url'];\n        return url;\n    case 'code':\n        const code = document.createElement('input');\n        code.setAttribute('name', 'code');\n        code.setAttribute('class', 'form-control col-10 col-md-6');\n        code.setAttribute('placeholder', '6-digit code');\n        code.required = true;\n        code.maxLength = 6;\n        code.minLength = 6;\n        return code;\n    case 'secret':\n        // As CopyClipboard wizard is backend only, mimic his behaviour to use it in frontend.\n        // Field\n        const secretSpan = document.createElement('span');\n        secretSpan.setAttribute('name', 'secret');\n        secretSpan.setAttribute('class', 'o_field_copy_url');\n        secretSpan.textContent = record['secret'];\n\n        // Copy Button\n        const copySpanIcon = document.createElement('span');\n        copySpanIcon.setAttribute('class', 'fa fa-clipboard');\n        const copySpanText = document.createElement('span');\n        copySpanText.textContent = _t(' Copy');\n\n        const copyButton = document.createElement('button');\n        copyButton.setAttribute('class', 'btn btn-sm btn-primary o_clipboard_button o_btn_char_copy py-0 px-2');\n        copyButton.onclick = function(event) {\n            event.preventDefault();\n            $(copyButton).tooltip({title: _t(\"Copied !\"), trigger: \"manual\", placement: \"bottom\"});\n            var clipboard = new ClipboardJS('.o_clipboard_button', {\n                target: function () {\n                    return $(secretSpan)[0];\n                },\n                container: this.el\n            });\n            clipboard.on('success', function () {\n                clipboard.destroy();\n                $(copyButton).tooltip('show');\n                _.delay(function () {\n                    $(copyButton).tooltip(\"hide\");\n                }, 800);\n            });\n            clipboard.on('error', function (e) {\n                clipboard.destroy();\n            });\n        };\n\n        copyButton.appendChild(copySpanIcon);\n        copyButton.appendChild(copySpanText);\n\n        // CopyClipboard Div\n        const secretDiv = document.createElement('div');\n        secretDiv.setAttribute('class', 'o_field_copy d-flex justify-content-center align-items-center');\n        secretDiv.appendChild(secretSpan);\n        secretDiv.appendChild(copyButton);\n\n        return secretDiv;\n    default: // just display the field's data\n        return document.createTextNode(record[f.getAttribute('name')] || '');\n    }\n}\n\n/**\n * Apparently chrome literally absolutely can't handle parsing XML and using\n * those nodes in an HTML document (even when parsing as application/xhtml+xml),\n * this results in broken rendering and a number of things not working (e.g.\n * classes) without any specific warning in the console or anything, things are\n * just broken with no indication of why.\n *\n * So... rebuild the entire f'ing body using document.createElement to ensure\n * we have HTML elements.\n *\n * This is a recursive implementation so it's not super efficient but the views\n * to fixup *should* be relatively simple.\n */\nfunction fixupViewBody(oldNode, record) {\n    let qrcode = null, code = null, node = null;\n\n    switch (oldNode.nodeType) {\n        case 1: // element\n            if (oldNode.tagName === 'field') {\n                node = fromField(oldNode, record);\n                switch (oldNode.getAttribute('name')) {\n                case 'qrcode':\n                    qrcode = node;\n                    break;\n                case 'code':\n                    code = node;\n                    break\n                }\n                break; // no need to recurse here\n            }\n            node = document.createElement(oldNode.tagName);\n            for(let i=0; i<oldNode.attributes.length; ++i) {\n                const attr = oldNode.attributes[i];\n                node.setAttribute(attr.name, attr.value);\n            }\n            for(let j=0; j<oldNode.childNodes.length; ++j) {\n                const [ch, qr, co] = fixupViewBody(oldNode.childNodes[j], record);\n                if (ch) { node.appendChild(ch); }\n                if (qr) { qrcode = qr; }\n                if (co) { code = co; }\n            }\n            break;\n        case 3: case 4: // text, cdata\n            node = document.createTextNode(oldNode.data);\n            break;\n        default:\n            // don't care about PI & al\n    }\n\n    return [node, qrcode, code]\n}\n\n/**\n * Converts a backend <button> element and a bunch of metadata into a structure\n * which can kinda be of use to Dialog.\n */\nclass Button {\n    constructor(parent, model, record_id, input_node, button_node) {\n        this._parent = parent;\n        this.model = model;\n        this.record_id = record_id;\n        this.input = input_node;\n        this.text = button_node.getAttribute('string');\n        this.classes = button_node.getAttribute('class') || null;\n        this.action = button_node.getAttribute('name');\n        if (button_node.getAttribute('special') === 'cancel') {\n            this.close = true;\n            this.click = null;\n        } else {\n            this.close = false;\n            // because Dialog doesnt' call() click on the descriptor object\n            this.click = this._click.bind(this);\n        }\n    }\n    async _click() {\n        if (!this.input.reportValidity()) {\n            this.input.classList.add('is-invalid');\n            return;\n        }\n\n        try {\n            await this.callAction(this.record_id, {code: this.input.value});\n        } catch (e) {\n            this.input.classList.add('is-invalid');\n            // show custom validity error message\n            this.input.setCustomValidity(e.message);\n            this.input.reportValidity();\n            return;\n        }\n        this.input.classList.remove('is-invalid');\n        // reloads page, avoid window.location.reload() because it re-posts forms\n        window.location = window.location;\n    }\n    async callAction(id, update) {\n        try {\n            await this._parent._rpc({model: this.model, method: 'write', args: [id, update]});\n            await handleCheckIdentity(\n                this._parent.proxy('_rpc'),\n                this._parent._rpc({model: this.model, method: this.action, args: [id]})\n            );\n        } catch(e) {\n            // avoid error toast (crashmanager)\n            e.event.preventDefault();\n            // try to unwrap mess of an error object to a usable error message\n            throw new Error(\n                !e.message ? e.toString()\n              : !e.message.data ? e.message.message\n              : e.message.data.message || _t(\"Operation failed for unknown reason.\")\n            );\n        }\n    }\n}\n\npublicWidget.registry.TOTPButton = publicWidget.Widget.extend({\n    selector: '#auth_totp_portal_enable',\n    events: {\n        click: '_onClick',\n    },\n\n    async _onClick(e) {\n        e.preventDefault();\n\n        const w = await handleCheckIdentity(this.proxy('_rpc'), this._rpc({\n            model: 'res.users',\n            method: 'action_totp_enable_wizard',\n            args: [this.getSession().user_id]\n        }));\n\n        if (!w) {\n            // TOTP probably already enabled, just reload page\n            window.location = window.location;\n            return;\n        }\n\n        const {res_model: model, res_id: wizard_id} = w;\n\n        const record = await this._rpc({\n            model, method: 'read', args: [wizard_id, []]\n        }).then(ar => ar[0]);\n\n        const doc = new DOMParser().parseFromString(\n            document.getElementById('totp_wizard_view').textContent,\n            'application/xhtml+xml'\n        );\n\n        const xmlBody = doc.querySelector('sheet *');\n        const [body, , codeInput] = fixupViewBody(xmlBody, record);\n        // remove custom validity error message any time the field changes\n        // otherwise it sticks and browsers suppress submit\n        codeInput.addEventListener('input', () => codeInput.setCustomValidity(''));\n\n        const buttons = [];\n        for(const button of doc.querySelectorAll('footer button')) {\n            buttons.push(new Button(this, model, record.id, codeInput, button));\n        }\n\n        // wrap in a root host of .modal-body otherwise it breaks our neat flex layout\n        const $content = document.createElement('form');\n        $content.appendChild(body);\n        // implicit submission by pressing [return] from within input\n        $content.addEventListener('submit', (e) => {\n            e.preventDefault();\n            // sadness: footer not available as normal element\n            dialog.$footer.find('.btn-primary').click();\n        });\n        var dialog = new Dialog(this, {$content, buttons}).open();\n    }\n});\npublicWidget.registry.DisableTOTPButton = publicWidget.Widget.extend({\n    selector: '#auth_totp_portal_disable',\n    events: {\n        click: '_onClick'\n    },\n\n    async _onClick(e) {\n        e.preventDefault();\n        await handleCheckIdentity(\n            this.proxy('_rpc'),\n            this._rpc({model: 'res.users', method: 'action_totp_disable', args: [this.getSession().user_id]})\n        )\n        window.location = window.location;\n    }\n});\npublicWidget.registry.RevokeTrustedDeviceButton = publicWidget.Widget.extend({\n    selector: '#totp_wizard_view + * .fa.fa-trash.text-danger',\n    events: {\n        click: '_onClick'\n    },\n\n    async _onClick(e){\n        e.preventDefault();\n        await handleCheckIdentity(\n            this.proxy('_rpc'),\n            this._rpc({\n                model: 'auth_totp.device',\n                method: 'remove',\n                args: [parseInt(this.target.id)]\n            })\n        );\n        window.location = window.location;\n    }\n});\npublicWidget.registry.RevokeAllTrustedDevicesButton = publicWidget.Widget.extend({\n    selector: '#auth_totp_portal_revoke_all_devices',\n    events: {\n        click: '_onClick'\n    },\n\n    async _onClick(e){\n        e.preventDefault();\n        await handleCheckIdentity(\n            this.proxy('_rpc'),\n            this._rpc({\n                model: 'res.users',\n                method: 'revoke_all_devices',\n                args: [this.getSession().user_id]\n            })\n        );\n        window.location = window.location;\n    }\n});\n});\n", "!function(a){\"function\"==typeof define&&define.amd?define([\"jquery\"],a):a(\"object\"==typeof exports?require(\"jquery\"):jQuery)}(function(a,b){function c(){return new Date(Date.UTC.apply(Date,arguments))}function d(){var a=new Date;return c(a.getFullYear(),a.getMonth(),a.getDate())}function e(a,b){return a.getUTCFullYear()===b.getUTCFullYear()&&a.getUTCMonth()===b.getUTCMonth()&&a.getUTCDate()===b.getUTCDate()}function f(c,d){return function(){return d!==b&&a.fn.datepicker.deprecated(d),this[c].apply(this,arguments)}}function g(a){return a&&!isNaN(a.getTime())}function h(b,c){function d(a,b){return b.toLowerCase()}var e,f=a(b).data(),g={},h=new RegExp(\"^\"+c.toLowerCase()+\"([A-Z])\");c=new RegExp(\"^\"+c.toLowerCase());for(var i in f)c.test(i)&&(e=i.replace(h,d),g[e]=f[i]);return g}function i(b){var c={};if(q[b]||(b=b.split(\"-\")[0],q[b])){var d=q[b];return a.each(p,function(a,b){b in d&&(c[b]=d[b])}),c}}var j=function(){var b={get:function(a){return this.slice(a)[0]},contains:function(a){for(var b=a&&a.valueOf(),c=0,d=this.length;c<d;c++)if(0<=this[c].valueOf()-b&&this[c].valueOf()-b<864e5)return c;return-1},remove:function(a){this.splice(a,1)},replace:function(b){b&&(a.isArray(b)||(b=[b]),this.clear(),this.push.apply(this,b))},clear:function(){this.length=0},copy:function(){var a=new j;return a.replace(this),a}};return function(){var c=[];return c.push.apply(c,arguments),a.extend(c,b),c}}(),k=function(b,c){a.data(b,\"datepicker\",this),this._events=[],this._secondaryEvents=[],this._process_options(c),this.dates=new j,this.viewDate=this.o.defaultViewDate,this.focusDate=null,this.element=a(b),this.isInput=this.element.is(\"input\"),this.inputField=this.isInput?this.element:this.element.find(\"input\"),this.component=!!this.element.hasClass(\"date\")&&this.element.find(\".add-on, .input-group-addon, .input-group-append, .input-group-prepend, .btn\"),this.component&&0===this.component.length&&(this.component=!1),this.isInline=!this.component&&this.element.is(\"div\"),this.picker=a(r.template),this._check_template(this.o.templates.leftArrow)&&this.picker.find(\".prev\").html(this.o.templates.leftArrow),this._check_template(this.o.templates.rightArrow)&&this.picker.find(\".next\").html(this.o.templates.rightArrow),this._buildEvents(),this._attachEvents(),this.isInline?this.picker.addClass(\"datepicker-inline\").appendTo(this.element):this.picker.addClass(\"datepicker-dropdown dropdown-menu\"),this.o.rtl&&this.picker.addClass(\"datepicker-rtl\"),this.o.calendarWeeks&&this.picker.find(\".datepicker-days .datepicker-switch, thead .datepicker-title, tfoot .today, tfoot .clear\").attr(\"colspan\",function(a,b){return Number(b)+1}),this._process_options({startDate:this._o.startDate,endDate:this._o.endDate,daysOfWeekDisabled:this.o.daysOfWeekDisabled,daysOfWeekHighlighted:this.o.daysOfWeekHighlighted,datesDisabled:this.o.datesDisabled}),this._allow_update=!1,this.setViewMode(this.o.startView),this._allow_update=!0,this.fillDow(),this.fillMonths(),this.update(),this.isInline&&this.show()};k.prototype={constructor:k,_resolveViewName:function(b){return a.each(r.viewModes,function(c,d){if(b===c||-1!==a.inArray(b,d.names))return b=c,!1}),b},_resolveDaysOfWeek:function(b){return a.isArray(b)||(b=b.split(/[,\\s]*/)),a.map(b,Number)},_check_template:function(c){try{if(c===b||\"\"===c)return!1;if((c.match(/[<>]/g)||[]).length<=0)return!0;return a(c).length>0}catch(a){return!1}},_process_options:function(b){this._o=a.extend({},this._o,b);var e=this.o=a.extend({},this._o),f=e.language;q[f]||(f=f.split(\"-\")[0],q[f]||(f=o.language)),e.language=f,e.startView=this._resolveViewName(e.startView),e.minViewMode=this._resolveViewName(e.minViewMode),e.maxViewMode=this._resolveViewName(e.maxViewMode),e.startView=Math.max(this.o.minViewMode,Math.min(this.o.maxViewMode,e.startView)),!0!==e.multidate&&(e.multidate=Number(e.multidate)||!1,!1!==e.multidate&&(e.multidate=Math.max(0,e.multidate))),e.multidateSeparator=String(e.multidateSeparator),e.weekStart%=7,e.weekEnd=(e.weekStart+6)%7;var g=r.parseFormat(e.format);e.startDate!==-1/0&&(e.startDate?e.startDate instanceof Date?e.startDate=this._local_to_utc(this._zero_time(e.startDate)):e.startDate=r.parseDate(e.startDate,g,e.language,e.assumeNearbyYear):e.startDate=-1/0),e.endDate!==1/0&&(e.endDate?e.endDate instanceof Date?e.endDate=this._local_to_utc(this._zero_time(e.endDate)):e.endDate=r.parseDate(e.endDate,g,e.language,e.assumeNearbyYear):e.endDate=1/0),e.daysOfWeekDisabled=this._resolveDaysOfWeek(e.daysOfWeekDisabled||[]),e.daysOfWeekHighlighted=this._resolveDaysOfWeek(e.daysOfWeekHighlighted||[]),e.datesDisabled=e.datesDisabled||[],a.isArray(e.datesDisabled)||(e.datesDisabled=e.datesDisabled.split(\",\")),e.datesDisabled=a.map(e.datesDisabled,function(a){return r.parseDate(a,g,e.language,e.assumeNearbyYear)});var h=String(e.orientation).toLowerCase().split(/\\s+/g),i=e.orientation.toLowerCase();if(h=a.grep(h,function(a){return/^auto|left|right|top|bottom$/.test(a)}),e.orientation={x:\"auto\",y:\"auto\"},i&&\"auto\"!==i)if(1===h.length)switch(h[0]){case\"top\":case\"bottom\":e.orientation.y=h[0];break;case\"left\":case\"right\":e.orientation.x=h[0]}else i=a.grep(h,function(a){return/^left|right$/.test(a)}),e.orientation.x=i[0]||\"auto\",i=a.grep(h,function(a){return/^top|bottom$/.test(a)}),e.orientation.y=i[0]||\"auto\";else;if(e.defaultViewDate instanceof Date||\"string\"==typeof e.defaultViewDate)e.defaultViewDate=r.parseDate(e.defaultViewDate,g,e.language,e.assumeNearbyYear);else if(e.defaultViewDate){var j=e.defaultViewDate.year||(new Date).getFullYear(),k=e.defaultViewDate.month||0,l=e.defaultViewDate.day||1;e.defaultViewDate=c(j,k,l)}else e.defaultViewDate=d()},_applyEvents:function(a){for(var c,d,e,f=0;f<a.length;f++)c=a[f][0],2===a[f].length?(d=b,e=a[f][1]):3===a[f].length&&(d=a[f][1],e=a[f][2]),c.on(e,d)},_unapplyEvents:function(a){for(var c,d,e,f=0;f<a.length;f++)c=a[f][0],2===a[f].length?(e=b,d=a[f][1]):3===a[f].length&&(e=a[f][1],d=a[f][2]),c.off(d,e)},_buildEvents:function(){var b={keyup:a.proxy(function(b){-1===a.inArray(b.keyCode,[27,37,39,38,40,32,13,9])&&this.update()},this),keydown:a.proxy(this.keydown,this),paste:a.proxy(this.paste,this)};!0===this.o.showOnFocus&&(b.focus=a.proxy(this.show,this)),this.isInput?this._events=[[this.element,b]]:this.component&&this.inputField.length?this._events=[[this.inputField,b],[this.component,{click:a.proxy(this.show,this)}]]:this._events=[[this.element,{click:a.proxy(this.show,this),keydown:a.proxy(this.keydown,this)}]],this._events.push([this.element,\"*\",{blur:a.proxy(function(a){this._focused_from=a.target},this)}],[this.element,{blur:a.proxy(function(a){this._focused_from=a.target},this)}]),this.o.immediateUpdates&&this._events.push([this.element,{\"changeYear changeMonth\":a.proxy(function(a){this.update(a.date)},this)}]),this._secondaryEvents=[[this.picker,{click:a.proxy(this.click,this)}],[this.picker,\".prev, .next\",{click:a.proxy(this.navArrowsClick,this)}],[this.picker,\".day:not(.disabled)\",{click:a.proxy(this.dayCellClick,this)}],[a(window),{resize:a.proxy(this.place,this)}],[a(document),{\"mousedown touchstart\":a.proxy(function(a){this.element.is(a.target)||this.element.find(a.target).length||this.picker.is(a.target)||this.picker.find(a.target).length||this.isInline||this.hide()},this)}]]},_attachEvents:function(){this._detachEvents(),this._applyEvents(this._events)},_detachEvents:function(){this._unapplyEvents(this._events)},_attachSecondaryEvents:function(){this._detachSecondaryEvents(),this._applyEvents(this._secondaryEvents)},_detachSecondaryEvents:function(){this._unapplyEvents(this._secondaryEvents)},_trigger:function(b,c){var d=c||this.dates.get(-1),e=this._utc_to_local(d);this.element.trigger({type:b,date:e,viewMode:this.viewMode,dates:a.map(this.dates,this._utc_to_local),format:a.proxy(function(a,b){0===arguments.length?(a=this.dates.length-1,b=this.o.format):\"string\"==typeof a&&(b=a,a=this.dates.length-1),b=b||this.o.format;var c=this.dates.get(a);return r.formatDate(c,b,this.o.language)},this)})},show:function(){if(!(this.inputField.is(\":disabled\")||this.inputField.prop(\"readonly\")&&!1===this.o.enableOnReadonly))return this.isInline||this.picker.appendTo(this.o.container),this.place(),this.picker.show(),this._attachSecondaryEvents(),this._trigger(\"show\"),(window.navigator.msMaxTouchPoints||\"ontouchstart\"in document)&&this.o.disableTouchKeyboard&&a(this.element).blur(),this},hide:function(){return this.isInline||!this.picker.is(\":visible\")?this:(this.focusDate=null,this.picker.hide().detach(),this._detachSecondaryEvents(),this.setViewMode(this.o.startView),this.o.forceParse&&this.inputField.val()&&this.setValue(),this._trigger(\"hide\"),this)},destroy:function(){return this.hide(),this._detachEvents(),this._detachSecondaryEvents(),this.picker.remove(),delete this.element.data().datepicker,this.isInput||delete this.element.data().date,this},paste:function(b){var c;if(b.originalEvent.clipboardData&&b.originalEvent.clipboardData.types&&-1!==a.inArray(\"text/plain\",b.originalEvent.clipboardData.types))c=b.originalEvent.clipboardData.getData(\"text/plain\");else{if(!window.clipboardData)return;c=window.clipboardData.getData(\"Text\")}this.setDate(c),this.update(),b.preventDefault()},_utc_to_local:function(a){if(!a)return a;var b=new Date(a.getTime()+6e4*a.getTimezoneOffset());return b.getTimezoneOffset()!==a.getTimezoneOffset()&&(b=new Date(a.getTime()+6e4*b.getTimezoneOffset())),b},_local_to_utc:function(a){return a&&new Date(a.getTime()-6e4*a.getTimezoneOffset())},_zero_time:function(a){return a&&new Date(a.getFullYear(),a.getMonth(),a.getDate())},_zero_utc_time:function(a){return a&&c(a.getUTCFullYear(),a.getUTCMonth(),a.getUTCDate())},getDates:function(){return a.map(this.dates,this._utc_to_local)},getUTCDates:function(){return a.map(this.dates,function(a){return new Date(a)})},getDate:function(){return this._utc_to_local(this.getUTCDate())},getUTCDate:function(){var a=this.dates.get(-1);return a!==b?new Date(a):null},clearDates:function(){this.inputField.val(\"\"),this.update(),this._trigger(\"changeDate\"),this.o.autoclose&&this.hide()},setDates:function(){var b=a.isArray(arguments[0])?arguments[0]:arguments;return this.update.apply(this,b),this._trigger(\"changeDate\"),this.setValue(),this},setUTCDates:function(){var b=a.isArray(arguments[0])?arguments[0]:arguments;return this.setDates.apply(this,a.map(b,this._utc_to_local)),this},setDate:f(\"setDates\"),setUTCDate:f(\"setUTCDates\"),remove:f(\"destroy\",\"Method `remove` is deprecated and will be removed in version 2.0. Use `destroy` instead\"),setValue:function(){var a=this.getFormattedDate();return this.inputField.val(a),this},getFormattedDate:function(c){c===b&&(c=this.o.format);var d=this.o.language;return a.map(this.dates,function(a){return r.formatDate(a,c,d)}).join(this.o.multidateSeparator)},getStartDate:function(){return this.o.startDate},setStartDate:function(a){return this._process_options({startDate:a}),this.update(),this.updateNavArrows(),this},getEndDate:function(){return this.o.endDate},setEndDate:function(a){return this._process_options({endDate:a}),this.update(),this.updateNavArrows(),this},setDaysOfWeekDisabled:function(a){return this._process_options({daysOfWeekDisabled:a}),this.update(),this},setDaysOfWeekHighlighted:function(a){return this._process_options({daysOfWeekHighlighted:a}),this.update(),this},setDatesDisabled:function(a){return this._process_options({datesDisabled:a}),this.update(),this},place:function(){if(this.isInline)return this;var b=this.picker.outerWidth(),c=this.picker.outerHeight(),d=a(this.o.container),e=d.width(),f=\"body\"===this.o.container?a(document).scrollTop():d.scrollTop(),g=d.offset(),h=[0];this.element.parents().each(function(){var b=a(this).css(\"z-index\");\"auto\"!==b&&0!==Number(b)&&h.push(Number(b))});var i=Math.max.apply(Math,h)+this.o.zIndexOffset,j=this.component?this.component.parent().offset():this.element.offset(),k=this.component?this.component.outerHeight(!0):this.element.outerHeight(!1),l=this.component?this.component.outerWidth(!0):this.element.outerWidth(!1),m=j.left-g.left,n=j.top-g.top;\"body\"!==this.o.container&&(n+=f),this.picker.removeClass(\"datepicker-orient-top datepicker-orient-bottom datepicker-orient-right datepicker-orient-left\"),\"auto\"!==this.o.orientation.x?(this.picker.addClass(\"datepicker-orient-\"+this.o.orientation.x),\"right\"===this.o.orientation.x&&(m-=b-l)):j.left<0?(this.picker.addClass(\"datepicker-orient-left\"),m-=j.left-10):m+b>e?(this.picker.addClass(\"datepicker-orient-right\"),m+=l-b):this.o.rtl?this.picker.addClass(\"datepicker-orient-right\"):this.picker.addClass(\"datepicker-orient-left\");var o,p=this.o.orientation.y;if(\"auto\"===p&&(o=-f+n-c,p=o<0?\"bottom\":\"top\"),this.picker.addClass(\"datepicker-orient-\"+p),\"top\"===p?n-=c+parseInt(this.picker.css(\"padding-top\")):n+=k,this.o.rtl){var q=e-(m+l);this.picker.css({top:n,right:q,zIndex:i})}else this.picker.css({top:n,left:m,zIndex:i});return this},_allow_update:!0,update:function(){if(!this._allow_update)return this;var b=this.dates.copy(),c=[],d=!1;return arguments.length?(a.each(arguments,a.proxy(function(a,b){b instanceof Date&&(b=this._local_to_utc(b)),c.push(b)},this)),d=!0):(c=this.isInput?this.element.val():this.element.data(\"date\")||this.inputField.val(),c=c&&this.o.multidate?c.split(this.o.multidateSeparator):[c],delete this.element.data().date),c=a.map(c,a.proxy(function(a){return r.parseDate(a,this.o.format,this.o.language,this.o.assumeNearbyYear)},this)),c=a.grep(c,a.proxy(function(a){return!this.dateWithinRange(a)||!a},this),!0),this.dates.replace(c),this.o.updateViewDate&&(this.dates.length?this.viewDate=new Date(this.dates.get(-1)):this.viewDate<this.o.startDate?this.viewDate=new Date(this.o.startDate):this.viewDate>this.o.endDate?this.viewDate=new Date(this.o.endDate):this.viewDate=this.o.defaultViewDate),d?(this.setValue(),this.element.change()):this.dates.length&&String(b)!==String(this.dates)&&d&&(this._trigger(\"changeDate\"),this.element.change()),!this.dates.length&&b.length&&(this._trigger(\"clearDate\"),this.element.change()),this.fill(),this},fillDow:function(){if(this.o.showWeekDays){var b=this.o.weekStart,c=\"<tr>\";for(this.o.calendarWeeks&&(c+='<th class=\"cw\">&#160;</th>');b<this.o.weekStart+7;)c+='<th class=\"dow',-1!==a.inArray(b,this.o.daysOfWeekDisabled)&&(c+=\" disabled\"),c+='\">'+q[this.o.language].daysMin[b++%7]+\"</th>\";c+=\"</tr>\",this.picker.find(\".datepicker-days thead\").append(c)}},fillMonths:function(){for(var a,b=this._utc_to_local(this.viewDate),c=\"\",d=0;d<12;d++)a=b&&b.getMonth()===d?\" focused\":\"\",c+='<span class=\"month'+a+'\">'+q[this.o.language].monthsShort[d]+\"</span>\";this.picker.find(\".datepicker-months td\").html(c)},setRange:function(b){b&&b.length?this.range=a.map(b,function(a){return a.valueOf()}):delete this.range,this.fill()},getClassNames:function(b){var c=[],f=this.viewDate.getUTCFullYear(),g=this.viewDate.getUTCMonth(),h=d();return b.getUTCFullYear()<f||b.getUTCFullYear()===f&&b.getUTCMonth()<g?c.push(\"old\"):(b.getUTCFullYear()>f||b.getUTCFullYear()===f&&b.getUTCMonth()>g)&&c.push(\"new\"),this.focusDate&&b.valueOf()===this.focusDate.valueOf()&&c.push(\"focused\"),this.o.todayHighlight&&e(b,h)&&c.push(\"today\"),-1!==this.dates.contains(b)&&c.push(\"active\"),this.dateWithinRange(b)||c.push(\"disabled\"),this.dateIsDisabled(b)&&c.push(\"disabled\",\"disabled-date\"),-1!==a.inArray(b.getUTCDay(),this.o.daysOfWeekHighlighted)&&c.push(\"highlighted\"),this.range&&(b>this.range[0]&&b<this.range[this.range.length-1]&&c.push(\"range\"),-1!==a.inArray(b.valueOf(),this.range)&&c.push(\"selected\"),b.valueOf()===this.range[0]&&c.push(\"range-start\"),b.valueOf()===this.range[this.range.length-1]&&c.push(\"range-end\")),c},_fill_yearsView:function(c,d,e,f,g,h,i){for(var j,k,l,m=\"\",n=e/10,o=this.picker.find(c),p=Math.floor(f/e)*e,q=p+9*n,r=Math.floor(this.viewDate.getFullYear()/n)*n,s=a.map(this.dates,function(a){return Math.floor(a.getUTCFullYear()/n)*n}),t=p-n;t<=q+n;t+=n)j=[d],k=null,t===p-n?j.push(\"old\"):t===q+n&&j.push(\"new\"),-1!==a.inArray(t,s)&&j.push(\"active\"),(t<g||t>h)&&j.push(\"disabled\"),t===r&&j.push(\"focused\"),i!==a.noop&&(l=i(new Date(t,0,1)),l===b?l={}:\"boolean\"==typeof l?l={enabled:l}:\"string\"==typeof l&&(l={classes:l}),!1===l.enabled&&j.push(\"disabled\"),l.classes&&(j=j.concat(l.classes.split(/\\s+/))),l.tooltip&&(k=l.tooltip)),m+='<span class=\"'+j.join(\" \")+'\"'+(k?' title=\"'+k+'\"':\"\")+\">\"+t+\"</span>\";o.find(\".datepicker-switch\").text(p+\"-\"+q),o.find(\"td\").html(m)},fill:function(){var e,f,g=new Date(this.viewDate),h=g.getUTCFullYear(),i=g.getUTCMonth(),j=this.o.startDate!==-1/0?this.o.startDate.getUTCFullYear():-1/0,k=this.o.startDate!==-1/0?this.o.startDate.getUTCMonth():-1/0,l=this.o.endDate!==1/0?this.o.endDate.getUTCFullYear():1/0,m=this.o.endDate!==1/0?this.o.endDate.getUTCMonth():1/0,n=q[this.o.language].today||q.en.today||\"\",o=q[this.o.language].clear||q.en.clear||\"\",p=q[this.o.language].titleFormat||q.en.titleFormat,s=d(),t=(!0===this.o.todayBtn||\"linked\"===this.o.todayBtn)&&s>=this.o.startDate&&s<=this.o.endDate&&!this.weekOfDateIsDisabled(s);if(!isNaN(h)&&!isNaN(i)){this.picker.find(\".datepicker-days .datepicker-switch\").text(r.formatDate(g,p,this.o.language)),this.picker.find(\"tfoot .today\").text(n).css(\"display\",t?\"table-cell\":\"none\"),this.picker.find(\"tfoot .clear\").text(o).css(\"display\",!0===this.o.clearBtn?\"table-cell\":\"none\"),this.picker.find(\"thead .datepicker-title\").text(this.o.title).css(\"display\",\"string\"==typeof this.o.title&&\"\"!==this.o.title?\"table-cell\":\"none\"),this.updateNavArrows(),this.fillMonths();var u=c(h,i,0),v=u.getUTCDate();u.setUTCDate(v-(u.getUTCDay()-this.o.weekStart+7)%7);var w=new Date(u);u.getUTCFullYear()<100&&w.setUTCFullYear(u.getUTCFullYear()),w.setUTCDate(w.getUTCDate()+42),w=w.valueOf();for(var x,y,z=[];u.valueOf()<w;){if((x=u.getUTCDay())===this.o.weekStart&&(z.push(\"<tr>\"),this.o.calendarWeeks)){var A=new Date(+u+(this.o.weekStart-x-7)%7*864e5),B=new Date(Number(A)+(11-A.getUTCDay())%7*864e5),C=new Date(Number(C=c(B.getUTCFullYear(),0,1))+(11-C.getUTCDay())%7*864e5),D=(B-C)/864e5/7+1;z.push('<td class=\"cw\">'+D+\"</td>\")}y=this.getClassNames(u),y.push(\"day\");var E=u.getUTCDate();this.o.beforeShowDay!==a.noop&&(f=this.o.beforeShowDay(this._utc_to_local(u)),f===b?f={}:\"boolean\"==typeof f?f={enabled:f}:\"string\"==typeof f&&(f={classes:f}),!1===f.enabled&&y.push(\"disabled\"),f.classes&&(y=y.concat(f.classes.split(/\\s+/))),f.tooltip&&(e=f.tooltip),f.content&&(E=f.content)),y=a.isFunction(a.uniqueSort)?a.uniqueSort(y):a.unique(y),z.push('<td class=\"'+y.join(\" \")+'\"'+(e?' title=\"'+e+'\"':\"\")+' data-date=\"'+u.getTime().toString()+'\">'+E+\"</td>\"),e=null,x===this.o.weekEnd&&z.push(\"</tr>\"),u.setUTCDate(u.getUTCDate()+1)}this.picker.find(\".datepicker-days tbody\").html(z.join(\"\"));var F=q[this.o.language].monthsTitle||q.en.monthsTitle||\"Months\",G=this.picker.find(\".datepicker-months\").find(\".datepicker-switch\").text(this.o.maxViewMode<2?F:h).end().find(\"tbody span\").removeClass(\"active\");if(a.each(this.dates,function(a,b){b.getUTCFullYear()===h&&G.eq(b.getUTCMonth()).addClass(\"active\")}),(h<j||h>l)&&G.addClass(\"disabled\"),h===j&&G.slice(0,k).addClass(\"disabled\"),h===l&&G.slice(m+1).addClass(\"disabled\"),this.o.beforeShowMonth!==a.noop){var H=this;a.each(G,function(c,d){var e=new Date(h,c,1),f=H.o.beforeShowMonth(e);f===b?f={}:\"boolean\"==typeof f?f={enabled:f}:\"string\"==typeof f&&(f={classes:f}),!1!==f.enabled||a(d).hasClass(\"disabled\")||a(d).addClass(\"disabled\"),f.classes&&a(d).addClass(f.classes),f.tooltip&&a(d).prop(\"title\",f.tooltip)})}this._fill_yearsView(\".datepicker-years\",\"year\",10,h,j,l,this.o.beforeShowYear),this._fill_yearsView(\".datepicker-decades\",\"decade\",100,h,j,l,this.o.beforeShowDecade),this._fill_yearsView(\".datepicker-centuries\",\"century\",1e3,h,j,l,this.o.beforeShowCentury)}},updateNavArrows:function(){if(this._allow_update){var a,b,c=new Date(this.viewDate),d=c.getUTCFullYear(),e=c.getUTCMonth(),f=this.o.startDate!==-1/0?this.o.startDate.getUTCFullYear():-1/0,g=this.o.startDate!==-1/0?this.o.startDate.getUTCMonth():-1/0,h=this.o.endDate!==1/0?this.o.endDate.getUTCFullYear():1/0,i=this.o.endDate!==1/0?this.o.endDate.getUTCMonth():1/0,j=1;switch(this.viewMode){case 4:j*=10;case 3:j*=10;case 2:j*=10;case 1:a=Math.floor(d/j)*j<=f,b=Math.floor(d/j)*j+j>h;break;case 0:a=d<=f&&e<=g,b=d>=h&&e>=i}this.picker.find(\".prev\").toggleClass(\"disabled\",a),this.picker.find(\".next\").toggleClass(\"disabled\",b)}},click:function(b){b.preventDefault(),b.stopPropagation();var e,f,g,h;e=a(b.target),e.hasClass(\"datepicker-switch\")&&this.viewMode!==this.o.maxViewMode&&this.setViewMode(this.viewMode+1),e.hasClass(\"today\")&&!e.hasClass(\"day\")&&(this.setViewMode(0),this._setDate(d(),\"linked\"===this.o.todayBtn?null:\"view\")),e.hasClass(\"clear\")&&this.clearDates(),e.hasClass(\"disabled\")||(e.hasClass(\"month\")||e.hasClass(\"year\")||e.hasClass(\"decade\")||e.hasClass(\"century\"))&&(this.viewDate.setUTCDate(1),f=1,1===this.viewMode?(h=e.parent().find(\"span\").index(e),g=this.viewDate.getUTCFullYear(),this.viewDate.setUTCMonth(h)):(h=0,g=Number(e.text()),this.viewDate.setUTCFullYear(g)),this._trigger(r.viewModes[this.viewMode-1].e,this.viewDate),this.viewMode===this.o.minViewMode?this._setDate(c(g,h,f)):(this.setViewMode(this.viewMode-1),this.fill())),this.picker.is(\":visible\")&&this._focused_from&&this._focused_from.focus(),delete this._focused_from},dayCellClick:function(b){var c=a(b.currentTarget),d=c.data(\"date\"),e=new Date(d);this.o.updateViewDate&&(e.getUTCFullYear()!==this.viewDate.getUTCFullYear()&&this._trigger(\"changeYear\",this.viewDate),e.getUTCMonth()!==this.viewDate.getUTCMonth()&&this._trigger(\"changeMonth\",this.viewDate)),this._setDate(e)},navArrowsClick:function(b){var c=a(b.currentTarget),d=c.hasClass(\"prev\")?-1:1;0!==this.viewMode&&(d*=12*r.viewModes[this.viewMode].navStep),this.viewDate=this.moveMonth(this.viewDate,d),this._trigger(r.viewModes[this.viewMode].e,this.viewDate),this.fill()},_toggle_multidate:function(a){var b=this.dates.contains(a);if(a||this.dates.clear(),-1!==b?(!0===this.o.multidate||this.o.multidate>1||this.o.toggleActive)&&this.dates.remove(b):!1===this.o.multidate?(this.dates.clear(),this.dates.push(a)):this.dates.push(a),\"number\"==typeof this.o.multidate)for(;this.dates.length>this.o.multidate;)this.dates.remove(0)},_setDate:function(a,b){b&&\"date\"!==b||this._toggle_multidate(a&&new Date(a)),(!b&&this.o.updateViewDate||\"view\"===b)&&(this.viewDate=a&&new Date(a)),this.fill(),this.setValue(),b&&\"view\"===b||this._trigger(\"changeDate\"),this.inputField.trigger(\"change\"),!this.o.autoclose||b&&\"date\"!==b||this.hide()},moveDay:function(a,b){var c=new Date(a);return c.setUTCDate(a.getUTCDate()+b),c},moveWeek:function(a,b){return this.moveDay(a,7*b)},moveMonth:function(a,b){if(!g(a))return this.o.defaultViewDate;if(!b)return a;var c,d,e=new Date(a.valueOf()),f=e.getUTCDate(),h=e.getUTCMonth(),i=Math.abs(b);if(b=b>0?1:-1,1===i)d=-1===b?function(){return e.getUTCMonth()===h}:function(){return e.getUTCMonth()!==c},c=h+b,e.setUTCMonth(c),c=(c+12)%12;else{for(var j=0;j<i;j++)e=this.moveMonth(e,b);c=e.getUTCMonth(),e.setUTCDate(f),d=function(){return c!==e.getUTCMonth()}}for(;d();)e.setUTCDate(--f),e.setUTCMonth(c);return e},moveYear:function(a,b){return this.moveMonth(a,12*b)},moveAvailableDate:function(a,b,c){do{if(a=this[c](a,b),!this.dateWithinRange(a))return!1;c=\"moveDay\"}while(this.dateIsDisabled(a));return a},weekOfDateIsDisabled:function(b){return-1!==a.inArray(b.getUTCDay(),this.o.daysOfWeekDisabled)},dateIsDisabled:function(b){return this.weekOfDateIsDisabled(b)||a.grep(this.o.datesDisabled,function(a){return e(b,a)}).length>0},dateWithinRange:function(a){return a>=this.o.startDate&&a<=this.o.endDate},keydown:function(a){if(!this.picker.is(\":visible\"))return void(40!==a.keyCode&&27!==a.keyCode||(this.show(),a.stopPropagation()));var b,c,d=!1,e=this.focusDate||this.viewDate;switch(a.keyCode){case 27:this.focusDate?(this.focusDate=null,this.viewDate=this.dates.get(-1)||this.viewDate,this.fill()):this.hide(),a.preventDefault(),a.stopPropagation();break;case 37:case 38:case 39:case 40:if(!this.o.keyboardNavigation||7===this.o.daysOfWeekDisabled.length)break;b=37===a.keyCode||38===a.keyCode?-1:1,0===this.viewMode?a.ctrlKey?(c=this.moveAvailableDate(e,b,\"moveYear\"))&&this._trigger(\"changeYear\",this.viewDate):a.shiftKey?(c=this.moveAvailableDate(e,b,\"moveMonth\"))&&this._trigger(\"changeMonth\",this.viewDate):37===a.keyCode||39===a.keyCode?c=this.moveAvailableDate(e,b,\"moveDay\"):this.weekOfDateIsDisabled(e)||(c=this.moveAvailableDate(e,b,\"moveWeek\")):1===this.viewMode?(38!==a.keyCode&&40!==a.keyCode||(b*=4),c=this.moveAvailableDate(e,b,\"moveMonth\")):2===this.viewMode&&(38!==a.keyCode&&40!==a.keyCode||(b*=4),c=this.moveAvailableDate(e,b,\"moveYear\")),c&&(this.focusDate=this.viewDate=c,this.setValue(),this.fill(),a.preventDefault());break;case 13:if(!this.o.forceParse)break;e=this.focusDate||this.dates.get(-1)||this.viewDate,this.o.keyboardNavigation&&(this._toggle_multidate(e),d=!0),this.focusDate=null,this.viewDate=this.dates.get(-1)||this.viewDate,this.setValue(),this.fill(),this.picker.is(\":visible\")&&(a.preventDefault(),a.stopPropagation(),this.o.autoclose&&this.hide());break;case 9:this.focusDate=null,this.viewDate=this.dates.get(-1)||this.viewDate,this.fill(),this.hide()}d&&(this.dates.length?this._trigger(\"changeDate\"):this._trigger(\"clearDate\"),this.inputField.trigger(\"change\"))},setViewMode:function(a){this.viewMode=a,this.picker.children(\"div\").hide().filter(\".datepicker-\"+r.viewModes[this.viewMode].clsName).show(),this.updateNavArrows(),this._trigger(\"changeViewMode\",new Date(this.viewDate))}};var l=function(b,c){a.data(b,\"datepicker\",this),this.element=a(b),this.inputs=a.map(c.inputs,function(a){return a.jquery?a[0]:a}),delete c.inputs,this.keepEmptyValues=c.keepEmptyValues,delete c.keepEmptyValues,n.call(a(this.inputs),c).on(\"changeDate\",a.proxy(this.dateUpdated,this)),this.pickers=a.map(this.inputs,function(b){return a.data(b,\"datepicker\")}),this.updateDates()};l.prototype={updateDates:function(){this.dates=a.map(this.pickers,function(a){return a.getUTCDate()}),this.updateRanges()},updateRanges:function(){var b=a.map(this.dates,function(a){return a.valueOf()});a.each(this.pickers,function(a,c){c.setRange(b)})},clearDates:function(){a.each(this.pickers,function(a,b){b.clearDates()})},dateUpdated:function(c){if(!this.updating){this.updating=!0;var d=a.data(c.target,\"datepicker\");if(d!==b){var e=d.getUTCDate(),f=this.keepEmptyValues,g=a.inArray(c.target,this.inputs),h=g-1,i=g+1,j=this.inputs.length;if(-1!==g){if(a.each(this.pickers,function(a,b){b.getUTCDate()||b!==d&&f||b.setUTCDate(e)}),e<this.dates[h])for(;h>=0&&e<this.dates[h];)this.pickers[h--].setUTCDate(e);else if(e>this.dates[i])for(;i<j&&e>this.dates[i];)this.pickers[i++].setUTCDate(e);this.updateDates(),delete this.updating}}}},destroy:function(){a.map(this.pickers,function(a){a.destroy()}),a(this.inputs).off(\"changeDate\",this.dateUpdated),delete this.element.data().datepicker},remove:f(\"destroy\",\"Method `remove` is deprecated and will be removed in version 2.0. Use `destroy` instead\")};var m=a.fn.datepicker,n=function(c){var d=Array.apply(null,arguments);d.shift();var e;if(this.each(function(){var b=a(this),f=b.data(\"datepicker\"),g=\"object\"==typeof c&&c;if(!f){var j=h(this,\"date\"),m=a.extend({},o,j,g),n=i(m.language),p=a.extend({},o,n,j,g);b.hasClass(\"input-daterange\")||p.inputs?(a.extend(p,{inputs:p.inputs||b.find(\"input\").toArray()}),f=new l(this,p)):f=new k(this,p),b.data(\"datepicker\",f)}\"string\"==typeof c&&\"function\"==typeof f[c]&&(e=f[c].apply(f,d))}),e===b||e instanceof k||e instanceof l)return this;if(this.length>1)throw new Error(\"Using only allowed for the collection of a single element (\"+c+\" function)\");return e};a.fn.datepicker=n;var o=a.fn.datepicker.defaults={assumeNearbyYear:!1,autoclose:!1,beforeShowDay:a.noop,beforeShowMonth:a.noop,beforeShowYear:a.noop,beforeShowDecade:a.noop,beforeShowCentury:a.noop,calendarWeeks:!1,clearBtn:!1,toggleActive:!1,daysOfWeekDisabled:[],daysOfWeekHighlighted:[],datesDisabled:[],endDate:1/0,forceParse:!0,format:\"mm/dd/yyyy\",keepEmptyValues:!1,keyboardNavigation:!0,language:\"en\",minViewMode:0,maxViewMode:4,multidate:!1,multidateSeparator:\",\",orientation:\"auto\",rtl:!1,startDate:-1/0,startView:0,todayBtn:!1,todayHighlight:!1,updateViewDate:!0,weekStart:0,disableTouchKeyboard:!1,enableOnReadonly:!0,showOnFocus:!0,zIndexOffset:10,container:\"body\",immediateUpdates:!1,title:\"\",templates:{leftArrow:\"&#x00AB;\",rightArrow:\"&#x00BB;\"},showWeekDays:!0},p=a.fn.datepicker.locale_opts=[\"format\",\"rtl\",\"weekStart\"];a.fn.datepicker.Constructor=k;var q=a.fn.datepicker.dates={en:{days:[\"Sunday\",\"Monday\",\"Tuesday\",\"Wednesday\",\"Thursday\",\"Friday\",\"Saturday\"],daysShort:[\"Sun\",\"Mon\",\"Tue\",\"Wed\",\"Thu\",\"Fri\",\"Sat\"],daysMin:[\"Su\",\"Mo\",\"Tu\",\"We\",\"Th\",\"Fr\",\"Sa\"],months:[\"January\",\"February\",\"March\",\"April\",\"May\",\"June\",\"July\",\"August\",\"September\",\"October\",\"November\",\"December\"],monthsShort:[\"Jan\",\"Feb\",\"Mar\",\"Apr\",\"May\",\"Jun\",\"Jul\",\"Aug\",\"Sep\",\"Oct\",\"Nov\",\"Dec\"],today:\"Today\",clear:\"Clear\",titleFormat:\"MM yyyy\"}},r={viewModes:[{names:[\"days\",\"month\"],clsName:\"days\",e:\"changeMonth\"},{names:[\"months\",\"year\"],clsName:\"months\",e:\"changeYear\",navStep:1},{names:[\"years\",\"decade\"],clsName:\"years\",e:\"changeDecade\",navStep:10},{names:[\"decades\",\"century\"],clsName:\"decades\",e:\"changeCentury\",navStep:100},{names:[\"centuries\",\"millennium\"],clsName:\"centuries\",e:\"changeMillennium\",navStep:1e3}],validParts:/dd?|DD?|mm?|MM?|yy(?:yy)?/g,nonpunctuation:/[^ -\\/:-@\\u5e74\\u6708\\u65e5\\[-`{-~\\t\\n\\r]+/g,parseFormat:function(a){if(\"function\"==typeof a.toValue&&\"function\"==typeof a.toDisplay)return a;var b=a.replace(this.validParts,\"\\0\").split(\"\\0\"),c=a.match(this.validParts);if(!b||!b.length||!c||0===c.length)throw new Error(\"Invalid date format.\");return{separators:b,parts:c}},parseDate:function(c,e,f,g){function h(a,b){return!0===b&&(b=10),a<100&&(a+=2e3)>(new Date).getFullYear()+b&&(a-=100),a}function i(){var a=this.slice(0,j[n].length),b=j[n].slice(0,a.length);return a.toLowerCase()===b.toLowerCase()}if(!c)return b;if(c instanceof Date)return c;if(\"string\"==typeof e&&(e=r.parseFormat(e)),e.toValue)return e.toValue(c,e,f);var j,l,m,n,o,p={d:\"moveDay\",m:\"moveMonth\",w:\"moveWeek\",y:\"moveYear\"},s={yesterday:\"-1d\",today:\"+0d\",tomorrow:\"+1d\"};if(c in s&&(c=s[c]),/^[\\-+]\\d+[dmwy]([\\s,]+[\\-+]\\d+[dmwy])*$/i.test(c)){for(j=c.match(/([\\-+]\\d+)([dmwy])/gi),c=new Date,n=0;n<j.length;n++)l=j[n].match(/([\\-+]\\d+)([dmwy])/i),m=Number(l[1]),o=p[l[2].toLowerCase()],c=k.prototype[o](c,m);return k.prototype._zero_utc_time(c)}j=c&&c.match(this.nonpunctuation)||[];var t,u,v={},w=[\"yyyy\",\"yy\",\"M\",\"MM\",\"m\",\"mm\",\"d\",\"dd\"],x={yyyy:function(a,b){return a.setUTCFullYear(g?h(b,g):b)},m:function(a,b){if(isNaN(a))return a;for(b-=1;b<0;)b+=12;for(b%=12,a.setUTCMonth(b);a.getUTCMonth()!==b;)a.setUTCDate(a.getUTCDate()-1);return a},d:function(a,b){return a.setUTCDate(b)}};x.yy=x.yyyy,x.M=x.MM=x.mm=x.m,x.dd=x.d,c=d();var y=e.parts.slice();if(j.length!==y.length&&(y=a(y).filter(function(b,c){return-1!==a.inArray(c,w)}).toArray()),j.length===y.length){var z;for(n=0,z=y.length;n<z;n++){if(t=parseInt(j[n],10),l=y[n],isNaN(t))switch(l){case\"MM\":u=a(q[f].months).filter(i),t=a.inArray(u[0],q[f].months)+1;break;case\"M\":u=a(q[f].monthsShort).filter(i),t=a.inArray(u[0],q[f].monthsShort)+1}v[l]=t}var A,B;for(n=0;n<w.length;n++)(B=w[n])in v&&!isNaN(v[B])&&(A=new Date(c),x[B](A,v[B]),isNaN(A)||(c=A))}return c},formatDate:function(b,c,d){if(!b)return\"\";if(\"string\"==typeof c&&(c=r.parseFormat(c)),c.toDisplay)return c.toDisplay(b,c,d);var e={d:b.getUTCDate(),D:q[d].daysShort[b.getUTCDay()],DD:q[d].days[b.getUTCDay()],m:b.getUTCMonth()+1,M:q[d].monthsShort[b.getUTCMonth()],MM:q[d].months[b.getUTCMonth()],yy:b.getUTCFullYear().toString().substring(2),yyyy:b.getUTCFullYear()};e.dd=(e.d<10?\"0\":\"\")+e.d,e.mm=(e.m<10?\"0\":\"\")+e.m,b=[];for(var f=a.extend([],c.separators),g=0,h=c.parts.length;g<=h;g++)f.length&&b.push(f.shift()),b.push(e[c.parts[g]]);return b.join(\"\")},\nheadTemplate:'<thead><tr><th colspan=\"7\" class=\"datepicker-title\"></th></tr><tr><th class=\"prev\">'+o.templates.leftArrow+'</th><th colspan=\"5\" class=\"datepicker-switch\"></th><th class=\"next\">'+o.templates.rightArrow+\"</th></tr></thead>\",contTemplate:'<tbody><tr><td colspan=\"7\"></td></tr></tbody>',footTemplate:'<tfoot><tr><th colspan=\"7\" class=\"today\"></th></tr><tr><th colspan=\"7\" class=\"clear\"></th></tr></tfoot>'};r.template='<div class=\"datepicker\"><div class=\"datepicker-days\"><table class=\"table-condensed\">'+r.headTemplate+\"<tbody></tbody>\"+r.footTemplate+'</table></div><div class=\"datepicker-months\"><table class=\"table-condensed\">'+r.headTemplate+r.contTemplate+r.footTemplate+'</table></div><div class=\"datepicker-years\"><table class=\"table-condensed\">'+r.headTemplate+r.contTemplate+r.footTemplate+'</table></div><div class=\"datepicker-decades\"><table class=\"table-condensed\">'+r.headTemplate+r.contTemplate+r.footTemplate+'</table></div><div class=\"datepicker-centuries\"><table class=\"table-condensed\">'+r.headTemplate+r.contTemplate+r.footTemplate+\"</table></div></div>\",a.fn.datepicker.DPGlobal=r,a.fn.datepicker.noConflict=function(){return a.fn.datepicker=m,this},a.fn.datepicker.version=\"1.9.0\",a.fn.datepicker.deprecated=function(a){var b=window.console;b&&b.warn&&b.warn(\"DEPRECATED: \"+a)},a(document).on(\"focus.datepicker.data-api click.datepicker.data-api\",'[data-provide=\"datepicker\"]',function(b){var c=a(this);c.data(\"datepicker\")||(b.preventDefault(),n.call(c,\"show\"))}),a(function(){n.call(a('[data-provide=\"datepicker-inline\"]'))})});", "\r\n(function ($, undefined) {\r\n    'use strict';\r\n    var defaults = {\r\n        item: 3,\r\n        autoWidth: false,\r\n        slideMove: 1,\r\n        slideMargin: 10,\r\n        addClass: '',\r\n        mode: 'slide',\r\n        useCSS: true,\r\n        cssEasing: 'ease', //'cubic-bezier(0.25, 0, 0.25, 1)',\r\n        easing: 'linear', //'for jquery animation',//\r\n        speed: 400, //ms'\r\n        auto: false,\r\n        loop: false,\r\n        slideEndAnimation: true,\r\n        pause: 2000,\r\n        keyPress: false,\r\n        controls: true,\r\n        prevHtml: '',\r\n        nextHtml: '',\r\n        rtl: false,\r\n        adaptiveHeight: false,\r\n        vertical: false,\r\n        verticalHeight: 500,\r\n        vThumbWidth: 100,\r\n        thumbItem: 10,\r\n        pager: true,\r\n        gallery: false,\r\n        galleryMargin: 5,\r\n        thumbMargin: 5,\r\n        currentPagerPosition: 'middle',\r\n        enableTouch: true,\r\n        enableDrag: true,\r\n        freeMove: true,\r\n        swipeThreshold: 40,\r\n        responsive: [],\r\n        /* jshint ignore:start */\r\n        onBeforeStart: function ($el) { },\r\n        onSliderLoad: function ($el) { },\r\n        onBeforeSlide: function ($el, scene) { },\r\n        onAfterSlide: function ($el, scene) { },\r\n        onBeforeNextSlide: function ($el, scene) { },\r\n        onBeforePrevSlide: function ($el, scene) { }\r\n        /* jshint ignore:end */\r\n    };\r\n    $.fn.lightSlider = function (options) {\r\n        if (this.length === 0) {\r\n            return this;\r\n        }\r\n\r\n        if (this.length > 1) {\r\n            this.each(function () {\r\n                $(this).lightSlider(options);\r\n            });\r\n            return this;\r\n        }\r\n\r\n        var plugin = {},\r\n            settings = $.extend(true, {}, defaults, options),\r\n            settingsTemp = {},\r\n            $el = this;\r\n        plugin.$el = this;\r\n\r\n        if (settings.mode === 'fade') {\r\n            settings.vertical = false;\r\n        }\r\n        var $children = $el.children(),\r\n            windowW = $(window).width(),\r\n            breakpoint = null,\r\n            resposiveObj = null,\r\n            length = 0,\r\n            w = 0,\r\n            on = false,\r\n            elSize = 0,\r\n            $slide = '',\r\n            scene = 0,\r\n            property = (settings.vertical === true) ? 'height' : 'width',\r\n            gutter = (settings.vertical === true) ? 'margin-bottom' : 'margin-right',\r\n            slideValue = 0,\r\n            pagerWidth = 0,\r\n            slideWidth = 0,\r\n            thumbWidth = 0,\r\n            interval = null,\r\n            isTouch = ('ontouchstart' in document.documentElement);\r\n        var refresh = {};\r\n\r\n        refresh.chbreakpoint = function () {\r\n            windowW = $(window).width();\r\n            if (settings.responsive.length) {\r\n                var item;\r\n                if (settings.autoWidth === false) {\r\n                    item = settings.item;\r\n                }\r\n                if (windowW < settings.responsive[0].breakpoint) {\r\n                    for (var i = 0; i < settings.responsive.length; i++) {\r\n                        if (windowW < settings.responsive[i].breakpoint) {\r\n                            breakpoint = settings.responsive[i].breakpoint;\r\n                            resposiveObj = settings.responsive[i];\r\n                        }\r\n                    }\r\n                }\r\n                if (typeof resposiveObj !== 'undefined' && resposiveObj !== null) {\r\n                    for (var j in resposiveObj.settings) {\r\n                        if (resposiveObj.settings.hasOwnProperty(j)) {\r\n                            if (typeof settingsTemp[j] === 'undefined' || settingsTemp[j] === null) {\r\n                                settingsTemp[j] = settings[j];\r\n                            }\r\n                            settings[j] = resposiveObj.settings[j];\r\n                        }\r\n                    }\r\n                }\r\n                if (!$.isEmptyObject(settingsTemp) && windowW > settings.responsive[0].breakpoint) {\r\n                    for (var k in settingsTemp) {\r\n                        if (settingsTemp.hasOwnProperty(k)) {\r\n                            settings[k] = settingsTemp[k];\r\n                        }\r\n                    }\r\n                }\r\n                if (settings.autoWidth === false) {\r\n                    if (slideValue > 0 && slideWidth > 0) {\r\n                        if (item !== settings.item) {\r\n                            scene = Math.round(slideValue / ((slideWidth + settings.slideMargin) * settings.slideMove));\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        };\r\n\r\n        refresh.calSW = function () {\r\n            if (settings.autoWidth === false) {\r\n                slideWidth = (elSize - ((settings.item * (settings.slideMargin)) - settings.slideMargin)) / settings.item;\r\n            }\r\n        };\r\n\r\n        refresh.calWidth = function (cln) {\r\n            var ln = cln === true ? $slide.find('.lslide').length : $children.length;\r\n            if (settings.autoWidth === false) {\r\n                w = ln * (slideWidth + settings.slideMargin);\r\n            } else {\r\n                w = 0;\r\n                for (var i = 0; i < ln; i++) {\r\n                    w += (parseInt($children.eq(i).width()) + settings.slideMargin);\r\n                }\r\n            }\r\n            return w;\r\n        };\r\n        plugin = {\r\n            doCss: function () {\r\n                var support = function () {\r\n                    var transition = ['transition', 'MozTransition', 'WebkitTransition', 'OTransition', 'msTransition', 'KhtmlTransition'];\r\n                    var root = document.documentElement;\r\n                    for (var i = 0; i < transition.length; i++) {\r\n                        if (transition[i] in root.style) {\r\n                            return true;\r\n                        }\r\n                    }\r\n                };\r\n                if (settings.useCSS && support()) {\r\n                    return true;\r\n                }\r\n                return false;\r\n            },\r\n            keyPress: function () {\r\n                if (settings.keyPress) {\r\n                    $(document).on('keyup.lightslider', function (e) {\r\n                        if (!$(':focus').is('input, textarea')) {\r\n                            if (e.preventDefault) {\r\n                                e.preventDefault();\r\n                            } else {\r\n                                e.returnValue = false;\r\n                            }\r\n                            if (e.keyCode === 37) {\r\n                                $el.goToPrevSlide();\r\n                                clearInterval(interval);\r\n                            } else if (e.keyCode === 39) {\r\n                                $el.goToNextSlide();\r\n                                clearInterval(interval);\r\n                            }\r\n                        }\r\n                    });\r\n                }\r\n            },\r\n            controls: function () {\r\n                if (settings.controls) {\r\n                    $el.after('<div class=\"lSAction\"><a class=\"lSPrev\">' + settings.prevHtml + '</a><a class=\"lSNext\">' + settings.nextHtml + '</a></div>');\r\n                    if (!settings.autoWidth) {\r\n                        if (length <= settings.item) {\r\n                            $slide.find('.lSAction').hide();\r\n                        }\r\n                    } else {\r\n                        if (refresh.calWidth(false) < elSize) {\r\n                            $slide.find('.lSAction').hide();\r\n                        }\r\n                    }\r\n                    $slide.find('.lSAction a').on('click', function (e) {\r\n                        if (e.preventDefault) {\r\n                            e.preventDefault();\r\n                        } else {\r\n                            e.returnValue = false;\r\n                        }\r\n                        if ($(this).attr('class') === 'lSPrev') {\r\n                            $el.goToPrevSlide();\r\n                        } else {\r\n                            $el.goToNextSlide();\r\n                        }\r\n                        clearInterval(interval);\r\n                        return false;\r\n                    });\r\n                }\r\n            },\r\n            initialStyle: function () {\r\n                var $this = this;\r\n                if (settings.mode === 'fade') {\r\n                    settings.autoWidth = false;\r\n                    settings.slideEndAnimation = false;\r\n                }\r\n                if (settings.auto) {\r\n                    settings.slideEndAnimation = false;\r\n                }\r\n                if (settings.autoWidth) {\r\n                    settings.slideMove = 1;\r\n                    settings.item = 1;\r\n                }\r\n                if (settings.loop) {\r\n                    settings.slideMove = 1;\r\n                    settings.freeMove = false;\r\n                }\r\n                settings.onBeforeStart.call(this, $el);\r\n                refresh.chbreakpoint();\r\n                $el.addClass('lightSlider').wrap('<div class=\"lSSlideOuter ' + settings.addClass + '\"><div class=\"lSSlideWrapper\"></div></div>');\r\n                $slide = $el.parent('.lSSlideWrapper');\r\n                if (settings.rtl === true) {\r\n                    $slide.parent().addClass('lSrtl');\r\n                }\r\n                if (settings.vertical) {\r\n                    $slide.parent().addClass('vertical');\r\n                    elSize = settings.verticalHeight;\r\n                    $slide.css('height', elSize + 'px');\r\n                } else {\r\n                    elSize = $el.outerWidth();\r\n                }\r\n                $children.addClass('lslide');\r\n                if (settings.loop === true && settings.mode === 'slide') {\r\n                    refresh.calSW();\r\n                    refresh.clone = function () {\r\n                        if (refresh.calWidth(true) > elSize) {\r\n                            /**/\r\n                            var tWr = 0,\r\n                                tI = 0;\r\n                            for (var k = 0; k < $children.length; k++) {\r\n                                tWr += (parseInt($el.find('.lslide').eq(k).width()) + settings.slideMargin);\r\n                                tI++;\r\n                                if (tWr >= (elSize + settings.slideMargin)) {\r\n                                    break;\r\n                                }\r\n                            }\r\n                            var tItem = settings.autoWidth === true ? tI : settings.item;\r\n\r\n                            /**/\r\n                            if (tItem < $el.find('.clone.left').length) {\r\n                                for (var i = 0; i < $el.find('.clone.left').length - tItem; i++) {\r\n                                    $children.eq(i).remove();\r\n                                }\r\n                            }\r\n                            if (tItem < $el.find('.clone.right').length) {\r\n                                for (var j = $children.length - 1; j > ($children.length - 1 - $el.find('.clone.right').length); j--) {\r\n                                    scene--;\r\n                                    $children.eq(j).remove();\r\n                                }\r\n                            }\r\n                            /**/\r\n                            for (var n = $el.find('.clone.right').length; n < tItem; n++) {\r\n                                $el.find('.lslide').eq(n).clone().removeClass('lslide').addClass('clone right').appendTo($el);\r\n                                scene++;\r\n                            }\r\n                            for (var m = $el.find('.lslide').length - $el.find('.clone.left').length; m > ($el.find('.lslide').length - tItem); m--) {\r\n                                $el.find('.lslide').eq(m - 1).clone().removeClass('lslide').addClass('clone left').prependTo($el);\r\n                            }\r\n                            $children = $el.children();\r\n                        } else {\r\n                            if ($children.hasClass('clone')) {\r\n                                $el.find('.clone').remove();\r\n                                $this.move($el, 0);\r\n                            }\r\n                        }\r\n                    };\r\n                    refresh.clone();\r\n                }\r\n                refresh.sSW = function () {\r\n                    length = $children.length;\r\n                    if (settings.rtl === true && settings.vertical === false) {\r\n                        gutter = 'margin-left';\r\n                    }\r\n                    if (settings.autoWidth === false) {\r\n                        $children.css(property, slideWidth + 'px');\r\n                    }\r\n                    $children.css(gutter, settings.slideMargin + 'px');\r\n                    w = refresh.calWidth(false);\r\n                    $el.css(property, w + 'px');\r\n                    if (settings.loop === true && settings.mode === 'slide') {\r\n                        if (on === false) {\r\n                            scene = $el.find('.clone.left').length;\r\n                        }\r\n                    }\r\n                };\r\n                refresh.calL = function () {\r\n                    $children = $el.children();\r\n                    length = $children.length;\r\n                };\r\n                if (this.doCss()) {\r\n                    $slide.addClass('usingCss');\r\n                }\r\n                refresh.calL();\r\n                if (settings.mode === 'slide') {\r\n                    refresh.calSW();\r\n                    refresh.sSW();\r\n                    if (settings.loop === true) {\r\n                        slideValue = $this.slideValue();\r\n                        this.move($el, slideValue);\r\n                    }\r\n                    if (settings.vertical === false) {\r\n                        this.setHeight($el, false);\r\n                    }\r\n\r\n                } else {\r\n                    this.setHeight($el, true);\r\n                    $el.addClass('lSFade');\r\n                    if (!this.doCss()) {\r\n                        $children.fadeOut(0);\r\n                        $children.eq(scene).fadeIn(0);\r\n                    }\r\n                }\r\n                if (settings.loop === true && settings.mode === 'slide') {\r\n                    $children.eq(scene).addClass('active');\r\n                } else {\r\n                    $children.first().addClass('active');\r\n                }\r\n            },\r\n            pager: function () {\r\n                var $this = this;\r\n                refresh.createPager = function () {\r\n                    thumbWidth = (elSize - ((settings.thumbItem * (settings.thumbMargin)) - settings.thumbMargin)) / settings.thumbItem;\r\n                    var $children = $slide.find('.lslide');\r\n                    var length = $slide.find('.lslide').length;\r\n                    var i = 0,\r\n                        pagers = '',\r\n                        v = 0;\r\n                    for (i = 0; i < length; i++) {\r\n                        if (settings.mode === 'slide') {\r\n                            // calculate scene * slide value\r\n                            if (!settings.autoWidth) {\r\n                                v = i * ((slideWidth + settings.slideMargin) * settings.slideMove);\r\n                            } else {\r\n                                v += ((parseInt($children.eq(i).width()) + settings.slideMargin) * settings.slideMove);\r\n                            }\r\n                        }\r\n                        var thumb = $children.eq(i * settings.slideMove).attr('data-thumb');\r\n                        if (settings.gallery === true) {\r\n                            pagers += '<li style=\"width:100%;' + property + ':' + thumbWidth + 'px;' + gutter + ':' + settings.thumbMargin + 'px\"><a href=\"#\"><img src=\"' + thumb + '\" /></a></li>';\r\n                        } else {\r\n                            pagers += '<li><a href=\"#\">' + (i + 1) + '</a></li>';\r\n                        }\r\n                        if (settings.mode === 'slide') {\r\n                            if ((v) >= w - elSize - settings.slideMargin) {\r\n                                i = i + 1;\r\n                                var minPgr = 2;\r\n                                if (settings.autoWidth) {\r\n                                    pagers += '<li><a href=\"#\">' + (i + 1) + '</a></li>';\r\n                                    minPgr = 1;\r\n                                }\r\n                                if (i < minPgr) {\r\n                                    pagers = null;\r\n                                    $slide.parent().addClass('noPager');\r\n                                } else {\r\n                                    $slide.parent().removeClass('noPager');\r\n                                }\r\n                                break;\r\n                            }\r\n                        }\r\n                    }\r\n                    var $cSouter = $slide.parent();\r\n                    $cSouter.find('.lSPager').html(pagers);\r\n                    if (settings.gallery === true) {\r\n                        if (settings.vertical === true) {\r\n                            // set Gallery thumbnail width\r\n                            $cSouter.find('.lSPager').css('width', settings.vThumbWidth + 'px');\r\n                        }\r\n                        pagerWidth = (i * (settings.thumbMargin + thumbWidth)) + 0.5;\r\n                        $cSouter.find('.lSPager').css({\r\n                            property: pagerWidth + 'px',\r\n                            'transition-duration': settings.speed + 'ms'\r\n                        });\r\n                        if (settings.vertical === true) {\r\n                            $slide.parent().css('padding-right', (settings.vThumbWidth + settings.galleryMargin) + 'px');\r\n                        }\r\n                        $cSouter.find('.lSPager').css(property, pagerWidth + 'px');\r\n                    }\r\n                    var $pager = $cSouter.find('.lSPager').find('li');\r\n                    $pager.first().addClass('active');\r\n                    $pager.on('click', function () {\r\n                        if (settings.loop === true && settings.mode === 'slide') {\r\n                            scene = scene + ($pager.index(this) - $cSouter.find('.lSPager').find('li.active').index());\r\n                        } else {\r\n                            scene = $pager.index(this);\r\n                        }\r\n                        $el.mode(false);\r\n                        if (settings.gallery === true) {\r\n                            $this.slideThumb();\r\n                        }\r\n                        clearInterval(interval);\r\n                        return false;\r\n                    });\r\n                };\r\n                if (settings.pager) {\r\n                    var cl = 'lSpg';\r\n                    if (settings.gallery) {\r\n                        cl = 'lSGallery';\r\n                    }\r\n                    $slide.after('<ul class=\"lSPager ' + cl + '\"></ul>');\r\n                    var gMargin = (settings.vertical) ? 'margin-left' : 'margin-top';\r\n                    $slide.parent().find('.lSPager').css(gMargin, settings.galleryMargin + 'px');\r\n                    refresh.createPager();\r\n                }\r\n\r\n                setTimeout(function () {\r\n                    refresh.init();\r\n                }, 0);\r\n            },\r\n            setHeight: function (ob, fade) {\r\n                var obj = null,\r\n                    $this = this;\r\n                if (settings.loop) {\r\n                    obj = ob.children('.lslide ').first();\r\n                } else {\r\n                    obj = ob.children().first();\r\n                }\r\n                var setCss = function () {\r\n                    var tH = obj.outerHeight(),\r\n                        tP = 0,\r\n                        tHT = tH;\r\n                    if (fade) {\r\n                        tH = 0;\r\n                        tP = ((tHT) * 100) / elSize;\r\n                    }\r\n                    ob.css({\r\n                        'height': tH + 'px',\r\n                        'padding-bottom': tP + '%'\r\n                    });\r\n                };\r\n                setCss();\r\n                if (obj.find('img').length) {\r\n                    if (obj.find('img')[0].complete) {\r\n                        setCss();\r\n                        if (!interval) {\r\n                            $this.auto();\r\n                        }\r\n                    }\r\n                } else {\r\n                    if (!interval) {\r\n                        $this.auto();\r\n                    }\r\n                }\r\n            },\r\n            active: function (ob, t) {\r\n                if (this.doCss() && settings.mode === 'fade') {\r\n                    $slide.addClass('on');\r\n                }\r\n                var sc = 0;\r\n                if (scene * settings.slideMove < length) {\r\n                    ob.removeClass('active');\r\n                    if (!this.doCss() && settings.mode === 'fade' && t === false) {\r\n                        ob.fadeOut(settings.speed);\r\n                    }\r\n                    if (t === true) {\r\n                        sc = scene;\r\n                    } else {\r\n                        sc = scene * settings.slideMove;\r\n                    }\r\n                    //t === true ? sc = scene : sc = scene * settings.slideMove;\r\n                    var l, nl;\r\n                    if (t === true) {\r\n                        l = ob.length;\r\n                        nl = l - 1;\r\n                        if (sc + 1 >= l) {\r\n                            sc = nl;\r\n                        }\r\n                    }\r\n                    if (settings.loop === true && settings.mode === 'slide') {\r\n                        //t === true ? sc = scene - $el.find('.clone.left').length : sc = scene * settings.slideMove;\r\n                        if (t === true) {\r\n                            sc = scene - $el.find('.clone.left').length;\r\n                        } else {\r\n                            sc = scene * settings.slideMove;\r\n                        }\r\n                        if (t === true) {\r\n                            l = ob.length;\r\n                            nl = l - 1;\r\n                            if (sc + 1 === l) {\r\n                                sc = nl;\r\n                            } else if (sc + 1 > l) {\r\n                                sc = 0;\r\n                            }\r\n                        }\r\n                    }\r\n\r\n                    if (!this.doCss() && settings.mode === 'fade' && t === false) {\r\n                        ob.eq(sc).fadeIn(settings.speed);\r\n                    }\r\n                    ob.eq(sc).addClass('active');\r\n                } else {\r\n                    ob.removeClass('active');\r\n                    ob.eq(ob.length - 1).addClass('active');\r\n                    if (!this.doCss() && settings.mode === 'fade' && t === false) {\r\n                        ob.fadeOut(settings.speed);\r\n                        ob.eq(sc).fadeIn(settings.speed);\r\n                    }\r\n                }\r\n            },\r\n            move: function (ob, v) {\r\n                if (settings.rtl === true) {\r\n                    v = -v;\r\n                }\r\n                if (this.doCss()) {\r\n                    if (settings.vertical === true) {\r\n                        ob.css({\r\n                            'transform': 'translate3d(0px, ' + (-v) + 'px, 0px)',\r\n                            '-webkit-transform': 'translate3d(0px, ' + (-v) + 'px, 0px)'\r\n                        });\r\n                    } else {\r\n                        ob.css({\r\n                            'transform': 'translate3d(' + (-v) + 'px, 0px, 0px)',\r\n                            '-webkit-transform': 'translate3d(' + (-v) + 'px, 0px, 0px)',\r\n                        });\r\n                    }\r\n                } else {\r\n                    if (settings.vertical === true) {\r\n                        ob.css('position', 'relative').animate({\r\n                            top: -v + 'px'\r\n                        }, settings.speed, settings.easing);\r\n                    } else {\r\n                        ob.css('position', 'relative').animate({\r\n                            left: -v + 'px'\r\n                        }, settings.speed, settings.easing);\r\n                    }\r\n                }\r\n                var $thumb = $slide.parent().find('.lSPager').find('li');\r\n                this.active($thumb, true);\r\n            },\r\n            fade: function () {\r\n                this.active($children, false);\r\n                var $thumb = $slide.parent().find('.lSPager').find('li');\r\n                this.active($thumb, true);\r\n            },\r\n            slide: function () {\r\n                var $this = this;\r\n                refresh.calSlide = function () {\r\n                    if (w > elSize) {\r\n                        slideValue = $this.slideValue();\r\n                        $this.active($children, false);\r\n                        if ((slideValue) > w - elSize - settings.slideMargin) {\r\n                            slideValue = w - elSize - settings.slideMargin;\r\n                        } else if (slideValue < 0) {\r\n                            slideValue = 0;\r\n                        }\r\n                        $this.move($el, slideValue);\r\n                        if (settings.loop === true && settings.mode === 'slide') {\r\n                            if (scene >= (length - ($el.find('.clone.left').length / settings.slideMove))) {\r\n                                $this.resetSlide($el.find('.clone.left').length);\r\n                            }\r\n                            if (scene === 0) {\r\n                                $this.resetSlide($slide.find('.lslide').length);\r\n                            }\r\n                        }\r\n                    }\r\n                };\r\n                refresh.calSlide();\r\n            },\r\n            resetSlide: function (s) {\r\n                var $this = this;\r\n                $slide.find('.lSAction a').addClass('disabled');\r\n                setTimeout(function () {\r\n                    scene = s;\r\n                    $slide.css('transition-duration', '0ms');\r\n                    slideValue = $this.slideValue();\r\n                    $this.active($children, false);\r\n                    plugin.move($el, slideValue);\r\n                    setTimeout(function () {\r\n                        $slide.css('transition-duration', settings.speed + 'ms');\r\n                        $slide.find('.lSAction a').removeClass('disabled');\r\n                    }, 50);\r\n                }, settings.speed + 100);\r\n            },\r\n            slideValue: function () {\r\n                var _sV = 0;\r\n                if (settings.autoWidth === false) {\r\n                    _sV = scene * ((slideWidth + settings.slideMargin) * settings.slideMove);\r\n                } else {\r\n                    _sV = 0;\r\n                    for (var i = 0; i < scene; i++) {\r\n                        _sV += (parseInt($children.eq(i).width()) + settings.slideMargin);\r\n                    }\r\n                }\r\n                return _sV;\r\n            },\r\n            slideThumb: function () {\r\n                var position;\r\n                switch (settings.currentPagerPosition) {\r\n                    case 'left':\r\n                        position = 0;\r\n                        break;\r\n                    case 'middle':\r\n                        position = (elSize / 2) - (thumbWidth / 2);\r\n                        break;\r\n                    case 'right':\r\n                        position = elSize - thumbWidth;\r\n                }\r\n                var sc = scene - $el.find('.clone.left').length;\r\n                var $pager = $slide.parent().find('.lSPager');\r\n                if (settings.mode === 'slide' && settings.loop === true) {\r\n                    if (sc >= $pager.children().length) {\r\n                        sc = 0;\r\n                    } else if (sc < 0) {\r\n                        sc = $pager.children().length;\r\n                    }\r\n                }\r\n                var thumbSlide = sc * ((thumbWidth + settings.thumbMargin)) - (position);\r\n                if ((thumbSlide + elSize) > pagerWidth) {\r\n                    thumbSlide = pagerWidth - elSize - settings.thumbMargin;\r\n                }\r\n                if (thumbSlide < 0) {\r\n                    thumbSlide = 0;\r\n                }\r\n                this.move($pager, thumbSlide);\r\n            },\r\n            auto: function () {\r\n                if (settings.auto) {\r\n                    interval = setInterval(function () {\r\n                        $el.goToNextSlide();\r\n                    }, settings.pause);\r\n                }\r\n            },\r\n\r\n            touchMove: function (endCoords, startCoords) {\r\n                $slide.css('transition-duration', '0ms');\r\n                if (settings.mode === 'slide') {\r\n                    var distance = endCoords - startCoords;\r\n                    var swipeVal = slideValue - distance;\r\n                    if ((swipeVal) >= w - elSize - settings.slideMargin) {\r\n                        if (settings.freeMove === false) {\r\n                            swipeVal = w - elSize - settings.slideMargin;\r\n                        } else {\r\n                            var swipeValT = w - elSize - settings.slideMargin;\r\n                            swipeVal = swipeValT + ((swipeVal - swipeValT) / 5);\r\n\r\n                        }\r\n                    } else if (swipeVal < 0) {\r\n                        if (settings.freeMove === false) {\r\n                            swipeVal = 0;\r\n                        } else {\r\n                            swipeVal = swipeVal / 5;\r\n                        }\r\n                    }\r\n                    this.move($el, swipeVal);\r\n                }\r\n            },\r\n\r\n            touchEnd: function (distance) {\r\n                $slide.css('transition-duration', settings.speed + 'ms');\r\n                clearInterval(interval);\r\n                if (settings.mode === 'slide') {\r\n                    var mxVal = false;\r\n                    var _next = true;\r\n                    slideValue = slideValue - distance;\r\n                    if ((slideValue) > w - elSize - settings.slideMargin) {\r\n                        slideValue = w - elSize - settings.slideMargin;\r\n                        if (settings.autoWidth === false) {\r\n                            mxVal = true;\r\n                        }\r\n                    } else if (slideValue < 0) {\r\n                        slideValue = 0;\r\n                    }\r\n                    var gC = function (next) {\r\n                        var ad = 0;\r\n                        if (!mxVal) {\r\n                            if (next) {\r\n                                ad = 1;\r\n                            }\r\n                        }\r\n                        if (!settings.autoWidth) {\r\n                            var num = slideValue / ((slideWidth + settings.slideMargin) * settings.slideMove);\r\n                            scene = parseInt(num) + ad;\r\n                            if (slideValue >= (w - elSize - settings.slideMargin)) {\r\n                                if (num % 1 !== 0) {\r\n                                    scene++;\r\n                                }\r\n                            }\r\n                        } else {\r\n                            var tW = 0;\r\n                            for (var i = 0; i < $children.length; i++) {\r\n                                tW += (parseInt($children.eq(i).width()) + settings.slideMargin);\r\n                                scene = i + ad;\r\n                                if (tW >= slideValue) {\r\n                                    break;\r\n                                }\r\n                            }\r\n                        }\r\n                    };\r\n                    if (distance >= settings.swipeThreshold) {\r\n                        gC(false);\r\n                        _next = false;\r\n                    } else if (distance <= -settings.swipeThreshold) {\r\n                        gC(true);\r\n                        _next = false;\r\n                    }\r\n                    $el.mode(_next);\r\n                    this.slideThumb();\r\n                } else {\r\n                    if (distance >= settings.swipeThreshold) {\r\n                        $el.goToPrevSlide();\r\n                    } else if (distance <= -settings.swipeThreshold) {\r\n                        $el.goToNextSlide();\r\n                    }\r\n                }\r\n            },\r\n\r\n\r\n\r\n            enableDrag: function () {\r\n                var $this = this;\r\n                if (!isTouch) {\r\n                    var startCoords = 0,\r\n                        endCoords = 0,\r\n                        isDraging = false;\r\n                    $slide.find('.lightSlider').addClass('lsGrab');\r\n                    $slide.on('mousedown', function (e) {\r\n                        if (w < elSize) {\r\n                            if (w !== 0) {\r\n                                return false;\r\n                            }\r\n                        }\r\n                        if ($(e.target).attr('class') !== ('lSPrev') && $(e.target).attr('class') !== ('lSNext')) {\r\n                            startCoords = (settings.vertical === true) ? e.pageY : e.pageX;\r\n                            isDraging = true;\r\n                            if (e.preventDefault) {\r\n                                e.preventDefault();\r\n                            } else {\r\n                                e.returnValue = false;\r\n                            }\r\n                            // ** Fix for webkit cursor issue https://code.google.com/p/chromium/issues/detail?id=26723\r\n                            $slide.scrollLeft += 1;\r\n                            $slide.scrollLeft -= 1;\r\n                            // *\r\n                            $slide.find('.lightSlider').removeClass('lsGrab').addClass('lsGrabbing');\r\n                            clearInterval(interval);\r\n                        }\r\n                    });\r\n                    $(window).on('mousemove', function (e) {\r\n                        if (isDraging) {\r\n                            endCoords = (settings.vertical === true) ? e.pageY : e.pageX;\r\n                            $this.touchMove(endCoords, startCoords);\r\n                        }\r\n                    });\r\n                    $(window).on('mouseup', function (e) {\r\n                        if (isDraging) {\r\n                            $slide.find('.lightSlider').removeClass('lsGrabbing').addClass('lsGrab');\r\n                            isDraging = false;\r\n                            endCoords = (settings.vertical === true) ? e.pageY : e.pageX;\r\n                            var distance = endCoords - startCoords;\r\n                            if (Math.abs(distance) >= settings.swipeThreshold) {\r\n                                $(window).on('click.ls', function (e) {\r\n                                    if (e.preventDefault) {\r\n                                        e.preventDefault();\r\n                                    } else {\r\n                                        e.returnValue = false;\r\n                                    }\r\n                                    e.stopImmediatePropagation();\r\n                                    e.stopPropagation();\r\n                                    $(window).off('click.ls');\r\n                                });\r\n                            }\r\n\r\n                            $this.touchEnd(distance);\r\n\r\n                        }\r\n                    });\r\n                }\r\n            },\r\n\r\n\r\n\r\n\r\n            enableTouch: function () {\r\n                var $this = this;\r\n                if (isTouch) {\r\n                    var startCoords = {},\r\n                        endCoords = {};\r\n                    $slide.on('touchstart', function (e) {\r\n                        endCoords = e.originalEvent.targetTouches[0];\r\n                        startCoords.pageX = e.originalEvent.targetTouches[0].pageX;\r\n                        startCoords.pageY = e.originalEvent.targetTouches[0].pageY;\r\n                        clearInterval(interval);\r\n                    });\r\n                    $slide.on('touchmove', function (e) {\r\n                        if (w < elSize) {\r\n                            if (w !== 0) {\r\n                                return false;\r\n                            }\r\n                        }\r\n                        var orig = e.originalEvent;\r\n                        endCoords = orig.targetTouches[0];\r\n                        var xMovement = Math.abs(endCoords.pageX - startCoords.pageX);\r\n                        var yMovement = Math.abs(endCoords.pageY - startCoords.pageY);\r\n                        if (settings.vertical === true) {\r\n                            if ((yMovement * 3) > xMovement) {\r\n                                e.preventDefault();\r\n                            }\r\n                            $this.touchMove(endCoords.pageY, startCoords.pageY);\r\n                        } else {\r\n                            if ((xMovement * 3) > yMovement) {\r\n                                e.preventDefault();\r\n                            }\r\n                            $this.touchMove(endCoords.pageX, startCoords.pageX);\r\n                        }\r\n\r\n                    });\r\n                    $slide.on('touchend', function () {\r\n                        if (w < elSize) {\r\n                            if (w !== 0) {\r\n                                return false;\r\n                            }\r\n                        }\r\n                        var distance;\r\n                        if (settings.vertical === true) {\r\n                            distance = endCoords.pageY - startCoords.pageY;\r\n                        } else {\r\n                            distance = endCoords.pageX - startCoords.pageX;\r\n                        }\r\n                        $this.touchEnd(distance);\r\n                    });\r\n                }\r\n            },\r\n            build: function () {\r\n                var $this = this;\r\n                $this.initialStyle();\r\n                if (this.doCss()) {\r\n\r\n                    if (settings.enableTouch === true) {\r\n                        $this.enableTouch();\r\n                    }\r\n                    if (settings.enableDrag === true) {\r\n                        $this.enableDrag();\r\n                    }\r\n                }\r\n                $this.pager();\r\n                $this.controls();\r\n                $this.keyPress();\r\n            }\r\n        };\r\n        plugin.build();\r\n        refresh.init = function () {\r\n            refresh.chbreakpoint();\r\n            if (settings.vertical === true) {\r\n                if (settings.item > 1) {\r\n                    elSize = settings.verticalHeight;\r\n                } else {\r\n                    elSize = $children.outerHeight();\r\n                }\r\n                $slide.css('height', elSize + 'px');\r\n            } else {\r\n                elSize = $slide.outerWidth();\r\n            }\r\n            if (settings.loop === true && settings.mode === 'slide') {\r\n                refresh.clone();\r\n            }\r\n            refresh.calL();\r\n            if (settings.mode === 'slide') {\r\n                $el.removeClass('lSSlide');\r\n            }\r\n            if (settings.mode === 'slide') {\r\n                refresh.calSW();\r\n                refresh.sSW();\r\n            }\r\n            setTimeout(function () {\r\n                if (settings.mode === 'slide') {\r\n                    $el.addClass('lSSlide');\r\n                }\r\n            }, 1000);\r\n            if (settings.pager) {\r\n                refresh.createPager();\r\n            }\r\n            if (settings.adaptiveHeight === true && settings.vertical === false) {\r\n                $el.css('height', $children.eq(scene).outerHeight(true));\r\n            }\r\n            if (settings.adaptiveHeight === false) {\r\n                if (settings.mode === 'slide') {\r\n                    if (settings.vertical === false) {\r\n                        plugin.setHeight($el, false);\r\n                    } else {\r\n                        plugin.auto();\r\n                    }\r\n                } else {\r\n                    plugin.setHeight($el, true);\r\n                }\r\n            }\r\n            if (settings.gallery === true) {\r\n                plugin.slideThumb();\r\n            }\r\n            if (settings.mode === 'slide') {\r\n                plugin.slide();\r\n            }\r\n            if (settings.autoWidth === false) {\r\n                if ($children.length <= settings.item) {\r\n                    $slide.find('.lSAction').hide();\r\n                } else {\r\n                    $slide.find('.lSAction').show();\r\n                }\r\n            } else {\r\n                if ((refresh.calWidth(false) < elSize) && (w !== 0)) {\r\n                    $slide.find('.lSAction').hide();\r\n                } else {\r\n                    $slide.find('.lSAction').show();\r\n                }\r\n            }\r\n        };\r\n        $el.goToPrevSlide = function () {\r\n            if (scene > 0) {\r\n                settings.onBeforePrevSlide.call(this, $el, scene);\r\n                scene--;\r\n                $el.mode(false);\r\n                if (settings.gallery === true) {\r\n                    plugin.slideThumb();\r\n                }\r\n            } else {\r\n                if (settings.loop === true) {\r\n                    settings.onBeforePrevSlide.call(this, $el, scene);\r\n                    if (settings.mode === 'fade') {\r\n                        var l = (length - 1);\r\n                        scene = parseInt(l / settings.slideMove);\r\n                    }\r\n                    $el.mode(false);\r\n                    if (settings.gallery === true) {\r\n                        plugin.slideThumb();\r\n                    }\r\n                } else if (settings.slideEndAnimation === true) {\r\n                    $el.addClass('leftEnd');\r\n                    setTimeout(function () {\r\n                        $el.removeClass('leftEnd');\r\n                    }, 400);\r\n                }\r\n            }\r\n        };\r\n        $el.goToNextSlide = function () {\r\n            var nextI = true;\r\n            if (settings.mode === 'slide') {\r\n                var _slideValue = plugin.slideValue();\r\n                nextI = _slideValue < w - elSize - settings.slideMargin;\r\n            }\r\n            if (((scene * settings.slideMove) < length - settings.slideMove) && nextI) {\r\n                settings.onBeforeNextSlide.call(this, $el, scene);\r\n                scene++;\r\n                $el.mode(false);\r\n                if (settings.gallery === true) {\r\n                    plugin.slideThumb();\r\n                }\r\n            } else {\r\n                if (settings.loop === true) {\r\n                    settings.onBeforeNextSlide.call(this, $el, scene);\r\n                    scene = 0;\r\n                    $el.mode(false);\r\n                    if (settings.gallery === true) {\r\n                        plugin.slideThumb();\r\n                    }\r\n                } else if (settings.slideEndAnimation === true) {\r\n                    $el.addClass('rightEnd');\r\n                    setTimeout(function () {\r\n                        $el.removeClass('rightEnd');\r\n                    }, 400);\r\n                }\r\n            }\r\n        };\r\n        $el.mode = function (_touch) {\r\n            if (settings.adaptiveHeight === true && settings.vertical === false) {\r\n                $el.css('height', $children.eq(scene).outerHeight(true));\r\n            }\r\n            if (on === false) {\r\n                if (settings.mode === 'slide') {\r\n                    if (plugin.doCss()) {\r\n                        $el.addClass('lSSlide');\r\n                        if (settings.speed !== '') {\r\n                            $slide.css('transition-duration', settings.speed + 'ms');\r\n                        }\r\n                        if (settings.cssEasing !== '') {\r\n                            $slide.css('transition-timing-function', settings.cssEasing);\r\n                        }\r\n                    }\r\n                } else {\r\n                    if (plugin.doCss()) {\r\n                        if (settings.speed !== '') {\r\n                            $el.css('transition-duration', settings.speed + 'ms');\r\n                        }\r\n                        if (settings.cssEasing !== '') {\r\n                            $el.css('transition-timing-function', settings.cssEasing);\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            if (!_touch) {\r\n                settings.onBeforeSlide.call(this, $el, scene);\r\n            }\r\n            if (settings.mode === 'slide') {\r\n                plugin.slide();\r\n            } else {\r\n                plugin.fade();\r\n            }\r\n            setTimeout(function () {\r\n                if (!_touch) {\r\n                    settings.onAfterSlide.call(this, $el, scene);\r\n                }\r\n            }, settings.speed);\r\n            on = true;\r\n        };\r\n        $el.play = function () {\r\n            clearInterval(interval);\r\n            $el.goToNextSlide();\r\n            interval = setInterval(function () {\r\n                $el.goToNextSlide();\r\n            }, settings.pause);\r\n        };\r\n        $el.pause = function () {\r\n            clearInterval(interval);\r\n        };\r\n        $el.refresh = function () {\r\n            refresh.init();\r\n        };\r\n        $el.getCurrentSlideCount = function () {\r\n            var sc = scene;\r\n            if (settings.loop) {\r\n                var ln = $slide.find('.lslide').length,\r\n                    cl = $el.find('.clone.left').length;\r\n                if (scene <= cl - 1) {\r\n                    sc = ln + (scene - cl);\r\n                } else if (scene >= (ln + cl)) {\r\n                    sc = scene - ln - cl;\r\n                } else {\r\n                    sc = scene - cl;\r\n                }\r\n            }\r\n            return sc + 1;\r\n        };\r\n        $el.getTotalSlideCount = function () {\r\n            return $slide.find('.lslide').length;\r\n        };\r\n        $el.goToSlide = function (s) {\r\n            if (settings.loop) {\r\n                scene = (s + $el.find('.clone.left').length - 1);\r\n            } else {\r\n                scene = s;\r\n            }\r\n            $el.mode(false);\r\n            if (settings.gallery === true) {\r\n                plugin.slideThumb();\r\n            }\r\n        };\r\n        setTimeout(function () {\r\n            settings.onSliderLoad.call(this, $el);\r\n        }, 10);\r\n        $(window).on('resize orientationchange', function (e) {\r\n            setTimeout(function () {\r\n                if (e.preventDefault) {\r\n                    e.preventDefault();\r\n                } else {\r\n                    e.returnValue = false;\r\n                }\r\n                refresh.init();\r\n            }, 200);\r\n        });\r\n        return this;\r\n    };\r\n}(jQuery));", "$(document).ready(function () {\n\tsetTimeout(function () {\n\t\tconsole.log(\"Light Slider\");\n\t\t$('.image-gallery-cls').lightSlider({\n\t\t\tgallery: true,\n\t\t\titem: 1,\n\t\t\tthumbItem: 4,\n\t\t\tslideMargin: 0,\n\t\t\tspeed: 500,\n\t\t\tauto: true,\n\t\t\tloop: true,\n\t\t\tonSliderLoad: function () {\n\t\t\t\t$('.image-gallery-cls').removeClass('cS-hidden');\n\t\t\t}\n\n\t\t});\n\t}, 1000);\n\n\t$(\"#room_form\").submit(function (e) {\n\n\t\tvar myform = document.getElementById('room_form');\n\t\tvar inputTags = myform.getElementsByTagName('input');\n\t\tvar select = myform.getElementsByTagName('select');\n\n\t\tvar checkboxCount = 0;\n\t\tvar flag = false;\n\t\tvar select_adult = false;\n\t\tvar select_NoOfRooms=false;\n\n\t\tfor (var i = 0, length = inputTags.length; i < length; i++) {\n//\t\t\tconsole.log(\"inputTags[i].type:::::::::::::\", inputTags[i].type)\n\t\t\tif (inputTags[i].type == 'checkbox') {\n\t\t\t\tvar cb = $(inputTags[i]);\n                console.log('------------ ,', cb.is(':checked'));\n\t\t\t\tif (cb.is(':checked')) {\n                    var selectEl = $(\"select[name='adult_\"+cb.attr('id')+\"']\");\n\t\t\t\t    var selectNoOfRooms = $(\"select[name='no_room_\"+cb.attr('id')+\"']\");\n\t\t\t\t    if (parseInt(selectEl.find(':selected').val()) == 0) {\n                        select_adult = true;\n                    }\n                    if(parseInt(selectNoOfRooms.find(':selected').val()) == 0){\n                        select_NoOfRooms = true\n                    }\n\t\t\t\t    flag = true;\n\t\t\t\t}else{\n\t\t\t\t    var selectEl = $(\"select[name='adult_\"+cb.attr('id')+\"']\");\n\t\t\t\t    var selectNoOfRooms = $(\"select[name='no_room_\"+cb.attr('id')+\"']\");\n\t\t\t\t    console.log('------------ ,',parseInt(selectEl.find(':selected').val()),selectNoOfRooms.find(':selected').val(), cb.is(':checked'));\n\t\t\t\t    if ((parseInt(selectEl.find(':selected').val()) > 0) || (parseInt(selectNoOfRooms.find(':selected').val()) > 0)) {\n                        cb.attr('checked',true);\n                    }\n\t\t\t\t}\n\t\t\t\tcheckboxCount++;\n\t\t\t}\n\t\t}\n\n        if(select_NoOfRooms == true){\n            alert('Please Select Atleast One room!');\n\t\t    return false;\n        }\n\n        if(select_adult == true){\n\t\t    alert('Please Select Atleast One adult!');\n\t\t    return false;\n        }\n\n\t\tif (flag == false) {\n\t\t\talert(\"Please Select Atleast One Room!\");\n\t\t\te.preventDefault();\n\n\t\t}\n\t});\n});\n\nfunction onchange_tax(val) {\n\ttot = parseFloat($(\"#total\").val()) + parseFloat($(\"#tax\").val())\n\t$(\"#grand_total\").val(tot)\n}\n", "$(document).ready(function() {\n    $.urlParam = function(name) {\n        console.log(name);\n        var results = new RegExp('[/?&]' + name + '=([^&#]*)').exec(window.location.href);\n        console.log(\"Results\", results)\n        return results;\n    }\n\n    var from_date = $.urlParam('from_date');\n    if (from_date === null) {\n        console.log(\"###############\", from_date)\n        from_date = '0'\n    } else {\n        type = typeof(from_date);\n        console.log(type)\n        from_date = decodeURIComponent(from_date[1])\n    }\n    console.log(\"After IFFFFFFFFFFFF==>>\", from_date)\n    $.urlParam('to_date');\n    var minDate = new Date();\n    $('#datePicker').datepicker({\n        format: 'mm/dd/yyyy',\n        minDate: minDate,\n        setDate: new Date(),\n        \"autoclose\": true,\n    }).on('changeDate', function(e) {});\n\n    var checkin = $('#dpd1').datepicker({\n        startDate: new Date(),\n\n    }).on('changeDate', function(ev) {\n        var to_date = $.urlParam('to_date');\n        if (to_date === null) {\n            var date2 = $('#dpd1').datepicker('getDate', '+1d');\n            date2.setDate(date2.getDate() + 1);\n        } else {\n            var date2 = decodeURIComponent(to_date[1])\n        }\n        var checkout = $('#dpd2').datepicker({\n            startDate: date2\n        }).datepicker('setDate', date2);\n    }).datepicker('setDate', from_date);\n\n});", "odoo.define('hotel_online.validate', function (require) {\n\t\"use strict\";\n\t//\n\tvar ajax = require('web.ajax');\n\tvar Dialog = require('web.Dialog');\n\tvar core = require('web.core');\n\tvar rpc = require(\"web.rpc\");\n\tvar _t = core._t;\n\n\t$(document).ready(function () {\n\t\tif ($(\".checkout_autoformat\").length) {\n\t\t\t$('.oe_website_sale').on('change', \"select[name='country_id']\", function () {\n\t\t\t\tsetTimeout(function () {\n\t\t\t\t\t//\t            \talert(\"Comingggggg\");\n\t\t\t\t\tif ($(\"#country_id\").val()) {\n\t\t\t\t\t\tajax.jsonRpc(\"/shop/country_infos/\" + $(\"#country_id\").val(),\n\t\t\t\t\t\t\t'call', {\n\t\t\t\t\t\t\tmode: 'shipping',\n\t\t\t\t\t\t}).then(\n\t\t\t\t\t\t\tfunction (data) {\n\t\t\t\t\t\t\t\t// placeholder phone_code\n\t\t\t\t\t\t\t\tvar selectStates = $(\"select[name='state_id']\");\n\n\t\t\t\t\t\t\t\t// dont reload state at first loading (done in qweb)\n\t\t\t\t\t\t\t\tif (selectStates.data('init') === 0 || selectStates.find('option').length === 1) {\n\t\t\t\t\t\t\t\t\tif (data.states.length) {\n\t\t\t\t\t\t\t\t\t\tselectStates.html('');\n\t\t\t\t\t\t\t\t\t\t_.each(data.states, function (x) {\n\t\t\t\t\t\t\t\t\t\t\tvar opt = $('<option>').text(x[1])\n\t\t\t\t\t\t\t\t\t\t\t\t.attr('value', x[0])\n\t\t\t\t\t\t\t\t\t\t\t\t.attr('data-code', x[2]);\n\t\t\t\t\t\t\t\t\t\t\tselectStates.append(opt);\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tselectStates.parent('div').show();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\t\tselectStates.val('').parent('div').hide();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tselectStates.data('init', 0);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\tselectStates.data('init', 0);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// manage fields order / visibility\n\t\t\t\t\t\t\t\tif (data.fields) {\n\t\t\t\t\t\t\t\t\tif ($.inArray('zip', data.fields) > $.inArray('city', data.fields)) {\n\t\t\t\t\t\t\t\t\t\t$(\".div_zip\").before($(\".div_city\"));\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\t\t$(\".div_zip\").after($(\".div_city\"));\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tvar all_fields = [\"street\", \"zip\", \"city\", \"country_name\"]; // \"state_code\"];\n\t\t\t\t\t\t\t\t\t_.each(all_fields, function (field) {\n\t\t\t\t\t\t\t\t\t\t$(\".checkout_autoformat .div_\" + field.split('_')[0]).toggle($.inArray(field, data.fields) >= 0);\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}, 500);\n\t\t\t});\n\t\t}\n\t\t$(\"select[name='country_id']\").change();\n\n\t\t$('.reservation_payment').click(function (ev) {\n\t\t\tvar self = this\n\t\t\tvar room_id = $('#room_id').val();\n\t\t\tev.stopPropagation()\n\t\t\tev.preventDefault();\n\n\t\t\tconsole.log(\"PPPPPPPP\", room_id, room_id.length);\n\t\t\tif (room_id.length) {\n\t\t\t\tajax.jsonRpc('/custom/search/read', 'call', {room_id}).then(function (data) {\n\t\t\t\t\tif (data == 'false') {\n\n\t\t\t\t\t\tDialog.alert(self, _t(\"Ohh ! Looks like you took long to make a reservation. Please re check the availability.\"), {\n\t\t\t\t\t\t\ttitle: _t('Warning'),\n\t\t\t\t\t\t\tconfirm_callback: function () {\n\t\t\t\t\t\t\t\twindow.location.href = '/page/hotel_online.product_show'\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\twindow.location.href = '/partner/checkout'\n\t\t\t\t\t}\n\n\n\t\t\t\t});\n\n\t\t\n\t\t\t\t\n\t\t\t\t// rpc.query({\n\t\t\t\t// \tmodel: 'hotel.reservation',\n\t\t\t\t// \tmethod: 'search_read',\n\t\t\t\t// \tfields: ['state'],\n\t\t\t\t// \tdomain: [['id', '=', room_id]]\n\t\t\t\t// }).then(function (data) {\n\t\t\t\t// \tconsole.log(\"******************\")\n\t\t\t\t// \tif (data[0].state == 'cancel') {\n\n\t\t\t\t// \t\tDialog.alert(self, _t(\"Ohh ! Looks like you took long to make a reservation. Please re check the availability.\"), {\n\t\t\t\t// \t\t\ttitle: _t('Warning'),\n\t\t\t\t// \t\t\tconfirm_callback: function () {\n\t\t\t\t// \t\t\t\twindow.location.href = '/page/hotel_online.product_show'\n\t\t\t\t// \t\t\t},\n\t\t\t\t// \t\t});\n\t\t\t\t// \t}\n\t\t\t\t// \telse {\n\t\t\t\t// \t\twindow.location.href = '/partner/checkout'\n\t\t\t\t// \t}\n\t\t\t\t// });\n\t\t\t}\n\t\t\treturn true;\n\t\t})\n\n\t\t$('.checkout_process').click(function (ev) {\n\t\t\tvar self = this\n\t\t\tvar room_id = $('#room_id').val();\n\t\t\tev.stopPropagation()\n\t\t\tev.preventDefault();\n\n\t\t\tconsole.log(\"PPPPPPPP\", room_id, room_id.length);\n\t\t\tif (room_id.length) {\n\t\t\t\tajax.jsonRpc('/custom/search/read', 'call', {room_id}).then(function (data) {\n\t\t\t\t\tif (data == 'false') {\n\n\t\t\t\t\t\tDialog.alert(self, _t(\"Ohh ! Looks like you took long to make a reservation. Please re check the availability.\"), {\n\t\t\t\t\t\t\ttitle: _t('Warning'),\n\t\t\t\t\t\t\tconfirm_callback: function () {\n\t\t\t\t\t\t\t\twindow.location.href = '/page/hotel_online.product_show'\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\twindow.location.href = '/partner/checkout'\n\t\t\t\t\t}\n\n\n\t\t\t\t});\n\n\t\t\t}\n\t\t\treturn true;\n\t\t})\n\n\n\t\t$('.js_delete_product').click(function (ev) {\n\t\t\tvar self = this\n\t\t\tvar other_items_id = $('.product_display_id').attr('data-id');\n\t\t\tev.stopPropagation()\n\t\t\tev.preventDefault();\n\n\t\t\tconsole.log(\"PPPPPPPP\",other_items_id);\n\t\t\n\t\t\tajax.jsonRpc('/other/items/unlink', 'call', {other_items_id}).then(function (data) {\n\t\t\t\tif (data == 'false') {\n\n\t\t\t\t\tDialog.alert(self, _t(\"Ohh ! Looks like you took long to make a reservation. Please re check the availability.\"), {\n\t\t\t\t\t\ttitle: _t('Warning'),\n\t\t\t\t\t\tconfirm_callback: function () {\n\t\t\t\t\t\t\twindow.location.href = '/page/hotel_online.product_show'\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\twindow.location.href = '/shop/payment'\n\t\t\t\t}\n\n\n\t\t\t});\n\n\t\t\treturn true;\n\t\t})\n\n\n\t});\n});", "odoo.define('hotel_online.cart', function (require) {\n    'use strict';\n\n    var publicWidget = require('web.public.widget');\n    var WebsiteSale = require('website_sale.website_sale')\n    const wUtils = require('website.utils');\n\n    publicWidget.registry.WebsiteSale.include({\n\n        /**\n         * @override\n         */\n        _onClickAdd: function (ev) {\n            ev.preventDefault();\n            var self = this\n            this.getCartHandlerOptions(ev);\n            var $form = $(ev.currentTarget).closest('form')\n            \n            var productID = $form.find('.product_id').val()\n            var qty_id = $form.find('.quantity').val()\n            \n            wUtils.sendRequest('/shop/cart/update', {\n                product_id: productID,\n                add_qty:qty_id,\n                express: 1,\n\n            });\n            \n            return self._super.apply(this, arguments)\n\n        },\n    })\n\n});", "odoo.define('web_unsplash.beacon', function (require) {\n'use strict';\n\nvar publicWidget = require('web.public.widget');\n\npublicWidget.registry.UnsplashBeacon = publicWidget.Widget.extend({\n    // /!\\ To adapt the day the beacon makes sense for backend customizations\n    selector: '#wrapwrap',\n\n    /**\n     * @override\n     */\n    start: function () {\n        var unsplashImages = _.map(this.$('img[src*=\"/unsplash/\"]'), function (img) {\n            // get image id from URL (`http://www.domain.com:1234/unsplash/xYdf5feoI/lion.jpg` -> `xYdf5feoI`)\n            return img.src.split('/unsplash/')[1].split('/')[0];\n        });\n        if (unsplashImages.length) {\n            this._rpc({\n                route: '/web_unsplash/get_app_id',\n            }).then(function (appID) {\n                if (!appID) {\n                    return;\n                }\n                $.get('https://views.unsplash.com/v', {\n                    'photo_id': unsplashImages.join(','),\n                    'app_id': appID,\n                });\n            });\n        }\n        return this._super.apply(this, arguments);\n    },\n});\n});\n", "odoo.define('website.home_menu', function (require) {\n    'use strict';\n\n    var session = require('web.session');\n    var websiteNavbarData = require('website.navbar');\n\n    websiteNavbarData.WebsiteNavbar.include({\n        events: _.extend({}, websiteNavbarData.WebsiteNavbar.prototype.events || {}, {\n            'click .o_menu_toggle': '_onMenuToggleClick',\n        }),\n\n        //----------------------------------------------------------------------\n        // Handlers\n        //----------------------------------------------------------------------\n\n        /**\n         * Called when the menu toggle is clicked -> redirects to backend\n         *\n         * @private\n         * @param {Event} ev\n         */\n        _onMenuToggleClick: function (ev) {\n            ev.preventDefault();\n\n            // We add a spinner for the user to understand the loading.\n            var $button = $(ev.currentTarget);\n            if (!$button.hasClass('fa')) {\n                return;\n            }\n            $button.removeClass('fa fa-th').append($('<span/>', { 'class': 'fa fa-spin fa-circle-o-notch'}));\n            var url = '/web#home';\n            window.location.href = url;\n        },\n    });\n});\n", "odoo.define('website_sale_stock.VariantMixin', function (require) {\n'use strict';\n\nconst {Markup} = require('web.utils');\nvar VariantMixin = require('sale.VariantMixin');\nvar publicWidget = require('web.public.widget');\nvar ajax = require('web.ajax');\nvar core = require('web.core');\nvar QWeb = core.qweb;\n\nconst loadXml = async () => {\n    return ajax.loadXML('/website_sale_stock/static/src/xml/website_sale_stock_product_availability.xml', QWeb);\n};\n\nrequire('website_sale.website_sale');\n\n/**\n * Addition to the variant_mixin._onChangeCombination\n *\n * This will prevent the user from selecting a quantity that is not available in the\n * stock for that product.\n *\n * It will also display various info/warning messages regarding the select product's stock.\n *\n * This behavior is only applied for the web shop (and not on the SO form)\n * and only for the main product.\n *\n * @param {MouseEvent} ev\n * @param {$.Element} $parent\n * @param {Array} combination\n */\nVariantMixin._onChangeCombinationStock = function (ev, $parent, combination) {\n    let product_id = 0;\n    // needed for list view of variants\n    if ($parent.find('input.product_id:checked').length) {\n        product_id = $parent.find('input.product_id:checked').val();\n    } else {\n        product_id = $parent.find('.product_id').val();\n    }\n    const isMainProduct = combination.product_id &&\n        ($parent.is('.js_main_product') || $parent.is('.main_product')) &&\n        combination.product_id === parseInt(product_id);\n\n    if (!this.isWebsite || !isMainProduct) {\n        return;\n    }\n\n    const $addQtyInput = $parent.find('input[name=\"add_qty\"]');\n    let qty = $addQtyInput.val();\n\n    $parent.find('#add_to_cart').removeClass('out_of_stock');\n    $parent.find('.o_we_buy_now').removeClass('out_of_stock');\n    if (combination.product_type === 'product' && !combination.allow_out_of_stock_order) {\n        combination.free_qty -= parseInt(combination.cart_qty);\n        $addQtyInput.data('max', combination.free_qty || 1);\n        if (combination.free_qty < 0) {\n            combination.free_qty = 0;\n        }\n        if (qty > combination.free_qty) {\n            qty = combination.free_qty || 1;\n            $addQtyInput.val(qty);\n        }\n        if (combination.free_qty < 1) {\n            $parent.find('#add_to_cart').addClass('disabled out_of_stock');\n            $parent.find('.o_we_buy_now').addClass('disabled out_of_stock');\n        }\n    }\n\n    loadXml().then(function (result) {\n        $('.oe_website_sale')\n            .find('.availability_message_' + combination.product_template)\n            .remove();\n        combination.has_out_of_stock_message = $(combination.out_of_stock_message).text() !== '';\n        combination.out_of_stock_message = Markup(combination.out_of_stock_message);\n        const $message = $(QWeb.render(\n            'website_sale_stock.product_availability',\n            combination\n        ));\n        $('div.availability_messages').html($message);\n    });\n};\n\npublicWidget.registry.WebsiteSale.include({\n    /**\n     * Adds the stock checking to the regular _onChangeCombination method\n     * @override\n     */\n    _onChangeCombination: function () {\n        this._super.apply(this, arguments);\n        VariantMixin._onChangeCombinationStock.apply(this, arguments);\n    },\n    /**\n     * Recomputes the combination after adding a product to the cart\n     * @override\n     */\n    _onClickAdd(ev) {\n        return this._super.apply(this, arguments).then(() => {\n            if ($('div.availability_messages').length) {\n                this._getCombinationInfo(ev);\n            }\n        });\n    }\n});\n\nreturn VariantMixin;\n\n});\n", "odoo.define('website.s_share', function (require) {\n'use strict';\n\nconst publicWidget = require('web.public.widget');\n\nconst ShareWidget = publicWidget.Widget.extend({\n    selector: '.s_share, .oe_share', // oe_share for compatibility\n\n    /**\n     * @override\n     */\n    start: function () {\n        const urlRegex = /(\\?(?:|.*&)(?:u|url|body)=)(.*?)(&|#|$)/;\n        const titleRegex = /(\\?(?:|.*&)(?:title|text|subject|description)=)(.*?)(&|#|$)/;\n        const mediaRegex = /(\\?(?:|.*&)(?:media)=)(.*?)(&|#|$)/;\n        const url = encodeURIComponent(window.location.href);\n        const title = encodeURIComponent($('title').text());\n        const media = encodeURIComponent($('meta[property=\"og:image\"]').attr('content'));\n\n        this.$('a').each((index, element) => {\n            const $a = $(element);\n            $a.attr('href', (i, href) => {\n                return href.replace(urlRegex, (match, a, b, c) => {\n                    return a + url + c;\n                }).replace(titleRegex, function (match, a, b, c) {\n                    if ($a.hasClass('s_share_whatsapp')) {\n                        // WhatsApp does not support the \"url\" GET parameter.\n                        // Instead we need to include the url within the passed \"text\" parameter, merging everything together.\n                        // e.g of output:\n                        // https://wa.me/?text=%20OpenWood%20Collection%20Online%20Reveal%20%7C%20My%20Website%20http%3A%2F%2Flocalhost%3A8888%2Fevent%2Fopenwood-collection-online-reveal-2021-06-21-2021-06-23-8%2Fregister\n                        // see https://faq.whatsapp.com/general/chats/how-to-use-click-to-chat/ for more details\n                        return a + title + url + c;\n                    }\n                    return a + title + c;\n                }).replace(mediaRegex, (match, a, b, c) => {\n                    return a + media + c;\n                });\n            });\n            if ($a.attr('target') && $a.attr('target').match(/_blank/i) && !$a.closest('.o_editable').length) {\n                $a.on('click.share_widget', function () {\n                    window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=550,width=600');\n                    return false;\n                });\n            }\n        });\n\n        return this._super.apply(this, arguments);\n    },\n    destroy: function () {\n        this._super.apply(this, arguments);\n        this.$('a').off('.share_widget');\n    }\n});\n\npublicWidget.registry.share = ShareWidget;\n\nreturn ShareWidget;\n});\n", "odoo.define('website.s_facebook_page', function (require) {\n'use strict';\n\nvar publicWidget = require('web.public.widget');\nvar utils = require('web.utils');\n\nconst FacebookPageWidget = publicWidget.Widget.extend({\n    selector: '.o_facebook_page',\n    disabledInEditableMode: false,\n\n    /**\n     * @override\n     */\n    start: function () {\n        var def = this._super.apply(this, arguments);\n\n        this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerUnactive();\n\n        const params = _.pick(this.$el.data(), 'href', 'id', 'height', 'tabs', 'small_header', 'hide_cover', 'show_facepile');\n        if (!params.href) {\n            return def;\n        }\n        if (params.id) {\n            params.href = `https://www.facebook.com/${params.id}`;\n        }\n        delete params.id;\n        params.width = utils.confine(Math.floor(this.$el.width()), 180, 500);\n\n        var src = $.param.querystring('https://www.facebook.com/plugins/page.php', params);\n        this.$iframe = $('<iframe/>', {\n            src: src,\n            width: params.width,\n            height: params.height,\n            css: {\n                border: 'none',\n                overflow: 'hidden',\n            },\n            scrolling: 'no',\n            frameborder: '0',\n            allowTransparency: 'true',\n        });\n        this.$el.append(this.$iframe);\n\n        this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerActive();\n        return def;\n    },\n    /**\n     * @override\n     */\n    destroy: function () {\n        this._super.apply(this, arguments);\n\n        this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerUnactive();\n        if (this.$iframe) {\n            this.$iframe.remove();\n        }\n        this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerActive();\n    },\n});\n\npublicWidget.registry.facebookPage = FacebookPageWidget;\n\nreturn FacebookPageWidget;\n});\n", "odoo.define('website.s_image_gallery', function (require) {\n'use strict';\n\nvar core = require('web.core');\nvar publicWidget = require('web.public.widget');\n\nvar qweb = core.qweb;\n\nconst GalleryWidget = publicWidget.Widget.extend({\n\n    selector: '.s_image_gallery:not(.o_slideshow)',\n    xmlDependencies: ['/website/static/src/snippets/s_image_gallery/000.xml'],\n    events: {\n        'click img': '_onClickImg',\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Called when an image is clicked. Opens a dialog to browse all the images\n     * with a bigger size.\n     *\n     * @private\n     * @param {Event} ev\n     */\n    _onClickImg: function (ev) {\n        if (this.$modal || ev.currentTarget.matches(\"a > img\")) {\n            return;\n        }\n        var self = this;\n        var $cur = $(ev.currentTarget);\n\n        var $images = $cur.closest('.s_image_gallery').find('img');\n        var size = 0.8;\n        var dimensions = {\n            min_width: Math.round(window.innerWidth * size * 0.9),\n            min_height: Math.round(window.innerHeight * size),\n            max_width: Math.round(window.innerWidth * size * 0.9),\n            max_height: Math.round(window.innerHeight * size),\n            width: Math.round(window.innerWidth * size * 0.9),\n            height: Math.round(window.innerHeight * size)\n        };\n\n        var $img = ($cur.is('img') === true) ? $cur : $cur.closest('img');\n\n        const milliseconds = $cur.closest('.s_image_gallery').data('interval') || false;\n        this.$modal = $(qweb.render('website.gallery.slideshow.lightbox', {\n            images: $images.get(),\n            index: $images.index($img),\n            dim: dimensions,\n            interval: milliseconds || 0,\n            id: _.uniqueId('slideshow_'),\n        }));\n        this.$modal.modal({\n            keyboard: true,\n            backdrop: true,\n        });\n        this.$modal.on('hidden.bs.modal', function () {\n            $(this).hide();\n            $(this).siblings().filter('.modal-backdrop').remove(); // bootstrap leaves a modal-backdrop\n            $(this).remove();\n            self.$modal = undefined;\n        });\n        this.$modal.find('.modal-content, .modal-body.o_slideshow').css('height', '100%');\n        this.$modal.appendTo(document.body);\n\n        this.$modal.one('shown.bs.modal', function () {\n            self.trigger_up('widgets_start_request', {\n                editableMode: false,\n                $target: self.$modal.find('.modal-body.o_slideshow'),\n            });\n        });\n    },\n});\n\nconst GallerySliderWidget = publicWidget.Widget.extend({\n    selector: '.o_slideshow',\n    xmlDependencies: ['/website/static/src/snippets/s_image_gallery/000.xml'],\n    disabledInEditableMode: false,\n\n    /**\n     * @override\n     */\n    start: function () {\n        var self = this;\n        this.$carousel = this.$target.is('.carousel') ? this.$target : this.$target.find('.carousel');\n        this.$indicator = this.$carousel.find('.carousel-indicators');\n        this.$prev = this.$indicator.find('li.o_indicators_left').css('visibility', ''); // force visibility as some databases have it hidden\n        this.$next = this.$indicator.find('li.o_indicators_right').css('visibility', '');\n        var $lis = this.$indicator.find('li[data-slide-to]');\n        let indicatorWidth = this.$indicator.width();\n        if (indicatorWidth === 0) {\n            // An ancestor may be hidden so we try to find it and make it\n            // visible just to take the correct width.\n            const $indicatorParent = this.$indicator.parents().not(':visible').last();\n            if (!$indicatorParent[0].style.display) {\n                $indicatorParent[0].style.display = 'block';\n                indicatorWidth = this.$indicator.width();\n                $indicatorParent[0].style.display = '';\n            }\n        }\n        let nbPerPage = Math.floor(indicatorWidth / $lis.first().outerWidth(true)) - 3; // - navigator - 1 to leave some space\n        var realNbPerPage = nbPerPage || 1;\n        var nbPages = Math.ceil($lis.length / realNbPerPage);\n\n        var index;\n        var page;\n        update();\n\n        function hide() {\n            $lis.each(function (i) {\n                $(this).toggleClass('d-none', i < page * nbPerPage || i >= (page + 1) * nbPerPage);\n            });\n            if (page <= 0) {\n                self.$prev.detach();\n            } else {\n                self.$prev.removeClass('d-none');\n                self.$prev.prependTo(self.$indicator);\n            }\n            if (page >= nbPages - 1) {\n                self.$next.detach();\n            } else {\n                self.$next.removeClass('d-none');\n                self.$next.appendTo(self.$indicator);\n            }\n        }\n\n        function update() {\n            const active = $lis.filter('.active');\n            index = active.length ? $lis.index(active) : 0;\n            page = Math.floor(index / realNbPerPage);\n            hide();\n        }\n\n        this.$carousel.on('slide.bs.carousel.gallery_slider', function () {\n            setTimeout(function () {\n                var $item = self.$carousel.find('.carousel-inner .carousel-item-prev, .carousel-inner .carousel-item-next');\n                var index = $item.index();\n                $lis.removeClass('active')\n                    .filter('[data-slide-to=\"' + index + '\"]')\n                    .addClass('active');\n            }, 0);\n        });\n        this.$indicator.on('click.gallery_slider', '> li:not([data-slide-to])', function () {\n            page += ($(this).hasClass('o_indicators_left') ? -1 : 1);\n            page = Math.max(0, Math.min(nbPages - 1, page)); // should not be necessary\n            self.$carousel.carousel(page * realNbPerPage);\n            // We dont use hide() before the slide animation in the editor because there is a traceback\n            // TO DO: fix this traceback\n            if (!self.editableMode) {\n                hide();\n            }\n        });\n        this.$carousel.on('slid.bs.carousel.gallery_slider', update);\n\n        return this._super.apply(this, arguments);\n    },\n    /**\n     * @override\n     */\n    destroy: function () {\n        this._super.apply(this, arguments);\n\n        if (!this.$indicator) {\n            return;\n        }\n\n        this.$prev.prependTo(this.$indicator);\n        this.$next.appendTo(this.$indicator);\n        this.$carousel.off('.gallery_slider');\n        this.$indicator.off('.gallery_slider');\n    },\n});\n\npublicWidget.registry.gallery = GalleryWidget;\npublicWidget.registry.gallerySlider = GallerySliderWidget;\n\nreturn {\n    GalleryWidget: GalleryWidget,\n    GallerySliderWidget: GallerySliderWidget,\n};\n});\n", "odoo.define('website.s_countdown', function (require) {\n'use strict';\n\nconst {ColorpickerWidget} = require('web.Colorpicker');\nconst core = require('web.core');\nconst publicWidget = require('web.public.widget');\nconst weUtils = require('web_editor.utils');\n\nconst qweb = core.qweb;\nconst _t = core._t;\n\nconst CountdownWidget = publicWidget.Widget.extend({\n    selector: '.s_countdown',\n    xmlDependencies: ['/website/static/src/snippets/s_countdown/000.xml'],\n    disabledInEditableMode: false,\n    defaultColor: 'rgba(0, 0, 0, 255)',\n\n    /**\n     * @override\n     */\n    start: function () {\n        this.$wrapper = this.$('.s_countdown_canvas_wrapper');\n        this.$wrapper.addClass('d-flex justify-content-center');\n        this.hereBeforeTimerEnds = false;\n        this.endAction = this.el.dataset.endAction;\n        this.endTime = parseInt(this.el.dataset.endTime);\n        this.size = parseInt(this.el.dataset.size);\n        this.display = this.el.dataset.display;\n\n        this.layout = this.el.dataset.layout;\n        this.layoutBackground = this.el.dataset.layoutBackground;\n        this.progressBarStyle = this.el.dataset.progressBarStyle;\n        this.progressBarWeight = this.el.dataset.progressBarWeight;\n\n        this.textColor = this._ensureCssColor(this.el.dataset.textColor);\n        this.layoutBackgroundColor = this._ensureCssColor(this.el.dataset.layoutBackgroundColor);\n        this.progressBarColor = this._ensureCssColor(this.el.dataset.progressBarColor);\n\n        this.onlyOneUnit = this.display === 'd';\n        this.width = parseInt(this.size);\n        if (this.layout === 'boxes') {\n            this.width /= 1.75;\n        }\n        this._initTimeDiff();\n\n        this._render();\n\n        this.setInterval = setInterval(this._render.bind(this), 1000);\n        return this._super(...arguments);\n    },\n    /**\n     * @override\n     */\n    destroy: function () {\n        this.$('.s_countdown_end_redirect_message').remove();\n        this.$('.s_countdown_end_message').addClass('d-none');\n        this.$('.s_countdown_text_wrapper').remove();\n        this.$('.s_countdown_canvas_wrapper').removeClass('d-none');\n        this.$('.s_countdown_canvas_flex').remove();\n\n        clearInterval(this.setInterval);\n        this._super(...arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Ensures the color is an actual css color. In case of a color variable,\n     * the color will be mapped to hexa.\n     *\n     * @private\n     * @param {string} color\n     * @returns {string}\n     */\n    _ensureCssColor: function (color) {\n        if (ColorpickerWidget.isCSSColor(color)) {\n            return color;\n        }\n        return weUtils.getCSSVariableValue(color) || this.defaultColor;\n    },\n    /**\n     * Gets the time difference in seconds between now and countdown due date.\n     *\n     * @private\n     */\n    _getDelta: function () {\n        const currentTimestamp = Date.now() / 1000;\n        return this.endTime - currentTimestamp;\n    },\n    /**\n     * Handles the action that should be executed once the countdown ends.\n     *\n     * @private\n     */\n    _handleEndCountdownAction: function () {\n        if (this.endAction === 'redirect') {\n            const redirectUrl = this.el.dataset.redirectUrl || '/';\n            if (this.hereBeforeTimerEnds) {\n                // Wait a bit, if the landing page has the same publish date\n                setTimeout(() => window.location = redirectUrl, 500);\n            } else {\n                // Show (non editable) msg when user lands on already finished countdown\n                if (!this.$('.s_countdown_end_redirect_message').length) {\n                    const $container = this.$('> .container, > .container-fluid, > .o_container_small');\n                    $container.append(\n                        $(qweb.render('website.s_countdown.end_redirect_message', {\n                            redirectUrl: redirectUrl,\n                        }))\n                    );\n                }\n            }\n        } else if (this.endAction === 'message' || this.endAction === 'message_no_countdown') {\n            this.$('.s_countdown_end_message').removeClass('d-none');\n        }\n    },\n    /**\n     * Initializes the `diff` object. It will contains every visible time unit\n     * which will each contain its related canvas, total step, label..\n     *\n     * @private\n     */\n    _initTimeDiff: function () {\n        const delta = this._getDelta();\n        this.diff = [];\n        if (this._isUnitVisible('d') && !(this.onlyOneUnit && delta < 86400)) {\n            this.diff.push({\n                canvas: $('<div class=\"s_countdown_canvas_flex\"><canvas class=\"w-100\"/></div>').appendTo(this.$wrapper)[0],\n                // There is no logical number of unit (total) on which day units\n                //  can be compared against, so we use an arbitrary number.\n                total: 15,\n                label: _t(\"Days\"),\n                nbSeconds: 86400,\n            });\n        }\n        if (this._isUnitVisible('h') || (this.onlyOneUnit && delta < 86400 && delta > 3600)) {\n            this.diff.push({\n                canvas: $('<div class=\"s_countdown_canvas_flex\"><canvas class=\"w-100\"/></div>').appendTo(this.$wrapper)[0],\n                total: 24,\n                label: _t(\"Hours\"),\n                nbSeconds: 3600,\n            });\n        }\n        if (this._isUnitVisible('m') || (this.onlyOneUnit && delta < 3600 && delta > 60)) {\n            this.diff.push({\n                canvas: $('<div class=\"s_countdown_canvas_flex\"><canvas class=\"w-100\"/></div>').appendTo(this.$wrapper)[0],\n                total: 60,\n                label: _t(\"Minutes\"),\n                nbSeconds: 60,\n            });\n        }\n        if (this._isUnitVisible('s') || (this.onlyOneUnit && delta < 60)) {\n            this.diff.push({\n                canvas: $('<div class=\"s_countdown_canvas_flex\"><canvas class=\"w-100\"/></div>').appendTo(this.$wrapper)[0],\n                total: 60,\n                label: _t(\"Seconds\"),\n                nbSeconds: 1,\n            });\n        }\n    },\n    /**\n     * Returns weither or not the countdown should be displayed for the given\n     * unit (days, sec..).\n     *\n     * @private\n     * @param {string} unit - either 'd', 'm', 'h', or 's'\n     * @returns {boolean}\n     */\n    _isUnitVisible: function (unit) {\n        return this.display.includes(unit);\n    },\n    /**\n     * Draws the whole countdown, including one countdown for each time unit.\n     *\n     * @private\n     */\n    _render: function () {\n\n        // If only one unit mode, restart widget on unit change to populate diff\n        if (this.onlyOneUnit && this._getDelta() < this.diff[0].nbSeconds) {\n            this.$('.s_countdown_canvas_flex').remove();\n            this._initTimeDiff();\n        }\n        this._updateTimeDiff();\n\n        const hideCountdown = this.isFinished && !this.editableMode && this.$el.hasClass('hide-countdown');\n        if (this.layout === 'text') {\n            this.$('.s_countdown_canvas_flex').addClass('d-none');\n            if (!this.$textWrapper) {\n                this.$textWrapper = $('<span/>').attr({\n                    class: 's_countdown_text_wrapper d-none',\n                });\n                this.$textWrapper.text(_t(\"Countdown ends in\"));\n                this.$textWrapper.append($('<span/>').attr({\n                    class: 's_countdown_text ml-1',\n                }));\n                this.$textWrapper.appendTo(this.$wrapper);\n            }\n\n            this.$textWrapper.toggleClass('d-none', hideCountdown);\n\n            const countdownText = this.diff.map(e => e.nb + ' ' + e.label).join(', ');\n            this.$('.s_countdown_text').text(countdownText.toLowerCase());\n        } else {\n            for (const val of this.diff) {\n                const canvas = val.canvas.querySelector('canvas');\n                const ctx = canvas.getContext(\"2d\");\n                ctx.canvas.width = this.width;\n                ctx.canvas.height = this.size;\n                this._clearCanvas(ctx);\n\n                $(canvas).toggleClass('d-none', hideCountdown);\n                if (hideCountdown) {\n                    continue;\n                }\n\n                // Draw canvas elements\n                if (this.layoutBackground !== 'none') {\n                    this._drawBgShape(ctx, this.layoutBackground === 'plain');\n                }\n                this._drawText(canvas, val.nb, val.label, this.layoutBackground === 'plain');\n                if (this.progressBarStyle === 'surrounded') {\n                    this._drawProgressBarBg(ctx, this.progressBarWeight === 'thin');\n                }\n                if (this.progressBarStyle !== 'none') {\n                    this._drawProgressBar(ctx, val.nb, val.total, this.progressBarWeight === 'thin');\n                }\n                this.$('.s_countdown_canvas_flex').toggleClass('mx-1', this.layout === 'boxes');\n            }\n        }\n\n        if (this.isFinished) {\n            clearInterval(this.setInterval);\n            if (!this.editableMode) {\n                this._handleEndCountdownAction();\n            }\n        }\n    },\n    /**\n     * Updates the remaining units into the `diff` object.\n     *\n     * @private\n     */\n    _updateTimeDiff: function () {\n        let delta = this._getDelta();\n        this.isFinished = delta < 0;\n        if (this.isFinished) {\n            for (const unitData of this.diff) {\n                  unitData.nb = 0;\n            }\n            return;\n        }\n\n        this.hereBeforeTimerEnds = true;\n        for (const unitData of this.diff) {\n              unitData.nb = Math.floor(delta / unitData.nbSeconds);\n              delta -= unitData.nb * unitData.nbSeconds;\n        }\n    },\n\n    //--------------------------------------------------------------------------\n    // Canvas drawing methods\n    //--------------------------------------------------------------------------\n\n    /**\n     * Erases the canvas.\n     *\n     * @private\n     * @param {RenderingContext} ctx - Context of the canvas\n     */\n    _clearCanvas: function (ctx) {\n        ctx.clearRect(0, 0, this.size, this.size);\n    },\n    /**\n     * Draws a text into the canvas.\n     *\n     * @private\n     * @param {HTMLCanvasElement} canvas\n     * @param {string} textNb - text to display in the center of the canvas, in big\n     * @param {string} textUnit - text to display bellow `textNb` in small\n     * @param {boolean} full - if true, the shape will be drawn up to the progressbar\n     */\n    _drawText: function (canvas, textNb, textUnit, full = false) {\n        const ctx = canvas.getContext(\"2d\");\n        const nbSize = this.size / 4;\n        ctx.font = `${nbSize}px Arial`;\n        ctx.fillStyle = this.textColor;\n        ctx.textAlign = 'center';\n        ctx.textBaseline = 'middle';\n        ctx.fillText(textNb, canvas.width / 2, canvas.height / 2);\n\n        const unitSize = this.size / 12;\n        ctx.font = `${unitSize}px Arial`;\n        ctx.fillText(textUnit, canvas.width / 2, canvas.height / 2 + nbSize / 1.5, this.width);\n\n        if (this.layout === 'boxes' && this.layoutBackground !== 'none' && this.progressBarStyle === 'none') {\n            let barWidth = this.size / (this.progressBarWeight === 'thin' ? 31 : 10);\n            if (full) {\n                barWidth = 0;\n            }\n            ctx.beginPath();\n            ctx.moveTo(barWidth, this.size / 2);\n            ctx.lineTo(this.width - barWidth, this.size / 2);\n            ctx.stroke();\n        }\n    },\n    /**\n     * Draws a plain shape into the canvas.\n     *\n     * @private\n     * @param {RenderingContext} ctx - Context of the canvas\n     * @param {boolean} full - if true, the shape will be drawn up to the progressbar\n     */\n    _drawBgShape: function (ctx, full = false) {\n        ctx.fillStyle = this.layoutBackgroundColor;\n        ctx.beginPath();\n        if (this.layout === 'circle') {\n            let rayon = this.size / 2;\n            if (this.progressBarWeight === 'thin') {\n                rayon -= full ? this.size / 29 : this.size / 15;\n            } else {\n                rayon -= full ? 0 : this.size / 10;\n            }\n            ctx.arc(this.size / 2, this.size / 2, rayon, 0, Math.PI * 2);\n            ctx.fill();\n        } else if (this.layout === 'boxes') {\n            let barWidth = this.size / (this.progressBarWeight === 'thin' ? 31 : 10);\n            if (full) {\n                barWidth = 0;\n            }\n\n            ctx.fillStyle = this.layoutBackgroundColor;\n            ctx.rect(barWidth, barWidth, this.width - barWidth * 2, this.size - barWidth * 2);\n            ctx.fill();\n\n            const gradient = ctx.createLinearGradient(0, this.width, 0, 0);\n            gradient.addColorStop(0, '#ffffff24');\n            gradient.addColorStop(1, this.layoutBackgroundColor);\n            ctx.fillStyle = gradient;\n            ctx.rect(barWidth, barWidth, this.width - barWidth * 2, this.size - barWidth * 2);\n            ctx.fill();\n            $(ctx.canvas).css({'border-radius': '8px'});\n        }\n    },\n    /**\n     * Draws a progress bar around the countdown shape.\n     *\n     * @private\n     * @param {RenderingContext} ctx - Context of the canvas\n     * @param {string} nbUnit - how many unit should fill progress bar\n     * @param {string} totalUnit - number of unit to do a complete progress bar\n     * @param {boolean} thinLine - if true, the progress bar will be thiner\n     */\n    _drawProgressBar: function (ctx, nbUnit, totalUnit, thinLine) {\n        ctx.strokeStyle = this.progressBarColor;\n        ctx.lineWidth = thinLine ? this.size / 35 : this.size / 10;\n        if (this.layout === 'circle') {\n            ctx.beginPath();\n            ctx.arc(this.size / 2, this.size / 2, this.size / 2 - this.size / 20, Math.PI / -2, (Math.PI * 2) * (nbUnit / totalUnit) + (Math.PI / -2));\n            ctx.stroke();\n        } else if (this.layout === 'boxes') {\n            ctx.lineWidth *= 2;\n            let pc = nbUnit / totalUnit * 100;\n\n            // Lines: Top(x1,y1,x2,y2) Right(x1,y1,x2,y2) Bottom(x1,y1,x2,y2) Left(x1,y1,x2,y2)\n            const linesCoordFuncs = [\n                (linePc) => [0 + ctx.lineWidth / 2, 0, (this.width - ctx.lineWidth / 2) * linePc / 25 + ctx.lineWidth / 2, 0],\n                (linePc) => [this.width, 0 + ctx.lineWidth / 2, this.width, (this.size - ctx.lineWidth / 2) * linePc / 25 + ctx.lineWidth / 2],\n                (linePc) => [this.width - ((this.width - ctx.lineWidth / 2) * linePc / 25) - ctx.lineWidth / 2, this.size, this.width - ctx.lineWidth / 2, this.size],\n                (linePc) => [0, this.size - ((this.size - ctx.lineWidth / 2) * linePc / 25) - ctx.lineWidth / 2, 0, this.size - ctx.lineWidth / 2],\n            ];\n            while (pc > 0 && linesCoordFuncs.length) {\n                const linePc = Math.min(pc, 25);\n                const lineCoord = (linesCoordFuncs.shift())(linePc);\n                ctx.beginPath();\n                ctx.moveTo(lineCoord[0], lineCoord[1]);\n                ctx.lineTo(lineCoord[2], lineCoord[3]);\n                ctx.stroke();\n                pc -= linePc;\n            }\n        }\n    },\n    /**\n     * Draws a full lighter background progressbar around the shape.\n     *\n     * @private\n     * @param {RenderingContext} ctx - Context of the canvas\n     * @param {boolean} thinLine - if true, the progress bar will be thiner\n     */\n    _drawProgressBarBg: function (ctx, thinLine) {\n        ctx.strokeStyle = this.progressBarColor;\n        ctx.globalAlpha = 0.2;\n        ctx.lineWidth = thinLine ? this.size / 35 : this.size / 10;\n        if (this.layout === 'circle') {\n            ctx.beginPath();\n            ctx.arc(this.size / 2, this.size / 2, this.size / 2 - this.size / 20, 0, Math.PI * 2);\n            ctx.stroke();\n        } else if (this.layout === 'boxes') {\n            ctx.lineWidth *= 2;\n\n            // Lines: Top(x1,y1,x2,y2) Right(x1,y1,x2,y2) Bottom(x1,y1,x2,y2) Left(x1,y1,x2,y2)\n            const points = [\n                [0 + ctx.lineWidth / 2, 0, this.width, 0],\n                [this.width, 0 + ctx.lineWidth / 2, this.width, this.size],\n                [0, this.size, this.width - ctx.lineWidth / 2, this.size],\n                [0, 0, 0, this.size - ctx.lineWidth / 2],\n            ];\n            while (points.length) {\n                const point = points.shift();\n                ctx.beginPath();\n                ctx.moveTo(point[0], point[1]);\n                ctx.lineTo(point[2], point[3]);\n                ctx.stroke();\n            }\n        }\n        ctx.globalAlpha = 1;\n    },\n});\n\npublicWidget.registry.countdown = CountdownWidget;\n\nreturn CountdownWidget;\n});\n", "odoo.define('website.s_popup', function (require) {\n'use strict';\n\nconst config = require('web.config');\nconst dom = require('web.dom');\nconst publicWidget = require('web.public.widget');\nconst utils = require('web.utils');\n\n// TODO In master, export this class too or merge it with PopupWidget\nconst SharedPopupWidget = publicWidget.Widget.extend({\n    selector: '.s_popup',\n    disabledInEditableMode: false,\n    events: {\n        // A popup element is composed of a `.s_popup` parent containing the\n        // actual `.modal` BS modal. Our internal logic and events are hiding\n        // and showing this inner `.modal` modal element without considering its\n        // `.s_popup` parent. It means that when the `.modal` is hidden, its\n        // `.s_popup` parent is not touched and kept visible.\n        // It might look like it's not an issue as it would just be an empty\n        // element (its only child is hidden) but it leads to some issues as for\n        // instance on chrome this div will have a forced `height` due to its\n        // `contenteditable=true` attribute in edit mode. It will result in a\n        // ugly white bar.\n        // tl;dr: this is keeping those 2 elements visibility synchronized.\n        'show.bs.modal': '_onModalShow',\n        'hidden.bs.modal': '_onModalHidden',\n    },\n\n    /**\n     * @override\n     */\n    destroy() {\n        this._super(...arguments);\n\n        if (!this._isNormalCase()) {\n            return;\n        }\n\n        // Popup are always closed when entering edit mode (see PopupWidget),\n        // this allows to make sure the class is sync on the .s_popup parent\n        // after that moment too.\n        if (!this.editableMode) {\n            this.el.classList.add('d-none');\n        }\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * This whole widget was added as a stable fix, this function allows to\n     * be a bit more stable friendly. TODO remove in master.\n     */\n    _isNormalCase() {\n        return this.el.children.length === 1\n            && this.el.firstElementChild.classList.contains('modal');\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onModalShow() {\n        if (!this._isNormalCase()) {\n            return;\n        }\n        this.el.classList.remove('d-none');\n    },\n    /**\n     * @private\n     */\n    _onModalHidden() {\n        if (!this._isNormalCase()) {\n            return;\n        }\n        if (this.el.querySelector('.s_popup_no_backdrop')) {\n            // We trigger a scroll event here to call the\n            // '_hideBottomFixedElements' method and re-display any bottom fixed\n            // elements that may have been hidden (e.g. the live chat button\n            // hidden when the cookies bar is open).\n            $().getScrollingElement()[0].dispatchEvent(new Event('scroll'));\n        }\n        this.el.classList.add('d-none');\n    },\n});\n\npublicWidget.registry.SharedPopup = SharedPopupWidget;\n\nconst PopupWidget = publicWidget.Widget.extend({\n    selector: '.s_popup',\n    events: {\n        'click .js_close_popup': '_onCloseClick',\n        'hide.bs.modal': '_onHideModal',\n        'show.bs.modal': '_onShowModal',\n    },\n\n    /**\n     * @override\n     */\n    start: function () {\n        this._popupAlreadyShown = !!utils.get_cookie(this.$el.attr('id'));\n        if (!this._popupAlreadyShown) {\n            this._bindPopup();\n        }\n        return this._super(...arguments);\n    },\n    /**\n     * @override\n     */\n    destroy: function () {\n        this._super.apply(this, arguments);\n        $(document).off('mouseleave.open_popup');\n        this.$target.find('.modal').modal('hide');\n        clearTimeout(this.timeout);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _bindPopup: function () {\n        const $main = this.$target.find('.modal');\n\n        let display = $main.data('display');\n        let delay = $main.data('showAfter');\n\n        if (config.device.isMobile) {\n            if (display === 'mouseExit') {\n                display = 'afterDelay';\n                delay = 5000;\n            }\n            this.$('.modal').removeClass('s_popup_middle').addClass('s_popup_bottom');\n        }\n\n        if (display === 'afterDelay') {\n            this.timeout = setTimeout(() => this._showPopup(), delay);\n        } else {\n            $(document).on('mouseleave.open_popup', () => this._showPopup());\n        }\n    },\n    /**\n     * @private\n     */\n    _canShowPopup() {\n        return true;\n    },\n    /**\n     * @private\n     */\n    _hidePopup: function () {\n        this.$target.find('.modal').modal('hide');\n    },\n    /**\n     * @private\n     */\n    _showPopup: function () {\n        if (this._popupAlreadyShown || !this._canShowPopup()) {\n            return;\n        }\n        this.$target.find('.modal').modal('show');\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onCloseClick: function () {\n        this._hidePopup();\n    },\n    /**\n     * @private\n     */\n    _onHideModal: function () {\n        const nbDays = this.$el.find('.modal').data('consentsDuration');\n        utils.set_cookie(this.$el.attr('id'), true, nbDays * 24 * 60 * 60);\n        this._popupAlreadyShown = true;\n\n        this.$target.find('.media_iframe_video iframe').each((i, iframe) => {\n            iframe.src = '';\n        });\n    },\n    /**\n     * @private\n     */\n    _onShowModal() {\n        this.el.querySelectorAll('.media_iframe_video').forEach(media => {\n            const iframe = media.querySelector('iframe');\n            iframe.src = media.dataset.oeExpression || media.dataset.src; // TODO still oeExpression to remove someday\n        });\n    },\n});\n\npublicWidget.registry.popup = PopupWidget;\n\n// Try to update the scrollbar based on the current context (modal state)\n// and only if the modal overflowing has changed\n\nfunction _updateScrollbar(ev) {\n    const context = ev.data;\n    const isOverflowing = dom.hasScrollableContent(context._element);\n    if (context._isOverflowingWindow !== isOverflowing) {\n        context._isOverflowingWindow = isOverflowing;\n        context._checkScrollbar();\n        context._setScrollbar();\n        if (isOverflowing) {\n            document.body.classList.add('modal-open');\n        } else {\n            document.body.classList.remove('modal-open');\n            context._resetScrollbar();\n        }\n    }\n}\n\n// Prevent bootstrap to prevent scrolling and to add the strange body\n// padding-right they add if the popup does not use a backdrop (especially\n// important for default cookie bar).\n\nconst _baseShowElement = $.fn.modal.Constructor.prototype._showElement;\n$.fn.modal.Constructor.prototype._showElement = function () {\n    _baseShowElement.apply(this, arguments);\n\n    if (this._element.classList.contains('s_popup_no_backdrop')) {\n        // Update the scrollbar if the content changes or if the window has been\n        // resized. Note this could technically be done for all modals and not\n        // only the ones with the s_popup_no_backdrop class but that would be\n        // useless as allowing content scroll while a modal with that class is\n        // opened is a very specific Odoo behavior.\n        $(this._element).on('content_changed.update_scrollbar', this, _updateScrollbar);\n        $(window).on('resize.update_scrollbar', this, _updateScrollbar);\n\n        this._odooLoadEventCaptureHandler = _.debounce(() => _updateScrollbar({ data: this }, 100));\n        this._element.addEventListener('load', this._odooLoadEventCaptureHandler, true);\n\n        _updateScrollbar({ data: this });\n    }\n};\n\nconst _baseHideModal = $.fn.modal.Constructor.prototype._hideModal;\n$.fn.modal.Constructor.prototype._hideModal = function () {\n    _baseHideModal.apply(this, arguments);\n\n    // Note: do this in all cases, not only for popup with the\n    // s_popup_no_backdrop class, as the modal may have lost that class during\n    // edition before being closed.\n    this._element.classList.remove('s_popup_overflow_page');\n\n    $(this._element).off('content_changed.update_scrollbar');\n    $(window).off('resize.update_scrollbar');\n\n    if (this._odooLoadEventCaptureHandler) {\n        this._element.removeEventListener('load', this._odooLoadEventCaptureHandler, true);\n        delete this._odooLoadEventCaptureHandler;\n    }\n};\n\nconst _baseSetScrollbar = $.fn.modal.Constructor.prototype._setScrollbar;\n$.fn.modal.Constructor.prototype._setScrollbar = function () {\n    if (this._element.classList.contains('s_popup_no_backdrop')) {\n        this._element.classList.toggle('s_popup_overflow_page', !!this._isOverflowingWindow);\n\n        if (!this._isOverflowingWindow) {\n            return;\n        }\n    }\n    return _baseSetScrollbar.apply(this, arguments);\n};\n\nconst _baseGetScrollbarWidth = $.fn.modal.Constructor.prototype._getScrollbarWidth;\n$.fn.modal.Constructor.prototype._getScrollbarWidth = function () {\n    if (this._element.classList.contains('s_popup_no_backdrop') && !this._isOverflowingWindow) {\n        return 0;\n    }\n    return _baseGetScrollbarWidth.apply(this, arguments);\n};\n\nreturn PopupWidget;\n});\n", "odoo.define('website.s_table_of_content', function (require) {\n'use strict';\n\nconst publicWidget = require('web.public.widget');\nconst {extraMenuUpdateCallbacks} = require('website.content.menu');\n\nconst TableOfContent = publicWidget.Widget.extend({\n    selector: 'section .s_table_of_content_navbar_sticky',\n    disabledInEditableMode: false,\n\n    /**\n     * @override\n     */\n    async start() {\n        this._stripNavbarStyles();\n\n        // Those links will be recreated when entering edit mode, see\n        // `_generateNav`. No need to recreate those.\n        this.$target.find('.table_of_content_link:hidden').remove();\n\n        await this._super(...arguments);\n        this.$scrollingElement = this.$target.closest(\".s_table_of_content\").closestScrollable();\n        this.previousPosition = -1;\n        this._updateTableOfContentNavbarPosition();\n\n        this.boundUpdateNavbar = this._updateTableOfContentNavbarPosition.bind(this);\n        extraMenuUpdateCallbacks.push(this.boundUpdateNavbar);\n    },\n    /**\n     * @override\n     */\n    destroy() {\n        const indexOfCallback = extraMenuUpdateCallbacks.indexOf(this.boundUpdateNavbar);\n        extraMenuUpdateCallbacks.splice(indexOfCallback, 1);\n        this.$target.css('top', '');\n        this.$target.find('.s_table_of_content_navbar').css({top: '', maxHeight: ''});\n        this._super(...arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _stripNavbarStyles() {\n        // TODO Introduce a `disabledInTranslateMode` flag instead.\n        // Ignore in translation mode.\n        if (this.el.querySelector('[data-oe-translation-id]')) {\n            return;\n        }\n\n        // This is needed for styles added on translations when the master text\n        // has no style.\n        for (const el of this.el.querySelectorAll('.s_table_of_content_navbar .table_of_content_link')) {\n            const text = el.textContent; // Get text from el.\n            el.textContent = text; // Replace all of el's content with that text.\n        }\n    },\n    /**\n     * @private\n     */\n    _updateTableOfContentNavbarPosition() {\n        let position = 0;\n        const $fixedElements = $('.o_top_fixed_element');\n        _.each($fixedElements, el => position += $(el).outerHeight());\n        const isHorizontalNavbar = this.$target.hasClass('s_table_of_content_horizontal_navbar');\n        this.$target.css('top', isHorizontalNavbar ? position : '');\n        this.$target.find('.s_table_of_content_navbar').css('top', isHorizontalNavbar ? '' : position + 20);\n        const $mainNavBar = $('#oe_main_menu_navbar');\n        const mainNavBarHidden = document.body.classList.contains('o_fullscreen') || this.editableMode;\n        position += !mainNavBarHidden && $mainNavBar.length ? $mainNavBar.outerHeight() : 0;\n        position += isHorizontalNavbar ? this.$target.outerHeight() : 0;\n        this.$target.find('.s_table_of_content_navbar').css('maxHeight', isHorizontalNavbar ? '' : `calc(100vh - ${position + 40}px)`);\n        if (this.previousPosition !== position) {\n            // The scrollSpy must be destroyed before calling it again.\n            // Otherwise the call has no effect. We also need to be sure that\n            // a scrollSpy instance exists to avoid targeting elements outside\n            // the table of content navbar on scrollSpy methods.\n            if (this.$scrollingElement.data('bs.scrollspy')) {\n                this.$scrollingElement.scrollspy('dispose');\n            }\n            this.$scrollingElement.scrollspy({\n                target: '.s_table_of_content_navbar',\n                method: 'offset',\n                offset: position + 100,\n                alwaysKeepFirstActive: true,\n            });\n            this.previousPosition = position;\n        }\n    },\n});\n\npublicWidget.registry.anchorSlide.include({\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Overridden to add the height of the horizontal sticky navbar at the scroll value\n     * when the link is from the table of content navbar\n     *\n     * @override\n     * @private\n     */\n    _computeExtraOffset() {\n        let extraOffset = this._super(...arguments);\n        if (this.$el.hasClass('table_of_content_link')) {\n            const tableOfContentNavbarEl = this.$el.closest('.s_table_of_content_navbar_sticky.s_table_of_content_horizontal_navbar');\n            if (tableOfContentNavbarEl.length > 0) {\n                extraOffset += $(tableOfContentNavbarEl).outerHeight();\n            }\n        }\n        return extraOffset;\n    },\n});\n\npublicWidget.registry.snippetTableOfContent = TableOfContent;\n\nreturn TableOfContent;\n});\n", "odoo.define('website.s_chart', function (require) {\n'use strict';\n\nconst publicWidget = require('web.public.widget');\nconst weUtils = require('web_editor.utils');\n\nconst ChartWidget = publicWidget.Widget.extend({\n    selector: '.s_chart',\n    disabledInEditableMode: false,\n    jsLibs: [\n        '/web/static/lib/Chart/Chart.js',\n    ],\n\n    /**\n     * @override\n     * @param {Object} parent\n     * @param {Object} options The default value of the chartbar.\n     */\n    init: function (parent, options) {\n        this._super.apply(this, arguments);\n        this.style = window.getComputedStyle(document.documentElement);\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        // Convert Theme colors to css color\n        const data = JSON.parse(this.el.dataset.data);\n        data.datasets.forEach(el => {\n            if (Array.isArray(el.backgroundColor)) {\n                el.backgroundColor = el.backgroundColor.map(el => this._convertToCssColor(el));\n                el.borderColor = el.borderColor.map(el => this._convertToCssColor(el));\n            } else {\n                el.backgroundColor = this._convertToCssColor(el.backgroundColor);\n                el.borderColor = this._convertToCssColor(el.borderColor);\n            }\n            el.borderWidth = this.el.dataset.borderWidth;\n        });\n\n        // Make chart data\n        const chartData = {\n            type: this.el.dataset.type,\n            data: data,\n            options: {\n                legend: {\n                    display: this.el.dataset.legendPosition !== 'none',\n                    position: this.el.dataset.legendPosition,\n                },\n                tooltips: {\n                    enabled: this.el.dataset.tooltipDisplay === 'true',\n                },\n                title: {\n                    display: !!this.el.dataset.title,\n                    text: this.el.dataset.title,\n                },\n            },\n        };\n\n        // Add type specific options\n        if (this.el.dataset.type === 'radar') {\n            chartData.options.scale = {\n                ticks: {\n                    beginAtZero: true,\n                }\n            };\n        } else if (['pie', 'doughnut'].includes(this.el.dataset.type)) {\n            chartData.options.tooltips.callbacks = {\n                label: (tooltipItem, data) => {\n                    const label = data.datasets[tooltipItem.datasetIndex].label;\n                    const secondLabel = data.labels[tooltipItem.index];\n                    let final = label;\n                    if (label) {\n                        if (secondLabel) {\n                            final = label + ' - ' + secondLabel;\n                        }\n                    } else if (secondLabel) {\n                        final = secondLabel;\n                    }\n                    return final + ':' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n                },\n            };\n        } else {\n            chartData.options.scales = {\n                xAxes: [{\n                    stacked: this.el.dataset.stacked === 'true',\n                    ticks: {\n                        beginAtZero: true\n                    },\n                }],\n                yAxes: [{\n                    stacked: this.el.dataset.stacked === 'true',\n                    ticks: {\n                        beginAtZero: true\n                    },\n                }],\n            };\n        }\n\n        // Disable animation in edit mode\n        if (this.editableMode) {\n            chartData.options.animation = {\n                duration: 0,\n            };\n        }\n\n        const canvas = this.el.querySelector('canvas');\n        this.chart = new window.Chart(canvas, chartData);\n        return this._super.apply(this, arguments);\n    },\n    /**\n     * @override\n     * Discard all library changes to reset the state of the Html.\n     */\n    destroy: function () {\n        if (this.chart) { // The widget can be destroyed before start has completed\n            this.chart.destroy();\n            this.el.querySelectorAll('.chartjs-size-monitor').forEach(el => el.remove());\n        }\n        this._super.apply(this, arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     * @param {string} color A css color or theme color string\n     * @returns {string} Css color\n     */\n    _convertToCssColor: function (color) {\n        if (!color) {\n            return 'transparent';\n        }\n        return weUtils.getCSSVariableValue(color, this.style) || color;\n    },\n});\n\npublicWidget.registry.chart = ChartWidget;\n\nreturn ChartWidget;\n});\n", "/* global google */\nodoo.define('website.s_google_map', function (require) {\n'use strict';\n\nconst publicWidget = require('web.public.widget');\n\npublicWidget.registry.GoogleMap = publicWidget.Widget.extend({\n    selector: '.s_google_map',\n    disabledInEditableMode: false,\n\n    mapColors: {\n        lightMonoMap: [{\"featureType\": \"administrative.locality\", \"elementType\": \"all\", \"stylers\": [{\"hue\": \"#2c2e33\"}, {\"saturation\": 7}, {\"lightness\": 19}, {\"visibility\": \"on\"}]}, {\"featureType\": \"landscape\", \"elementType\": \"all\", \"stylers\": [{\"hue\": \"#ffffff\"}, {\"saturation\": -100}, {\"lightness\": 100}, {\"visibility\": \"simplified\"}]}, {\"featureType\": \"poi\", \"elementType\": \"all\", \"stylers\": [{\"hue\": \"#ffffff\"}, {\"saturation\": -100}, {\"lightness\": 100}, {\"visibility\": \"off\"}]}, {\"featureType\": \"road\", \"elementType\": \"geometry\", \"stylers\": [{\"hue\": \"#bbc0c4\"}, {\"saturation\": -93}, {\"lightness\": 31}, {\"visibility\": \"simplified\"}]}, {\"featureType\": \"road\", \"elementType\": \"labels\", \"stylers\": [{\"hue\": \"#bbc0c4\"}, {\"saturation\": -93}, {\"lightness\": 31}, {\"visibility\": \"on\"}]}, {\"featureType\": \"road.arterial\", \"elementType\": \"labels\", \"stylers\": [{\"hue\": \"#bbc0c4\"}, {\"saturation\": -93}, {\"lightness\": -2}, {\"visibility\": \"simplified\"}]}, {\"featureType\": \"road.local\", \"elementType\": \"geometry\", \"stylers\": [{\"hue\": \"#e9ebed\"}, {\"saturation\": -90}, {\"lightness\": -8}, {\"visibility\": \"simplified\"}]}, {\"featureType\": \"transit\", \"elementType\": \"all\", \"stylers\": [{\"hue\": \"#e9ebed\"}, {\"saturation\": 10}, {\"lightness\": 69}, {\"visibility\": \"on\"}]}, {\"featureType\": \"water\", \"elementType\": \"all\", \"stylers\": [{\"hue\": \"#e9ebed\"}, {\"saturation\": -78}, {\"lightness\": 67}, {\"visibility\": \"simplified\"}]}],\n        lillaMap: [{elementType: \"labels\", stylers: [{saturation: -20}]}, {featureType: \"poi\", elementType: \"labels\", stylers: [{visibility: \"off\"}]}, {featureType: 'road.highway', elementType: 'labels', stylers: [{visibility: \"off\"}]}, {featureType: \"road.local\", elementType: \"labels.icon\", stylers: [{visibility: \"off\"}]}, {featureType: \"road.arterial\", elementType: \"labels.icon\", stylers: [{visibility: \"off\"}]}, {featureType: \"road\", elementType: \"geometry.stroke\", stylers: [{visibility: \"off\"}]}, {featureType: \"transit\", elementType: \"geometry.fill\", stylers: [{hue: '#2d313f'}, {visibility: \"on\"}, {lightness: 5}, {saturation: -20}]}, {featureType: \"poi\", elementType: \"geometry.fill\", stylers: [{hue: '#2d313f'}, {visibility: \"on\"}, {lightness: 5}, {saturation: -20}]}, {featureType: \"poi.government\", elementType: \"geometry.fill\", stylers: [{hue: '#2d313f'}, {visibility: \"on\"}, {lightness: 5}, {saturation: -20}]}, {featureType: \"poi.sport_complex\", elementType: \"geometry.fill\", stylers: [{hue: '#2d313f'}, {visibility: \"on\"}, {lightness: 5}, {saturation: -20}]}, {featureType: \"poi.attraction\", elementType: \"geometry.fill\", stylers: [{hue: '#2d313f'}, {visibility: \"on\"}, {lightness: 5}, {saturation: -20}]}, {featureType: \"poi.business\", elementType: \"geometry.fill\", stylers: [{hue: '#2d313f'}, {visibility: \"on\"}, {lightness: 5}, {saturation: -20}]}, {featureType: \"transit\", elementType: \"geometry.fill\", stylers: [{hue: '#2d313f'}, {visibility: \"on\"}, {lightness: 5}, {saturation: -20}]}, {featureType: \"transit.station\", elementType: \"geometry.fill\", stylers: [{hue: '#2d313f'}, {visibility: \"on\"}, {lightness: 5}, {saturation: -20}]}, {featureType: \"landscape\", stylers: [{hue: '#2d313f'}, {visibility: \"on\"}, {lightness: 5}, {saturation: -20}]}, {featureType: \"road\", elementType: \"geometry.fill\", stylers: [{hue: '#2d313f'}, {visibility: \"on\"}, {lightness: 5}, {saturation: -20}]}, {featureType: \"road.highway\", elementType: \"geometry.fill\", stylers: [{hue: '#2d313f'}, {visibility: \"on\"}, {lightness: 5}, {saturation: -20}]}, {featureType: \"water\", elementType: \"geometry\", stylers: [{hue: '#2d313f'}, {visibility: \"on\"}, {lightness: 5}, {saturation: -20}]}],\n        blueMap: [{stylers: [{hue: \"#00ffe6\"}, {saturation: -20}]}, {featureType: \"road\", elementType: \"geometry\", stylers: [{lightness: 100}, {visibility: \"simplified\"}]}, {featureType: \"road\", elementType: \"labels\", stylers: [{visibility: \"off\"}]}],\n        retroMap: [{\"featureType\": \"administrative\", \"elementType\": \"all\", \"stylers\": [{\"visibility\": \"on\"}, {\"lightness\": 33}]}, {\"featureType\": \"landscape\", \"elementType\": \"all\", \"stylers\": [{\"color\": \"#f2e5d4\"}]}, {\"featureType\": \"poi.park\", \"elementType\": \"geometry\", \"stylers\": [{\"color\": \"#c5dac6\"}]}, {\"featureType\": \"poi.park\", \"elementType\": \"labels\", \"stylers\": [{\"visibility\": \"on\"}, {\"lightness\": 20}]}, {\"featureType\": \"road\", \"elementType\": \"all\", \"stylers\": [{\"lightness\": 20}]}, {\"featureType\": \"road.highway\", \"elementType\": \"geometry\", \"stylers\": [{\"color\": \"#c5c6c6\"}]}, {\"featureType\": \"road.arterial\", \"elementType\": \"geometry\", \"stylers\": [{\"color\": \"#e4d7c6\"}]}, {\"featureType\": \"road.local\", \"elementType\": \"geometry\", \"stylers\": [{\"color\": \"#fbfaf7\"}]}, {\"featureType\": \"water\", \"elementType\": \"all\", \"stylers\": [{\"visibility\": \"on\"}, {\"color\": \"#acbcc9\"}]}],\n        flatMap: [{\"stylers\": [{\"visibility\": \"off\"}]}, {\"featureType\": \"road\", \"stylers\": [{\"visibility\": \"on\"}, {\"color\": \"#ffffff\"}]}, {\"featureType\": \"road.arterial\", \"stylers\": [{\"visibility\": \"on\"}, {\"color\": \"#fee379\"}]}, {\"featureType\": \"road.highway\", \"stylers\": [{\"visibility\": \"on\"}, {\"color\": \"#fee379\"}]}, {\"featureType\": \"landscape\", \"stylers\": [{\"visibility\": \"on\"}, {\"color\": \"#f3f4f4\"}]}, {\"featureType\": \"water\", \"stylers\": [{\"visibility\": \"on\"}, {\"color\": \"#7fc8ed\"}]}, {}, {\"featureType\": \"road\", \"elementType\": \"labels\", \"stylers\": [{\"visibility\": \"on\"}]}, {\"featureType\": \"poi.park\", \"elementType\": \"geometry.fill\", \"stylers\": [{\"visibility\": \"on\"}, {\"color\": \"#83cead\"}]}, {\"elementType\": \"labels\", \"stylers\": [{\"visibility\": \"on\"}]}, {\"featureType\": \"landscape.man_made\", \"elementType\": \"geometry\", \"stylers\": [{\"weight\": 0.9}, {\"visibility\": \"off\"}]}],\n        cobaltMap: [{\"featureType\": \"all\", \"elementType\": \"all\", \"stylers\": [{\"invert_lightness\": true}, {\"saturation\": 10}, {\"lightness\": 30}, {\"gamma\": 0.5}, {\"hue\": \"#435158\"}]}],\n        cupertinoMap: [{\"featureType\": \"water\", \"elementType\": \"geometry\", \"stylers\": [{\"color\": \"#a2daf2\"}]}, {\"featureType\": \"landscape.man_made\", \"elementType\": \"geometry\", \"stylers\": [{\"color\": \"#f7f1df\"}]}, {\"featureType\": \"landscape.natural\", \"elementType\": \"geometry\", \"stylers\": [{\"color\": \"#d0e3b4\"}]}, {\"featureType\": \"landscape.natural.terrain\", \"elementType\": \"geometry\", \"stylers\": [{\"visibility\": \"off\"}]}, {\"featureType\": \"poi.park\", \"elementType\": \"geometry\", \"stylers\": [{\"color\": \"#bde6ab\"}]}, {\"featureType\": \"poi\", \"elementType\": \"labels\", \"stylers\": [{\"visibility\": \"off\"}]}, {\"featureType\": \"poi.medical\", \"elementType\": \"geometry\", \"stylers\": [{\"color\": \"#fbd3da\"}]}, {\"featureType\": \"poi.business\", \"stylers\": [{\"visibility\": \"off\"}]}, {\"featureType\": \"road\", \"elementType\": \"geometry.stroke\", \"stylers\": [{\"visibility\": \"off\"}]}, {\"featureType\": \"road\", \"elementType\": \"labels\", \"stylers\": [{\"visibility\": \"off\"}]}, {\"featureType\": \"road.highway\", \"elementType\": \"geometry.fill\", \"stylers\": [{\"color\": \"#ffe15f\"}]}, {\"featureType\": \"road.highway\", \"elementType\": \"geometry.stroke\", \"stylers\": [{\"color\": \"#efd151\"}]}, {\"featureType\": \"road.arterial\", \"elementType\": \"geometry.fill\", \"stylers\": [{\"color\": \"#ffffff\"}]}, {\"featureType\": \"road.local\", \"elementType\": \"geometry.fill\", \"stylers\": [{\"color\": \"black\"}]}, {\"featureType\": \"transit.station.airport\", \"elementType\": \"geometry.fill\", \"stylers\": [{\"color\": \"#cfb2db\"}]}],\n        carMap: [{\"featureType\": \"administrative\", \"stylers\": [{\"visibility\": \"off\"}]}, {\"featureType\": \"poi\", \"stylers\": [{\"visibility\": \"simplified\"}]}, {\"featureType\": \"road\", \"stylers\": [{\"visibility\": \"simplified\"}]}, {\"featureType\": \"water\", \"stylers\": [{\"visibility\": \"simplified\"}]}, {\"featureType\": \"transit\", \"stylers\": [{\"visibility\": \"simplified\"}]}, {\"featureType\": \"landscape\", \"stylers\": [{\"visibility\": \"simplified\"}]}, {\"featureType\": \"road.highway\", \"stylers\": [{\"visibility\": \"off\"}]}, {\"featureType\": \"road.local\", \"stylers\": [{\"visibility\": \"on\"}]}, {\"featureType\": \"road.highway\", \"elementType\": \"geometry\", \"stylers\": [{\"visibility\": \"on\"}]}, {\"featureType\": \"water\", \"stylers\": [{\"color\": \"#84afa3\"}, {\"lightness\": 52}]}, {\"stylers\": [{\"saturation\": -77}]}, {\"featureType\": \"road\"}],\n        bwMap: [{stylers: [{hue: \"#00ffe6\"}, {saturation: -100}]}, {featureType: \"road\", elementType: \"geometry\", stylers: [{lightness: 100}, {visibility: \"simplified\"}]}, {featureType: \"road\", elementType: \"labels\", stylers: [{visibility: \"off\"}]}],\n    },\n\n    /**\n     * @override\n     */\n    async start() {\n        await this._super(...arguments);\n\n        if (typeof google !== 'object' || typeof google.maps !== 'object') {\n            await new Promise(resolve => {\n                this.trigger_up('gmap_api_request', {\n                    editableMode: this.editableMode,\n                    onSuccess: () => resolve(),\n                });\n            });\n            // The animation will be restarted for all maps as soon as the\n            // google map script has been executed.\n            return;\n        }\n\n        // Define a default map's colors set\n        const std = [];\n        new google.maps.StyledMapType(std, {name: \"Std Map\"});\n\n        // Default options, will be overwritten by the user\n        const myOptions = {\n            zoom: 12,\n            center: new google.maps.LatLng(50.854975, 4.3753899),\n            mapTypeId: google.maps.MapTypeId.ROADMAP,\n            panControl: false,\n            zoomControl: false,\n            mapTypeControl: false,\n            streetViewControl: false,\n            scrollwheel: false,\n            mapTypeControlOptions: {\n                mapTypeIds: [google.maps.MapTypeId.ROADMAP, 'map_style']\n            }\n        };\n\n        // Render Map\n        const mapC = this.$('.map_container');\n        const map = new google.maps.Map(mapC.get(0), myOptions);\n\n        // Update GPS position\n        const p = this.el.dataset.mapGps.substring(1).slice(0, -1).split(',');\n        const gps = new google.maps.LatLng(p[0], p[1]);\n        map.setCenter(gps);\n\n        // Update Map on screen resize\n        window.addEventListener('resize', () => {\n            map.setCenter(gps);\n        });\n\n        // Create Marker & Infowindow\n        const markerOptions = {\n            map: map,\n            animation: google.maps.Animation.DROP,\n            position: new google.maps.LatLng(p[0], p[1])\n        };\n        if (this.el.dataset.pinStyle === 'flat') {\n            markerOptions.icon = '/website/static/src/img/snippets_thumbs/s_google_map_marker.png';\n        }\n        new google.maps.Marker(markerOptions);\n\n        map.setMapTypeId(google.maps.MapTypeId[this.el.dataset.mapType]); // Update Map Type\n        map.setZoom(parseInt(this.el.dataset.mapZoom)); // Update Map Zoom\n\n        // Update Map Color\n        const mapColorAttr = this.el.dataset.mapColor;\n        if (mapColorAttr) {\n            const mapColor = this.mapColors[mapColorAttr];\n            map.mapTypes.set('map_style', new google.maps.StyledMapType(mapColor, {name: \"Styled Map\"}));\n            map.setMapTypeId('map_style');\n        }\n    },\n});\n});\n", "odoo.define('website.s_dynamic_snippet', function (require) {\n'use strict';\n\nconst core = require('web.core');\nconst config = require('web.config');\nconst publicWidget = require('web.public.widget');\nconst {Markup} = require('web.utils');\n\nconst DynamicSnippet = publicWidget.Widget.extend({\n    selector: '.s_dynamic_snippet',\n    xmlDependencies: ['/website/static/src/snippets/s_dynamic_snippet/000.xml'],\n    read_events: {\n        'click [data-url]': '_onCallToAction',\n    },\n    disabledInEditableMode: false,\n\n    /**\n     *\n     * @override\n     */\n    init: function () {\n        this._super.apply(this, arguments);\n        /**\n         * The dynamic filter data source data formatted with the chosen template.\n         * Can be accessed when overriding the _render_content() function in order to generate\n         * a new renderedContent from the original data.\n         *\n         * @type {*|jQuery.fn.init|jQuery|HTMLElement}\n         */\n        this.data = [];\n        this.renderedContent = '';\n        this.isDesplayedAsMobile = config.device.isMobile;\n        this.uniqueId = _.uniqueId('s_dynamic_snippet_');\n        this.template_key = 'website.s_dynamic_snippet.grid';\n    },\n    /**\n     *\n     * @override\n     */\n    willStart: function () {\n        return this._super.apply(this, arguments).then(\n            () => Promise.all([\n                this._fetchData(),\n            ])\n        );\n    },\n    /**\n     *\n     * @override\n     */\n    start: function () {\n        return this._super.apply(this, arguments)\n            .then(() => {\n                this._setupSizeChangedManagement(true);\n                this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerUnactive();\n                this._render();\n                this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerActive();\n            });\n    },\n    /**\n     *\n     * @override\n     */\n    destroy: function () {\n        this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerUnactive();\n        this._toggleVisibility(false);\n        this._setupSizeChangedManagement(false);\n        this._clearContent();\n        this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerActive();\n        this._super.apply(this, arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _clearContent: function () {\n        const $templateArea = this.$el.find('.dynamic_snippet_template');\n        this.trigger_up('widgets_stop_request', {\n            $target: $templateArea,\n        });\n        $templateArea.html('');\n    },\n    /**\n     * Method to be overridden in child components if additional configuration elements\n     * are required in order to fetch data.\n     * @private\n     */\n    _isConfigComplete: function () {\n        return this.$el.get(0).dataset.filterId !== undefined && this.$el.get(0).dataset.templateKey !== undefined;\n    },\n    /**\n     * Method to be overridden in child components in order to provide a search\n     * domain if needed.\n     * @private\n     */\n    _getSearchDomain: function () {\n        return [];\n    },\n    /**\n     * Method to be overridden in child components in order to add custom parameters if needed.\n     * @private\n     */\n    _getRpcParameters: function () {\n        return {};\n    },\n    /**\n     * Fetches the data.\n     * @private\n     */\n    async _fetchData() {\n        if (this._isConfigComplete()) {\n            const nodeData = this.el.dataset;\n            const filterFragments = await this._rpc({\n                'route': '/website/snippet/filters',\n                'params': Object.assign({\n                    'filter_id': parseInt(nodeData.filterId),\n                    'template_key': nodeData.templateKey,\n                    'limit': parseInt(nodeData.numberOfRecords),\n                    'search_domain': this._getSearchDomain(),\n                    'with_sample': this.editableMode,\n                    'context': {\n                        // TODO adapt in master (see _bugfix_force_minimum_max_limit_to_16)\n                        // in python. The `forceMinimumMaxLimitTo16` value in the\n                        // dataset is there only in dynamic snippets whose options\n                        // have been configured after this fix was merged.\n                        '_bugfix_force_minimum_max_limit_to_16': !!nodeData.forceMinimumMaxLimitTo16,\n                    },\n                }, this._getRpcParameters()),\n            });\n            this.data = filterFragments.map(Markup);\n        } else {\n            this.data = [];\n        }\n    },\n    /**\n     * Method to be overridden in child components in order to prepare content\n     * before rendering.\n     * @private\n     */\n    _prepareContent: function () {\n        if (this.$target[0].dataset.numberOfElements && this.$target[0].dataset.numberOfElementsSmallDevices) {\n            this.renderedContent = core.qweb.render(\n                this.template_key,\n                this._getQWebRenderOptions());\n        } else {\n            this.renderedContent = '';\n        }\n    },\n    /**\n     * Method to be overridden in child components in order to prepare QWeb\n     * options.\n     * @private\n     */\n    _getQWebRenderOptions: function () {\n        return {\n            chunkSize: parseInt(\n                config.device.isMobile\n                    ? this.$target[0].dataset.numberOfElementsSmallDevices\n                    : this.$target[0].dataset.numberOfElements\n            ),\n            data: this.data,\n            uniqueId: this.uniqueId\n        };\n    },\n    /**\n     *\n     * @private\n     */\n    _render: function () {\n        if (this.data.length > 0 || this.editableMode) {\n            this.$el.removeClass('o_dynamic_empty');\n            this._prepareContent();\n        } else {\n            this.$el.addClass('o_dynamic_empty');\n            this.renderedContent = '';\n        }\n        // TODO Remove in master: adapt already existing snippet from former version.\n        const classList = [...this.$el[0].classList];\n        if (classList.includes('d-none') && !classList.some(className => className.match(/^d-(md|lg)-/))) {\n            // Remove the 'd-none' of the old template if it is not related to\n            // the visible on mobile option.\n            this.$el[0].classList.remove('d-none');\n        }\n        this._renderContent();\n    },\n    /**\n     * @private\n     */\n    _renderContent: function () {\n        const $templateArea = this.$el.find('.dynamic_snippet_template');\n        this.trigger_up('widgets_stop_request', {\n            $target: $templateArea,\n        });\n        $templateArea.html(this.renderedContent);\n        // TODO this is probably not the only public widget which creates DOM\n        // which should be attached to another public widget. Maybe a generic\n        // method could be added to properly do this operation of DOM addition.\n        this.trigger_up('widgets_start_request', {\n            $target: $templateArea,\n            editableMode: this.editableMode,\n        });\n    },\n    /**\n     *\n     * @param {Boolean} enable\n     * @private\n     */\n    _setupSizeChangedManagement: function (enable) {\n        if (enable === true) {\n            config.device.bus.on('size_changed', this, this._onSizeChanged);\n        } else {\n            config.device.bus.off('size_changed', this, this._onSizeChanged);\n        }\n    },\n    /**\n     *\n     * @param visible\n     * @private\n     */\n    _toggleVisibility: function (visible) {\n        this.$el.toggleClass('o_dynamic_empty', !visible);\n    },\n\n    //------------------------------------- -------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * Navigates to the call to action url.\n     * @private\n     */\n    _onCallToAction: function (ev) {\n        window.location = $(ev.currentTarget).attr('data-url');\n    },\n    /**\n     * Called when the size has reached a new bootstrap breakpoint.\n     *\n     * @private\n     * @param {number} size as Integer @see web.config.device.SIZES\n     */\n    _onSizeChanged: function (size) {\n        if (this.isDesplayedAsMobile !== config.device.isMobile) {\n            this.isDesplayedAsMobile = config.device.isMobile;\n            this._render();\n        }\n    },\n});\n\npublicWidget.registry.dynamic_snippet = DynamicSnippet;\n\nreturn DynamicSnippet;\n\n});\n", "odoo.define('website.s_dynamic_snippet_carousel', function (require) {\n'use strict';\n\nconst config = require('web.config');\nconst core = require('web.core');\nconst publicWidget = require('web.public.widget');\nconst DynamicSnippet = require('website.s_dynamic_snippet');\n\nconst DynamicSnippetCarousel = DynamicSnippet.extend({\n    selector: '.s_dynamic_snippet_carousel',\n    xmlDependencies: (DynamicSnippet.prototype.xmlDependencies || []).concat(\n        ['/website/static/src/snippets/s_dynamic_snippet_carousel/000.xml']\n    ),\n\n    /**\n     *\n     * @override\n     */\n    init: function () {\n        this._super.apply(this, arguments);\n        this.template_key = 'website.s_dynamic_snippet.carousel';\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @override\n     */\n    _getQWebRenderOptions: function () {\n        return Object.assign(\n            this._super.apply(this, arguments),\n            {\n                interval: parseInt(this.$target[0].dataset.carouselInterval),\n            },\n        );\n    },\n    /**\n     * @todo remove me in master.\n     */\n    _renderContent: function () {\n        this._super.apply(this, arguments);\n        this._computeHeights();\n    },\n    /**\n     * @todo remove me in master. This is already automatically done by the\n     * related public widget which is also in charge of initializing the\n     * carousel behaviors. This is left to be done twice in stable to not break\n     * potential custo.\n     */\n    _computeHeights: function () {\n        var maxHeight = 0;\n        var $items = this.$('.carousel-item');\n        $items.css('min-height', '');\n        _.each($items, function (el) {\n            var $item = $(el);\n            var isActive = $item.hasClass('active');\n            $item.addClass('active');\n            var height = $item.outerHeight();\n            if (height > maxHeight) {\n                maxHeight = height;\n            }\n            $item.toggleClass('active', isActive);\n        });\n        $items.css('min-height', maxHeight);\n    },\n});\npublicWidget.registry.dynamic_snippet_carousel = DynamicSnippetCarousel;\n\nreturn DynamicSnippetCarousel;\n});\n", "odoo.define('website.s_website_form', function (require) {\n    'use strict';\n\n    var core = require('web.core');\n    var time = require('web.time');\n    const {ReCaptcha} = require('google_recaptcha.ReCaptchaV3');\n    const session = require('web.session');\n    var ajax = require('web.ajax');\n    var publicWidget = require('web.public.widget');\n    const dom = require('web.dom');\n    const concurrency = require('web.concurrency');\n\n    var _t = core._t;\n    var qweb = core.qweb;\n\n    publicWidget.registry.EditModeWebsiteForm = publicWidget.Widget.extend({\n        selector: '.s_website_form form, form.s_website_form', // !compatibility\n        disabledInEditableMode: false,\n        /**\n         * @override\n         */\n        start: function () {\n            if (this.editableMode) {\n                // We do not initialize the datetime picker in edit mode but want the dates to be formated\n                const dateTimeFormat = time.getLangDatetimeFormat();\n                const dateFormat = time.getLangDateFormat();\n                this.$target[0].querySelectorAll('.s_website_form_input.datetimepicker-input').forEach(el => {\n                    const value = el.getAttribute('value');\n                    if (value) {\n                        const format = el.closest('.s_website_form_field').dataset.type === 'date' ? dateFormat : dateTimeFormat;\n                        el.value = moment.unix(value).format(format);\n                    }\n                });\n            }\n            return this._super(...arguments);\n        },\n    });\n\n    publicWidget.registry.s_website_form = publicWidget.Widget.extend({\n        selector: '.s_website_form form, form.s_website_form', // !compatibility\n        xmlDependencies: ['/website/static/src/xml/website_form.xml'],\n        events: {\n            'click .s_website_form_send, .o_website_form_send': 'send', // !compatibility\n        },\n\n        /**\n         * @constructor\n         */\n        init: function () {\n            this._super(...arguments);\n            this._recaptcha = new ReCaptcha();\n            this.initialValues = new Map();\n            this._visibilityFunctionByFieldName = new Map();\n            this._visibilityFunctionByFieldEl = new Map();\n            this.__started = new Promise(resolve => this.__startResolve = resolve);\n        },\n        willStart: async function () {\n            const res = this._super(...arguments);\n            if (!this.$target[0].classList.contains('s_website_form_no_recaptcha')) {\n                this._recaptchaLoaded = true;\n                this._recaptcha.loadLibs();\n            }\n            // fetch user data (required by fill-with behavior)\n            this.preFillValues = {};\n            if (session.user_id) {\n                this.preFillValues = (await this._rpc({\n                    model: 'res.users',\n                    method: 'read',\n                    args: [session.user_id, this._getUserPreFillFields()],\n                }))[0] || {};\n            }\n\n            return res;\n        },\n        start: function () {\n            // Reset the form first, as it is still filled when coming back\n            // after a redirect.\n            this.$target[0].reset();\n\n            // Prepare visibility data and update field visibilities\n            const visibilityFunctionsByFieldName = new Map();\n            for (const fieldEl of this.$target[0].querySelectorAll('[data-visibility-dependency]')) {\n                const inputName = fieldEl.querySelector('.s_website_form_input').name;\n                if (!visibilityFunctionsByFieldName.has(inputName)) {\n                    visibilityFunctionsByFieldName.set(inputName, []);\n                }\n                const func = this._buildVisibilityFunction(fieldEl);\n                visibilityFunctionsByFieldName.get(inputName).push(func);\n                this._visibilityFunctionByFieldEl.set(fieldEl, func);\n            }\n            for (const [name, funcs] of visibilityFunctionsByFieldName.entries()) {\n                this._visibilityFunctionByFieldName.set(name, () => funcs.some(func => func()));\n            }\n\n            this._onFieldInputDebounced = _.debounce(this._onFieldInput.bind(this), 400);\n            this.$el.on('input.s_website_form', '.s_website_form_field', this._onFieldInputDebounced);\n\n            // Initialize datetimepickers\n            var datepickers_options = {\n                minDate: moment({y: 1000}),\n                maxDate: moment({y: 9999, M: 11, d: 31}),\n                calendarWeeks: true,\n                icons: {\n                    time: 'fa fa-clock-o',\n                    date: 'fa fa-calendar',\n                    next: 'fa fa-chevron-right',\n                    previous: 'fa fa-chevron-left',\n                    up: 'fa fa-chevron-up',\n                    down: 'fa fa-chevron-down',\n                },\n                locale: moment.locale(),\n                format: time.getLangDatetimeFormat(),\n                extraFormats: ['X'],\n            };\n            const $datetimes = this.$target.find('.s_website_form_datetime, .o_website_form_datetime'); // !compatibility\n            $datetimes.datetimepicker(datepickers_options);\n\n            // Adapt options to date-only pickers\n            datepickers_options.format = time.getLangDateFormat();\n            const $dates = this.$target.find('.s_website_form_date, .o_website_form_date'); // !compatibility\n            $dates.datetimepicker(datepickers_options);\n\n            this.$allDates = $datetimes.add($dates);\n            this.$allDates.addClass('s_website_form_datepicker_initialized');\n\n            // Display form values from tag having data-for attribute\n            // It's necessary to handle field values generated on server-side\n            // Because, using t-att- inside form make it non-editable\n            // Data-fill-with attribute is given during registry and is used by\n            // to know which user data should be used to prfill fields.\n            const dataForEl = document.querySelector(`[data-for='${this.$target[0].id}']`);\n            if (dataForEl || Object.keys(this.preFillValues).length) {\n                const dataForValues = dataForEl ?\n                    JSON.parse(dataForEl.dataset.values\n                        // replaces `True` by `true` if they are after `,` or `:` or `[`\n                        .replace(/([,:\\[]\\s*)True/g, '$1true')\n                        // replaces `False` and `None` by `\"\"` if they are after `,` or `:` or `[`\n                        .replace(/([,:\\[]\\s*)(False|None)/g, '$1\"\"')\n                        // replaces the `'` by `\"` if they are before `,` or `:` or `]` or `}`\n                        .replace(/'(\\s*[,:\\]}])/g, '\"$1')\n                        // replaces the `'` by `\"` if they are after `{` or `[` or `,` or `:`\n                        .replace(/([{\\[:,]\\s*)'/g, '$1\"')\n                    ) : {};\n                const fieldNames = this.$target.serializeArray().map(el => el.name);\n                // All types of inputs do not have a value property (eg:hidden),\n                // for these inputs any function that is supposed to put a value\n                // property actually puts a HTML value attribute. Because of\n                // this, we have to clean up these values at destroy or else the\n                // data loaded here could become default values. We could set\n                // the values to submit() for these fields but this could break\n                // customizations that use the current behavior as a feature.\n                for (const name of fieldNames) {\n                    const fieldEl = this.$target[0].querySelector(`[name=\"${name}\"]`);\n\n                    // In general, we want the data-for and prefill values to\n                    // take priority over set default values. The 'email_to'\n                    // field is however treated as an exception at the moment\n                    // so that values set by users are always used.\n                    if (name === 'email_to' && fieldEl.value\n                            // The following value is the default value that\n                            // is set if the form is edited in any way. (see the\n                            // website.form_editor_registry module in editor\n                            // assets bundle).\n                            // TODO that value should probably never be forced\n                            // unless explicitely manipulated by the user or on\n                            // custom form addition but that seems risky to\n                            // change as a stable fix.\n                            && fieldEl.value !== 'info@yourcompany.example.com') {\n                        continue;\n                    }\n\n                    let newValue;\n                    if (dataForValues && dataForValues[name]) {\n                        newValue = dataForValues[name];\n                    } else if (this.preFillValues[fieldEl.dataset.fillWith]) {\n                        newValue = this.preFillValues[fieldEl.dataset.fillWith];\n                    }\n                    if (newValue) {\n                        this.initialValues.set(fieldEl, fieldEl.getAttribute('value'));\n                        fieldEl.value = newValue;\n                    }\n                }\n            }\n            this._updateFieldsVisibility();\n\n            // Check disabled states\n            this.inputEls = this.$target[0].querySelectorAll('.s_website_form_field.s_website_form_field_hidden_if .s_website_form_input');\n            this._disabledStates = new Map();\n            for (const inputEl of this.inputEls) {\n                this._disabledStates[inputEl] = inputEl.disabled;\n            }\n\n            return this._super(...arguments).then(() => this.__startResolve());\n        },\n\n        destroy: function () {\n            this._super.apply(this, arguments);\n            this.$target.find('button').off('click');\n\n            // Empty imputs\n            this.$target[0].reset();\n\n            // Apply default values\n            const dateTimeFormat = time.getLangDatetimeFormat();\n            const dateFormat = time.getLangDateFormat();\n            this.$target[0].querySelectorAll('input[type=\"text\"], input[type=\"email\"], input[type=\"number\"]').forEach(el => {\n                let value = el.getAttribute('value');\n                if (value) {\n                    if (el.classList.contains('datetimepicker-input')) {\n                        const format = el.closest('.s_website_form_field').dataset.type === 'date' ? dateFormat : dateTimeFormat;\n                        value = moment.unix(value).format(format);\n                    }\n                    el.value = value;\n                }\n            });\n            this.$target[0].querySelectorAll('textarea').forEach(el => el.value = el.textContent);\n\n            // Remove saving of the error colors\n            this.$target.find('.o_has_error').removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');\n\n            // Remove the status message\n            this.$target.find('#s_website_form_result, #o_website_form_result').empty(); // !compatibility\n\n            // Remove the success message and display the form\n            this.$target.removeClass('d-none');\n            this.$target.parent().find('.s_website_form_end_message').addClass('d-none');\n\n            // Reinitialize dates\n            this.$allDates.removeClass('s_website_form_datepicker_initialized');\n\n            // Restore disabled attribute\n            for (const inputEl of this.inputEls) {\n                inputEl.disabled = !!this._disabledStates.get(inputEl);\n            }\n\n            // All 'hidden if' fields start with d-none\n            this.$target[0].querySelectorAll('.s_website_form_field_hidden_if:not(.d-none)').forEach(el => el.classList.add('d-none'));\n\n            // Reset the initial default values.\n            for (const [fieldEl, initialValue] of this.initialValues.entries()) {\n                if (initialValue) {\n                    fieldEl.setAttribute('value', initialValue);\n                } else {\n                    fieldEl.removeAttribute('value');\n                }\n            }\n\n            this.$el.off('.s_website_form');\n        },\n\n        send: async function (e) {\n            e.preventDefault(); // Prevent the default submit behavior\n             // Prevent users from crazy clicking\n            const $button = this.$target.find('.s_website_form_send, .o_website_form_send');\n            $button.addClass('disabled') // !compatibility\n                   .attr('disabled', 'disabled');\n            this.restoreBtnLoading = dom.addButtonLoadingEffect($button[0]);\n\n            var self = this;\n\n            self.$target.find('#s_website_form_result, #o_website_form_result').empty(); // !compatibility\n            if (!self.check_error_fields({})) {\n                self.update_status('error', _t(\"Please fill in the form correctly.\"));\n                return false;\n            }\n\n            // Prepare form inputs\n            this.form_fields = this.$target.serializeArray();\n            $.each(this.$target.find('input[type=file]:not([disabled])'), (outer_index, input) => {\n                $.each($(input).prop('files'), function (index, file) {\n                    // Index field name as ajax won't accept arrays of files\n                    // when aggregating multiple files into a single field value\n                    self.form_fields.push({\n                        name: input.name + '[' + outer_index + '][' + index + ']',\n                        value: file\n                    });\n                });\n            });\n\n            // Serialize form inputs into a single object\n            // Aggregate multiple values into arrays\n            var form_values = {};\n            _.each(this.form_fields, function (input) {\n                if (input.name in form_values) {\n                    // If a value already exists for this field,\n                    // we are facing a x2many field, so we store\n                    // the values in an array.\n                    if (Array.isArray(form_values[input.name])) {\n                        form_values[input.name].push(input.value);\n                    } else {\n                        form_values[input.name] = [form_values[input.name], input.value];\n                    }\n                } else {\n                    if (input.value !== '') {\n                        form_values[input.name] = input.value;\n                    }\n                }\n            });\n\n            // force server date format usage for existing fields\n            this.$target.find('.s_website_form_field:not(.s_website_form_custom)')\n            .find('.s_website_form_date, .s_website_form_datetime').each(function () {\n                const inputEl = this.querySelector('input');\n\n                // Datetimepicker('viewDate') will return `new Date()` if the\n                // input is empty but we want to keep the empty value\n                if (!inputEl.value) {\n                    return;\n                }\n\n                var date = $(this).datetimepicker('viewDate').clone().locale('en');\n                var format = 'YYYY-MM-DD';\n                if ($(this).hasClass('s_website_form_datetime')) {\n                    date = date.utc();\n                    format = 'YYYY-MM-DD HH:mm:ss';\n                }\n                form_values[inputEl.getAttribute('name')] = date.format(format);\n            });\n\n            if (this._recaptchaLoaded) {\n                const tokenObj = await this._recaptcha.getToken('website_form');\n                if (tokenObj.token) {\n                    form_values['recaptcha_token_response'] = tokenObj.token;\n                } else if (tokenObj.error) {\n                    self.update_status('error', tokenObj.error);\n                    return false;\n                }\n            }\n\n            // Post form and handle result\n            ajax.post(this.$target.attr('action') + (this.$target.data('force_action') || this.$target.data('model_name')), form_values)\n            .then(async function (result_data) {\n                // Restore send button behavior\n                self.$target.find('.s_website_form_send, .o_website_form_send')\n                    .removeAttr('disabled')\n                    .removeClass('disabled'); // !compatibility\n                result_data = JSON.parse(result_data);\n                if (!result_data.id) {\n                    // Failure, the server didn't return the created record ID\n                    self.update_status('error', result_data.error ? result_data.error : false);\n                    if (result_data.error_fields) {\n                        // If the server return a list of bad fields, show these fields for users\n                        self.check_error_fields(result_data.error_fields);\n                    }\n                } else {\n                    // Success, redirect or update status\n                    let successMode = self.$target[0].dataset.successMode;\n                    let successPage = self.$target[0].dataset.successPage;\n                    if (!successMode) {\n                        successPage = self.$target.attr('data-success_page'); // Compatibility\n                        successMode = successPage ? 'redirect' : 'nothing';\n                    }\n                    switch (successMode) {\n                        case 'redirect': {\n                            let hashIndex = successPage.indexOf(\"#\");\n                            if (hashIndex > 0) {\n                                // URL containing an anchor detected: extract\n                                // the anchor from the URL if the URL is the\n                                // same as the current page URL so we can scroll\n                                // directly to the element (if found) later\n                                // instead of redirecting.\n                                // Note that both currentUrlPath and successPage\n                                // can exist with or without a trailing slash\n                                // before the hash (e.g. \"domain.com#footer\" or\n                                // \"domain.com/#footer\"). Therefore, if they are\n                                // not present, we add them to be able to\n                                // compare the two variables correctly.\n                                let currentUrlPath = window.location.pathname;\n                                if (!currentUrlPath.endsWith(\"/\")) {\n                                    currentUrlPath = currentUrlPath + \"/\";\n                                }\n                                if (!successPage.includes(\"/#\")) {\n                                    successPage = successPage.replace(\"#\", \"/#\");\n                                    hashIndex++;\n                                }\n                                if ([successPage, \"/\" + session.lang_url_code + successPage].some(link => link.startsWith(currentUrlPath + '#'))) {\n                                    successPage = successPage.substring(hashIndex);\n                                }\n                            }\n                            if (successPage.charAt(0) === \"#\") {\n                                const successAnchorEl = document.getElementById(successPage.substring(1));\n                                if (successAnchorEl) {\n                                    await dom.scrollTo(successAnchorEl, {\n                                        duration: 500,\n                                        extraOffset: 0,\n                                    });\n                                }\n                                break;\n                            }\n                            $(window.location).attr('href', successPage);\n                            return;\n                        }\n                        case 'message': {\n                            // Prevent double-clicking on the send button and\n                            // add a upload loading effect (delay before success\n                            // message)\n                            await concurrency.delay(dom.DEBOUNCE);\n\n                            self.$target[0].classList.add('d-none');\n                            self.$target[0].parentElement.querySelector('.s_website_form_end_message').classList.remove('d-none');\n                            break;\n                        }\n                        default: {\n                            // Prevent double-clicking on the send button and\n                            // add a upload loading effect (delay before success\n                            // message)\n                            await concurrency.delay(dom.DEBOUNCE);\n\n                            self.update_status('success');\n                            break;\n                        }\n                    }\n\n                    self.$target[0].reset();\n                    self.restoreBtnLoading();\n                }\n            })\n            .guardedCatch(error => {\n                this.update_status(\n                    'error',\n                    error.status && error.status === 413 ? _t(\"Uploaded file is too large.\") : \"\",\n                );\n            });\n        },\n\n        check_error_fields: function (error_fields) {\n            var self = this;\n            var form_valid = true;\n            // Loop on all fields\n            this.$target.find('.form-field, .s_website_form_field').each(function (k, field) { // !compatibility\n                var $field = $(field);\n                // FIXME that seems broken, \"for\" does not contain the field\n                // but this is used to retrieve errors sent from the server...\n                // need more investigation.\n                var field_name = $field.find('.col-form-label').attr('for');\n\n                // Validate inputs for this field\n                var inputs = $field.find('.s_website_form_input, .o_website_form_input').not('#editable_select'); // !compatibility\n                var invalid_inputs = inputs.toArray().filter(function (input, k, inputs) {\n                    // Special check for multiple required checkbox for same\n                    // field as it seems checkValidity forces every required\n                    // checkbox to be checked, instead of looking at other\n                    // checkboxes with the same name and only requiring one\n                    // of them to be valid.\n                    if (input.required && input.type === 'checkbox') {\n                        // Considering we are currently processing a single\n                        // field, we can assume that all checkboxes in the\n                        // inputs variable have the same name\n                        // TODO should be improved: probably do not need to\n                        // filter neither on required, nor on checkbox and\n                        // checking the validity of the group of checkbox is\n                        // currently done for each checkbox of that group...\n                        var checkboxes = _.filter(inputs, function (input) {\n                            return input.required && input.type === 'checkbox';\n                        });\n                        return !_.any(checkboxes, checkbox => checkbox.checkValidity());\n\n                    // Special cases for dates and datetimes\n                    // FIXME this seems like dead code, the inputs do not use\n                    // those classes, their parent does (but it seemed to work\n                    // at some point given that https://github.com/odoo/odoo/commit/75e03c0f7692a112e1b0fa33267f4939363f3871\n                    // was made)... need more investigation (if restored,\n                    // consider checking the date inputs are not disabled before\n                    // saying they are invalid (see checkValidity used here))\n                    } else if ($(input).hasClass('s_website_form_date') || $(input).hasClass('o_website_form_date')) { // !compatibility\n                        if (!self.is_datetime_valid(input.value, 'date')) {\n                            return true;\n                        }\n                    } else if ($(input).hasClass('s_website_form_datetime') || $(input).hasClass('o_website_form_datetime')) { // !compatibility\n                        if (!self.is_datetime_valid(input.value, 'datetime')) {\n                            return true;\n                        }\n                    }\n\n                    // Note that checkValidity also takes care of the case where\n                    // the input is disabled, in which case, it is considered\n                    // valid (as the data will not be sent anyway).\n                    // This takes care of conditionally-hidden fields (whose\n                    // inputs are disabled while they are hidden) which should\n                    // not require validation while they are hidden. Indeed,\n                    // their purpose is to be able to enter additional data when\n                    // some condition is fulfilled. If such a field is required,\n                    // it is only required when visible for example.\n                    return !input.checkValidity();\n                });\n\n                // Update field color if invalid or erroneous\n                const $controls = $field.find('.form-control, .custom-select, .form-check-input, .form-control-file');\n                $field.removeClass('o_has_error');\n                $controls.removeClass('is-invalid');\n                if (invalid_inputs.length || error_fields[field_name]) {\n                    $field.addClass('o_has_error');\n                    $controls.addClass('is-invalid');\n                    if (_.isString(error_fields[field_name])) {\n                        $field.popover({content: error_fields[field_name], trigger: 'hover', container: 'body', placement: 'top'});\n                        // update error message and show it.\n                        $field.data(\"bs.popover\").config.content = error_fields[field_name];\n                        $field.popover('show');\n                    }\n                    form_valid = false;\n                }\n            });\n            return form_valid;\n        },\n\n        is_datetime_valid: function (value, type_of_date) {\n            if (value === \"\") {\n                return true;\n            } else {\n                try {\n                    this.parse_date(value, type_of_date);\n                    return true;\n                } catch (e) {\n                    return false;\n                }\n            }\n        },\n\n        // This is a stripped down version of format.js parse_value function\n        parse_date: function (value, type_of_date, value_if_empty) {\n            var date_pattern = time.getLangDateFormat(),\n                time_pattern = time.getLangTimeFormat();\n            var date_pattern_wo_zero = date_pattern.replace('MM', 'M').replace('DD', 'D'),\n                time_pattern_wo_zero = time_pattern.replace('HH', 'H').replace('mm', 'm').replace('ss', 's');\n            switch (type_of_date) {\n                case 'datetime':\n                    var datetime = moment(value, [date_pattern + ' ' + time_pattern, date_pattern_wo_zero + ' ' + time_pattern_wo_zero], true);\n                    if (datetime.isValid()) {\n                        return time.datetime_to_str(datetime.toDate());\n                    }\n                    throw new Error(_.str.sprintf(_t(\"'%s' is not a correct datetime\"), value));\n                case 'date':\n                    var date = moment(value, [date_pattern, date_pattern_wo_zero], true);\n                    if (date.isValid()) {\n                        return time.date_to_str(date.toDate());\n                    }\n                    throw new Error(_.str.sprintf(_t(\"'%s' is not a correct date\"), value));\n            }\n            return value;\n        },\n\n        update_status: function (status, message) {\n            if (status !== 'success') { // Restore send button behavior if result is an error\n                this.$target.find('.s_website_form_send, .o_website_form_send')\n                    .removeAttr('disabled')\n                    .removeClass('disabled'); // !compatibility\n                this.restoreBtnLoading();\n            }\n            var $result = this.$('#s_website_form_result, #o_website_form_result'); // !compatibility\n\n            if (status === 'error' && !message) {\n                message = _t(\"An error has occured, the form has not been sent.\");\n            }\n\n            // Note: we still need to wait that the widget is properly started\n            // before any qweb rendering which depends on xmlDependencies\n            // because the event handlers are binded before the call to\n            // willStart for public widgets...\n            this.__started.then(() => $result.replaceWith(qweb.render(`website.s_website_form_status_${status}`, {\n                message: message,\n            })));\n        },\n\n        //----------------------------------------------------------------------\n        // Private\n        //----------------------------------------------------------------------\n\n        /**\n         * Gets the user's field needed to be fetched to pre-fill the form.\n         *\n         * @returns {string[]} List of user's field that have to be fetched.\n         */\n        _getUserPreFillFields() {\n            return ['name', 'phone', 'email', 'commercial_company_name'];\n        },\n        /**\n         * Compares the value with the comparable (and the between) with\n         * comparator as a means to compare\n         *\n         * @private\n         * @param {string} comparator The way that $value and $comparable have\n         *      to be compared\n         * @param {string} [value] The value of the field\n         * @param {string} [comparable] The value to compare\n         * @param {string} [between] The maximum date value in case comparator\n         *      is between or !between\n         * @returns {boolean}\n         */\n        _compareTo(comparator, value = '', comparable, between) {\n            // Value can be null when the compared field is supposed to be\n            // visible, but is not yet retrievable from the FormData() because\n            // the field was conditionally hidden. It can be considered empty.\n            if (value === null) {\n                value = '';\n            }\n\n            switch (comparator) {\n                case 'contains':\n                    return value.includes(comparable);\n                case '!contains':\n                    return !value.includes(comparable);\n                case 'equal':\n                case 'selected':\n                    return value === comparable;\n                case '!equal':\n                case '!selected':\n                    return value !== comparable;\n                case 'set':\n                    return value;\n                case '!set':\n                    return !value;\n                case 'greater':\n                    return parseFloat(value) > parseFloat(comparable);\n                case 'less':\n                    return parseFloat(value) < parseFloat(comparable);\n                case 'greater or equal':\n                    return parseFloat(value) >= parseFloat(comparable);\n                case 'less or equal':\n                    return parseFloat(value) <= parseFloat(comparable);\n                case 'fileSet':\n                    return value.name !== '';\n                case '!fileSet':\n                    return value.name === '';\n            }\n            // Date & Date Time comparison requires formatting the value\n            if (value.includes(':')) {\n                const datetimeFormat = time.getLangDatetimeFormat();\n                value = moment(value, datetimeFormat)._d.getTime() / 1000;\n            } else {\n                const dateFormat = time.getLangDateFormat();\n                value = moment(value, dateFormat)._d.getTime() / 1000;\n            }\n            comparable = parseInt(comparable);\n            between = parseInt(between) || '';\n            switch (comparator) {\n                case 'dateEqual':\n                    return value === comparable;\n                case 'date!equal':\n                    return value !== comparable;\n                case 'before':\n                    return value < comparable;\n                case 'after':\n                    return value > comparable;\n                case 'equal or before':\n                    return value <= comparable;\n                case 'between':\n                    return value >= comparable && value <= between;\n                case '!between':\n                    return !(value >= comparable && value <= between);\n                case 'equal or after':\n                    return value >= comparable;\n            }\n        },\n        /**\n         * @private\n         * @param {HTMLElement} fieldEl the field we want to have a function\n         *      that calculates its visibility\n         * @returns {function} the function to be executed when we want to\n         *      recalculate the visibility of fieldEl\n         */\n        _buildVisibilityFunction(fieldEl) {\n            const visibilityCondition = fieldEl.dataset.visibilityCondition;\n            const dependencyName = fieldEl.dataset.visibilityDependency;\n            const comparator = fieldEl.dataset.visibilityComparator;\n            const between = fieldEl.dataset.visibilityBetween;\n            return () => {\n                // To be visible, at least one field with the dependency name must be visible.\n                const dependencyVisibilityFunction = this._visibilityFunctionByFieldName.get(dependencyName);\n                const dependencyIsVisible = !dependencyVisibilityFunction || dependencyVisibilityFunction();\n                if (!dependencyIsVisible) {\n                    return false;\n                }\n\n                const formData = new FormData(this.$target[0]);\n                const currentValueOfDependency = [\"contains\",\"!contains\"].includes(comparator)\n                    ? formData.getAll(dependencyName).join()\n                    : formData.get(dependencyName);\n                return this._compareTo(comparator, currentValueOfDependency, visibilityCondition, between);\n            };\n        },\n        /**\n         * Calculates the visibility for each field with conditional visibility\n         */\n        _updateFieldsVisibility() {\n            let anyFieldVisibilityUpdated = false;\n            for (const [fieldEl, visibilityFunction] of this._visibilityFunctionByFieldEl.entries()) {\n                const wasVisible = !fieldEl.closest(\".s_website_form_field\")\n                    .classList.contains(\"d-none\");\n                const isVisible = !!visibilityFunction();\n                this._updateFieldVisibility(fieldEl, isVisible);\n                anyFieldVisibilityUpdated |= wasVisible !== isVisible;\n            }\n            // Recursive check needed in case of a field (C) that\n            // conditionally displays a prefilled field (B), which in turn\n            // triggers a conditional visibility on another field (A),\n            // registered before B.\n            if (anyFieldVisibilityUpdated) {\n                this._updateFieldsVisibility();\n            }\n        },\n        /**\n         * Changes the visibility of a field.\n         *\n         * @param {HTMLElement} fieldEl\n         * @param {boolean} haveToBeVisible\n         */\n        _updateFieldVisibility(fieldEl, haveToBeVisible) {\n            const fieldContainerEl = fieldEl.closest('.s_website_form_field');\n            fieldContainerEl.classList.toggle('d-none', !haveToBeVisible);\n            for (const inputEl of fieldContainerEl.querySelectorAll('.s_website_form_input')) {\n                // Hidden inputs should also be disabled so that their data are\n                // not sent on form submit.\n                inputEl.disabled = !haveToBeVisible;\n            }\n        },\n\n        //----------------------------------------------------------------------\n        // Handlers\n        //----------------------------------------------------------------------\n\n        /**\n         * Calculates the visibility of the fields at each input event on the\n         * form (this method should be debounced in the start).\n         */\n        _onFieldInput() {\n            this._updateFieldsVisibility();\n        },\n    });\n});\n", "/** @odoo-module **/\n\nimport concurrency from 'web.concurrency';\nimport publicWidget from 'web.public.widget';\n\nimport {qweb} from 'web.core';\nimport {Markup} from 'web.utils';\n\npublicWidget.registry.searchBar = publicWidget.Widget.extend({\n    selector: '.o_searchbar_form',\n    xmlDependencies: ['/website/static/src/snippets/s_searchbar/000.xml'],\n    events: {\n        'input .search-query': '_onInput',\n        'focusout': '_onFocusOut',\n        'keydown .search-query': '_onKeydown',\n        'search .search-query': '_onSearch',\n    },\n    autocompleteMinWidth: 300,\n\n    /**\n     * @constructor\n     */\n    init: function () {\n        this._super.apply(this, arguments);\n\n        this._dp = new concurrency.DropPrevious();\n\n        this._onInput = _.debounce(this._onInput, 400);\n        this._onFocusOut = _.debounce(this._onFocusOut, 100);\n    },\n    /**\n     * @override\n     */\n    start: function () {\n        this.$input = this.$('.search-query');\n\n        this.searchType = this.$input.data('searchType');\n        this.order = this.$('.o_search_order_by').val();\n        this.limit = parseInt(this.$input.data('limit'));\n        this.displayDescription = this.$input.data('displayDescription');\n        this.displayExtraLink = this.$input.data('displayExtraLink');\n        this.displayDetail = this.$input.data('displayDetail');\n        this.displayImage = this.$input.data('displayImage');\n        this.wasEmpty = !this.$input.val();\n        // Make it easy for customization to disable fuzzy matching on specific searchboxes\n        this.allowFuzzy = !this.$input.data('noFuzzy');\n        if (this.limit) {\n            this.$input.attr('autocomplete', 'off');\n        }\n\n        this.options = {\n            'displayImage': this.displayImage,\n            'displayDescription': this.displayDescription,\n            'displayExtraLink': this.displayExtraLink,\n            'displayDetail': this.displayDetail,\n            'allowFuzzy': this.allowFuzzy,\n        };\n        const form = this.$('.o_search_order_by').parents('form');\n        for (const field of form.find(\"input[type='hidden']\")) {\n            this.options[field.name] = field.value;\n        }\n        const action = form.attr('action') || window.location.pathname + window.location.search;\n        const [urlPath, urlParams] = action.split('?');\n        if (urlParams) {\n            for (const keyValue of urlParams.split('&')) {\n                const [key, value] = keyValue.split('=');\n                if (value && key !== 'search') {\n                    // Decode URI parameters: revert + to space then decodeURIComponent.\n                    this.options[decodeURIComponent(key.replace(/\\+/g, '%20'))] = decodeURIComponent(value.replace(/\\+/g, '%20'));\n                }\n            }\n        }\n        const pathParts = urlPath.split('/');\n        for (const index in pathParts) {\n            const value = decodeURIComponent(pathParts[index]);\n            if (index > 0 && /-[0-9]+$/.test(value)) { // is sluggish\n                this.options[decodeURIComponent(pathParts[index - 1])] = value;\n            }\n        }\n\n        if (this.$input.data('noFuzzy')) {\n            $(\"<input type='hidden' name='noFuzzy' value='true'/>\").appendTo(this.$input);\n        }\n        return this._super.apply(this, arguments);\n    },\n    /**\n     * @override\n     */\n    destroy() {\n        this._super(...arguments);\n        this._render(null);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _adaptToScrollingParent() {\n        const bcr = this.el.getBoundingClientRect();\n        this.$menu[0].style.setProperty('position', 'fixed', 'important');\n        this.$menu[0].style.setProperty('top', `${bcr.bottom}px`, 'important');\n        this.$menu[0].style.setProperty('left', `${bcr.left}px`, 'important');\n        this.$menu[0].style.setProperty('max-width', `${bcr.width}px`, 'important');\n        this.$menu[0].style.setProperty('max-height', `${document.body.clientHeight - bcr.bottom - 16}px`, 'important');\n    },\n    /**\n     * @private\n     */\n    async _fetch() {\n        const res = await this._rpc({\n            route: '/website/snippet/autocomplete',\n            params: {\n                'search_type': this.searchType,\n                'term': this.$input.val(),\n                'order': this.order,\n                'limit': this.limit,\n                'max_nb_chars': Math.round(Math.max(this.autocompleteMinWidth, parseInt(this.$el.width())) * 0.22),\n                'options': this.options,\n            },\n        });\n        const fieldNames = [\n            'name',\n            'description',\n            'extra_link',\n            'detail',\n            'detail_strike',\n            'detail_extra',\n        ];\n        res.results.forEach(record => {\n            for (const fieldName of fieldNames) {\n                if (record[fieldName]) {\n                    if (typeof record[fieldName] === \"object\") {\n                        for (const fieldKey of Object.keys(record[fieldName])) {\n                            record[fieldName][fieldKey] = Markup(record[fieldName][fieldKey]);\n                        }\n                    } else {\n                        record[fieldName] = Markup(record[fieldName]);\n                    }\n                }\n            }\n        });\n        return res;\n    },\n    /**\n     * @private\n     */\n    _render: function (res) {\n        if (this._scrollingParentEl) {\n            this._scrollingParentEl.removeEventListener('scroll', this._menuScrollAndResizeHandler);\n            window.removeEventListener('resize', this._menuScrollAndResizeHandler);\n            delete this._scrollingParentEl;\n            delete this._menuScrollAndResizeHandler;\n        }\n\n        const $prevMenu = this.$menu;\n        this.$el.toggleClass('dropdown show', !!res);\n        if (res && this.limit) {\n            const results = res['results'];\n            let template = 'website.s_searchbar.autocomplete';\n            const candidate = template + '.' + this.searchType;\n            if (qweb.has_template(candidate)) {\n                template = candidate;\n            }\n            this.$menu = $(qweb.render(template, {\n                results: results,\n                parts: res['parts'],\n                hasMoreResults: results.length < res['results_count'],\n                search: this.$input.val(),\n                fuzzySearch: res['fuzzy_search'],\n                widget: this,\n            }));\n\n            // TODO adapt directly in the template in master\n            const mutedItemTextEl = this.$menu.find('span.dropdown-item-text.text-muted')[0];\n            if (mutedItemTextEl) {\n                const newItemTextEl = document.createElement('span');\n                newItemTextEl.classList.add('dropdown-item-text');\n                mutedItemTextEl.after(newItemTextEl);\n                mutedItemTextEl.classList.remove('dropdown-item-text');\n                newItemTextEl.appendChild(mutedItemTextEl);\n            }\n\n            this.$menu.css('min-width', this.autocompleteMinWidth);\n\n            // Handle the case where the searchbar is in a mega menu by making\n            // it position:fixed and forcing its size. Note: this could be the\n            // default behavior or at least needed in more cases than the mega\n            // menu only (all scrolling parents). But as a stable fix, it was\n            // easier to fix that case only as a first step, especially since\n            // this cannot generically work on all scrolling parent.\n            const megaMenuEl = this.el.closest('.o_mega_menu');\n            if (megaMenuEl) {\n                const navbarEl = this.el.closest('.navbar');\n                const navbarTogglerEl = navbarEl ? navbarEl.querySelector('.navbar-toggler') : null;\n                if (navbarTogglerEl && navbarTogglerEl.clientWidth < 1) {\n                    this._scrollingParentEl = megaMenuEl;\n                    this._menuScrollAndResizeHandler = () => this._adaptToScrollingParent();\n                    this._scrollingParentEl.addEventListener('scroll', this._menuScrollAndResizeHandler);\n                    window.addEventListener('resize', this._menuScrollAndResizeHandler);\n\n                    this._adaptToScrollingParent();\n                }\n            }\n\n            this.$el.append(this.$menu);\n\n            this.$el.find('button.extra_link').on('click', function (event) {\n                event.preventDefault();\n                window.location.href = event.currentTarget.dataset['target'];\n            });\n            this.$el.find('.s_searchbar_fuzzy_submit').on('click', (event) => {\n                event.preventDefault();\n                this.$input.val(res['fuzzy_search']);\n                const form = this.$('.o_search_order_by').parents('form');\n                form.submit();\n            });\n        }\n\n        if ($prevMenu) {\n            $prevMenu.remove();\n        }\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onInput: function () {\n        if (!this.limit) {\n            return;\n        }\n        if (this.searchType === 'all' && !this.$input.val().trim().length) {\n            this._render();\n        } else {\n            this._dp.add(this._fetch()).then(this._render.bind(this));\n        }\n    },\n    /**\n     * @private\n     */\n    _onFocusOut: function () {\n        if (!this.$el.has(document.activeElement).length) {\n            this._render();\n        }\n    },\n    /**\n     * @private\n     */\n    _onKeydown: function (ev) {\n        switch (ev.which) {\n            case $.ui.keyCode.ESCAPE:\n                this._render();\n                break;\n            case $.ui.keyCode.UP:\n            case $.ui.keyCode.DOWN:\n                ev.preventDefault();\n                if (this.$menu) {\n                    let $element = ev.which === $.ui.keyCode.UP ? this.$menu.children().last() : this.$menu.children().first();\n                    $element.focus();\n                }\n                break;\n            case $.ui.keyCode.ENTER:\n                this.limit = 0; // prevent autocomplete\n                break;\n        }\n    },\n    /**\n     * @private\n     */\n    _onSearch: function (ev) {\n        if (this.$input[0].value) { // actual search\n            this.limit = 0; // prevent autocomplete\n        } else { // clear button clicked\n            this._render(); // remove existing suggestions\n            ev.preventDefault();\n            if (!this.wasEmpty) {\n                this.limit = 0; // prevent autocomplete\n                const form = this.$('.o_search_order_by').parents('form');\n                form.submit();\n            }\n        }\n    },\n});\n\nexport default {\n    searchBar: publicWidget.registry.searchBar,\n};\n", "/** @odoo-module **/\n\nimport {_t} from 'web.core';\nimport publicWidget from 'web.public.widget';\n\nconst CUSTOM_BUTTON_EXTRA_WIDTH = 10;\n\npublicWidget.registry.DonationSnippet = publicWidget.Widget.extend({\n    selector: '.s_donation',\n    disabledInEditableMode: false,\n    events: {\n        'click .s_donation_btn': '_onClickPrefilledButton',\n        'click .s_donation_donate_btn': '_onClickDonateNowButton',\n        'input #s_donation_range_slider': '_onInputRangeSlider',\n    },\n\n    /**\n     * @override\n     */\n    async start() {\n        await this._super(...arguments);\n        this.$rangeSlider = this.$('#s_donation_range_slider');\n        this.defaultAmount = this.$target[0].dataset.defaultAmount;\n        if (this.$rangeSlider.length) {\n            this.$rangeSlider.val(this.defaultAmount);\n            this._setBubble(this.$rangeSlider);\n        }\n        await this._displayCurrencies();\n        const customButtonEl = this.el.querySelector(\"#s_donation_amount_input\");\n        if (customButtonEl) {\n            const canvasEl = document.createElement(\"canvas\");\n            const context = canvasEl.getContext(\"2d\");\n            context.font = window.getComputedStyle(customButtonEl).font;\n            const width = context.measureText(customButtonEl.placeholder).width;\n            customButtonEl.style.maxWidth = `${Math.ceil(width) + CUSTOM_BUTTON_EXTRA_WIDTH}px`;\n        }\n    },\n    /**\n     * @override\n     */\n    destroy() {\n        const customButtonEl = this.el.querySelector(\"#s_donation_amount_input\");\n        if (customButtonEl) {\n            customButtonEl.style.maxWidth = \"\";\n        }\n        this.$target.find('.s_donation_currency').remove();\n        this._deselectPrefilledButtons();\n        this.$('.alert-danger').remove();\n        this._super(...arguments);\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _deselectPrefilledButtons() {\n        this.$('.s_donation_btn').removeClass('active');\n    },\n    /**\n     * @private\n     * @param {jQuery} $range\n     */\n    _setBubble($range) {\n        const $bubble = this.$('.s_range_bubble');\n        const val = $range.val();\n        const min = $range[0].min || 0;\n        const max = $range[0].max || 100;\n        const newVal = Number(((val - min) * 100) / (max - min));\n        const tipOffsetLow = 8 - (newVal * 0.16); // the range thumb size is 16px*16px. The '8' and the '0.16' are related to that 16px (50% and 1% of 16px)\n        $bubble.contents().filter(function () {\n            return this.nodeType === 3;\n        }).replaceWith(val);\n\n        // Sorta magic numbers based on size of the native UI thumb (source: https://css-tricks.com/value-bubbles-for-range-inputs/)\n        $bubble[0].style.left = `calc(${newVal}% + (${tipOffsetLow}px))`;\n    },\n    /**\n     * @private\n     */\n    _displayCurrencies() {\n        return this._rpc({\n            route: '/website/get_current_currency',\n        }).then((result) => {\n            this.currency = result;\n            this.$('.s_donation_currency').remove();\n            const $prefilledButtons = this.$('.s_donation_btn, .s_range_bubble');\n            _.each($prefilledButtons, button => {\n                const before = result.position === \"before\";\n                const $currencySymbol = document.createElement('span');\n                $currencySymbol.innerText = result.symbol;\n                $currencySymbol.classList.add('s_donation_currency', before ? \"pr-1\" : \"pl-1\");\n                if (before) {\n                    $(button).prepend($currencySymbol);\n                } else {\n                    $(button).append($currencySymbol);\n                }\n            });\n        });\n    },\n\n    //--------------------------------------------------------------------------\n    // Handlers\n    //--------------------------------------------------------------------------\n\n    /**\n     * @private\n     */\n    _onClickPrefilledButton(ev) {\n        const $button = $(ev.currentTarget);\n        this._deselectPrefilledButtons();\n        $button.addClass('active');\n        if (this.$rangeSlider.length) {\n            this.$rangeSlider.val($button[0].dataset.donationValue);\n            this._setBubble(this.$rangeSlider);\n        }\n    },\n    /**\n     * @private\n     */\n    _onClickDonateNowButton(ev) {\n        if (this.editableMode) {\n            return;\n        };\n        this.$('.alert-danger').remove();\n        const $buttons = this.$('.s_donation_btn');\n        const $selectedButton = $buttons.filter('.active');\n        let amount = $selectedButton.length ? $selectedButton[0].dataset.donationValue : 0;\n        if (this.$target[0].dataset.displayOptions && !amount) {\n            if (this.$rangeSlider.length) {\n                amount = this.$rangeSlider.val();\n            } else if ($buttons.length) {\n                amount = parseFloat(this.$('#s_donation_amount_input').val());\n                let errorMessage = '';\n                const minAmount = this.$target[0].dataset.minimumAmount;\n                if (!amount) {\n                    errorMessage = _t(\"Please select or enter an amount\");\n                } else if (amount < parseFloat(minAmount)) {\n                    const before = this.currency.position === \"before\" ? this.currency.symbol : \"\";\n                    const after = this.currency.position === \"after\" ? this.currency.symbol : \"\";\n                    errorMessage = _.str.sprintf(_t(\"The minimum donation amount is %s%s%s\"), before, minAmount, after);\n                }\n                if (errorMessage) {\n                    $(ev.currentTarget).before($('<p>', {\n                        class: 'alert alert-danger',\n                        text: errorMessage,\n                    }));\n                    return;\n                }\n            }\n        }\n        if (!amount) {\n            amount = this.defaultAmount;\n        }\n        const $form = this.$('.s_donation_form');\n        $('<input>').attr({type: 'hidden', name: 'amount', value: amount}).appendTo($form);\n        $('<input>').attr({type: 'hidden', name: 'currency_id', value: this.currency.id}).appendTo($form);\n        $('<input>').attr({type: 'hidden', name: 'csrf_token', value: odoo.csrf_token}).appendTo($form);\n        $('<input>').attr({type: 'hidden', name: 'donation_options', value: JSON.stringify(this.el.dataset)}).appendTo($form);\n        $form.submit();\n    },\n    /**\n     * @private\n     */\n    _onInputRangeSlider(ev) {\n        this._deselectPrefilledButtons();\n        this._setBubble($(ev.currentTarget));\n    },\n});\n\nexport default {\n    DonationSnippet: publicWidget.registry.DonationSnippet,\n};\n", "odoo.define('website_sale.s_dynamic_snippet_products', function (require) {\n'use strict';\n\nconst config = require('web.config');\nconst core = require('web.core');\nconst publicWidget = require('web.public.widget');\nconst DynamicSnippetCarousel = require('website.s_dynamic_snippet_carousel');\nvar wSaleUtils = require('website_sale.utils');\n\nconst DynamicSnippetProducts = DynamicSnippetCarousel.extend({\n    selector: '.s_dynamic_snippet_products',\n    read_events: {\n        'click .js_add_cart': '_onAddToCart',\n        'click .js_remove': '_onRemoveFromRecentlyViewed',\n    },\n\n    //--------------------------------------------------------------------------\n    // Private\n    //--------------------------------------------------------------------------\n\n    /**\n     * Method to be overridden in child components in order to provide a search\n     * domain if needed.\n     * @override\n     * @private\n     */\n    _getSearchDomain: function () {\n        const searchDomain = this._super.apply(this, arguments);\n        let productCategoryId = this.$el.get(0).dataset.productCategoryId;\n        if (productCategoryId && productCategoryId !== 'all') {\n            if (productCategoryId === 'current') {\n                productCategoryId = undefined;\n                const productCategoryField = $(\"#product_details\").find(\".product_category_id\");\n                if (productCategoryField && productCategoryField.length) {\n                    productCategoryId = parseInt(productCategoryField[0].value);\n                }\n                if (!productCategoryId) {\n                    this.trigger_up('main_object_request', {\n                        callback: function (value) {\n                            if (value.model === \"product.public.category\") {\n                                productCategoryId = value.id;\n                            }\n                        },\n                    });\n                }\n                if (!productCategoryId) {\n                    // Try with categories from product, unfortunately the category hierarchy is not matched with this approach\n                    const productTemplateId = $(\"#product_details\").find(\".product_template_id\");\n                    if (productTemplateId && productTemplateId.length) {\n                        searchDomain.push(['public_categ_ids.product_tmpl_ids', '=', parseInt(productTemplateId[0].value)]);\n                    }\n                }\n            }\n            if (productCategoryId) {\n                searchDomain.push(['public_categ_ids', 'child_of', parseInt(productCategoryId)]);\n            }\n        }\n        const productNames = this.$el.get(0).dataset.productNames;\n        if (productNames) {\n            const nameDomain = [];\n            for (const productName of productNames.split(',')) {\n                if (nameDomain.length) {\n                    nameDomain.unshift('|');\n                }\n                nameDomain.push(['name', 'ilike', productName]);\n            }\n            searchDomain.push(...nameDomain);\n        }\n        return searchDomain;\n    },\n    /**\n     * @override\n     */\n    _getRpcParameters: function () {\n        const productTemplateId = $(\"#product_details\").find(\".product_template_id\");\n        return Object.assign(this._super.apply(this, arguments), {\n            productTemplateId: productTemplateId && productTemplateId.length ? productTemplateId[0].value : undefined,\n        });\n    },\n    /**\n     * Add product to cart and reload the carousel.\n     * @private\n     * @param {Event} ev\n     */\n    _onAddToCart: function (ev) {\n        var self = this;\n        var $card = $(ev.currentTarget).closest('.card');\n        this._rpc({\n            route: \"/shop/cart/update_json\",\n            params: {\n                product_id: $card.find('input[data-product-id]').data('product-id'),\n                add_qty: 1\n            },\n        }).then(function (data) {\n            var $navButton = $('header .o_wsale_my_cart').first();\n            var fetch = self._fetchData();\n            var animation = wSaleUtils.animateClone($navButton, $(ev.currentTarget).parents('.card'), 25, 40);\n            Promise.all([fetch, animation]).then(function (values) {\n                wSaleUtils.updateCartNavBar(data);\n                if (self.add2cartRerender) {\n                     self._render();\n                }\n            });\n        });\n    },\n    /**\n     * @override \n     * @private\n     */\n    _renderContent() {\n        this._super(...arguments);\n        this.add2cartRerender = !!this.el.querySelector('[data-add2cart-rerender=\"True\"]');\n    },\n    /**\n     * Remove product from recently viewed products.\n     * @private\n     * @param {Event} ev\n     */\n    _onRemoveFromRecentlyViewed: function (ev) {\n        var self = this;\n        var $card = $(ev.currentTarget).closest('.card');\n        this._rpc({\n            route: \"/shop/products/recently_viewed_delete\",\n            params: {\n                product_id: $card.find('input[data-product-id]').data('product-id'),\n            },\n        }).then(function (data) {\n            self._fetchData().then(() => self._render());\n        });\n    },\n\n});\npublicWidget.registry.dynamic_snippet_products = DynamicSnippetProducts;\n\nreturn DynamicSnippetProducts;\n});\n"], "file": "/web/assets/4170-ba1fb05/1/web.assets_frontend_lazy.js", "sourceRoot": "../../../../"}